saasy 0.0.1
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.
- data/CHANGELOG.md +114 -0
- data/Gemfile +26 -0
- data/README.md +118 -0
- data/Rakefile +38 -0
- data/app/controllers/accounts_controller.rb +68 -0
- data/app/controllers/billings_controller.rb +25 -0
- data/app/controllers/invitations_controller.rb +65 -0
- data/app/controllers/memberships_controller.rb +45 -0
- data/app/controllers/plans_controller.rb +24 -0
- data/app/controllers/profiles_controller.rb +19 -0
- data/app/helpers/limits_helper.rb +13 -0
- data/app/mailers/billing_mailer.rb +53 -0
- data/app/mailers/invitation_mailer.rb +18 -0
- data/app/models/invitation.rb +113 -0
- data/app/models/limit.rb +49 -0
- data/app/models/membership.rb +26 -0
- data/app/models/permission.rb +19 -0
- data/app/models/signup.rb +163 -0
- data/app/views/accounts/_account.html.erb +9 -0
- data/app/views/accounts/_blank_slate.html.erb +6 -0
- data/app/views/accounts/_projects.html.erb +12 -0
- data/app/views/accounts/_subnav.html.erb +10 -0
- data/app/views/accounts/edit.html.erb +34 -0
- data/app/views/accounts/index.html.erb +9 -0
- data/app/views/accounts/new.html.erb +36 -0
- data/app/views/billing_mailer/completed_trial.text.erb +13 -0
- data/app/views/billing_mailer/expiring_trial.text.erb +15 -0
- data/app/views/billing_mailer/new_unactivated.text.erb +1 -0
- data/app/views/billing_mailer/problem.html.erb +13 -0
- data/app/views/billing_mailer/problem.text.erb +14 -0
- data/app/views/billing_mailer/receipt.html.erb +41 -0
- data/app/views/billing_mailer/receipt.text.erb +25 -0
- data/app/views/billings/_form.html.erb +8 -0
- data/app/views/billings/edit.html.erb +13 -0
- data/app/views/billings/show.html.erb +29 -0
- data/app/views/invitation_mailer/invitation.text.erb +6 -0
- data/app/views/invitations/new.html.erb +17 -0
- data/app/views/invitations/show.html.erb +22 -0
- data/app/views/layouts/saucy.html.erb +36 -0
- data/app/views/limits/_meter.html.erb +13 -0
- data/app/views/memberships/edit.html.erb +21 -0
- data/app/views/memberships/index.html.erb +17 -0
- data/app/views/plans/_plan.html.erb +32 -0
- data/app/views/plans/_terms.html.erb +15 -0
- data/app/views/plans/edit.html.erb +33 -0
- data/app/views/plans/index.html.erb +12 -0
- data/app/views/profiles/_inputs.html.erb +5 -0
- data/app/views/profiles/edit.html.erb +36 -0
- data/app/views/projects/_form.html.erb +36 -0
- data/app/views/projects/edit.html.erb +22 -0
- data/app/views/projects/index.html.erb +28 -0
- data/app/views/projects/new.html.erb +13 -0
- data/app/views/projects/show.html.erb +0 -0
- data/app/views/shared/_project_dropdown.html.erb +55 -0
- data/app/views/shared/_saucy_javascript.html.erb +33 -0
- data/config/locales/en.yml +37 -0
- data/config/routes.rb +19 -0
- data/features/run_features.feature +83 -0
- data/features/step_definitions/clearance_steps.rb +45 -0
- data/features/step_definitions/rails_steps.rb +73 -0
- data/features/step_definitions/saucy_steps.rb +8 -0
- data/features/support/env.rb +4 -0
- data/features/support/file.rb +11 -0
- data/lib/generators/saucy/base.rb +18 -0
- data/lib/generators/saucy/features/features_generator.rb +91 -0
- data/lib/generators/saucy/features/templates/README +3 -0
- data/lib/generators/saucy/features/templates/factories.rb +71 -0
- data/lib/generators/saucy/features/templates/features/edit_profile.feature +9 -0
- data/lib/generators/saucy/features/templates/features/edit_project_permissions.feature +37 -0
- data/lib/generators/saucy/features/templates/features/edit_user_permissions.feature +47 -0
- data/lib/generators/saucy/features/templates/features/manage_account.feature +35 -0
- data/lib/generators/saucy/features/templates/features/manage_billing.feature +93 -0
- data/lib/generators/saucy/features/templates/features/manage_plan.feature +143 -0
- data/lib/generators/saucy/features/templates/features/manage_projects.feature +139 -0
- data/lib/generators/saucy/features/templates/features/manage_users.feature +142 -0
- data/lib/generators/saucy/features/templates/features/new_account.feature +33 -0
- data/lib/generators/saucy/features/templates/features/project_dropdown.feature +77 -0
- data/lib/generators/saucy/features/templates/features/sign_up.feature +32 -0
- data/lib/generators/saucy/features/templates/features/sign_up_paid.feature +65 -0
- data/lib/generators/saucy/features/templates/features/trial_plans.feature +82 -0
- data/lib/generators/saucy/features/templates/step_definitions/account_steps.rb +30 -0
- data/lib/generators/saucy/features/templates/step_definitions/braintree_steps.rb +25 -0
- data/lib/generators/saucy/features/templates/step_definitions/cron_steps.rb +23 -0
- data/lib/generators/saucy/features/templates/step_definitions/email_steps.rb +40 -0
- data/lib/generators/saucy/features/templates/step_definitions/factory_girl_steps.rb +1 -0
- data/lib/generators/saucy/features/templates/step_definitions/html_steps.rb +51 -0
- data/lib/generators/saucy/features/templates/step_definitions/plan_steps.rb +16 -0
- data/lib/generators/saucy/features/templates/step_definitions/project_steps.rb +4 -0
- data/lib/generators/saucy/features/templates/step_definitions/session_steps.rb +37 -0
- data/lib/generators/saucy/features/templates/step_definitions/user_steps.rb +100 -0
- data/lib/generators/saucy/features/templates/support/braintree.rb +5 -0
- data/lib/generators/saucy/install/install_generator.rb +40 -0
- data/lib/generators/saucy/install/templates/controllers/projects_controller.rb +3 -0
- data/lib/generators/saucy/install/templates/create_saucy_tables.rb +115 -0
- data/lib/generators/saucy/install/templates/models/account.rb +3 -0
- data/lib/generators/saucy/install/templates/models/plan.rb +3 -0
- data/lib/generators/saucy/install/templates/models/project.rb +3 -0
- data/lib/generators/saucy/specs/specs_generator.rb +20 -0
- data/lib/generators/saucy/specs/templates/support/braintree.rb +5 -0
- data/lib/generators/saucy/views/views_generator.rb +23 -0
- data/lib/saucy.rb +10 -0
- data/lib/saucy/account.rb +132 -0
- data/lib/saucy/account_authorization.rb +67 -0
- data/lib/saucy/configuration.rb +29 -0
- data/lib/saucy/engine.rb +35 -0
- data/lib/saucy/fake_braintree.rb +134 -0
- data/lib/saucy/layouts.rb +36 -0
- data/lib/saucy/plan.rb +54 -0
- data/lib/saucy/project.rb +125 -0
- data/lib/saucy/projects_controller.rb +94 -0
- data/lib/saucy/railties/tasks.rake +28 -0
- data/lib/saucy/routing_extensions.rb +121 -0
- data/lib/saucy/subscription.rb +237 -0
- data/lib/saucy/user.rb +30 -0
- data/spec/controllers/accounts_controller_spec.rb +228 -0
- data/spec/controllers/application_controller_spec.rb +32 -0
- data/spec/controllers/invitations_controller_spec.rb +215 -0
- data/spec/controllers/memberships_controller_spec.rb +117 -0
- data/spec/controllers/plans_controller_spec.rb +13 -0
- data/spec/controllers/profiles_controller_spec.rb +48 -0
- data/spec/controllers/projects_controller_spec.rb +216 -0
- data/spec/environment.rb +95 -0
- data/spec/layouts_spec.rb +21 -0
- data/spec/mailers/billing_mailer_spec.rb +68 -0
- data/spec/mailers/invitiation_mailer_spec.rb +19 -0
- data/spec/models/account_spec.rb +218 -0
- data/spec/models/invitation_spec.rb +320 -0
- data/spec/models/limit_spec.rb +70 -0
- data/spec/models/membership_spec.rb +37 -0
- data/spec/models/permission_spec.rb +30 -0
- data/spec/models/plan_spec.rb +81 -0
- data/spec/models/project_spec.rb +223 -0
- data/spec/models/signup_spec.rb +177 -0
- data/spec/models/subscription_spec.rb +481 -0
- data/spec/models/user_spec.rb +72 -0
- data/spec/route_extensions_spec.rb +51 -0
- data/spec/saucy_spec.rb +62 -0
- data/spec/scaffold/config/routes.rb +5 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/authentication_helpers.rb +81 -0
- data/spec/support/authorization_helpers.rb +56 -0
- data/spec/support/braintree.rb +7 -0
- data/spec/support/clearance_matchers.rb +55 -0
- data/spec/support/notifications.rb +57 -0
- data/spec/views/accounts/_account.html.erb_spec.rb +37 -0
- metadata +325 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Limit do
|
|
4
|
+
subject { Factory(:limit) }
|
|
5
|
+
|
|
6
|
+
it { should belong_to(:plan) }
|
|
7
|
+
it { should validate_presence_of(:name) }
|
|
8
|
+
it { should validate_presence_of(:value) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe Limit, "various kinds" do
|
|
12
|
+
let!(:users) { Factory(:limit, :name => "users", :value => 1) }
|
|
13
|
+
let!(:ssl) { Factory(:limit, :name => "ssl", :value => 0, :value_type => :boolean) }
|
|
14
|
+
let!(:lighthouse) { Factory(:limit, :name => "lighthouse", :value => 1, :value_type => :boolean) }
|
|
15
|
+
|
|
16
|
+
it "gives the numbered limits" do
|
|
17
|
+
Limit.numbered.should == [users]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "gives the boolean limits" do
|
|
21
|
+
Limit.boolean.should == [ssl, lighthouse]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "gives the limits by name" do
|
|
25
|
+
Limit.named(:users).should == users
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "reports true for booleans with 1" do
|
|
29
|
+
lighthouse.allowed?.should be
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "reports false for booleans with 0" do
|
|
33
|
+
ssl.allowed?.should_not be
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe Limit, "with account and limits" do
|
|
38
|
+
subject { Factory(:limit, :name => "users", :value => 1) }
|
|
39
|
+
|
|
40
|
+
before do
|
|
41
|
+
@account = Factory(:account, :plan => subject.plan)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "indicates whether the account is below the limit" do
|
|
45
|
+
subject.within?(@account)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "gives the current count for the account of the limit" do
|
|
49
|
+
@account.stubs(:users_count => 99)
|
|
50
|
+
subject.current_count(@account).should == 99
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "can query whether the given account is below the specified limit" do
|
|
54
|
+
Limit.within?("users", @account)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "indicates whether the specified account can add one" do
|
|
58
|
+
Limit.can_add_one?("users", @account).should be
|
|
59
|
+
|
|
60
|
+
Factory(:membership, :account => @account)
|
|
61
|
+
Factory(:project, :account => @account)
|
|
62
|
+
|
|
63
|
+
Limit.can_add_one?("users", @account).should_not be
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "returns true if there is no limit" do
|
|
67
|
+
Limit.can_add_one?("nomatch", @account).should be
|
|
68
|
+
Limit.within?("nomatch", @account).should be
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Membership do
|
|
4
|
+
it { should belong_to(:account) }
|
|
5
|
+
it { should belong_to(:user) }
|
|
6
|
+
it { should validate_presence_of(:account_id) }
|
|
7
|
+
it { should validate_presence_of(:user_id) }
|
|
8
|
+
it { should have_many(:permissions).dependent(:destroy) }
|
|
9
|
+
it { should have_many(:projects).through(:permissions) }
|
|
10
|
+
|
|
11
|
+
describe "given an existing account membership" do
|
|
12
|
+
before { Factory(:membership) }
|
|
13
|
+
it { should validate_uniqueness_of(:user_id).scoped_to(:account_id) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "delegates the user's name" do
|
|
17
|
+
user = Factory(:user)
|
|
18
|
+
membership = Factory(:membership, :user => user)
|
|
19
|
+
|
|
20
|
+
membership.name.should == user.name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "delegates the user's email" do
|
|
24
|
+
user = Factory(:user)
|
|
25
|
+
membership = Factory(:membership, :user => user)
|
|
26
|
+
|
|
27
|
+
membership.email.should == user.email
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "returns memberships by name" do
|
|
31
|
+
Factory(:membership, :user => Factory(:user, :name => "def"))
|
|
32
|
+
Factory(:membership, :user => Factory(:user, :name => "abc"))
|
|
33
|
+
Factory(:membership, :user => Factory(:user, :name => "ghi"))
|
|
34
|
+
|
|
35
|
+
Membership.by_name.map(&:name).should == %w(abc def ghi)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Permission do
|
|
4
|
+
it { should belong_to(:project) }
|
|
5
|
+
it { should belong_to(:membership) }
|
|
6
|
+
it { should belong_to(:user) }
|
|
7
|
+
|
|
8
|
+
it "doesn't allow the same member to be added to a project twice" do
|
|
9
|
+
original = Factory(:permission)
|
|
10
|
+
duplicate = Factory.build(:permission, :membership => original.membership, :project => original.project)
|
|
11
|
+
duplicate.should_not be_valid
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "allows different members to be added to a project" do
|
|
15
|
+
original = Factory(:permission)
|
|
16
|
+
duplicate = Factory.build(:permission, :project => original.project)
|
|
17
|
+
duplicate.should be_valid
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "caches the user from the account membership" do
|
|
21
|
+
membership = Factory(:membership)
|
|
22
|
+
permission = Factory(:permission, :membership => membership)
|
|
23
|
+
permission.user_id.should == membership.user_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "doesn't allow the user to be assigned" do
|
|
27
|
+
expect { subject.user = Factory.build(:user) }.to raise_error
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Plan do
|
|
4
|
+
subject { Factory(:plan) }
|
|
5
|
+
|
|
6
|
+
it { should have_many(:limits) }
|
|
7
|
+
it { should have_many(:accounts) }
|
|
8
|
+
it { should validate_presence_of(:name) }
|
|
9
|
+
|
|
10
|
+
it "finds ordered paid plans" do
|
|
11
|
+
Factory(:plan, :name => "Free", :price => 0)
|
|
12
|
+
Factory(:plan, :name => "Two", :price => 2)
|
|
13
|
+
Factory(:plan, :name => "One", :price => 1)
|
|
14
|
+
Factory(:plan, :name => "Three", :price => 3)
|
|
15
|
+
|
|
16
|
+
Plan.paid_by_price.to_a.map(&:name).should == %w(Three Two One)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "finds the trial plan" do
|
|
20
|
+
paid = Factory(:plan, :name => "Paid", :price => 1)
|
|
21
|
+
trial = Factory(:plan, :name => "Free", :price => 0)
|
|
22
|
+
|
|
23
|
+
Plan.trial.should == trial
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe Plan, "free" do
|
|
28
|
+
subject { Factory(:plan) }
|
|
29
|
+
|
|
30
|
+
it "is free" do
|
|
31
|
+
subject.free?.should be
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "is not billed" do
|
|
35
|
+
subject.billed?.should_not be
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe Plan, "paid" do
|
|
40
|
+
subject { Factory(:paid_plan) }
|
|
41
|
+
|
|
42
|
+
it "is not free" do
|
|
43
|
+
subject.free?.should_not be
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "is billed" do
|
|
47
|
+
subject.billed?.should be
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe Plan, "with limits" do
|
|
52
|
+
subject { Factory(:plan) }
|
|
53
|
+
|
|
54
|
+
before do
|
|
55
|
+
Factory(:limit, :name => "users", :value => 1, :plan => subject)
|
|
56
|
+
Factory(:limit, :name => "ssl", :value => 0, :value_type => :boolean, :plan => subject)
|
|
57
|
+
Factory(:limit, :name => "lighthouse", :value => 1, :value_type => :boolean, :plan => subject)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "indicates whether or not more users can be created" do
|
|
61
|
+
subject.can_add_more?(:users, 0).should be
|
|
62
|
+
subject.can_add_more?(:users, 1).should_not be
|
|
63
|
+
subject.can_add_more?(:users, 2).should_not be
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "indicates whether a plan can do something or not" do
|
|
67
|
+
subject.allows?(:ssl).should_not be
|
|
68
|
+
subject.allows?(:lighthouse).should be
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
describe Plan, "with prices" do
|
|
74
|
+
let!(:free) { Factory(:plan, :price => 0) }
|
|
75
|
+
let!(:least) { Factory(:plan, :price => 1) }
|
|
76
|
+
let!(:most) { Factory(:plan, :price => 2) }
|
|
77
|
+
|
|
78
|
+
it "gives them from most to least expensive when ordered" do
|
|
79
|
+
Plan.ordered.should == [most, least, free]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Project do
|
|
4
|
+
it { should belong_to(:account) }
|
|
5
|
+
it { should validate_presence_of(:account_id) }
|
|
6
|
+
it { should have_many(:permissions) }
|
|
7
|
+
it { should validate_presence_of(:keyword) }
|
|
8
|
+
it { should validate_presence_of(:name) }
|
|
9
|
+
|
|
10
|
+
it { should allow_value("hello").for(:keyword) }
|
|
11
|
+
it { should allow_value("0123").for(:keyword) }
|
|
12
|
+
it { should allow_value("hello_world").for(:keyword) }
|
|
13
|
+
it { should allow_value("hello-world").for(:keyword) }
|
|
14
|
+
it { should_not allow_value("HELLO").for(:keyword) }
|
|
15
|
+
it { should_not allow_value("hello world").for(:keyword) }
|
|
16
|
+
|
|
17
|
+
it { should_not allow_mass_assignment_of(:account_id) }
|
|
18
|
+
it { should_not allow_mass_assignment_of(:account) }
|
|
19
|
+
|
|
20
|
+
it "finds projects visible to a user" do
|
|
21
|
+
account = Factory(:account)
|
|
22
|
+
user = Factory(:user)
|
|
23
|
+
membership = Factory(:membership, :user => user, :account => account)
|
|
24
|
+
visible_projects = [Factory(:project, :account => account),
|
|
25
|
+
Factory(:project, :account => account)]
|
|
26
|
+
invisible_project = Factory(:project, :account => account)
|
|
27
|
+
visible_projects.each do |visible_project|
|
|
28
|
+
Factory(:permission, :project => visible_project,
|
|
29
|
+
:membership => membership)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Project.visible_to(user).to_a.should =~ visible_projects
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "returns projects by name" do
|
|
36
|
+
Factory(:project, :name => "def")
|
|
37
|
+
Factory(:project, :name => "abc")
|
|
38
|
+
Factory(:project, :name => "ghi")
|
|
39
|
+
|
|
40
|
+
Project.by_name.map(&:name).should == %w(abc def ghi)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context "archived and non-archived projects" do
|
|
44
|
+
before do
|
|
45
|
+
@archived = [Factory(:project, :archived => true), Factory(:project, :archived => true)]
|
|
46
|
+
@active = [Factory(:project, :archived => false)]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "returns only archived projects with archived" do
|
|
50
|
+
Project.archived.all.should == @archived
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "returns only active projects with active" do
|
|
54
|
+
Project.active.all.should == @active
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context "archived project for an account at its project limit" do
|
|
59
|
+
before do
|
|
60
|
+
@archived = Factory(:project, :archived => true)
|
|
61
|
+
@account = @archived.account
|
|
62
|
+
Limit.stubs(:can_add_one?).with("projects", @account).returns(false)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "cannot be unarchived" do
|
|
66
|
+
@archived.archived = false
|
|
67
|
+
@archived.save.should_not be
|
|
68
|
+
@archived.errors[:archived].first.should match(/at your limit/)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "project moving to an account at its project limit" do
|
|
73
|
+
before do
|
|
74
|
+
@project = Factory(:project)
|
|
75
|
+
@account = Factory(:account)
|
|
76
|
+
Limit.stubs(:can_add_one?).with("projects", @account).returns(false)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "cannot be moved" do
|
|
80
|
+
@project.account = @account
|
|
81
|
+
@project.save.should_not be
|
|
82
|
+
@project.errors[:account_id].first.should match(/at your limit/)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "should give its keyword for to_param" do
|
|
87
|
+
project = Factory(:project)
|
|
88
|
+
project.to_param.should == project.keyword
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe Project, "keyword uniqueness" do
|
|
93
|
+
let(:project) { Factory(:project) }
|
|
94
|
+
subject do
|
|
95
|
+
Factory.build(:project, :account => project.account)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "validates uniqueness of it's keyword" do
|
|
99
|
+
subject.keyword = project.keyword
|
|
100
|
+
subject.save
|
|
101
|
+
subject.errors[:keyword].should include("has already been taken")
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
share_examples_for "default project permissions" do
|
|
106
|
+
it "is viewable by admins by default" do
|
|
107
|
+
admins.each do |admin|
|
|
108
|
+
subject.users.should include(admin)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "isn't viewable by non-members" do
|
|
113
|
+
subject.users.should_not include(non_admin)
|
|
114
|
+
subject.users.should_not include(non_member)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe Project, "for an account with admin and non-admin users" do
|
|
119
|
+
let!(:account) { Factory(:account, :name => "Account") }
|
|
120
|
+
let!(:other_account) { Factory(:account, :name => "Other") }
|
|
121
|
+
let!(:non_admin) { Factory(:user) }
|
|
122
|
+
let!(:admins) { [Factory(:user), Factory(:user)] }
|
|
123
|
+
let!(:non_member) { Factory(:user) }
|
|
124
|
+
subject { Factory.build(:project, :account => account) }
|
|
125
|
+
|
|
126
|
+
before do
|
|
127
|
+
Factory(:membership, :account => account, :user => non_admin, :admin => false)
|
|
128
|
+
Factory(:membership, :account => other_account,
|
|
129
|
+
:user => non_member,
|
|
130
|
+
:admin => true)
|
|
131
|
+
admins.each do |admin|
|
|
132
|
+
Factory(:membership, :user => admin, :account => account, :admin => true)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
subject.assign_default_permissions
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context "before saving" do
|
|
139
|
+
it_behaves_like "default project permissions"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context "after saving" do
|
|
143
|
+
before do
|
|
144
|
+
subject.save!
|
|
145
|
+
subject.reload
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it_behaves_like "default project permissions"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe Project, "saved" do
|
|
153
|
+
subject { Factory(:project) }
|
|
154
|
+
|
|
155
|
+
it "has a member with a membership" do
|
|
156
|
+
user = Factory(:user)
|
|
157
|
+
membership = Factory(:membership, :account => subject.account,
|
|
158
|
+
:user => user)
|
|
159
|
+
membership = Factory(:permission, :project => subject,
|
|
160
|
+
:membership => membership)
|
|
161
|
+
should have_member(user)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "doesn't have a member without a membership" do
|
|
165
|
+
user = Factory(:user)
|
|
166
|
+
should_not have_member(user)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe Project, "assigning users on update" do
|
|
171
|
+
subject { Factory(:project) }
|
|
172
|
+
|
|
173
|
+
let(:account) { subject.account }
|
|
174
|
+
let!(:user_to_remove) { Factory(:user) }
|
|
175
|
+
let!(:user_to_add) { Factory(:user) }
|
|
176
|
+
let!(:admin) { Factory(:user) }
|
|
177
|
+
|
|
178
|
+
before do
|
|
179
|
+
membership_to_remove =
|
|
180
|
+
Factory(:membership, :account => account, :user => user_to_remove)
|
|
181
|
+
membership_to_add =
|
|
182
|
+
Factory(:membership, :account => account, :user => user_to_add)
|
|
183
|
+
admin_membership =
|
|
184
|
+
Factory(:membership, :account => account, :user => admin, :admin => true)
|
|
185
|
+
Factory(:permission, :membership => membership_to_remove,
|
|
186
|
+
:project => subject)
|
|
187
|
+
|
|
188
|
+
subject.reload.update_attributes!(:user_ids => [user_to_add.id, ""])
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "adds an added user" do
|
|
192
|
+
user_to_add.should be_member_of(subject)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "removes a removed user" do
|
|
196
|
+
user_to_remove.should_not be_member_of(subject)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "keeps an admin" do
|
|
200
|
+
admin.should be_member_of(subject)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
describe Project, "assigning users on create" do
|
|
205
|
+
subject { Factory.build(:project) }
|
|
206
|
+
let(:account) { subject.account }
|
|
207
|
+
let!(:member) { Factory(:user) }
|
|
208
|
+
let!(:admin) { Factory(:user) }
|
|
209
|
+
|
|
210
|
+
before do
|
|
211
|
+
Factory(:membership, :account => account, :user => member)
|
|
212
|
+
Factory(:membership, :account => account, :user => admin, :admin => true)
|
|
213
|
+
subject.save
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it "adds admins to the project" do
|
|
217
|
+
admin.should be_member_of(subject)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "ignores normal users" do
|
|
221
|
+
member.should_not be_member_of(subject)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Signup do
|
|
4
|
+
it "complies with the activemodel api" do
|
|
5
|
+
subject.class.should be_kind_of(ActiveModel::Naming)
|
|
6
|
+
should_not be_persisted
|
|
7
|
+
should be_kind_of(ActiveModel::Conversion)
|
|
8
|
+
should be_kind_of(ActiveModel::Validations)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe Signup, "with attributes in the constructor" do
|
|
13
|
+
subject { Signup.new(:email => 'person@example.com', :password => 'password') }
|
|
14
|
+
it "assigns attributes" do
|
|
15
|
+
subject.email.should == 'person@example.com'
|
|
16
|
+
subject.password.should == 'password'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe Signup, "with nil to the constructor" do
|
|
21
|
+
subject { Signup.new(nil) }
|
|
22
|
+
|
|
23
|
+
it "assigns no attributes" do
|
|
24
|
+
subject.email.should be_blank
|
|
25
|
+
subject.password.should be_blank
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
share_examples_for "invalid signup" do
|
|
30
|
+
before { @previous_user_count = User.count }
|
|
31
|
+
|
|
32
|
+
it "returns false" do
|
|
33
|
+
@result.should be_false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "doesn't create a user" do
|
|
37
|
+
User.count.should == @previous_user_count
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "doesn't create an account" do
|
|
41
|
+
subject.account.should be_new_record
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
share_examples_for "valid signup" do
|
|
46
|
+
it "returns true" do
|
|
47
|
+
@result.should be_true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "saves the account" do
|
|
51
|
+
subject.account.should be_persisted
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe Signup, "with a valid user and account" do
|
|
56
|
+
it_should_behave_like "valid signup"
|
|
57
|
+
let(:email) { "user@example.com" }
|
|
58
|
+
subject { Factory.build(:signup, :email => email) }
|
|
59
|
+
|
|
60
|
+
before do
|
|
61
|
+
@result = subject.save
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "saves the user" do
|
|
65
|
+
subject.user.should be_persisted
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "assigns the user to the account" do
|
|
69
|
+
subject.user.reload.accounts.should include(subject.account)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "creates an admin user" do
|
|
73
|
+
subject.user.should be_admin_of(subject.account)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "gives a friendly account name" do
|
|
77
|
+
subject.account.name.should == "user"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "gives a friendly account keyword" do
|
|
81
|
+
subject.account.keyword.should =~ /^user.+/
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "gives it's account a name and keyword" do
|
|
85
|
+
subject.account.name.should_not be_blank
|
|
86
|
+
subject.account.keyword.should_not be_blank
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "gives it's user a name that is the first part of it's email" do
|
|
90
|
+
subject.user.name.should == "user"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe Signup, "with an email with symbols in it" do
|
|
95
|
+
subject { Factory.build(:signup, :email => "user+extra@example.com") }
|
|
96
|
+
|
|
97
|
+
before do
|
|
98
|
+
subject.save
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "gives a friendly account name" do
|
|
102
|
+
subject.account.name.should == "user-extra"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "gives a friendly account keyword" do
|
|
106
|
+
subject.account.keyword.should =~ /^user-extra+/
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe Signup, "with an invalid user" do
|
|
111
|
+
subject { Factory.build(:signup, :email => nil) }
|
|
112
|
+
it_should_behave_like "invalid signup"
|
|
113
|
+
|
|
114
|
+
before do
|
|
115
|
+
@result = subject.save
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "adds error messages" do
|
|
119
|
+
subject.errors[:email].should_not be_empty
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe Signup, "valid with an existing user and correct password" do
|
|
124
|
+
it_should_behave_like "valid signup"
|
|
125
|
+
let(:email) { "user@example.com" }
|
|
126
|
+
let(:password) { "test" }
|
|
127
|
+
let!(:user) { Factory(:user, :email => email, :password => password) }
|
|
128
|
+
subject { Factory.build(:signup, :email => email, :password => password) }
|
|
129
|
+
before { @result = subject.save }
|
|
130
|
+
|
|
131
|
+
it "doesn't create a user" do
|
|
132
|
+
User.count.should == 1
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "assigns the user to the account as an admin" do
|
|
136
|
+
user.reload.accounts.should include(subject.account)
|
|
137
|
+
user.should be_admin_of(subject.account)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe Signup, "valid with a signed in user" do
|
|
142
|
+
it_should_behave_like "valid signup"
|
|
143
|
+
let!(:user) { Factory(:user) }
|
|
144
|
+
subject { Factory.build(:signup, :user => user, :password => '') }
|
|
145
|
+
before { @result = subject.save }
|
|
146
|
+
|
|
147
|
+
it "doesn't create a user" do
|
|
148
|
+
User.count.should == 1
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "assigns the user to the account as an admin" do
|
|
152
|
+
user.reload.accounts.should include(subject.account)
|
|
153
|
+
user.should be_admin_of(subject.account)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe Signup, "valid with an existing user and incorrect password" do
|
|
158
|
+
it_should_behave_like "invalid signup"
|
|
159
|
+
let(:email) { "user@example.com" }
|
|
160
|
+
let(:password) { "test" }
|
|
161
|
+
let!(:user) { Factory(:user, :email => email, :password => password) }
|
|
162
|
+
subject { Factory.build(:signup, :email => email, :password => 'wrong') }
|
|
163
|
+
before { @result = subject.save }
|
|
164
|
+
|
|
165
|
+
it "adds error messages" do
|
|
166
|
+
subject.errors.full_messages.should include("Password is incorrect")
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe Signup, "with an account that doesn't save" do
|
|
171
|
+
subject { Factory.build(:signup) }
|
|
172
|
+
|
|
173
|
+
it "doesn't raise the transaction and returns false" do
|
|
174
|
+
Account.any_instance.stubs(:save!).raises(ActiveRecord::RecordNotSaved)
|
|
175
|
+
subject.save.should_not be
|
|
176
|
+
end
|
|
177
|
+
end
|