saucy 0.14.3 → 0.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/Gemfile +2 -1
  3. data/Gemfile.lock +60 -58
  4. data/app/controllers/accounts_controller.rb +0 -1
  5. data/app/controllers/admin/trial_extensions_controller.rb +13 -0
  6. data/app/views/admin/accounts/show.html.erb +13 -0
  7. data/config/routes.rb +1 -0
  8. data/features/run_features.feature +3 -2
  9. data/lib/generators/saucy/features/templates/features/manage_billing.feature +12 -7
  10. data/lib/generators/saucy/features/templates/features/manage_plan.feature +12 -6
  11. data/lib/generators/saucy/features/templates/features/new_account.feature +4 -9
  12. data/lib/generators/saucy/features/templates/features/sign_up.feature +1 -1
  13. data/lib/generators/saucy/features/templates/features/sign_up_coupon.feature +1 -2
  14. data/lib/generators/saucy/features/templates/features/sign_up_paid.feature +1 -1
  15. data/lib/generators/saucy/features/templates/features/trial_plans.feature +1 -1
  16. data/lib/saucy/account.rb +8 -0
  17. data/lib/saucy/projects_controller.rb +2 -5
  18. data/lib/saucy/subscription.rb +5 -1
  19. data/lib/saucy/version.rb +1 -1
  20. data/saucy.gemspec +2 -2
  21. data/spec/controllers/accounts_controller_spec.rb +9 -9
  22. data/spec/controllers/admin/trial_extensions_controller_spec.rb +67 -0
  23. data/spec/controllers/invitations_controller_spec.rb +13 -7
  24. data/spec/controllers/memberships_controller_spec.rb +10 -7
  25. data/spec/controllers/profiles_controller_spec.rb +8 -2
  26. data/spec/controllers/projects_controller_spec.rb +20 -16
  27. data/spec/environment.rb +3 -3
  28. data/spec/models/account_spec.rb +29 -0
  29. data/spec/models/subscription_spec.rb +33 -11
  30. data/spec/support/clearance.rb +1 -0
  31. data/spec/views/accounts/show.html.erb_spec.rb +41 -0
  32. metadata +59 -14
  33. data/spec/support/clearance_matchers.rb +0 -55
@@ -29,4 +29,4 @@ Feature: Sign up
29
29
  And I fill in "Email" with "email@person.com"
30
30
  And I fill in "Password" with "password"
31
31
  And I press "Sign up"
32
- Then I should see "created"
32
+ Then I should be on the new project page for the newest account by "email@person.com"
@@ -18,7 +18,7 @@ Feature: Sign up with coupon
18
18
  When I fill in "Email" with "email@person.com"
19
19
  And I fill in "Password" with "password"
20
20
  And I press "Sign up"
21
- Then I should see "created"
21
+ Then I should be on the new project page for the newest account by "email@person.com"
22
22
 
23
23
  Scenario: Receive a reminder about an expiring trial plan with coupon
24
24
  Given a "Free" account with "RAMEN" coupon exists with a name of "Test" created 83 days ago
@@ -28,4 +28,3 @@ Feature: Sign up with coupon
28
28
  And I sign in as "admin@example.com"
29
29
  And I follow the link sent to "admin@example.com" with subject "Your trial is expiring soon"
30
30
  Then I should be on the upgrade plan page for the "Test" account
31
-
@@ -38,7 +38,7 @@ Feature: Sign up
38
38
  And I fill in "ZIP or Postal Code" with "02110"
39
39
  And I select "United States of America" from "Country"
40
40
  And I press "Sign up"
41
- Then I should see "created"
41
+ Then I should be on the new project page for the newest account by "email@person.com"
42
42
 
43
43
  Scenario: User signs up for a paid plan with an invalid credit card number
44
44
  Given that the credit card "4111112" is invalid
@@ -20,7 +20,7 @@ Feature: Trial plans
20
20
  When I fill in "Email" with "email@person.com"
21
21
  And I fill in "Password" with "password"
22
22
  And I press "Sign up"
23
- Then I should see "created"
23
+ Then I should be on the new project page for the newest account by "email@person.com"
24
24
 
25
25
  Scenario: use an account during the trial
26
26
  Given a "Temp" account exists with a name of "Test" created 29 days ago
@@ -83,6 +83,14 @@ module Saucy
83
83
  self.trial_expires_at = number_free_days.days.from_now(created_at || Time.zone.now)
84
84
  end
85
85
 
86
+ def reset_trial_expiration(length_in_days = nil)
87
+ new_length = length_in_days || Saucy::Configuration.trial_length
88
+ new_trial_start_date = [trial_expires_at, Time.zone.now].max
89
+ self.trial_expires_at = new_length.days.from_now(new_trial_start_date)
90
+ self.notified_of_completed_trial = false
91
+ self.notified_of_expiration = false
92
+ end
93
+
86
94
  def record_new_activations
87
95
  if activated_changed? && activated?
88
96
  Saucy::Notifications.notify_observers("activated", :account => self)
@@ -24,7 +24,6 @@ module Saucy
24
24
  def create
25
25
  @project = current_account.projects.build(params[:project])
26
26
  if @project.save
27
- flash[:notice] = "Project successfully created"
28
27
  redirect_to project_url(@project)
29
28
  else
30
29
  render :action => :new
@@ -40,8 +39,7 @@ module Saucy
40
39
  @project = current_project
41
40
  set_project_account_if_moving
42
41
  if @project.update_attributes params[:project]
43
- flash[:success] = 'Project was updated.'
44
- redirect_to account_projects_url(@project.account)
42
+ redirect_to account_projects_url(@project.account), :notice => 'Project was updated.'
45
43
  else
46
44
  render :action => :edit
47
45
  end
@@ -53,8 +51,7 @@ module Saucy
53
51
 
54
52
  def destroy
55
53
  current_project.destroy
56
- flash[:success] = "Project has been deleted"
57
- redirect_to account_projects_url(current_project.account)
54
+ redirect_to account_projects_url(current_project.account), :notice => "Project has been deleted"
58
55
  end
59
56
 
60
57
  def index
@@ -223,7 +223,7 @@ module Saucy
223
223
  Airbrake.notify(nil,
224
224
  :error_class => "handle_errors",
225
225
  :error_message => "handle_errors",
226
- :parameters => { :result => result, :remote_errors => remote_errors }
226
+ :parameters => { :result => result.inspect, :remote_errors => remote_errors.inspect }
227
227
  )
228
228
  if result && result.status == "processor_declined"
229
229
  errors[:card_number] << "was denied by the payment processor with the message: #{result.processor_response_text}"
@@ -239,8 +239,12 @@ module Saucy
239
239
  errors[:expiration_month] << error.message.gsub("Expiration month ", "")
240
240
  elsif error.attribute == "expiration_year"
241
241
  errors[:expiration_year] << error.message.gsub("Expiration year ", "")
242
+ else
243
+ errors[:card_number] << error.message
242
244
  end
243
245
  end
246
+ else
247
+ errors[:card_number] << "there was an unknown status"
244
248
  end
245
249
  end
246
250
 
@@ -1,3 +1,3 @@
1
1
  module Saucy
2
- VERSION = '0.14.3'
2
+ VERSION = '0.14.5'
3
3
  end
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
18
18
  s.email = %q{support@thoughtbot.com}
19
19
  s.homepage = "http://github.com/thoughtbot/saucy"
20
20
 
21
- s.add_dependency('clearance', '~> 0.11.2')
22
- s.add_dependency('formtastic', '>= 1.2')
21
+ s.add_dependency('clearance', '~> 0.14.0')
22
+ s.add_dependency('formtastic', '~> 1.2.4')
23
23
  s.add_dependency('railties', '>= 3.0.3')
24
24
  s.add_dependency('braintree', '>= 2.6.2')
25
25
  s.add_dependency('sham_rack', '1.3.3')
@@ -66,8 +66,6 @@ describe AccountsController, "successful create for a confirmed user" do
66
66
  signup.should have_received(:user=).with(user)
67
67
  end
68
68
 
69
- it { should set_the_flash.to(/created/i) }
70
- it { should_not set_the_flash.to(/confirm/i) }
71
69
  it { should be_signed_in.as(user) }
72
70
 
73
71
  it "notifies observers" do
@@ -223,11 +221,13 @@ describe AccountsController, "destroy", :as => :account_admin do
223
221
  end
224
222
 
225
223
  describe AccountsController, "permissions", :as => :account_member do
226
- it { should deny_access.
227
- on(:get, :edit, :id => account.to_param).
228
- flash(/admin/) }
229
- it { should deny_access.
230
- on(:put, :update, :id => account.to_param).
231
- flash(/admin/) }
232
- end
224
+ context "get to account edit" do
225
+ before { get :edit, :id => account.to_param }
226
+ it { should deny_access :flash => "You must be an admin to access that page." }
227
+ end
233
228
 
229
+ context "put to account update" do
230
+ before { put :update, :id => account.to_param }
231
+ it { should deny_access :flash => "You must be an admin to access that page." }
232
+ end
233
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Admin::TrialExtensionsController, "routes" do
4
+ it { should route(:post, "/admin/accounts/test/trial_extensions").to(:action => :create, :account_id => 'test') }
5
+ end
6
+
7
+ describe Admin::TrialExtensionsController, "post create without http auth" do
8
+ before { post :create, :account_id => "test" }
9
+ it { should respond_with(:unauthorized) }
10
+ end
11
+
12
+ describe Admin::TrialExtensionsController, "post create with http auth" do
13
+ let(:account) { Factory(:account, :keyword => 'test') }
14
+ before do
15
+ http_basic_auth_sign_in 'admin', 'admin'
16
+ post :create, :account_id => account.keyword
17
+ end
18
+ it { should redirect_to(admin_account_path(account.keyword)) }
19
+ end
20
+
21
+ describe Admin::TrialExtensionsController, "extending an unexpired trial" do
22
+ let(:plan) { Factory(:plan, :trial => true) }
23
+ let(:account) { Factory(:account, :keyword => 'test', :plan => plan) }
24
+ let!(:original_trial_expiration_date) { account.trial_expires_at }
25
+ before do
26
+ http_basic_auth_sign_in 'admin', 'admin'
27
+ post :create, :account_id => account.keyword
28
+ account.reload
29
+ end
30
+ it "extends the trial by the standard saucy trial length" do
31
+ difference_in_dates = (account.trial_expires_at - original_trial_expiration_date)
32
+ difference_in_dates.should == Saucy::Configuration.trial_length.days
33
+ end
34
+ end
35
+
36
+ describe Admin::TrialExtensionsController, "extending an expired trial" do
37
+ let(:plan) { Factory(:plan, :trial => true) }
38
+ let(:account) { Factory(:account, :keyword => 'test', :plan => plan) }
39
+ before do
40
+ Timecop.freeze(1.day.ago.beginning_of_day) do
41
+ account.update_attribute(:trial_expires_at, 60.days.ago)
42
+ http_basic_auth_sign_in 'admin', 'admin'
43
+ post :create, :account_id => account.keyword
44
+ account.reload
45
+ end
46
+ end
47
+ it "extends the trial out from today by the standard saucy trial length" do
48
+ Timecop.freeze(1.day.ago.beginning_of_day) do
49
+ difference_in_dates = (account.trial_expires_at - Time.zone.now)
50
+ difference_in_dates.should == Saucy::Configuration.trial_length.days
51
+ end
52
+ end
53
+ end
54
+
55
+ describe Admin::TrialExtensionsController, "extending an invalid account" do
56
+ let(:plan) { Factory(:plan, :trial => true) }
57
+ let(:account) { Factory(:account, :keyword => 'test', :plan => plan) }
58
+ before do
59
+ account.update_attribute(:keyword, '!@#$%')
60
+
61
+ http_basic_auth_sign_in 'admin', 'admin'
62
+ post :create, :account_id => account.keyword
63
+ account.reload
64
+ end
65
+ it { should render_template('admin/accounts/show') }
66
+ it { should set_the_flash.to(/cannot/i) }
67
+ end
@@ -13,13 +13,19 @@ end
13
13
 
14
14
  describe InvitationsController, "permissions" do
15
15
  let(:account) { Factory(:account) }
16
- before { sign_in }
17
- it { should deny_access.
18
- on(:get, :new, :account_id => account.to_param).
19
- flash(/admin/) }
20
- it { should deny_access.
21
- on(:post, :create, :account_id => account.to_param).
22
- flash(/admin/) }
16
+ before do
17
+ sign_in
18
+ end
19
+
20
+ context "on get to new" do
21
+ before { get :new, :account_id => account.to_param }
22
+ it { should deny_access :flash => "You must be an admin to access that page." }
23
+ end
24
+
25
+ context "on post to create" do
26
+ before { post :create, :account_id => account.to_param }
27
+ it { should deny_access :flash => "You must be an admin to access that page." }
28
+ end
23
29
  end
24
30
 
25
31
  describe InvitationsController, "new", :as => :account_admin do
@@ -13,13 +13,16 @@ end
13
13
 
14
14
  describe MembershipsController, "permissions", :as => :account_member do
15
15
  let(:membership) { Factory(:membership, :account => account) }
16
- it { should deny_access.
17
- on(:get, :index, :account_id => account.to_param).
18
- flash(/admin/) }
19
- it { should deny_access.
20
- on(:get, :edit, :id => membership.to_param,
21
- :account_id => account.to_param).
22
- flash(/admin/) }
16
+
17
+ context "on get to index" do
18
+ before { get :index, :account_id => account.to_param }
19
+ it { should deny_access :flash => "You must be an admin to access that page." }
20
+ end
21
+
22
+ context "on get to edit" do
23
+ before { get :edit, :id => membership.to_param, :account_id => account.to_param }
24
+ it { should deny_access :flash => "You must be an admin to access that page." }
25
+ end
23
26
  end
24
27
 
25
28
  describe MembershipsController, "index", :as => :account_admin do
@@ -6,8 +6,14 @@ describe ProfilesController, "routes" do
6
6
  end
7
7
 
8
8
  describe ProfilesController, "signed out" do
9
- it { should deny_access.on(:get, :edit) }
10
- it { should deny_access.on(:put, :update) }
9
+ context "on get to edit" do
10
+ before { get :edit }
11
+ it { should deny_access }
12
+ end
13
+ context "on put to update" do
14
+ before { put :update }
15
+ it { should deny_access }
16
+ end
11
17
  end
12
18
 
13
19
  describe ProfilesController, "edit", :as => :user do
@@ -183,22 +183,26 @@ describe ProjectsController, "index", :as => :account_admin do
183
183
  end
184
184
 
185
185
  describe ProjectsController, "as a non-admin", :as => :project_member do
186
- it { should deny_access.on(:get, :edit, :id => project.to_param,
187
- :account_id => account.to_param).
188
- flash(/admin/) }
189
-
190
- it { should deny_access.on(:put, :update, :id => project.to_param,
191
- :account_id => account.to_param).
192
- flash(/admin/) }
193
-
194
- it { should deny_access.on(:delete, :destroy, :id => project.to_param,
195
- :account_id => account.to_param).
196
- flash(/admin/) }
197
-
198
- it { should deny_access.on(:get, :new, :account_id => account.to_param).flash(/admin/) }
199
-
200
- it { should deny_access.on(:post, :create, :account_id => account.to_param).
201
- flash(/admin/) }
186
+ context "on get to edit" do
187
+ before { get :edit, :id => project.to_param, :account_id => account.to_param }
188
+ it { should deny_access :flash => "You must be an admin to access that page." }
189
+ end
190
+ context "on put to update" do
191
+ before { put :update, :id => project.to_param, :account_id => account.to_param }
192
+ it { should deny_access :flash => "You must be an admin to access that page." }
193
+ end
194
+ context "on delete to destroy" do
195
+ before { delete :destroy, :id => project.to_param, :account_id => account.to_param }
196
+ it { should deny_access :flash => "You must be an admin to access that page." }
197
+ end
198
+ context "on get to new" do
199
+ before { get :new, :account_id => account.to_param }
200
+ it { should deny_access :flash => "You must be an admin to access that page." }
201
+ end
202
+ context "on post to create" do
203
+ before { post :create, :account_id => account.to_param }
204
+ it { should deny_access :flash => "You must be an admin to access that page." }
205
+ end
202
206
  end
203
207
 
204
208
  describe ProjectsController, "show for a duplicate project keyword", :as => :project_admin do
@@ -9,6 +9,9 @@ require 'bourne'
9
9
 
10
10
  FileUtils.rm_f(File.join(PROJECT_ROOT, 'tmp', 'test.sqlite3'))
11
11
 
12
+ Clearance.configure do |config|
13
+ end
14
+
12
15
  class ApplicationController < ActionController::Base
13
16
  include Clearance::Authentication
14
17
  include Saucy::AccountAuthorization
@@ -90,9 +93,6 @@ class ClearanceMailer
90
93
  end
91
94
  end
92
95
 
93
- Clearance.configure do |config|
94
- end
95
-
96
96
  ClearanceCreateUsers.suppress_messages { ClearanceCreateUsers.migrate(:up) }
97
97
  CreateSaucyTables.suppress_messages { CreateSaucyTables.migrate(:up) }
98
98
  ActiveRecord::Migrator.up(ActiveRecord::Migrator.migrations_paths)
@@ -133,6 +133,35 @@ describe Account do
133
133
  account.should_not be_expired
134
134
  end
135
135
 
136
+ it "extends the trial period on unexpired accounts" do
137
+ Timecop.freeze do
138
+ plan = Factory.stub(:plan, :trial => true)
139
+ account = Factory(:account, :plan => plan)
140
+ original_expiration = account.trial_expires_at
141
+
142
+ account.reset_trial_expiration
143
+
144
+ (account.trial_expires_at - original_expiration).should == Saucy::Configuration.trial_length.days
145
+ end
146
+ end
147
+
148
+ it "resets the trial period and emails on expired accounts" do
149
+ Timecop.freeze do
150
+ plan = Factory.stub(:plan, :trial => true)
151
+ account = Factory(:account,
152
+ :plan => plan,
153
+ :notified_of_completed_trial => true,
154
+ :notified_of_expiration => true,
155
+ :created_at => 1.year.ago)
156
+
157
+ account.reset_trial_expiration
158
+
159
+ (account.trial_expires_at - Time.zone.now).should == Saucy::Configuration.trial_length.days
160
+ account.notified_of_expiration.should be_false
161
+ account.notified_of_completed_trial.should be_false
162
+ end
163
+ end
164
+
136
165
  context "with expiring accounts" do
137
166
  before do
138
167
  trial = Factory(:plan, :trial => true)
@@ -16,7 +16,7 @@ describe Account do
16
16
  it "manifests braintree processor_declined errors as errors on number and doesn't save" do
17
17
  FakeBraintree.failures["4111111111111112"] = { "message" => "Do Not Honor", "code" => "2000", "status" => "processor_declined" }
18
18
  account = Factory.build(:paid_account,
19
- :card_number => "4111111111111112",
19
+ :card_number => "4111111111111112",
20
20
  :plan => Factory(:paid_plan))
21
21
  account.save.should_not be
22
22
  FakeBraintree.customers.should be_empty
@@ -27,7 +27,7 @@ describe Account do
27
27
  it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
28
28
  FakeBraintree.failures["4111111111111112"] = { "message" => "Gateway Rejected: cvv", "code" => "N", "status" => "gateway_rejected" }
29
29
  account = Factory.build(:paid_account,
30
- :card_number => "4111111111111112",
30
+ :card_number => "4111111111111112",
31
31
  :plan => Factory(:paid_plan))
32
32
  account.save.should_not be
33
33
  FakeBraintree.customers.should be_empty
@@ -38,13 +38,35 @@ describe Account do
38
38
  it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
39
39
  FakeBraintree.failures["4111111111111112"] = { "message" => "Credit card number is invalid.", "errors" => { "customer" => { "errors" => [], "credit-card" => { "errors" => [{ "message" => "Credit card number is invalid.", "code" => 81715, "attribute" => :number }] }}}}
40
40
  account = Factory.build(:paid_account,
41
- :card_number => "4111111111111112",
41
+ :card_number => "4111111111111112",
42
42
  :plan => Factory(:paid_plan))
43
43
  account.save.should_not be
44
44
  FakeBraintree.customers.should be_empty
45
45
  account.persisted?.should_not be
46
46
  account.errors[:card_number].any? { |e| e =~ /is invalid/ }.should be
47
- end
47
+ end
48
+
49
+ it "manifests braintree gateway_rejected unknown errors and doesn't save" do
50
+ FakeBraintree.failures["4111111111111112"] = { "message" => "Credit card number is invalid.", "errors" => { "customer" => { "errors" => [], "credit-card" => { "errors" => [{ "message" => "There was an unkown error.", "code" => 81715, "attribute" => :unknown }] }}}}
51
+ account = Factory.build(:paid_account,
52
+ :card_number => "4111111111111112",
53
+ :plan => Factory(:paid_plan))
54
+ account.save.should_not be
55
+ FakeBraintree.customers.should be_empty
56
+ account.persisted?.should_not be
57
+ account.errors[:card_number].any? { |e| e =~ /There was an unkown error./ }.should be
58
+ end
59
+
60
+ it "manifests braintree unknown status error" do
61
+ FakeBraintree.failures["4111111111111112"] = { "message" => "Do Not Honor", "code" => "2000", "status" => "unknown" }
62
+ account = Factory.build(:paid_account,
63
+ :card_number => "4111111111111112",
64
+ :plan => Factory(:paid_plan))
65
+ account.save.should_not be
66
+ FakeBraintree.customers.should be_empty
67
+ account.persisted?.should_not be
68
+ account.errors[:card_number].any? { |e| e =~ /unknown status/ }.should be
69
+ end
48
70
  end
49
71
 
50
72
  describe Account, "given free and paid plans" do
@@ -139,10 +161,10 @@ describe Account, "with a paid plan" do
139
161
  context "updating customer credit card information when changed" do
140
162
  before do
141
163
  subject.save_customer_and_subscription!(:billing_email => "jrobot@example.com",
142
- :cardholder_name => "Jim Robot",
164
+ :cardholder_name => "Jim Robot",
143
165
  :card_number => "4111111111111115",
144
166
  :verification_code => "123",
145
- :expiration_month => 5,
167
+ :expiration_month => 5,
146
168
  :expiration_year => 2013)
147
169
  end
148
170
 
@@ -170,10 +192,10 @@ describe Account, "with a paid plan" do
170
192
  context "updating customer credit card billing address information when changed" do
171
193
  before do
172
194
  subject.save_customer_and_subscription!(:billing_email => "jrobot@example.com",
173
- :cardholder_name => "Jim Robot",
195
+ :cardholder_name => "Jim Robot",
174
196
  :card_number => "4111111111111115",
175
197
  :verification_code => "123",
176
- :expiration_month => 5,
198
+ :expiration_month => 5,
177
199
  :expiration_year => 2013,
178
200
  :street_address => "1 E Main St",
179
201
  :extended_address => "Suite 3",
@@ -322,7 +344,7 @@ end
322
344
 
323
345
  describe Account, "with a paid subscription" do
324
346
  subject do
325
- Factory(:paid_account,
347
+ Factory(:paid_account,
326
348
  :plan => Factory(:paid_plan))
327
349
  end
328
350
 
@@ -358,7 +380,7 @@ describe Account, "with a paid subscription" do
358
380
  end
359
381
 
360
382
  it "delivers the rest of the emails even if one fails" do
361
- Factory(:paid_account,
383
+ Factory(:paid_account,
362
384
  :plan => Factory(:paid_plan))
363
385
  Timecop.travel(subject.next_billing_date + 1.day) do
364
386
  Account.update_subscriptions!
@@ -545,7 +567,7 @@ end
545
567
 
546
568
  describe Account, "with a paid subscription that is past due" do
547
569
  subject do
548
- Factory(:paid_account,
570
+ Factory(:paid_account,
549
571
  :plan => Factory(:paid_plan))
550
572
  end
551
573