From e364618bc88dbfc08b3b9b60383ff4d703b2ca69 Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Sun, 29 Jul 2012 11:27:24 -0400 Subject: [PATCH] validate tag format; rearranged specs --- models/comment_thread.rb | 28 ++++++++-- spec/{ => api}/comment_spec.rb | 0 spec/{ => api}/comment_thread_spec.rb | 0 spec/{ => api}/commentable_spec.rb | 31 +++++++++-- spec/api/search_spec.rb | 54 +++++++++++++++++++ .../subscription_and_notification_spec.rb | 0 spec/{ => api}/vote_spec.rb | 0 spec/models/comment_thread.rb | 23 ++++++++ 8 files changed, 128 insertions(+), 8 deletions(-) rename spec/{ => api}/comment_spec.rb (100%) rename spec/{ => api}/comment_thread_spec.rb (100%) rename spec/{ => api}/commentable_spec.rb (70%) create mode 100644 spec/api/search_spec.rb rename spec/{ => api}/subscription_and_notification_spec.rb (100%) rename spec/{ => api}/vote_spec.rb (100%) create mode 100644 spec/models/comment_thread.rb diff --git a/models/comment_thread.rb b/models/comment_thread.rb index 29c339a..eb2d001 100644 --- a/models/comment_thread.rb +++ b/models/comment_thread.rb @@ -35,7 +35,8 @@ class CommentThread < Content validates_presence_of :course_id # do we really need this? validates_presence_of :commentable_id - validate :valid_tag_names + validate :tag_names_valid + validate :tag_names_unique after_create :generate_notifications @@ -77,6 +78,10 @@ class CommentThread < Content end end + def self.tag_name_valid?(tag) + !!(tag =~ RE_TAG) + end + private def generate_notifications if subscribers or (author.followers if author) @@ -101,9 +106,24 @@ private end end - def valid_tag_names - unless tags_array.all? {|tag| tag =~ /^\w+(\s*\w+)*$/} - errors.add :tag, "must consist of words, numbers, underscores and spaces only" + RE_HEADCHAR = /[a-z0-9]/ + RE_ENDONLYCHAR = /\+/ + RE_ENDCHAR = /[a-z0-9\#]/ + RE_CHAR = /[a-z0-9\-\#\.]/ + RE_WORD = /#{RE_HEADCHAR}(((#{RE_CHAR})*(#{RE_ENDCHAR})+)?(#{RE_ENDONLYCHAR})*)?/ + RE_TAG = /^#{RE_WORD}( #{RE_WORD})*$/ + + + + def tag_names_valid + unless tags_array.all? {|tag| self.class.tag_name_valid? tag} + errors.add :tag, "can consist of words, numbers, dashes and spaces only and cannot start with dash" + end + end + + def tag_names_unique + unless tags_array.uniq.size == tags_array.size + errors.add :tags, "must be unique" end end diff --git a/spec/comment_spec.rb b/spec/api/comment_spec.rb similarity index 100% rename from spec/comment_spec.rb rename to spec/api/comment_spec.rb diff --git a/spec/comment_thread_spec.rb b/spec/api/comment_thread_spec.rb similarity index 100% rename from spec/comment_thread_spec.rb rename to spec/api/comment_thread_spec.rb diff --git a/spec/commentable_spec.rb b/spec/api/commentable_spec.rb similarity index 70% rename from spec/commentable_spec.rb rename to spec/api/commentable_spec.rb index 490c516..c198e05 100644 --- a/spec/commentable_spec.rb +++ b/spec/api/commentable_spec.rb @@ -50,26 +50,49 @@ describe "app" do end end describe "POST /api/v1/:commentable_id/threads" do + default_params = {title: "Interesting question", body: "cool", course_id: "1", user_id: "1"} it "create a new comment thread for the commentable object" do - post '/api/v1/question_1/threads', title: "Interesting question", body: "cool", course_id: "1", user_id: "1" + post '/api/v1/question_1/threads', default_params last_response.should be_ok CommentThread.count.should == 3 CommentThread.where(title: "Interesting question").first.should_not be_nil end it "allows anonymous thread" do - post '/api/v1/question_1/threads', title: "Interesting question", body: "cool", course_id: "1" + params = default_params.dup + params.delete(:user_id) + post '/api/v1/question_1/threads', params last_response.should be_ok CommentThread.count.should == 3 CommentThread.where(title: "Interesting question").first.should_not be_nil end it "create a new comment thread for a new commentable object" do - post '/api/v1/does_not_exist/threads', title: "Interesting question", body: "cool", course_id: "1", user_id: "1" + post '/api/v1/does_not_exist/threads', default_params last_response.should be_ok Commentable.find("does_not_exist").comment_threads.length.should == 1 Commentable.find("does_not_exist").comment_threads.first.body.should == "cool" end it "create a new comment thread with tag" do - post '/api/v1/question_1/threads', title: "Interesting question", body: "cool", course_id: "1", user_id: "1", tags: "a, b, c" + post '/api/v1/question_1/threads', default_params.merge(tags: "a, b, c") + last_response.should be_ok + CommentThread.count.should == 3 + thread = CommentThread.where(title: "Interesting question").first + thread.tags_array.length.should == 3 + thread.tags_array.should include "a" + thread.tags_array.should include "b" + thread.tags_array.should include "c" + end + it "strip spaces in tags" do + post '/api/v1/question_1/threads', default_params.merge(tags: " a, b ,c ") + last_response.should be_ok + CommentThread.count.should == 3 + thread = CommentThread.where(title: "Interesting question").first + thread.tags_array.length.should == 3 + thread.tags_array.should include "a" + thread.tags_array.should include "b" + thread.tags_array.should include "c" + end + it "accepts [a-z 0-9 + # - .]words, numbers, dashes, spaces but no underscores in tags" do + post '/api/v1/question_1/threads', default_params.merge(tags: "artificial-intelligence, machine-learning, 7-is-a-lucky-number, interesting problem") last_response.should be_ok CommentThread.count.should == 3 thread = CommentThread.where(title: "Interesting question").first diff --git a/spec/api/search_spec.rb b/spec/api/search_spec.rb new file mode 100644 index 0000000..0accea6 --- /dev/null +++ b/spec/api/search_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe "app" do + describe "search" do + before(:each) { init_without_subscriptions } + describe "GET /api/v1/search/tags" do + it "returns all threads tagged with all tags" do + require 'uri' + thread1 = CommentThread.all.to_a.first + thread2 = CommentThread.all.to_a.last + ai = "artificial intelligence" + ml = "marchine learning" + random1 = "random1" + random2 = "random2" + random3 = "random3" + thread1.tags = [ai, ml, random1].join "," + thread1.save + thread2.tags = [ai, ml, random2].join "," + thread2.save + + post "/api/v1/search/tags", tags: [ai, ml] + last_response.should be_ok + threads = parse last_response.body + threads.length.should == 2 + threads.select{|t| t["id"] == thread1.id.to_s}.first.should_not be_nil + threads.select{|t| t["id"] == thread2.id.to_s}.first.should_not be_nil + + post "/api/v1/search/tags", tags: [ai] + last_response.should be_ok + threads = parse last_response.body + threads.length.should == 2 + threads.select{|t| t["id"] == thread1.id.to_s}.first.should_not be_nil + threads.select{|t| t["id"] == thread2.id.to_s}.first.should_not be_nil + + post "/api/v1/search/tags", tags: [ai, random1] + last_response.should be_ok + threads = parse last_response.body + threads.length.should == 1 + threads.select{|t| t["id"] == thread1.id.to_s}.first.should_not be_nil + + post "/api/v1/search/tags", tags: [random1] + last_response.should be_ok + threads = parse last_response.body + threads.length.should == 1 + threads.select{|t| t["id"] == thread1.id.to_s}.first.should_not be_nil + + post "/api/v1/search/tags", tags: [random1, random2] + last_response.should be_ok + threads = parse last_response.body + threads.length.should == 0 + end + end + end +end diff --git a/spec/subscription_and_notification_spec.rb b/spec/api/subscription_and_notification_spec.rb similarity index 100% rename from spec/subscription_and_notification_spec.rb rename to spec/api/subscription_and_notification_spec.rb diff --git a/spec/vote_spec.rb b/spec/api/vote_spec.rb similarity index 100% rename from spec/vote_spec.rb rename to spec/api/vote_spec.rb diff --git a/spec/models/comment_thread.rb b/spec/models/comment_thread.rb new file mode 100644 index 0000000..1bffe17 --- /dev/null +++ b/spec/models/comment_thread.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe CommentThread do + it "validates tag name" do + CommentThread.tag_name_valid?("a++").should be_true + CommentThread.tag_name_valid?("a++ b++ c++").should be_true + CommentThread.tag_name_valid?("a#b+").should be_true + CommentThread.tag_name_valid?("a##").should be_true + CommentThread.tag_name_valid?("a#-b#").should be_true + CommentThread.tag_name_valid?("000a123").should be_true + CommentThread.tag_name_valid?("artificial-intelligence").should be_true + CommentThread.tag_name_valid?("artificial intelligence").should be_true + CommentThread.tag_name_valid?("well-known formulas").should be_true + + CommentThread.tag_name_valid?("a#+b#").should be_false + CommentThread.tag_name_valid?("a# +b#").should be_false + CommentThread.tag_name_valid?("--a").should be_false + CommentThread.tag_name_valid?("artificial_intelligence").should be_false + CommentThread.tag_name_valid?("#this-is-a-tag").should be_false + CommentThread.tag_name_valid?("_this-is-a-tag").should be_false + CommentThread.tag_name_valid?("this-is+a-tag").should be_false + end +end