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