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 | ... | ... |