saucy 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +106 -0
  3. data/README +9 -0
  4. data/app/controllers/accounts_controller.rb +57 -0
  5. data/app/controllers/invitations_controller.rb +36 -0
  6. data/app/controllers/memberships_controller.rb +9 -0
  7. data/app/controllers/permissions_controller.rb +17 -0
  8. data/app/controllers/plans_controller.rb +7 -0
  9. data/app/controllers/profiles_controller.rb +17 -0
  10. data/app/controllers/projects_controller.rb +56 -0
  11. data/app/models/account_membership.rb +8 -0
  12. data/app/models/invitation.rb +86 -0
  13. data/app/models/project_membership.rb +16 -0
  14. data/app/models/signup.rb +134 -0
  15. data/app/views/accounts/_account.html.erb +12 -0
  16. data/app/views/accounts/_blank_slate.html.erb +6 -0
  17. data/app/views/accounts/_projects.html.erb +12 -0
  18. data/app/views/accounts/_tab_bar.html.erb +8 -0
  19. data/app/views/accounts/edit.html.erb +17 -0
  20. data/app/views/accounts/index.html.erb +3 -0
  21. data/app/views/accounts/new.html.erb +26 -0
  22. data/app/views/invitation_mailer/invitation.text.erb +6 -0
  23. data/app/views/invitations/new.html.erb +11 -0
  24. data/app/views/invitations/show.html.erb +20 -0
  25. data/app/views/memberships/index.html.erb +16 -0
  26. data/app/views/permissions/edit.html.erb +15 -0
  27. data/app/views/plans/index.html.erb +3 -0
  28. data/app/views/profiles/_inputs.html.erb +6 -0
  29. data/app/views/profiles/edit.html.erb +35 -0
  30. data/app/views/projects/_form.html.erb +4 -0
  31. data/app/views/projects/edit.html.erb +18 -0
  32. data/app/views/projects/index.html.erb +13 -0
  33. data/app/views/projects/new.html.erb +11 -0
  34. data/config/routes.rb +18 -0
  35. data/features/run_features.feature +73 -0
  36. data/features/step_definitions/clearance_steps.rb +45 -0
  37. data/features/step_definitions/rails_steps.rb +70 -0
  38. data/features/support/env.rb +4 -0
  39. data/features/support/file.rb +11 -0
  40. data/lib/generators/saucy/base.rb +18 -0
  41. data/lib/generators/saucy/features/features_generator.rb +68 -0
  42. data/lib/generators/saucy/features/templates/factories.rb +55 -0
  43. data/lib/generators/saucy/features/templates/step_definitions/email_steps.rb +13 -0
  44. data/lib/generators/saucy/features/templates/step_definitions/factory_girl_steps.rb +1 -0
  45. data/lib/generators/saucy/features/templates/step_definitions/html_steps.rb +3 -0
  46. data/lib/generators/saucy/features/templates/step_definitions/project_steps.rb +4 -0
  47. data/lib/generators/saucy/features/templates/step_definitions/session_steps.rb +27 -0
  48. data/lib/generators/saucy/features/templates/step_definitions/user_steps.rb +54 -0
  49. data/lib/generators/saucy/install/install_generator.rb +36 -0
  50. data/lib/generators/saucy/install/templates/create_saucy_tables.rb +72 -0
  51. data/lib/generators/saucy/install/templates/models/account.rb +3 -0
  52. data/lib/generators/saucy/install/templates/models/invitation_mailer.rb +9 -0
  53. data/lib/generators/saucy/install/templates/models/plan.rb +3 -0
  54. data/lib/generators/saucy/install/templates/models/project.rb +3 -0
  55. data/lib/generators/saucy/views/views_generator.rb +23 -0
  56. data/lib/saucy.rb +7 -0
  57. data/lib/saucy/account.rb +50 -0
  58. data/lib/saucy/account_authorization.rb +34 -0
  59. data/lib/saucy/configuration.rb +12 -0
  60. data/lib/saucy/engine.rb +9 -0
  61. data/lib/saucy/layouts.rb +36 -0
  62. data/lib/saucy/plan.rb +11 -0
  63. data/lib/saucy/project.rb +39 -0
  64. data/lib/saucy/user.rb +37 -0
  65. data/spec/controllers/accounts_controller_spec.rb +204 -0
  66. data/spec/controllers/application_controller_spec.rb +27 -0
  67. data/spec/controllers/invitations_controller_spec.rb +155 -0
  68. data/spec/controllers/memberships_controller_spec.rb +33 -0
  69. data/spec/controllers/permissions_controller_spec.rb +69 -0
  70. data/spec/controllers/profiles_controller_spec.rb +43 -0
  71. data/spec/controllers/projects_controller_spec.rb +123 -0
  72. data/spec/layouts_spec.rb +21 -0
  73. data/spec/models/account_membership_spec.rb +13 -0
  74. data/spec/models/account_spec.rb +61 -0
  75. data/spec/models/invitation_spec.rb +160 -0
  76. data/spec/models/project_membership_spec.rb +26 -0
  77. data/spec/models/project_spec.rb +80 -0
  78. data/spec/models/signup_spec.rb +175 -0
  79. data/spec/models/user_spec.rb +96 -0
  80. data/spec/saucy_spec.rb +7 -0
  81. data/spec/spec_helper.rb +31 -0
  82. data/spec/support/authentication_helpers.rb +71 -0
  83. data/spec/support/authorization_helpers.rb +56 -0
  84. data/spec/support/clearance_matchers.rb +55 -0
  85. data/spec/views/accounts/_account.html.erb_spec.rb +66 -0
  86. metadata +203 -0
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Saucy::Layouts do
4
+ it "allows a layout to be assigned" do
5
+ subject.accounts.index = "custom"
6
+ subject.accounts.index.should == "custom"
7
+ end
8
+
9
+ it "defaults to the application layout" do
10
+ subject.accounts.index.should == "application"
11
+ end
12
+
13
+ it "selects a layout for a controller" do
14
+ controller = AccountsController.new
15
+ controller.stubs(:action_name => 'index')
16
+ block = subject.class.to_proc
17
+
18
+ block.call(controller).should == 'application'
19
+ end
20
+ end
21
+
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe AccountMembership 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
+
9
+ describe "given an existing account membership" do
10
+ before { Factory(:account_membership) }
11
+ it { should validate_uniqueness_of(:user_id).scoped_to(:account_id) }
12
+ end
13
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Account do
4
+ subject { Factory(:account) }
5
+
6
+ it { should have_many(:account_memberships) }
7
+ it { should have_many(:users).through(:account_memberships) }
8
+ it { should have_many(:projects) }
9
+
10
+ it { should validate_uniqueness_of(:name) }
11
+ it { should validate_uniqueness_of(:url) }
12
+ it { should validate_presence_of( :name) }
13
+ it { should validate_presence_of(:url) }
14
+
15
+ it { should_not allow_mass_assignment_of(:id) }
16
+ it { should_not allow_mass_assignment_of(:updated_at) }
17
+ it { should_not allow_mass_assignment_of(:created_at) }
18
+ it { should allow_mass_assignment_of(:url) }
19
+
20
+ [nil, "", "a b", "a.b", "a%b"].each do |value|
21
+ it { should_not allow_value(value).for(:url).with_message(/letters/i) }
22
+ end
23
+
24
+ ["admin", "blog", "dev", "ftp", "mail", "pop", "pop3", "imap", "smtp",
25
+ "staging", "stats", "status","www"].each do |value|
26
+ it { should_not allow_value(value).for(:url).with_message(/reserved/i) }
27
+ end
28
+
29
+ ["foo", "f00", "37signals"].each do |value|
30
+ it { should allow_value(value).for(:url) }
31
+ end
32
+
33
+ it "should give its url for to_param" do
34
+ subject.to_param.should == subject.url
35
+ end
36
+
37
+ it "finds admin users" do
38
+ admins = [Factory(:user), Factory(:user)]
39
+ non_admin = Factory(:user)
40
+ non_member = Factory(:user)
41
+ admins.each do |admin|
42
+ Factory(:account_membership, :user => admin, :account => subject, :admin => true)
43
+ end
44
+ Factory(:account_membership, :user => non_admin, :account => subject, :admin => false)
45
+
46
+ result = subject.admins
47
+
48
+ result.to_a.should =~ admins
49
+ end
50
+
51
+ it "has a member with a membership" do
52
+ membership = Factory(:account_membership, :account => subject)
53
+ should have_member(membership.user)
54
+ end
55
+
56
+ it "doesn't have a member without a membership" do
57
+ membership = Factory(:account_membership, :account => subject)
58
+ should_not have_member(Factory(:user))
59
+ end
60
+ end
61
+
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ describe Invitation do
4
+ it { should validate_presence_of(:account_id) }
5
+ it { should validate_presence_of(:email) }
6
+ it { should belong_to(:account) }
7
+ it { should_not allow_mass_assignment_of(:account_id) }
8
+
9
+ %w(new_user_name new_user_email new_user_password
10
+ new_user_password_confirmation existing_user_password).each do |attribute|
11
+ it "allows assignment of #{attribute}" do
12
+ should respond_to(attribute)
13
+ should respond_to(:"#{attribute}=")
14
+ end
15
+ end
16
+ end
17
+
18
+ describe Invitation, "saved" do
19
+ let(:mail) { stub('invitation', :deliver => true) }
20
+ before { InvitationMailer.stubs(:invitation => mail) }
21
+ subject { Factory(:invitation) }
22
+ let(:email) { subject.email }
23
+
24
+ it "sends an invitation email" do
25
+ InvitationMailer.should have_received(:invitation).with(subject)
26
+ mail.should have_received(:deliver)
27
+ end
28
+
29
+ it "delegates account name" do
30
+ subject.account_name.should == subject.account.name
31
+ end
32
+
33
+ it "defauls new user email to invited email" do
34
+ subject.new_user_email.should == subject.email
35
+ end
36
+
37
+ it "defauls existing user email to invited email" do
38
+ subject.existing_user_email.should == subject.email
39
+ end
40
+ end
41
+
42
+ describe Invitation, "valid accept for a new user" do
43
+ let(:password) { 'secret' }
44
+ let(:name) { 'Rocket' }
45
+ subject { Factory(:invitation) }
46
+
47
+ let!(:result) do
48
+ subject.accept(:new_user_password => password,
49
+ :new_user_password_confirmation => password,
50
+ :new_user_name => name)
51
+ end
52
+
53
+ let(:user) { subject.user }
54
+ let(:account) { subject.account }
55
+
56
+ it "returns true" do
57
+ result.should be_true
58
+ end
59
+
60
+ it "creates a saved, confirmed user" do
61
+ user.should_not be_nil
62
+ user.should be_persisted
63
+ user.should be_email_confirmed
64
+ user.name.should == name
65
+ end
66
+
67
+ it "adds the user to the account" do
68
+ account.users.should include(user)
69
+ end
70
+ end
71
+
72
+ describe Invitation, "invalid accept for a new user" do
73
+ subject { Factory(:invitation) }
74
+ let!(:result) { subject.accept({}) }
75
+ let(:user) { subject.user }
76
+ let(:account) { subject.account }
77
+
78
+ it "returns false" do
79
+ result.should be_false
80
+ end
81
+
82
+ it "doesn't create a user" do
83
+ user.should be_new_record
84
+ end
85
+
86
+ it "adds error messages" do
87
+ subject.errors[:new_user_password].should be_present
88
+ end
89
+ end
90
+
91
+ describe Invitation, "valid accept for an existing user" do
92
+ let(:password) { 'secret' }
93
+ let(:user) { Factory(:email_confirmed_user,
94
+ :password => password,
95
+ :password_confirmation => password) }
96
+ subject { Factory(:invitation, :email => user.email) }
97
+ let(:account) { subject.account }
98
+
99
+ let!(:result) do
100
+ subject.accept(:existing_user_password => password)
101
+ end
102
+
103
+ it "returns true" do
104
+ result.should be_true
105
+ end
106
+
107
+ it "adds the user to the account" do
108
+ account.users.should include(user)
109
+ end
110
+ end
111
+
112
+ describe Invitation, "accepting with an invalid password" do
113
+ let(:user) { Factory(:email_confirmed_user) }
114
+ subject { Factory(:invitation, :email => user.email) }
115
+ let(:account) { subject.account }
116
+ let!(:result) { subject.accept(:existing_user_password => 'wrong') }
117
+
118
+ it "adds error messages" do
119
+ subject.errors[:existing_user_password].should be_present
120
+ end
121
+
122
+ it "doesn't add the user to the account" do
123
+ subject.account.users.should_not include(subject.user)
124
+ end
125
+
126
+ it "returns false" do
127
+ result.should be_false
128
+ end
129
+ end
130
+
131
+ describe Invitation, "accepting with an unknown email" do
132
+ subject { Factory(:invitation, :email => 'unknown') }
133
+ let(:account) { subject.account }
134
+ let!(:result) { subject.accept(:existing_user_password => 'secret') }
135
+
136
+ it "adds error messages" do
137
+ subject.errors[:existing_user_email].should be_present
138
+ end
139
+
140
+ it "returns false" do
141
+ result.should be_false
142
+ end
143
+ end
144
+
145
+ describe Invitation, "accepting an admin invite" do
146
+ let(:password) { 'secret' }
147
+ let(:user) { Factory(:email_confirmed_user,
148
+ :password => password,
149
+ :password_confirmation => password) }
150
+ subject { Factory(:invitation, :email => user.email, :admin => true) }
151
+ let(:account) { subject.account }
152
+
153
+ let!(:result) do
154
+ subject.accept(:existing_user_password => password)
155
+ end
156
+
157
+ it "adds the user as an admin" do
158
+ user.should be_admin_of(account)
159
+ end
160
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProjectMembership do
4
+ it { should allow_mass_assignment_of(:user_id) }
5
+ it { should allow_mass_assignment_of(:user) }
6
+ it { should_not allow_mass_assignment_of(:updated_at) }
7
+ it { should_not allow_mass_assignment_of(:created_at) }
8
+ it { should belong_to(:project) }
9
+ it { should belong_to(:user) }
10
+ end
11
+
12
+ describe ProjectMembership, "for a user that isn't an account member" do
13
+ let!(:account) { Factory(:account) }
14
+ let!(:other_account) { Factory(:account) }
15
+ let!(:project) { Factory(:project, :account => account) }
16
+ let!(:user) { Factory(:user) }
17
+ subject { Factory.build(:project_membership, :project => project,
18
+ :user => user) }
19
+
20
+ before { Factory(:account_membership, :user => user, :account => other_account) }
21
+
22
+ it "isn't valid" do
23
+ should_not be_valid
24
+ subject.errors[:base].first.should =~ /account/i
25
+ end
26
+ end
@@ -0,0 +1,80 @@
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(:project_memberships) }
7
+ it { should have_many(:users) }
8
+
9
+ it "finds projects visible to a user" do
10
+ account = Factory(:account)
11
+ user = Factory(:user)
12
+ Factory(:account_membership, :user => user, :account => account)
13
+ visible_projects = [Factory(:project, :account => account),
14
+ Factory(:project, :account => account)]
15
+ invisible_project = Factory(:project, :account => account)
16
+ visible_projects.each do |visible_project|
17
+ Factory(:project_membership, :project => visible_project, :user => user)
18
+ end
19
+
20
+ Project.visible_to(user).to_a.should =~ visible_projects
21
+ end
22
+
23
+ it "returns projects by name" do
24
+ Factory(:project, :name => "def")
25
+ Factory(:project, :name => "abc")
26
+ Factory(:project, :name => "ghi")
27
+
28
+ Project.by_name.map(&:name).should == %w(abc def ghi)
29
+ end
30
+ end
31
+
32
+ describe Project, "for an account with admin and non-admin users" do
33
+ let!(:account) { Factory(:account, :name => "Account") }
34
+ let!(:other_account) { Factory(:account, :name => "Other") }
35
+ let!(:non_admin) { Factory(:user) }
36
+ let!(:admins) { [Factory(:user), Factory(:user)] }
37
+ let!(:non_member) { Factory(:user) }
38
+ subject { Factory(:project, :account => account) }
39
+
40
+ before do
41
+ Factory(:account_membership, :account => account, :user => non_admin, :admin => false)
42
+ Factory(:account_membership, :account => other_account,
43
+ :user => non_member,
44
+ :admin => true)
45
+ admins.each do |admin|
46
+ Factory(:account_membership, :user => admin, :account => account, :admin => true)
47
+ end
48
+ end
49
+
50
+ it "has users" do
51
+ subject.users.should_not be_empty
52
+ end
53
+
54
+ it "is viewable by admins by default" do
55
+ admins.each do |admin|
56
+ should have_member(admin)
57
+ end
58
+ end
59
+
60
+ it "isn't viewable by non-members" do
61
+ should_not have_member(non_admin)
62
+ should_not have_member(non_member)
63
+ end
64
+ end
65
+
66
+ describe Project, "saved" do
67
+ subject { Factory(:project) }
68
+
69
+ it "has a member with a membership" do
70
+ user = Factory(:user)
71
+ Factory(:account_membership, :account => subject.account, :user => user)
72
+ membership = Factory(:project_membership, :project => subject, :user => user)
73
+ should have_member(user)
74
+ end
75
+
76
+ it "doesn't have a member without a membership" do
77
+ user = Factory(:user)
78
+ should_not have_member(user)
79
+ end
80
+ end
@@ -0,0 +1,175 @@
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(:user_name => 'Joe', :account_name => 'blokes', :url => 'blk') }
14
+ it "assigns attributes" do
15
+ subject.user_name.should == 'Joe'
16
+ subject.account_name.should == 'blokes'
17
+ subject.url.should == 'blk'
18
+ end
19
+ end
20
+
21
+ share_examples_for "invalid signup" do
22
+ before { @previous_user_count = User.count }
23
+
24
+ it "returns false" do
25
+ @result.should be_false
26
+ end
27
+
28
+ it "doesn't create a user" do
29
+ User.count.should == @previous_user_count
30
+ end
31
+
32
+ it "doesn't create an account" do
33
+ subject.account.should be_new_record
34
+ end
35
+ end
36
+
37
+ share_examples_for "valid signup" do
38
+ it "returns true" do
39
+ @result.should be_true
40
+ end
41
+
42
+ it "saves the account" do
43
+ subject.account.should be_persisted
44
+ end
45
+ end
46
+
47
+ describe Signup, "with a valid user and account" do
48
+ it_should_behave_like "valid signup"
49
+ let(:email) { "user@example.com" }
50
+ subject { Factory.build(:signup, :email => email) }
51
+
52
+ before do
53
+ @result = subject.save
54
+ end
55
+
56
+ it "saves the user" do
57
+ subject.user.should be_persisted
58
+ end
59
+
60
+ it "assigns the user to the account" do
61
+ subject.user.reload.accounts.should include(subject.account)
62
+ end
63
+
64
+ it "creates an admin user" do
65
+ subject.user.should be_admin_of(subject.account)
66
+ end
67
+ end
68
+
69
+ describe Signup, "with a valid user but invalid account" do
70
+ it_should_behave_like "invalid signup"
71
+ subject { Factory.build(:signup, :account_name => "") }
72
+
73
+ before do
74
+ @result = subject.save
75
+ end
76
+
77
+ it "adds error messages" do
78
+ subject.errors[:account_name].should_not be_empty
79
+ end
80
+ end
81
+
82
+ describe Signup, "with an invalid user but valid account" do
83
+ subject { Factory.build(:signup, :user_name => nil) }
84
+ it_should_behave_like "invalid signup"
85
+
86
+ before do
87
+ @result = subject.save
88
+ end
89
+
90
+ it "adds error messages" do
91
+ subject.errors[:user_name].should_not be_empty
92
+ end
93
+ end
94
+
95
+ describe Signup, "with an invalid user and nvalid account" do
96
+ subject { Factory.build(:signup, :user_name => nil, :account_name => nil) }
97
+ it_should_behave_like "invalid signup"
98
+
99
+ before do
100
+ @result = subject.save
101
+ end
102
+
103
+ it "adds error messages for both" do
104
+ subject.errors[:user_name].should_not be_empty
105
+ subject.errors[:account_name].should_not be_empty
106
+ end
107
+ end
108
+
109
+ describe Signup, "valid with an existing user and correct password" do
110
+ it_should_behave_like "valid signup"
111
+ let(:email) { "user@example.com" }
112
+ let(:password) { "test" }
113
+ let!(:user) { Factory(:user, :email => email,
114
+ :password => password,
115
+ :password_confirmation => password) }
116
+ subject { Factory.build(:signup, :email => email, :password => password) }
117
+ before { @result = subject.save }
118
+
119
+ it "doesn't create a user" do
120
+ User.count.should == 1
121
+ end
122
+
123
+ it "assigns the user to the account as an admin" do
124
+ user.reload.accounts.should include(subject.account)
125
+ user.should be_admin_of(subject.account)
126
+ end
127
+ end
128
+
129
+ describe Signup, "valid with a signed in user" do
130
+ it_should_behave_like "valid signup"
131
+ let!(:user) { Factory(:user) }
132
+ subject { Factory.build(:signup, :user => user, :password => '') }
133
+ before { @result = subject.save }
134
+
135
+ it "doesn't create a user" do
136
+ User.count.should == 1
137
+ end
138
+
139
+ it "assigns the user to the account as an admin" do
140
+ user.reload.accounts.should include(subject.account)
141
+ user.should be_admin_of(subject.account)
142
+ end
143
+ end
144
+
145
+ describe Signup, "valid with an existing user and incorrect password" do
146
+ it_should_behave_like "invalid signup"
147
+ let(:email) { "user@example.com" }
148
+ let(:password) { "test" }
149
+ let!(:user) { Factory(:user, :email => email,
150
+ :password => password,
151
+ :password_confirmation => password) }
152
+ subject { Factory.build(:signup, :email => email, :password => 'wrong') }
153
+ before { @result = subject.save }
154
+
155
+ it "adds error messages" do
156
+ subject.errors.full_messages.should include("Password is incorrect")
157
+ end
158
+ end
159
+
160
+ describe Signup, "invalid with an existing user and correct password" do
161
+ it_should_behave_like "invalid signup"
162
+ let(:email) { "user@example.com" }
163
+ let(:password) { "test" }
164
+ let!(:user) { Factory(:user, :email => email,
165
+ :password => password,
166
+ :password_confirmation => password) }
167
+ subject { Factory.build(:signup, :account_name => '',
168
+ :email => email,
169
+ :password => password) }
170
+ before { @result = subject.save }
171
+
172
+ it "doesn't assign the user to the account" do
173
+ user.reload.accounts.should_not include(subject.account)
174
+ end
175
+ end