diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index e971ae9..5b8942b 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -158,7 +158,7 @@ class QuestionsController < InheritedResources::Base elsif object_type == "uploaded_ideas" uploaded_ideas_by_visitor_id = @question.choices.find(:all, :select => 'creator_id, count(*) as ideas_count', - :conditions => "choices.creator_id != #{@question.creator_id}", + :conditions => "choices.creator_id != #{@question.creator_id}", :group => 'creator_id') count = 0 @@ -307,7 +307,30 @@ class QuestionsController < InheritedResources::Base end def index - @questions = current_user.questions.find(:all) + @questions = current_user.questions.scoped({}) + @questions = @questions.created_by(params[:creator]) if params[:creator] + + counts = {} + if params[:user_ideas] + counts[:user_ideas] = Choice.count(:joins => :question, + :conditions => "choices.creator_id <> questions.creator_id", + :group => "choices.question_id") + end + if params[:active_user_ideas] + counts[:active_user_ideas] = Choice.count(:joins => :question, + :conditions => "choices.active = 1 AND choices.creator_id <> questions.creator_id", + :group => "choices.question_id") + end + if params[:votes_since] + counts[:recent_votes] = Vote.count(:joins => :question, + :conditions => ["votes.created_at > ?", params[:votes_since]], + :group => "votes.question_id") + end + + counts.each_pair do |attr,hash| + @questions.each{ |q| q[attr] = hash[q.id] || 0 } + end + index! end diff --git a/app/controllers/visitors_controller.rb b/app/controllers/visitors_controller.rb index e7b03f5..673f7c9 100644 --- a/app/controllers/visitors_controller.rb +++ b/app/controllers/visitors_controller.rb @@ -1,7 +1,49 @@ class VisitorsController < InheritedResources::Base respond_to :xml, :json before_filter :authenticate - actions :none + actions :index + + def index + cond = params[:question_id] ? "question_id = #{params[:question_id]}" : nil + + counts = {} + if params[:votes_count] + counts[:votes_count] = Vote.count(:conditions => cond, :group => "voter_id") + end + if params[:skips_count] + counts[:skips_count] = Skip.count(:conditions => cond, :group => "skipper_id") + end + if params[:ideas_count] + idea_cond = "choices.creator_id != questions.creator_id" + + (cond ? " AND #{cond}" : "") + counts[:ideas_count] = Choice.count(:joins => :question, + :conditions => idea_cond, + :group => "choices.creator_id") + end + if params[:bounces] + counts[:bounces] = Appearance.count(:conditions => cond, + :group => "voter_id", + :having => "count(answerable_id) = 0") + end + if params[:questions_created] + counts[:questions_created] = Question.count(:group => :creator_id) + end + + # visitors belong to a site, so we can't just scope them to a question. + # instead, take the union of visitor ids with counted objects + if counts.empty? + @visitors = current_user.visitors.scoped({}) + else + ids = counts.inject([]){ |ids,(k,v)| ids | v.keys } + @visitors = current_user.visitors.scoped(:conditions => { :id => ids }) + end + + counts.each_pair do |attr,values| + @visitors.each{ |v| v[attr] = values[v.id] || 0 } + end + + index! + end def objects_by_session_ids session_ids = params[:session_ids] diff --git a/app/models/question.rb b/app/models/question.rb index ec1941e..328e0d4 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -29,6 +29,10 @@ class Question < ActiveRecord::Base attr_readonly :site_id + named_scope :created_by, lambda { |id| + {:conditions => { :local_identifier => id } } + } + def create_choices_from_ideas if ideas && ideas.any? ideas.each do |idea| diff --git a/config/routes.rb b/config/routes.rb index 5e56577..e3620cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,6 @@ ActionController::Routing::Routes.draw do |map| map.resources :densities, :only => :index - map.resources :visitors, :only => :none, + map.resources :visitors, :only => :index, :collection => {:objects_by_session_ids => :post}, :member => {:votes => :get} map.resources :questions, :except => [:edit, :destroy], diff --git a/spec/factories.rb b/spec/factories.rb index 3273854..de2e509 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -58,6 +58,19 @@ Factory.define(:vote) do |f| f.voter {|v| v.question.creator} end +Factory.define(:skip) do |f| + f.association :question, :factory => :aoi_question + f.prompt {|s| s.question.prompts.first} + f.skipper {|s| s.question.creator} +end + +Factory.define(:appearance) do |f| + f.association :question, :factory => :aoi_question + f.prompt {|a| a.question.prompts.rand} + f.voter {|a| a.question.creator} + f.answerable { nil } +end + Factory.sequence :email do |n| "user#{n}@example.com" end diff --git a/spec/integration/choices_spec.rb b/spec/integration/choices_spec.rb index 8cb9361..86f95fb 100644 --- a/spec/integration/choices_spec.rb +++ b/spec/integration/choices_spec.rb @@ -22,7 +22,7 @@ describe "Choices" do end after do - post_auth question_choices_path(@question, :format => 'xml'), @params + post_auth question_choices_path(@question), @params response.should be_success response.should have_tag "choice" end @@ -35,7 +35,7 @@ describe "Choices" do :data => "foo", :local_identifier => "bar" } } - post_auth question_choices_path(@question, :format => 'xml'), @params + post_auth question_choices_path(@question), @params response.should be_success response.should have_tag "choice creator-id", @visitor.id.to_s @@ -52,32 +52,23 @@ describe "Choices" do end it "should return the deactivated choice given no arguments" do - put_auth flag_question_choice_path(@question, @choice, :format => 'xml') + put_auth flag_question_choice_path(@question, @choice) response.should be_success response.should have_tag "choice active", "false" end it "should return the deactivated choice given an explanation" do - put_auth flag_question_choice_path(@question, @choice, :format => 'xml'), :explanation => "foo" + put_auth flag_question_choice_path(@question, @choice), :explanation => "foo" response.should be_success response.should have_tag "choice active", "false" end - context "when trying to flag another site's choices" do - before do - # this is ugly - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) - end - - it "should fail" do - put_auth flag_question_choice_path(@question, @choice, :format => 'xml'), :explanation => "foo" - response.should_not be_success - end - - after { @api_user = @orig_user } + it "should fail when trying to flag another site's choices" do + other_user = Factory(:email_confirmed_user) + put_auth other_user, flag_question_choice_path(@question, @choice), :explanation => "foo" + response.should_not be_success end end @@ -89,14 +80,14 @@ describe "Choices" do end it "should return all active choices given no optional parameters" do - get_auth question_choices_path(@question, :format => 'xml') + get_auth question_choices_path(@question) response.should be_success response.should have_tag "choices choice", 5 end it "should return all choices if include_inactive is set" do - get_auth question_choices_path(@question, :format => 'xml'), :include_inactive => true + get_auth question_choices_path(@question), :include_inactive => true response.should be_success response.should have_tag "choices choice", 10 @@ -105,31 +96,23 @@ describe "Choices" do it "should return 3 choices when limt is set to 3" do - get_auth question_choices_path(@question, :format => 'xml'), :limit => 3 + get_auth question_choices_path(@question), :limit => 3 response.should be_success response.should have_tag "choices choice", 3 end it "should return the remaining choices when offset is provided" do - get_auth question_choices_path(@question, :format => 'xml'), :offset => 2, :limit => 4 + get_auth question_choices_path(@question), :offset => 2, :limit => 4 response.should be_success response.should have_tag "choices choice", 3 end - context "when trying to access another site's choices" do - before do - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) - end - - it "should fail" do - get_auth question_choices_path(@question, :format => 'xml'), :offset => 2, :limit => 4 - response.should_not be_success - end - - after { @api_user = @orig_user } + it "should fail when trying to access another site's choices" do + other_user = Factory(:email_confirmed_user) + get_auth other_user, question_choices_path(@question), :offset => 2, :limit => 4 + response.should_not be_success end end @@ -141,23 +124,16 @@ describe "Choices" do end it "should return a choice" do - get_auth question_choice_path(@question, @choice, :format => 'xml') + get_auth question_choice_path(@question, @choice) response.should be_success response.should have_tag "choice", 1 end - context "when requesting a choice from another site" do - before do - @other_user = Factory(:email_confirmed_user) - @other_question = Factory.create(:aoi_question, :site => @other_user) - @other_choice = Factory.create(:choice, :question => @other_question) - end - - it "should fail" do - get_auth question_choice_path(@other_question, @other_choice, :format => 'xml') - response.should_not be_success - end + it "should fail when requesting a choice from another site" do + other_user = Factory(:email_confirmed_user) + get_auth other_user, question_choice_path(@question, @choice) + response.should_not be_success end end @@ -171,23 +147,15 @@ describe "Choices" do it "should succeed given valid attributes" do params = { :choice => { :data => "foo" } } - put_auth question_choice_path(@question, @choice, :format => 'xml'), params + put_auth question_choice_path(@question, @choice), params response.should be_success end - context "when updatng another site's choice" do - before do - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) - end - - it "should fail" do - params = { :choice => { :data => "foo" } } - put_auth question_choice_path(@question, @choice, :format => 'xml'), params - response.should_not be_success - end - - after { @api_user = @orig_user } + it "should fail when updating another site's choice" do + other_user = Factory(:email_confirmed_user) + params = { :choice => { :data => "foo" } } + put_auth other_user, question_choice_path(@question, @choice), params + response.should_not be_success end end diff --git a/spec/integration/prompts_spec.rb b/spec/integration/prompts_spec.rb index 3eac043..7ae68d3 100644 --- a/spec/integration/prompts_spec.rb +++ b/spec/integration/prompts_spec.rb @@ -13,7 +13,7 @@ describe "Prompts" do end it "returns a prompt object" do - get_auth question_prompt_path(@question, @prompt, :format => 'xml') + get_auth question_prompt_path(@question, @prompt) response.should be_success response.should have_tag "prompt", 1 end @@ -31,11 +31,11 @@ describe "Prompts" do :with_prompt => true, :visitor_identifier => @visitor.identifier ) @appearance_id = info[:appearance_id] - @prompt_id = info[:picked_prompt_id] + @picked_prompt_id = info[:picked_prompt_id] end it "should return a new skip object given no optional parameters" do - post_auth skip_question_prompt_path(@question.id, @prompt_id, :format => 'xml') + post_auth skip_question_prompt_path(@question.id, @picked_prompt_id) response.should be_success response.should have_tag "skip", 1 end @@ -48,7 +48,7 @@ describe "Prompts" do :skip_reason => "bar", :appearance_lookup => @appearance_id, :time_viewed => 47 } } - post_auth skip_question_prompt_path(@question, @prompt_id, :format => 'xml'), params + post_auth skip_question_prompt_path(@question, @picked_prompt_id), params response.should be_success response.should have_tag "skip", 1 response.should have_tag "skip appearance-id", @appearance_id.to_s @@ -65,7 +65,7 @@ describe "Prompts" do :with_appearance => true, :algorithm => "catchup", :with_visitor_stats => true } } - post_auth skip_question_prompt_path(@question, @prompt_id, :format => 'xml'), params + post_auth skip_question_prompt_path(@question, @picked_prompt_id), params response.should be_success response.should have_tag "prompt", 1 response.should have_tag "prompt appearance_id", /.+/ @@ -73,19 +73,12 @@ describe "Prompts" do response.should have_tag "prompt visitor_ideas", /\d+/ end - context "when trying to skip another site's questions" do - before do - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) - end - - it "should fail" do - post_auth skip_question_prompt_path(@question.id, @prompt_id, :format => 'xml') - response.should_not be_success - end - - after { @api_user = @orig_user } + it "should fail when trying to skip another site's questions" do + other_user = Factory(:email_confirmed_user) + post_auth other_user, skip_question_prompt_path(@question, @picked_prompt_id) + response.should_not be_success end + end describe "POST 'vote'" do @@ -101,17 +94,17 @@ describe "Prompts" do :with_prompt => true, :visitor_identifier => @visitor.identifier ) @appearance_id = info[:appearance_id] - @prompt_id = info[:picked_prompt_id] + @picked_prompt_id = info[:picked_prompt_id] end it "should fail without the required 'direction' parameter" do - post_auth vote_question_prompt_path(@question.id, @prompt_id, :format => 'xml') + post_auth vote_question_prompt_path(@question.id, @picked_prompt_id) response.should_not be_success end it "should return a new vote object given no optional parameters" do params = { :vote => { :direction => "left" } } - post_auth vote_question_prompt_path(@question.id, @prompt_id, :format => 'xml'), params + post_auth vote_question_prompt_path(@question.id, @picked_prompt_id), params response.should be_success response.should have_tag "vote", 1 end @@ -124,7 +117,7 @@ describe "Prompts" do :direction => "right", :appearance_lookup => @appearance_id, :time_viewed => 47 } } - post_auth vote_question_prompt_path(@question, @prompt_id, :format => 'xml'), params + post_auth vote_question_prompt_path(@question, @picked_prompt_id), params response.should be_success response.should have_tag "vote", 1 response.should have_tag "vote appearance-id", @appearance_id.to_s @@ -143,7 +136,7 @@ describe "Prompts" do :with_appearance => true, :algorithm => "catchup", :with_visitor_stats => true } } - post_auth vote_question_prompt_path(@question, @prompt_id, :format => 'xml'), params + post_auth vote_question_prompt_path(@question, @picked_prompt_id), params response.should be_success response.should have_tag "prompt", 1 response.should have_tag "prompt appearance_id", /.+/ @@ -151,19 +144,11 @@ describe "Prompts" do response.should have_tag "prompt visitor_ideas", /\d+/ end - context "when trying to vote on another site's questions" do - before do - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) - end - - it "should fail" do - params = { :vote => { :direction => "left" } } - post_auth vote_question_prompt_path(@question.id, @prompt_id, :format => 'xml'), params - response.should_not be_success - end - - after { @api_user = @orig_user } + it "should fail when trying to vote on another site's questions" do + other_user = Factory(:email_confirmed_user) + params = { :vote => { :direction => "left" } } + post_auth other_user, vote_question_prompt_path(@question.id, @picked_prompt_id), params + response.should_not be_success end end diff --git a/spec/integration/questions_spec.rb b/spec/integration/questions_spec.rb index 899ce9a..184e83a 100644 --- a/spec/integration/questions_spec.rb +++ b/spec/integration/questions_spec.rb @@ -3,29 +3,81 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe "Questions" do include IntegrationSupport before do - 3.times{ Factory.create(:aoi_question, :site => @api_user) } + @user = self.default_user = Factory(:email_confirmed_user) + @choices = {} + @questions = Array.new(5){ Factory(:aoi_question, :site => @user) }.each do |q| + @choices[q.id] = Array.new(rand(10)){ Factory(:choice, :question => q, :active => (rand(2)==1)) } + end end describe "GET 'index'" do it "should return an array of questions" do get_auth questions_path(:format => 'xml') + response.should be_success + response.body.should have_tag("questions question", @questions.size) + end + + it "should not return other users' questions" do + other_user = Factory(:email_confirmed_user) + other_questions = Array.new(5){ Factory(:aoi_question, :site => other_user) } + + get_auth other_user, questions_path(:format => 'xml') + + response.should be_success + response.body.should have_tag "questions question site-id", :count => 5, :text => other_user.id.to_s + response.body.should_not have_tag "site-id", @user.id.to_s + end + + it "should return a list of questions for a specific creator" do + 3.times{ Factory(:aoi_question, + :site => @user, + :local_identifier => "jim") } + + get_auth questions_path(:format => 'xml'), {:creator => "jim"} + response.should be_success response.body.should have_tag("questions question", 3) + response.body.should have_tag("questions question local-identifier", "jim") + end + + it "should calculate the total number of user-submitted choices" do + get_auth questions_path(:format => 'xml'), :user_ideas => true + response.should be_success + response.body.should have_tag("question", @questions.size) + @choices.each_value do |cs| + response.body.should have_tag("user-ideas", :text => cs.size) + end end - context "when calling index as another user" do - before do - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) + it "should calculate the number of active user-submitted choices" do + get_auth questions_path(:format => 'xml'), :active_user_ideas => true + + response.should be_success + response.body.should have_tag("question", @questions.size) + @choices.each_value do |cs| + count = cs.select{|c| c.active}.size + response.body.should have_tag "active-user-ideas", :text => count end - - it "should not return the questions of the original user" do - get_auth questions_path(:format => 'xml') - response.should be_success - response.body.should_not have_tag("question") + end + + it "should calculate the number of votes submitted since some date" do + votes = {} + @questions.each do |q| + votes[q.id] = Array.new(20) do + Factory(:vote, :question => q, :created_at => rand(365).days.ago) + end + end + date = rand(365).days.ago + get_auth questions_path(:format => 'xml'), :votes_since => date.strftime("%Y-%m-%d") + + response.should be_success + response.body.should have_tag("question", @questions.size) + votes.each_value do |vs| + count = vs.select{|v| v.created_at > date}.size + response.body.should have_tag"recent-votes", :text => count end - after { @api_user = @orig_user } end + end describe "GET 'new'" do @@ -91,7 +143,7 @@ describe "Questions" do it "should fail given invalid parameters" do params = { :type => "ideas", :response_type => "foo", :redisk_key => "bar" } - post_auth export_question_path(@question, :format => 'xml') + post_auth export_question_path(@question) response.should be_success response.body.should =~ /Error/ end @@ -108,7 +160,7 @@ describe "Questions" do before { @question = Factory.create(:aoi_question, :site => @api_user) } it "should succeed given no optional parameters" do - get_auth question_path(@question, :format => 'xml') + get_auth question_path(@question) response.should be_success response.should have_tag "question", 1 response.should have_tag "question id", @question.id.to_s @@ -121,7 +173,7 @@ describe "Questions" do :with_prompt => true, :with_appearance => true, :with_visitor_stats => true } - get_auth question_path(@question, :format => 'xml'), params + get_auth question_path(@question), params response.should be_success response.should have_tag "question", 1 response.should have_tag "question id", @question.id.to_s @@ -134,22 +186,15 @@ describe "Questions" do it "should fail if 'with_prompt' is set but 'visitor_identifier' not provided" do pending("figure out argument dependencies") do params = { :with_prompt => true } - get_auth question_path(@question, :format => 'xml'), params + get_auth question_path(@question), params response.should_not be_success end end - context "GET 'show' trying to view others sites' questions" do - before do - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) - end - - it "should fail" do - get_auth question_path(@question, :format => 'xml') - response.should_not be_success - end - after { @api_user = @orig_user } + it "should fail when trying to view other sites' questions" do + other_user = Factory(:email_confirmed_user) + get_auth other_user, question_path(@question) + response.should_not be_success end end @@ -163,38 +208,32 @@ describe "Questions" do :information => "foo", :name => "bar", :local_identifier => "baz" } } - put_auth question_path(@question, :format => 'xml'), params + put_auth question_path(@question), params response.should be_success end it "should not be able to change the site id" do original_site_id = @question.site_id params = { :question => { :site_id => -1 } } - put_auth question_path(@question, :format => 'xml'), params + put_auth question_path(@question), params @question.reload.site_id.should == original_site_id end it "should ignore protected attributes" do params = { :question => { :votes_count => 999 } } - put_auth question_path(@question, :format => 'xml'), params + put_auth question_path(@question), params response.should be_success - @question.reload.site_id.should_not == 999 + @question.reload.votes_count.should_not == 999 end - context "when updatng another site's question" do - before do - @orig_user = @api_user - @api_user = Factory(:email_confirmed_user) - end + it "should fail when updating another site's question" do + other_user = Factory(:email_confirmed_user) + params = { :question => { :name => "foo" } } + put_auth other_user, question_path(@question), params + response.should_not be_success + end - it "should fail" do - params = { :question => { :name => "foo" } } - put_auth question_path(@question, :format => 'xml'), params - response.should_not be_success - end - after { @api_user = @orig_user } - end end describe "GET 'all_object_info_totals_by_date'" do diff --git a/spec/integration/visitors_spec.rb b/spec/integration/visitors_spec.rb index 5e718fe..24b3a76 100644 --- a/spec/integration/visitors_spec.rb +++ b/spec/integration/visitors_spec.rb @@ -2,7 +2,200 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe "Visitors" do include IntegrationSupport + + before do + @user = self.default_user = Factory(:email_confirmed_user) + @visitors = @user.visitors << Array.new(30){ Factory(:visitor, :site => @user) } + @questions = Array.new(3){ Factory(:aoi_question, :site => @user, :creator => @visitors.rand) } + end + + describe "GET 'index'" do + it "should return an array of visitors" do + get_auth visitors_path + response.should be_success + response.body.should have_tag("visitors visitor", @visitors.size) + end + + it "should not return other sites' visitors" do + other_user = Factory(:email_confirmed_user) + other_visitors = other_user.visitors << Array.new(10) do + Factory(:visitor, :site => other_user) + end + get_auth other_user, visitors_path + + response.should be_success + response.body.should have_tag("visitors visitor", other_visitors.size) + end + + it "should return the number of votes for each visitor" do + counts = Hash.new(0) + 20.times do + visitor = @visitors.rand + Factory(:vote, :question => @questions.rand, :voter => visitor) + counts[visitor.id] += 1 + end + get_auth visitors_path, :votes_count => true + + response.should be_success + response.should have_tag "visitor", counts.size do |nodes| + nodes.each do |node| + id = node.content("id").to_i + node.should have_tag("id"), :text => id + node.should have_tag("votes-count"), :text => counts[id] + end + end + end + + it "should return the number of skips for each visitor" do + counts = Hash.new(0) + 20.times do + visitor = @visitors.rand + Factory(:skip, :question => @questions.rand, :skipper => visitor) + counts[visitor.id] += 1 + end + get_auth visitors_path, :skips_count => true + + response.should be_success + response.should have_tag "visitor", counts.size do |nodes| + nodes.each do |node| + id = node.content("id").to_i + node.should have_tag("id"), :text => id + node.should have_tag("skips-count"), :text => counts[id] + end + end + end + + it "should return the number of user-submitted choices" do + 10.times do + question = @questions.rand + creator = question.creator + Factory(:choice, :question => question, :creator => creator) + end + counts = Hash.new(0) + 10.times do + question = @questions.rand + creator = (@visitors - [question.creator]).rand + counts[creator.id] += 1 + Factory(:choice, :question => question, :creator => creator) + end + get_auth visitors_path :ideas_count => true + + response.should be_success + response.should have_tag "visitor", counts.size do |nodes| + nodes.each do |node| + id = node.content("id").to_i + node.should have_tag("id"), :text => id + node.should have_tag("ideas-count"), :text => counts[id] + end + end + end + + it "should show which visitors are bounces" do + bounce = {} + @visitors.each do |v| + if [true,false].rand + Factory(:appearance, :question => @questions.rand, :voter => v) + bounce[v.id] = 1 + else + vote = Factory(:vote, :question => @questions.rand, :voter => v) + Factory(:appearance, :question => @questions.rand, + :voter => v, :answerable => vote) + end + end + get_auth visitors_path, :bounces => true + + response.should be_success + response.should have_tag "visitor", bounce.size do |nodes| + nodes.each do |node| + id = node.content("id").to_i + node.should have_tag "id", :text => id + node.should have_tag "bounces", :text => 1 + end + end + end + + it "should return the number of questions created for each visitor" do + count = @visitors.inject({}) do |h,v| + n = @questions.select{ |q| q.creator == v }.size + h[v.id] = n unless n.zero? + h + end + get_auth visitors_path, :questions_created => true + + response.should be_success + response.should have_tag "visitor", count.size do |nodes| + nodes.each do |node| + id = node.content("id").to_i + node.should have_tag "id", :text => id + node.should have_tag "questions-created", :text => count[id] + end + end + end + + it "should return the visitor counts for a single question" do + votes, skips, choices = Array.new(3){ Hash.new(0) } + the_question = @questions.rand + 20.times do + question = @questions.rand + visitor = (@visitors - [question.creator]).rand + case rand(3) + when 0 then + Factory(:vote, :question => question, :voter => visitor) + votes[visitor.id] += 1 if question == the_question + when 1 then + Factory(:skip, :question => question, :skipper => visitor) + skips[visitor.id] += 1 if question == the_question + when 2 then + Factory(:choice, :question => question, :creator => visitor) + choices[visitor.id] += 1 if question == the_question + end + end + visitors = (votes.keys | skips.keys | choices.keys) + + get_auth visitors_path, { + :votes_count => true, + :skips_count => true, + :ideas_count => true, + :question_id => the_question.id + } + + response.should be_success + response.should have_tag "visitor", visitors.size do |nodes| + nodes.each do |node| + id = node.content("id").to_i + node.should have_tag "id", :text => id + node.should have_tag "votes-count", :text => votes[id] + node.should have_tag "skips-count", :text => skips[id] + node.should have_tag "ideas-count", :text => choices[id] + end + end + end + + it "should return the bounces for a single question" do + the_question = @questions.rand + bounces = @visitors.inject({}) do |h,v| + if v.id.odd? # bounce! + question = @questions.rand + Factory(:appearance, :question => question, :voter => v) + h[v.id] = 1 if question == the_question + else # appearance w/ answerable + vote = Factory(:vote, :question => @questions.rand, :voter => v) + Factory(:appearance, :question => @questions.rand, :voter => v, :answerable => vote) + end + h + end + + get_auth visitors_path, :bounces => true, :question_id => the_question.id + response.should be_success + response.should have_tag "visitor", bounces.size do |nodes| + nodes.each do |node| + id = node.content("id").to_i + node.should have_tag "id", :text => id + node.should have_tag "bounces", :text => bounces[id] + end + end + end + end - describe "index" - + end diff --git a/spec/support/integration_support.rb b/spec/support/integration_support.rb index e808aa2..eb5c72a 100644 --- a/spec/support/integration_support.rb +++ b/spec/support/integration_support.rb @@ -1,42 +1,63 @@ module IntegrationSupport + @@default_user = nil + # todo: make automatically included in integration tests Spec::Runner.configure do |config| config.before(:each, :type => :integration) do - @api_user = Factory(:email_confirmed_user) + # compatibility with old tests using @api_user, remove this + @api_user = self.default_user = Factory(:email_confirmed_user) end end - def get_auth(path, parameters = {}, headers = {}) - auth_wrap(:get, path, parameters, headers) + def default_user=(user) + @api_user = @@default_user = user end - def put_auth(path, parameters = {}, headers = {}) - auth_wrap(:put, path, parameters, headers) + # generate _auth variation of get/put/post, etc. to automatically + # send requests with the authentication and accept headers set + %w(get put post delete head).each do |method| + define_method(method + "_auth") do |*args| + if args[0].is_a? User + user, path, parameters, headers, *ignored = *args + else + path, parameters, headers, *ignored = *args + end + + user ||= @@default_user + raise ArgumentError, "No user provided and default user not set" unless user + + auth = ActionController::HttpAuthentication:: + Basic.encode_credentials(user.email, user.password) + (headers ||= {}).merge!( :authorization => auth, + :accept => "application/xml" ) + + send(method, path, parameters, headers) + end end - def post_auth(path, parameters = {}, headers = {} ) - auth_wrap(:post, path, parameters, headers) + # need a way to easily fetch content of a Tag + class HTML::Tag + def content(tag) + n = self.find(:tag => tag) or return nil + n.children.each{ |c| return c.content if c.is_a? HTML::Text } + nil + end end - def delete_auth(path, parameters = {}, headers = {}) - auth_wrap(:delete, path, parameters, headers) - end - def head_auth(path, parameters = {}, headers = {}) - auth_wrap(:head, path, parameters, headers) - end - - private - def auth_wrap(method, path, parameters, headers) - return nil unless [:get, :put, :post, :delete, :head].include? method - - auth = ActionController::HttpAuthentication::Basic.encode_credentials(@api_user.email, @api_user.password) - headers.merge!(:authorization => auth) - # headers.merge!(:content_type => "application/xml", :authorization => auth) - # parameters.merge!(:format => 'xml') + # have_tag doesn't let you iterate over individual nodes like + # assert_select does for some reason, and using css matchers + # to do this is ugly. Time for a patch! + class Spec::Rails::Matchers::AssertSelect + def doc_from_with_node(node) + return node if node.is_a? HTML::Node + doc_from_without_node(node) + end - send(method, path, parameters, headers) + alias_method_chain :doc_from, :node end + + end -- libgit2 0.21.2