Commit e6b5fe35992613ac39492bd140c027571360682f
1 parent
d5055b63
Exists in
master
and in
1 other branch
Added reporting functionality to /index of questions and visitors
Showing
10 changed files
with
453 additions
and
165 deletions
Show diff stats
app/controllers/questions_controller.rb
| ... | ... | @@ -158,7 +158,7 @@ class QuestionsController < InheritedResources::Base |
| 158 | 158 | elsif object_type == "uploaded_ideas" |
| 159 | 159 | |
| 160 | 160 | uploaded_ideas_by_visitor_id = @question.choices.find(:all, :select => 'creator_id, count(*) as ideas_count', |
| 161 | - :conditions => "choices.creator_id != #{@question.creator_id}", | |
| 161 | + :conditions => "choices.creator_id != #{@question.creator_id}", | |
| 162 | 162 | :group => 'creator_id') |
| 163 | 163 | |
| 164 | 164 | count = 0 |
| ... | ... | @@ -307,7 +307,30 @@ class QuestionsController < InheritedResources::Base |
| 307 | 307 | end |
| 308 | 308 | |
| 309 | 309 | def index |
| 310 | - @questions = current_user.questions.find(:all) | |
| 310 | + @questions = current_user.questions.scoped({}) | |
| 311 | + @questions = @questions.created_by(params[:creator]) if params[:creator] | |
| 312 | + | |
| 313 | + counts = {} | |
| 314 | + if params[:user_ideas] | |
| 315 | + counts[:user_ideas] = Choice.count(:joins => :question, | |
| 316 | + :conditions => "choices.creator_id <> questions.creator_id", | |
| 317 | + :group => "choices.question_id") | |
| 318 | + end | |
| 319 | + if params[:active_user_ideas] | |
| 320 | + counts[:active_user_ideas] = Choice.count(:joins => :question, | |
| 321 | + :conditions => "choices.active = 1 AND choices.creator_id <> questions.creator_id", | |
| 322 | + :group => "choices.question_id") | |
| 323 | + end | |
| 324 | + if params[:votes_since] | |
| 325 | + counts[:recent_votes] = Vote.count(:joins => :question, | |
| 326 | + :conditions => ["votes.created_at > ?", params[:votes_since]], | |
| 327 | + :group => "votes.question_id") | |
| 328 | + end | |
| 329 | + | |
| 330 | + counts.each_pair do |attr,hash| | |
| 331 | + @questions.each{ |q| q[attr] = hash[q.id] || 0 } | |
| 332 | + end | |
| 333 | + | |
| 311 | 334 | index! |
| 312 | 335 | end |
| 313 | 336 | ... | ... |
app/controllers/visitors_controller.rb
| 1 | 1 | class VisitorsController < InheritedResources::Base |
| 2 | 2 | respond_to :xml, :json |
| 3 | 3 | before_filter :authenticate |
| 4 | - actions :none | |
| 4 | + actions :index | |
| 5 | + | |
| 6 | + def index | |
| 7 | + cond = params[:question_id] ? "question_id = #{params[:question_id]}" : nil | |
| 8 | + | |
| 9 | + counts = {} | |
| 10 | + if params[:votes_count] | |
| 11 | + counts[:votes_count] = Vote.count(:conditions => cond, :group => "voter_id") | |
| 12 | + end | |
| 13 | + if params[:skips_count] | |
| 14 | + counts[:skips_count] = Skip.count(:conditions => cond, :group => "skipper_id") | |
| 15 | + end | |
| 16 | + if params[:ideas_count] | |
| 17 | + idea_cond = "choices.creator_id != questions.creator_id" + | |
| 18 | + (cond ? " AND #{cond}" : "") | |
| 19 | + counts[:ideas_count] = Choice.count(:joins => :question, | |
| 20 | + :conditions => idea_cond, | |
| 21 | + :group => "choices.creator_id") | |
| 22 | + end | |
| 23 | + if params[:bounces] | |
| 24 | + counts[:bounces] = Appearance.count(:conditions => cond, | |
| 25 | + :group => "voter_id", | |
| 26 | + :having => "count(answerable_id) = 0") | |
| 27 | + end | |
| 28 | + if params[:questions_created] | |
| 29 | + counts[:questions_created] = Question.count(:group => :creator_id) | |
| 30 | + end | |
| 31 | + | |
| 32 | + # visitors belong to a site, so we can't just scope them to a question. | |
| 33 | + # instead, take the union of visitor ids with counted objects | |
| 34 | + if counts.empty? | |
| 35 | + @visitors = current_user.visitors.scoped({}) | |
| 36 | + else | |
| 37 | + ids = counts.inject([]){ |ids,(k,v)| ids | v.keys } | |
| 38 | + @visitors = current_user.visitors.scoped(:conditions => { :id => ids }) | |
| 39 | + end | |
| 40 | + | |
| 41 | + counts.each_pair do |attr,values| | |
| 42 | + @visitors.each{ |v| v[attr] = values[v.id] || 0 } | |
| 43 | + end | |
| 44 | + | |
| 45 | + index! | |
| 46 | + end | |
| 5 | 47 | |
| 6 | 48 | def objects_by_session_ids |
| 7 | 49 | session_ids = params[:session_ids] | ... | ... |
app/models/question.rb
| ... | ... | @@ -29,6 +29,10 @@ class Question < ActiveRecord::Base |
| 29 | 29 | |
| 30 | 30 | attr_readonly :site_id |
| 31 | 31 | |
| 32 | + named_scope :created_by, lambda { |id| | |
| 33 | + {:conditions => { :local_identifier => id } } | |
| 34 | + } | |
| 35 | + | |
| 32 | 36 | def create_choices_from_ideas |
| 33 | 37 | if ideas && ideas.any? |
| 34 | 38 | ideas.each do |idea| | ... | ... |
config/routes.rb
| 1 | 1 | ActionController::Routing::Routes.draw do |map| |
| 2 | 2 | map.resources :densities, :only => :index |
| 3 | - map.resources :visitors, :only => :none, | |
| 3 | + map.resources :visitors, :only => :index, | |
| 4 | 4 | :collection => {:objects_by_session_ids => :post}, |
| 5 | 5 | :member => {:votes => :get} |
| 6 | 6 | map.resources :questions, :except => [:edit, :destroy], | ... | ... |
spec/factories.rb
| ... | ... | @@ -58,6 +58,19 @@ Factory.define(:vote) do |f| |
| 58 | 58 | f.voter {|v| v.question.creator} |
| 59 | 59 | end |
| 60 | 60 | |
| 61 | +Factory.define(:skip) do |f| | |
| 62 | + f.association :question, :factory => :aoi_question | |
| 63 | + f.prompt {|s| s.question.prompts.first} | |
| 64 | + f.skipper {|s| s.question.creator} | |
| 65 | +end | |
| 66 | + | |
| 67 | +Factory.define(:appearance) do |f| | |
| 68 | + f.association :question, :factory => :aoi_question | |
| 69 | + f.prompt {|a| a.question.prompts.rand} | |
| 70 | + f.voter {|a| a.question.creator} | |
| 71 | + f.answerable { nil } | |
| 72 | +end | |
| 73 | + | |
| 61 | 74 | Factory.sequence :email do |n| |
| 62 | 75 | "user#{n}@example.com" |
| 63 | 76 | end | ... | ... |
spec/integration/choices_spec.rb
| ... | ... | @@ -22,7 +22,7 @@ describe "Choices" do |
| 22 | 22 | end |
| 23 | 23 | |
| 24 | 24 | after do |
| 25 | - post_auth question_choices_path(@question, :format => 'xml'), @params | |
| 25 | + post_auth question_choices_path(@question), @params | |
| 26 | 26 | response.should be_success |
| 27 | 27 | response.should have_tag "choice" |
| 28 | 28 | end |
| ... | ... | @@ -35,7 +35,7 @@ describe "Choices" do |
| 35 | 35 | :data => "foo", |
| 36 | 36 | :local_identifier => "bar" } } |
| 37 | 37 | |
| 38 | - post_auth question_choices_path(@question, :format => 'xml'), @params | |
| 38 | + post_auth question_choices_path(@question), @params | |
| 39 | 39 | |
| 40 | 40 | response.should be_success |
| 41 | 41 | response.should have_tag "choice creator-id", @visitor.id.to_s |
| ... | ... | @@ -52,32 +52,23 @@ describe "Choices" do |
| 52 | 52 | end |
| 53 | 53 | |
| 54 | 54 | it "should return the deactivated choice given no arguments" do |
| 55 | - put_auth flag_question_choice_path(@question, @choice, :format => 'xml') | |
| 55 | + put_auth flag_question_choice_path(@question, @choice) | |
| 56 | 56 | |
| 57 | 57 | response.should be_success |
| 58 | 58 | response.should have_tag "choice active", "false" |
| 59 | 59 | end |
| 60 | 60 | |
| 61 | 61 | it "should return the deactivated choice given an explanation" do |
| 62 | - put_auth flag_question_choice_path(@question, @choice, :format => 'xml'), :explanation => "foo" | |
| 62 | + put_auth flag_question_choice_path(@question, @choice), :explanation => "foo" | |
| 63 | 63 | |
| 64 | 64 | response.should be_success |
| 65 | 65 | response.should have_tag "choice active", "false" |
| 66 | 66 | end |
| 67 | 67 | |
| 68 | - context "when trying to flag another site's choices" do | |
| 69 | - before do | |
| 70 | - # this is ugly | |
| 71 | - @orig_user = @api_user | |
| 72 | - @api_user = Factory(:email_confirmed_user) | |
| 73 | - end | |
| 74 | - | |
| 75 | - it "should fail" do | |
| 76 | - put_auth flag_question_choice_path(@question, @choice, :format => 'xml'), :explanation => "foo" | |
| 77 | - response.should_not be_success | |
| 78 | - end | |
| 79 | - | |
| 80 | - after { @api_user = @orig_user } | |
| 68 | + it "should fail when trying to flag another site's choices" do | |
| 69 | + other_user = Factory(:email_confirmed_user) | |
| 70 | + put_auth other_user, flag_question_choice_path(@question, @choice), :explanation => "foo" | |
| 71 | + response.should_not be_success | |
| 81 | 72 | end |
| 82 | 73 | end |
| 83 | 74 | |
| ... | ... | @@ -89,14 +80,14 @@ describe "Choices" do |
| 89 | 80 | end |
| 90 | 81 | |
| 91 | 82 | it "should return all active choices given no optional parameters" do |
| 92 | - get_auth question_choices_path(@question, :format => 'xml') | |
| 83 | + get_auth question_choices_path(@question) | |
| 93 | 84 | |
| 94 | 85 | response.should be_success |
| 95 | 86 | response.should have_tag "choices choice", 5 |
| 96 | 87 | end |
| 97 | 88 | |
| 98 | 89 | it "should return all choices if include_inactive is set" do |
| 99 | - get_auth question_choices_path(@question, :format => 'xml'), :include_inactive => true | |
| 90 | + get_auth question_choices_path(@question), :include_inactive => true | |
| 100 | 91 | |
| 101 | 92 | response.should be_success |
| 102 | 93 | response.should have_tag "choices choice", 10 |
| ... | ... | @@ -105,31 +96,23 @@ describe "Choices" do |
| 105 | 96 | |
| 106 | 97 | |
| 107 | 98 | it "should return 3 choices when limt is set to 3" do |
| 108 | - get_auth question_choices_path(@question, :format => 'xml'), :limit => 3 | |
| 99 | + get_auth question_choices_path(@question), :limit => 3 | |
| 109 | 100 | |
| 110 | 101 | response.should be_success |
| 111 | 102 | response.should have_tag "choices choice", 3 |
| 112 | 103 | end |
| 113 | 104 | |
| 114 | 105 | it "should return the remaining choices when offset is provided" do |
| 115 | - get_auth question_choices_path(@question, :format => 'xml'), :offset => 2, :limit => 4 | |
| 106 | + get_auth question_choices_path(@question), :offset => 2, :limit => 4 | |
| 116 | 107 | |
| 117 | 108 | response.should be_success |
| 118 | 109 | response.should have_tag "choices choice", 3 |
| 119 | 110 | end |
| 120 | 111 | |
| 121 | - context "when trying to access another site's choices" do | |
| 122 | - before do | |
| 123 | - @orig_user = @api_user | |
| 124 | - @api_user = Factory(:email_confirmed_user) | |
| 125 | - end | |
| 126 | - | |
| 127 | - it "should fail" do | |
| 128 | - get_auth question_choices_path(@question, :format => 'xml'), :offset => 2, :limit => 4 | |
| 129 | - response.should_not be_success | |
| 130 | - end | |
| 131 | - | |
| 132 | - after { @api_user = @orig_user } | |
| 112 | + it "should fail when trying to access another site's choices" do | |
| 113 | + other_user = Factory(:email_confirmed_user) | |
| 114 | + get_auth other_user, question_choices_path(@question), :offset => 2, :limit => 4 | |
| 115 | + response.should_not be_success | |
| 133 | 116 | end |
| 134 | 117 | |
| 135 | 118 | end |
| ... | ... | @@ -141,23 +124,16 @@ describe "Choices" do |
| 141 | 124 | end |
| 142 | 125 | |
| 143 | 126 | it "should return a choice" do |
| 144 | - get_auth question_choice_path(@question, @choice, :format => 'xml') | |
| 127 | + get_auth question_choice_path(@question, @choice) | |
| 145 | 128 | |
| 146 | 129 | response.should be_success |
| 147 | 130 | response.should have_tag "choice", 1 |
| 148 | 131 | end |
| 149 | 132 | |
| 150 | - context "when requesting a choice from another site" do | |
| 151 | - before do | |
| 152 | - @other_user = Factory(:email_confirmed_user) | |
| 153 | - @other_question = Factory.create(:aoi_question, :site => @other_user) | |
| 154 | - @other_choice = Factory.create(:choice, :question => @other_question) | |
| 155 | - end | |
| 156 | - | |
| 157 | - it "should fail" do | |
| 158 | - get_auth question_choice_path(@other_question, @other_choice, :format => 'xml') | |
| 159 | - response.should_not be_success | |
| 160 | - end | |
| 133 | + it "should fail when requesting a choice from another site" do | |
| 134 | + other_user = Factory(:email_confirmed_user) | |
| 135 | + get_auth other_user, question_choice_path(@question, @choice) | |
| 136 | + response.should_not be_success | |
| 161 | 137 | end |
| 162 | 138 | |
| 163 | 139 | end |
| ... | ... | @@ -171,23 +147,15 @@ describe "Choices" do |
| 171 | 147 | |
| 172 | 148 | it "should succeed given valid attributes" do |
| 173 | 149 | params = { :choice => { :data => "foo" } } |
| 174 | - put_auth question_choice_path(@question, @choice, :format => 'xml'), params | |
| 150 | + put_auth question_choice_path(@question, @choice), params | |
| 175 | 151 | response.should be_success |
| 176 | 152 | end |
| 177 | 153 | |
| 178 | - context "when updatng another site's choice" do | |
| 179 | - before do | |
| 180 | - @orig_user = @api_user | |
| 181 | - @api_user = Factory(:email_confirmed_user) | |
| 182 | - end | |
| 183 | - | |
| 184 | - it "should fail" do | |
| 185 | - params = { :choice => { :data => "foo" } } | |
| 186 | - put_auth question_choice_path(@question, @choice, :format => 'xml'), params | |
| 187 | - response.should_not be_success | |
| 188 | - end | |
| 189 | - | |
| 190 | - after { @api_user = @orig_user } | |
| 154 | + it "should fail when updating another site's choice" do | |
| 155 | + other_user = Factory(:email_confirmed_user) | |
| 156 | + params = { :choice => { :data => "foo" } } | |
| 157 | + put_auth other_user, question_choice_path(@question, @choice), params | |
| 158 | + response.should_not be_success | |
| 191 | 159 | end |
| 192 | 160 | end |
| 193 | 161 | ... | ... |
spec/integration/prompts_spec.rb
| ... | ... | @@ -13,7 +13,7 @@ describe "Prompts" do |
| 13 | 13 | end |
| 14 | 14 | |
| 15 | 15 | it "returns a prompt object" do |
| 16 | - get_auth question_prompt_path(@question, @prompt, :format => 'xml') | |
| 16 | + get_auth question_prompt_path(@question, @prompt) | |
| 17 | 17 | response.should be_success |
| 18 | 18 | response.should have_tag "prompt", 1 |
| 19 | 19 | end |
| ... | ... | @@ -31,11 +31,11 @@ describe "Prompts" do |
| 31 | 31 | :with_prompt => true, |
| 32 | 32 | :visitor_identifier => @visitor.identifier ) |
| 33 | 33 | @appearance_id = info[:appearance_id] |
| 34 | - @prompt_id = info[:picked_prompt_id] | |
| 34 | + @picked_prompt_id = info[:picked_prompt_id] | |
| 35 | 35 | end |
| 36 | 36 | |
| 37 | 37 | it "should return a new skip object given no optional parameters" do |
| 38 | - post_auth skip_question_prompt_path(@question.id, @prompt_id, :format => 'xml') | |
| 38 | + post_auth skip_question_prompt_path(@question.id, @picked_prompt_id) | |
| 39 | 39 | response.should be_success |
| 40 | 40 | response.should have_tag "skip", 1 |
| 41 | 41 | end |
| ... | ... | @@ -48,7 +48,7 @@ describe "Prompts" do |
| 48 | 48 | :skip_reason => "bar", |
| 49 | 49 | :appearance_lookup => @appearance_id, |
| 50 | 50 | :time_viewed => 47 } } |
| 51 | - post_auth skip_question_prompt_path(@question, @prompt_id, :format => 'xml'), params | |
| 51 | + post_auth skip_question_prompt_path(@question, @picked_prompt_id), params | |
| 52 | 52 | response.should be_success |
| 53 | 53 | response.should have_tag "skip", 1 |
| 54 | 54 | response.should have_tag "skip appearance-id", @appearance_id.to_s |
| ... | ... | @@ -65,7 +65,7 @@ describe "Prompts" do |
| 65 | 65 | :with_appearance => true, |
| 66 | 66 | :algorithm => "catchup", |
| 67 | 67 | :with_visitor_stats => true } } |
| 68 | - post_auth skip_question_prompt_path(@question, @prompt_id, :format => 'xml'), params | |
| 68 | + post_auth skip_question_prompt_path(@question, @picked_prompt_id), params | |
| 69 | 69 | response.should be_success |
| 70 | 70 | response.should have_tag "prompt", 1 |
| 71 | 71 | response.should have_tag "prompt appearance_id", /.+/ |
| ... | ... | @@ -73,19 +73,12 @@ describe "Prompts" do |
| 73 | 73 | response.should have_tag "prompt visitor_ideas", /\d+/ |
| 74 | 74 | end |
| 75 | 75 | |
| 76 | - context "when trying to skip another site's questions" do | |
| 77 | - before do | |
| 78 | - @orig_user = @api_user | |
| 79 | - @api_user = Factory(:email_confirmed_user) | |
| 80 | - end | |
| 81 | - | |
| 82 | - it "should fail" do | |
| 83 | - post_auth skip_question_prompt_path(@question.id, @prompt_id, :format => 'xml') | |
| 84 | - response.should_not be_success | |
| 85 | - end | |
| 86 | - | |
| 87 | - after { @api_user = @orig_user } | |
| 76 | + it "should fail when trying to skip another site's questions" do | |
| 77 | + other_user = Factory(:email_confirmed_user) | |
| 78 | + post_auth other_user, skip_question_prompt_path(@question, @picked_prompt_id) | |
| 79 | + response.should_not be_success | |
| 88 | 80 | end |
| 81 | + | |
| 89 | 82 | end |
| 90 | 83 | |
| 91 | 84 | describe "POST 'vote'" do |
| ... | ... | @@ -101,17 +94,17 @@ describe "Prompts" do |
| 101 | 94 | :with_prompt => true, |
| 102 | 95 | :visitor_identifier => @visitor.identifier ) |
| 103 | 96 | @appearance_id = info[:appearance_id] |
| 104 | - @prompt_id = info[:picked_prompt_id] | |
| 97 | + @picked_prompt_id = info[:picked_prompt_id] | |
| 105 | 98 | end |
| 106 | 99 | |
| 107 | 100 | it "should fail without the required 'direction' parameter" do |
| 108 | - post_auth vote_question_prompt_path(@question.id, @prompt_id, :format => 'xml') | |
| 101 | + post_auth vote_question_prompt_path(@question.id, @picked_prompt_id) | |
| 109 | 102 | response.should_not be_success |
| 110 | 103 | end |
| 111 | 104 | |
| 112 | 105 | it "should return a new vote object given no optional parameters" do |
| 113 | 106 | params = { :vote => { :direction => "left" } } |
| 114 | - post_auth vote_question_prompt_path(@question.id, @prompt_id, :format => 'xml'), params | |
| 107 | + post_auth vote_question_prompt_path(@question.id, @picked_prompt_id), params | |
| 115 | 108 | response.should be_success |
| 116 | 109 | response.should have_tag "vote", 1 |
| 117 | 110 | end |
| ... | ... | @@ -124,7 +117,7 @@ describe "Prompts" do |
| 124 | 117 | :direction => "right", |
| 125 | 118 | :appearance_lookup => @appearance_id, |
| 126 | 119 | :time_viewed => 47 } } |
| 127 | - post_auth vote_question_prompt_path(@question, @prompt_id, :format => 'xml'), params | |
| 120 | + post_auth vote_question_prompt_path(@question, @picked_prompt_id), params | |
| 128 | 121 | response.should be_success |
| 129 | 122 | response.should have_tag "vote", 1 |
| 130 | 123 | response.should have_tag "vote appearance-id", @appearance_id.to_s |
| ... | ... | @@ -143,7 +136,7 @@ describe "Prompts" do |
| 143 | 136 | :with_appearance => true, |
| 144 | 137 | :algorithm => "catchup", |
| 145 | 138 | :with_visitor_stats => true } } |
| 146 | - post_auth vote_question_prompt_path(@question, @prompt_id, :format => 'xml'), params | |
| 139 | + post_auth vote_question_prompt_path(@question, @picked_prompt_id), params | |
| 147 | 140 | response.should be_success |
| 148 | 141 | response.should have_tag "prompt", 1 |
| 149 | 142 | response.should have_tag "prompt appearance_id", /.+/ |
| ... | ... | @@ -151,19 +144,11 @@ describe "Prompts" do |
| 151 | 144 | response.should have_tag "prompt visitor_ideas", /\d+/ |
| 152 | 145 | end |
| 153 | 146 | |
| 154 | - context "when trying to vote on another site's questions" do | |
| 155 | - before do | |
| 156 | - @orig_user = @api_user | |
| 157 | - @api_user = Factory(:email_confirmed_user) | |
| 158 | - end | |
| 159 | - | |
| 160 | - it "should fail" do | |
| 161 | - params = { :vote => { :direction => "left" } } | |
| 162 | - post_auth vote_question_prompt_path(@question.id, @prompt_id, :format => 'xml'), params | |
| 163 | - response.should_not be_success | |
| 164 | - end | |
| 165 | - | |
| 166 | - after { @api_user = @orig_user } | |
| 147 | + it "should fail when trying to vote on another site's questions" do | |
| 148 | + other_user = Factory(:email_confirmed_user) | |
| 149 | + params = { :vote => { :direction => "left" } } | |
| 150 | + post_auth other_user, vote_question_prompt_path(@question.id, @picked_prompt_id), params | |
| 151 | + response.should_not be_success | |
| 167 | 152 | end |
| 168 | 153 | |
| 169 | 154 | end | ... | ... |
spec/integration/questions_spec.rb
| ... | ... | @@ -3,29 +3,81 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') |
| 3 | 3 | describe "Questions" do |
| 4 | 4 | include IntegrationSupport |
| 5 | 5 | before do |
| 6 | - 3.times{ Factory.create(:aoi_question, :site => @api_user) } | |
| 6 | + @user = self.default_user = Factory(:email_confirmed_user) | |
| 7 | + @choices = {} | |
| 8 | + @questions = Array.new(5){ Factory(:aoi_question, :site => @user) }.each do |q| | |
| 9 | + @choices[q.id] = Array.new(rand(10)){ Factory(:choice, :question => q, :active => (rand(2)==1)) } | |
| 10 | + end | |
| 7 | 11 | end |
| 8 | 12 | |
| 9 | 13 | describe "GET 'index'" do |
| 10 | 14 | it "should return an array of questions" do |
| 11 | 15 | get_auth questions_path(:format => 'xml') |
| 16 | + response.should be_success | |
| 17 | + response.body.should have_tag("questions question", @questions.size) | |
| 18 | + end | |
| 19 | + | |
| 20 | + it "should not return other users' questions" do | |
| 21 | + other_user = Factory(:email_confirmed_user) | |
| 22 | + other_questions = Array.new(5){ Factory(:aoi_question, :site => other_user) } | |
| 23 | + | |
| 24 | + get_auth other_user, questions_path(:format => 'xml') | |
| 25 | + | |
| 26 | + response.should be_success | |
| 27 | + response.body.should have_tag "questions question site-id", :count => 5, :text => other_user.id.to_s | |
| 28 | + response.body.should_not have_tag "site-id", @user.id.to_s | |
| 29 | + end | |
| 30 | + | |
| 31 | + it "should return a list of questions for a specific creator" do | |
| 32 | + 3.times{ Factory(:aoi_question, | |
| 33 | + :site => @user, | |
| 34 | + :local_identifier => "jim") } | |
| 35 | + | |
| 36 | + get_auth questions_path(:format => 'xml'), {:creator => "jim"} | |
| 37 | + response.should be_success | |
| 12 | 38 | response.body.should have_tag("questions question", 3) |
| 39 | + response.body.should have_tag("questions question local-identifier", "jim") | |
| 40 | + end | |
| 41 | + | |
| 42 | + it "should calculate the total number of user-submitted choices" do | |
| 43 | + get_auth questions_path(:format => 'xml'), :user_ideas => true | |
| 44 | + | |
| 13 | 45 | response.should be_success |
| 46 | + response.body.should have_tag("question", @questions.size) | |
| 47 | + @choices.each_value do |cs| | |
| 48 | + response.body.should have_tag("user-ideas", :text => cs.size) | |
| 49 | + end | |
| 14 | 50 | end |
| 15 | 51 | |
| 16 | - context "when calling index as another user" do | |
| 17 | - before do | |
| 18 | - @orig_user = @api_user | |
| 19 | - @api_user = Factory(:email_confirmed_user) | |
| 52 | + it "should calculate the number of active user-submitted choices" do | |
| 53 | + get_auth questions_path(:format => 'xml'), :active_user_ideas => true | |
| 54 | + | |
| 55 | + response.should be_success | |
| 56 | + response.body.should have_tag("question", @questions.size) | |
| 57 | + @choices.each_value do |cs| | |
| 58 | + count = cs.select{|c| c.active}.size | |
| 59 | + response.body.should have_tag "active-user-ideas", :text => count | |
| 20 | 60 | end |
| 21 | - | |
| 22 | - it "should not return the questions of the original user" do | |
| 23 | - get_auth questions_path(:format => 'xml') | |
| 24 | - response.should be_success | |
| 25 | - response.body.should_not have_tag("question") | |
| 61 | + end | |
| 62 | + | |
| 63 | + it "should calculate the number of votes submitted since some date" do | |
| 64 | + votes = {} | |
| 65 | + @questions.each do |q| | |
| 66 | + votes[q.id] = Array.new(20) do | |
| 67 | + Factory(:vote, :question => q, :created_at => rand(365).days.ago) | |
| 68 | + end | |
| 69 | + end | |
| 70 | + date = rand(365).days.ago | |
| 71 | + get_auth questions_path(:format => 'xml'), :votes_since => date.strftime("%Y-%m-%d") | |
| 72 | + | |
| 73 | + response.should be_success | |
| 74 | + response.body.should have_tag("question", @questions.size) | |
| 75 | + votes.each_value do |vs| | |
| 76 | + count = vs.select{|v| v.created_at > date}.size | |
| 77 | + response.body.should have_tag"recent-votes", :text => count | |
| 26 | 78 | end |
| 27 | - after { @api_user = @orig_user } | |
| 28 | 79 | end |
| 80 | + | |
| 29 | 81 | end |
| 30 | 82 | |
| 31 | 83 | describe "GET 'new'" do |
| ... | ... | @@ -91,7 +143,7 @@ describe "Questions" do |
| 91 | 143 | |
| 92 | 144 | it "should fail given invalid parameters" do |
| 93 | 145 | params = { :type => "ideas", :response_type => "foo", :redisk_key => "bar" } |
| 94 | - post_auth export_question_path(@question, :format => 'xml') | |
| 146 | + post_auth export_question_path(@question) | |
| 95 | 147 | response.should be_success |
| 96 | 148 | response.body.should =~ /Error/ |
| 97 | 149 | end |
| ... | ... | @@ -108,7 +160,7 @@ describe "Questions" do |
| 108 | 160 | before { @question = Factory.create(:aoi_question, :site => @api_user) } |
| 109 | 161 | |
| 110 | 162 | it "should succeed given no optional parameters" do |
| 111 | - get_auth question_path(@question, :format => 'xml') | |
| 163 | + get_auth question_path(@question) | |
| 112 | 164 | response.should be_success |
| 113 | 165 | response.should have_tag "question", 1 |
| 114 | 166 | response.should have_tag "question id", @question.id.to_s |
| ... | ... | @@ -121,7 +173,7 @@ describe "Questions" do |
| 121 | 173 | :with_prompt => true, |
| 122 | 174 | :with_appearance => true, |
| 123 | 175 | :with_visitor_stats => true } |
| 124 | - get_auth question_path(@question, :format => 'xml'), params | |
| 176 | + get_auth question_path(@question), params | |
| 125 | 177 | response.should be_success |
| 126 | 178 | response.should have_tag "question", 1 |
| 127 | 179 | response.should have_tag "question id", @question.id.to_s |
| ... | ... | @@ -134,22 +186,15 @@ describe "Questions" do |
| 134 | 186 | it "should fail if 'with_prompt' is set but 'visitor_identifier' not provided" do |
| 135 | 187 | pending("figure out argument dependencies") do |
| 136 | 188 | params = { :with_prompt => true } |
| 137 | - get_auth question_path(@question, :format => 'xml'), params | |
| 189 | + get_auth question_path(@question), params | |
| 138 | 190 | response.should_not be_success |
| 139 | 191 | end |
| 140 | 192 | end |
| 141 | 193 | |
| 142 | - context "GET 'show' trying to view others sites' questions" do | |
| 143 | - before do | |
| 144 | - @orig_user = @api_user | |
| 145 | - @api_user = Factory(:email_confirmed_user) | |
| 146 | - end | |
| 147 | - | |
| 148 | - it "should fail" do | |
| 149 | - get_auth question_path(@question, :format => 'xml') | |
| 150 | - response.should_not be_success | |
| 151 | - end | |
| 152 | - after { @api_user = @orig_user } | |
| 194 | + it "should fail when trying to view other sites' questions" do | |
| 195 | + other_user = Factory(:email_confirmed_user) | |
| 196 | + get_auth other_user, question_path(@question) | |
| 197 | + response.should_not be_success | |
| 153 | 198 | end |
| 154 | 199 | end |
| 155 | 200 | |
| ... | ... | @@ -163,38 +208,32 @@ describe "Questions" do |
| 163 | 208 | :information => "foo", |
| 164 | 209 | :name => "bar", |
| 165 | 210 | :local_identifier => "baz" } } |
| 166 | - put_auth question_path(@question, :format => 'xml'), params | |
| 211 | + put_auth question_path(@question), params | |
| 167 | 212 | response.should be_success |
| 168 | 213 | end |
| 169 | 214 | |
| 170 | 215 | it "should not be able to change the site id" do |
| 171 | 216 | original_site_id = @question.site_id |
| 172 | 217 | params = { :question => { :site_id => -1 } } |
| 173 | - put_auth question_path(@question, :format => 'xml'), params | |
| 218 | + put_auth question_path(@question), params | |
| 174 | 219 | @question.reload.site_id.should == original_site_id |
| 175 | 220 | end |
| 176 | 221 | |
| 177 | 222 | it "should ignore protected attributes" do |
| 178 | 223 | params = { :question => { :votes_count => 999 } } |
| 179 | - put_auth question_path(@question, :format => 'xml'), params | |
| 224 | + put_auth question_path(@question), params | |
| 180 | 225 | response.should be_success |
| 181 | - @question.reload.site_id.should_not == 999 | |
| 226 | + @question.reload.votes_count.should_not == 999 | |
| 182 | 227 | end |
| 183 | 228 | |
| 184 | - context "when updatng another site's question" do | |
| 185 | - before do | |
| 186 | - @orig_user = @api_user | |
| 187 | - @api_user = Factory(:email_confirmed_user) | |
| 188 | - end | |
| 229 | + it "should fail when updating another site's question" do | |
| 230 | + other_user = Factory(:email_confirmed_user) | |
| 231 | + params = { :question => { :name => "foo" } } | |
| 232 | + put_auth other_user, question_path(@question), params | |
| 233 | + response.should_not be_success | |
| 234 | + end | |
| 189 | 235 | |
| 190 | - it "should fail" do | |
| 191 | - params = { :question => { :name => "foo" } } | |
| 192 | - put_auth question_path(@question, :format => 'xml'), params | |
| 193 | - response.should_not be_success | |
| 194 | - end | |
| 195 | 236 | |
| 196 | - after { @api_user = @orig_user } | |
| 197 | - end | |
| 198 | 237 | end |
| 199 | 238 | |
| 200 | 239 | describe "GET 'all_object_info_totals_by_date'" do | ... | ... |
spec/integration/visitors_spec.rb
| ... | ... | @@ -2,7 +2,200 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') |
| 2 | 2 | |
| 3 | 3 | describe "Visitors" do |
| 4 | 4 | include IntegrationSupport |
| 5 | + | |
| 6 | + before do | |
| 7 | + @user = self.default_user = Factory(:email_confirmed_user) | |
| 8 | + @visitors = @user.visitors << Array.new(30){ Factory(:visitor, :site => @user) } | |
| 9 | + @questions = Array.new(3){ Factory(:aoi_question, :site => @user, :creator => @visitors.rand) } | |
| 10 | + end | |
| 11 | + | |
| 12 | + describe "GET 'index'" do | |
| 13 | + it "should return an array of visitors" do | |
| 14 | + get_auth visitors_path | |
| 15 | + response.should be_success | |
| 16 | + response.body.should have_tag("visitors visitor", @visitors.size) | |
| 17 | + end | |
| 18 | + | |
| 19 | + it "should not return other sites' visitors" do | |
| 20 | + other_user = Factory(:email_confirmed_user) | |
| 21 | + other_visitors = other_user.visitors << Array.new(10) do | |
| 22 | + Factory(:visitor, :site => other_user) | |
| 23 | + end | |
| 24 | + get_auth other_user, visitors_path | |
| 25 | + | |
| 26 | + response.should be_success | |
| 27 | + response.body.should have_tag("visitors visitor", other_visitors.size) | |
| 28 | + end | |
| 29 | + | |
| 30 | + it "should return the number of votes for each visitor" do | |
| 31 | + counts = Hash.new(0) | |
| 32 | + 20.times do | |
| 33 | + visitor = @visitors.rand | |
| 34 | + Factory(:vote, :question => @questions.rand, :voter => visitor) | |
| 35 | + counts[visitor.id] += 1 | |
| 36 | + end | |
| 37 | + get_auth visitors_path, :votes_count => true | |
| 38 | + | |
| 39 | + response.should be_success | |
| 40 | + response.should have_tag "visitor", counts.size do |nodes| | |
| 41 | + nodes.each do |node| | |
| 42 | + id = node.content("id").to_i | |
| 43 | + node.should have_tag("id"), :text => id | |
| 44 | + node.should have_tag("votes-count"), :text => counts[id] | |
| 45 | + end | |
| 46 | + end | |
| 47 | + end | |
| 48 | + | |
| 49 | + it "should return the number of skips for each visitor" do | |
| 50 | + counts = Hash.new(0) | |
| 51 | + 20.times do | |
| 52 | + visitor = @visitors.rand | |
| 53 | + Factory(:skip, :question => @questions.rand, :skipper => visitor) | |
| 54 | + counts[visitor.id] += 1 | |
| 55 | + end | |
| 56 | + get_auth visitors_path, :skips_count => true | |
| 57 | + | |
| 58 | + response.should be_success | |
| 59 | + response.should have_tag "visitor", counts.size do |nodes| | |
| 60 | + nodes.each do |node| | |
| 61 | + id = node.content("id").to_i | |
| 62 | + node.should have_tag("id"), :text => id | |
| 63 | + node.should have_tag("skips-count"), :text => counts[id] | |
| 64 | + end | |
| 65 | + end | |
| 66 | + end | |
| 67 | + | |
| 68 | + it "should return the number of user-submitted choices" do | |
| 69 | + 10.times do | |
| 70 | + question = @questions.rand | |
| 71 | + creator = question.creator | |
| 72 | + Factory(:choice, :question => question, :creator => creator) | |
| 73 | + end | |
| 74 | + counts = Hash.new(0) | |
| 75 | + 10.times do | |
| 76 | + question = @questions.rand | |
| 77 | + creator = (@visitors - [question.creator]).rand | |
| 78 | + counts[creator.id] += 1 | |
| 79 | + Factory(:choice, :question => question, :creator => creator) | |
| 80 | + end | |
| 81 | + get_auth visitors_path :ideas_count => true | |
| 82 | + | |
| 83 | + response.should be_success | |
| 84 | + response.should have_tag "visitor", counts.size do |nodes| | |
| 85 | + nodes.each do |node| | |
| 86 | + id = node.content("id").to_i | |
| 87 | + node.should have_tag("id"), :text => id | |
| 88 | + node.should have_tag("ideas-count"), :text => counts[id] | |
| 89 | + end | |
| 90 | + end | |
| 91 | + end | |
| 92 | + | |
| 93 | + it "should show which visitors are bounces" do | |
| 94 | + bounce = {} | |
| 95 | + @visitors.each do |v| | |
| 96 | + if [true,false].rand | |
| 97 | + Factory(:appearance, :question => @questions.rand, :voter => v) | |
| 98 | + bounce[v.id] = 1 | |
| 99 | + else | |
| 100 | + vote = Factory(:vote, :question => @questions.rand, :voter => v) | |
| 101 | + Factory(:appearance, :question => @questions.rand, | |
| 102 | + :voter => v, :answerable => vote) | |
| 103 | + end | |
| 104 | + end | |
| 105 | + get_auth visitors_path, :bounces => true | |
| 106 | + | |
| 107 | + response.should be_success | |
| 108 | + response.should have_tag "visitor", bounce.size do |nodes| | |
| 109 | + nodes.each do |node| | |
| 110 | + id = node.content("id").to_i | |
| 111 | + node.should have_tag "id", :text => id | |
| 112 | + node.should have_tag "bounces", :text => 1 | |
| 113 | + end | |
| 114 | + end | |
| 115 | + end | |
| 116 | + | |
| 117 | + it "should return the number of questions created for each visitor" do | |
| 118 | + count = @visitors.inject({}) do |h,v| | |
| 119 | + n = @questions.select{ |q| q.creator == v }.size | |
| 120 | + h[v.id] = n unless n.zero? | |
| 121 | + h | |
| 122 | + end | |
| 123 | + get_auth visitors_path, :questions_created => true | |
| 124 | + | |
| 125 | + response.should be_success | |
| 126 | + response.should have_tag "visitor", count.size do |nodes| | |
| 127 | + nodes.each do |node| | |
| 128 | + id = node.content("id").to_i | |
| 129 | + node.should have_tag "id", :text => id | |
| 130 | + node.should have_tag "questions-created", :text => count[id] | |
| 131 | + end | |
| 132 | + end | |
| 133 | + end | |
| 134 | + | |
| 135 | + it "should return the visitor counts for a single question" do | |
| 136 | + votes, skips, choices = Array.new(3){ Hash.new(0) } | |
| 137 | + the_question = @questions.rand | |
| 138 | + 20.times do | |
| 139 | + question = @questions.rand | |
| 140 | + visitor = (@visitors - [question.creator]).rand | |
| 141 | + case rand(3) | |
| 142 | + when 0 then | |
| 143 | + Factory(:vote, :question => question, :voter => visitor) | |
| 144 | + votes[visitor.id] += 1 if question == the_question | |
| 145 | + when 1 then | |
| 146 | + Factory(:skip, :question => question, :skipper => visitor) | |
| 147 | + skips[visitor.id] += 1 if question == the_question | |
| 148 | + when 2 then | |
| 149 | + Factory(:choice, :question => question, :creator => visitor) | |
| 150 | + choices[visitor.id] += 1 if question == the_question | |
| 151 | + end | |
| 152 | + end | |
| 153 | + visitors = (votes.keys | skips.keys | choices.keys) | |
| 154 | + | |
| 155 | + get_auth visitors_path, { | |
| 156 | + :votes_count => true, | |
| 157 | + :skips_count => true, | |
| 158 | + :ideas_count => true, | |
| 159 | + :question_id => the_question.id | |
| 160 | + } | |
| 161 | + | |
| 162 | + response.should be_success | |
| 163 | + response.should have_tag "visitor", visitors.size do |nodes| | |
| 164 | + nodes.each do |node| | |
| 165 | + id = node.content("id").to_i | |
| 166 | + node.should have_tag "id", :text => id | |
| 167 | + node.should have_tag "votes-count", :text => votes[id] | |
| 168 | + node.should have_tag "skips-count", :text => skips[id] | |
| 169 | + node.should have_tag "ideas-count", :text => choices[id] | |
| 170 | + end | |
| 171 | + end | |
| 172 | + end | |
| 173 | + | |
| 174 | + it "should return the bounces for a single question" do | |
| 175 | + the_question = @questions.rand | |
| 176 | + bounces = @visitors.inject({}) do |h,v| | |
| 177 | + if v.id.odd? # bounce! | |
| 178 | + question = @questions.rand | |
| 179 | + Factory(:appearance, :question => question, :voter => v) | |
| 180 | + h[v.id] = 1 if question == the_question | |
| 181 | + else # appearance w/ answerable | |
| 182 | + vote = Factory(:vote, :question => @questions.rand, :voter => v) | |
| 183 | + Factory(:appearance, :question => @questions.rand, :voter => v, :answerable => vote) | |
| 184 | + end | |
| 185 | + h | |
| 186 | + end | |
| 187 | + | |
| 188 | + get_auth visitors_path, :bounces => true, :question_id => the_question.id | |
| 189 | + response.should be_success | |
| 190 | + response.should have_tag "visitor", bounces.size do |nodes| | |
| 191 | + nodes.each do |node| | |
| 192 | + id = node.content("id").to_i | |
| 193 | + node.should have_tag "id", :text => id | |
| 194 | + node.should have_tag "bounces", :text => bounces[id] | |
| 195 | + end | |
| 196 | + end | |
| 197 | + end | |
| 198 | + end | |
| 5 | 199 | |
| 6 | - describe "index" | |
| 7 | - | |
| 200 | + | |
| 8 | 201 | end | ... | ... |
spec/support/integration_support.rb
| 1 | 1 | module IntegrationSupport |
| 2 | 2 | |
| 3 | + @@default_user = nil | |
| 4 | + | |
| 3 | 5 | # todo: make automatically included in integration tests |
| 4 | 6 | Spec::Runner.configure do |config| |
| 5 | 7 | config.before(:each, :type => :integration) do |
| 6 | - @api_user = Factory(:email_confirmed_user) | |
| 8 | + # compatibility with old tests using @api_user, remove this | |
| 9 | + @api_user = self.default_user = Factory(:email_confirmed_user) | |
| 7 | 10 | end |
| 8 | 11 | end |
| 9 | 12 | |
| 10 | - def get_auth(path, parameters = {}, headers = {}) | |
| 11 | - auth_wrap(:get, path, parameters, headers) | |
| 13 | + def default_user=(user) | |
| 14 | + @api_user = @@default_user = user | |
| 12 | 15 | end |
| 13 | 16 | |
| 14 | - def put_auth(path, parameters = {}, headers = {}) | |
| 15 | - auth_wrap(:put, path, parameters, headers) | |
| 17 | + # generate _auth variation of get/put/post, etc. to automatically | |
| 18 | + # send requests with the authentication and accept headers set | |
| 19 | + %w(get put post delete head).each do |method| | |
| 20 | + define_method(method + "_auth") do |*args| | |
| 21 | + if args[0].is_a? User | |
| 22 | + user, path, parameters, headers, *ignored = *args | |
| 23 | + else | |
| 24 | + path, parameters, headers, *ignored = *args | |
| 25 | + end | |
| 26 | + | |
| 27 | + user ||= @@default_user | |
| 28 | + raise ArgumentError, "No user provided and default user not set" unless user | |
| 29 | + | |
| 30 | + auth = ActionController::HttpAuthentication:: | |
| 31 | + Basic.encode_credentials(user.email, user.password) | |
| 32 | + (headers ||= {}).merge!( :authorization => auth, | |
| 33 | + :accept => "application/xml" ) | |
| 34 | + | |
| 35 | + send(method, path, parameters, headers) | |
| 36 | + end | |
| 16 | 37 | end |
| 17 | 38 | |
| 18 | - def post_auth(path, parameters = {}, headers = {} ) | |
| 19 | - auth_wrap(:post, path, parameters, headers) | |
| 39 | + # need a way to easily fetch content of a Tag | |
| 40 | + class HTML::Tag | |
| 41 | + def content(tag) | |
| 42 | + n = self.find(:tag => tag) or return nil | |
| 43 | + n.children.each{ |c| return c.content if c.is_a? HTML::Text } | |
| 44 | + nil | |
| 45 | + end | |
| 20 | 46 | end |
| 21 | 47 | |
| 22 | - def delete_auth(path, parameters = {}, headers = {}) | |
| 23 | - auth_wrap(:delete, path, parameters, headers) | |
| 24 | - end | |
| 25 | 48 | |
| 26 | - def head_auth(path, parameters = {}, headers = {}) | |
| 27 | - auth_wrap(:head, path, parameters, headers) | |
| 28 | - end | |
| 29 | - | |
| 30 | - private | |
| 31 | - def auth_wrap(method, path, parameters, headers) | |
| 32 | - return nil unless [:get, :put, :post, :delete, :head].include? method | |
| 33 | - | |
| 34 | - auth = ActionController::HttpAuthentication::Basic.encode_credentials(@api_user.email, @api_user.password) | |
| 35 | - headers.merge!(:authorization => auth) | |
| 36 | - # headers.merge!(:content_type => "application/xml", :authorization => auth) | |
| 37 | - # parameters.merge!(:format => 'xml') | |
| 49 | + # have_tag doesn't let you iterate over individual nodes like | |
| 50 | + # assert_select does for some reason, and using css matchers | |
| 51 | + # to do this is ugly. Time for a patch! | |
| 52 | + class Spec::Rails::Matchers::AssertSelect | |
| 53 | + def doc_from_with_node(node) | |
| 54 | + return node if node.is_a? HTML::Node | |
| 55 | + doc_from_without_node(node) | |
| 56 | + end | |
| 38 | 57 | |
| 39 | - send(method, path, parameters, headers) | |
| 58 | + alias_method_chain :doc_from, :node | |
| 40 | 59 | end |
| 60 | + | |
| 61 | + | |
| 41 | 62 | end |
| 42 | 63 | ... | ... |