aaf-gumboot 1.0.0.pre.alpha.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +15 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +18 -0
  7. data/LICENSE +202 -0
  8. data/README.md +1069 -0
  9. data/Rakefile +8 -0
  10. data/aaf-gumboot.gemspec +42 -0
  11. data/lib/aaf-gumboot.rb +1 -0
  12. data/lib/gumboot.rb +5 -0
  13. data/lib/gumboot/shared_examples/anonymous_controller.rb +17 -0
  14. data/lib/gumboot/shared_examples/api_constraints.rb +29 -0
  15. data/lib/gumboot/shared_examples/api_controller.rb +206 -0
  16. data/lib/gumboot/shared_examples/api_subjects.rb +44 -0
  17. data/lib/gumboot/shared_examples/application_controller.rb +223 -0
  18. data/lib/gumboot/shared_examples/database_schema.rb +45 -0
  19. data/lib/gumboot/shared_examples/foreign_keys.rb +65 -0
  20. data/lib/gumboot/shared_examples/permissions.rb +45 -0
  21. data/lib/gumboot/shared_examples/roles.rb +15 -0
  22. data/lib/gumboot/shared_examples/subjects.rb +29 -0
  23. data/lib/gumboot/strap.rb +121 -0
  24. data/lib/gumboot/version.rb +3 -0
  25. data/spec/dummy/README.rdoc +28 -0
  26. data/spec/dummy/Rakefile +3 -0
  27. data/spec/dummy/app/assets/images/.keep +0 -0
  28. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  29. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  30. data/spec/dummy/app/controllers/api/api_controller.rb +78 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +64 -0
  32. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  33. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  34. data/spec/dummy/app/mailers/.keep +0 -0
  35. data/spec/dummy/app/models/.keep +0 -0
  36. data/spec/dummy/app/models/api_subject.rb +23 -0
  37. data/spec/dummy/app/models/api_subject_role.rb +6 -0
  38. data/spec/dummy/app/models/concerns/.keep +0 -0
  39. data/spec/dummy/app/models/permission.rb +7 -0
  40. data/spec/dummy/app/models/role.rb +11 -0
  41. data/spec/dummy/app/models/subject.rb +20 -0
  42. data/spec/dummy/app/models/subject_role.rb +6 -0
  43. data/spec/dummy/app/views/dynamic_errors/forbidden.html.erb +0 -0
  44. data/spec/dummy/app/views/dynamic_errors/unauthorized.html.erb +0 -0
  45. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  46. data/spec/dummy/bin/bundle +3 -0
  47. data/spec/dummy/bin/rails +4 -0
  48. data/spec/dummy/bin/rake +4 -0
  49. data/spec/dummy/config.ru +4 -0
  50. data/spec/dummy/config/application.rb +18 -0
  51. data/spec/dummy/config/boot.rb +5 -0
  52. data/spec/dummy/config/database.yml +5 -0
  53. data/spec/dummy/config/environment.rb +5 -0
  54. data/spec/dummy/config/environments/development.rb +32 -0
  55. data/spec/dummy/config/environments/production.rb +37 -0
  56. data/spec/dummy/config/environments/test.rb +33 -0
  57. data/spec/dummy/config/initializers/assets.rb +4 -0
  58. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -0
  59. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  60. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/spec/dummy/config/initializers/inflections.rb +15 -0
  62. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  63. data/spec/dummy/config/initializers/session_store.rb +3 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  65. data/spec/dummy/config/locales/en.yml +23 -0
  66. data/spec/dummy/config/routes.rb +2 -0
  67. data/spec/dummy/config/secrets.yml +22 -0
  68. data/spec/dummy/db/schema.rb +51 -0
  69. data/spec/dummy/db/test.sqlite3 +0 -0
  70. data/spec/dummy/lib/api_constraints.rb +16 -0
  71. data/spec/dummy/lib/assets/.keep +0 -0
  72. data/spec/dummy/public/404.html +67 -0
  73. data/spec/dummy/public/422.html +67 -0
  74. data/spec/dummy/public/500.html +66 -0
  75. data/spec/dummy/public/favicon.ico +0 -0
  76. data/spec/factories/api_subjects.rb +20 -0
  77. data/spec/factories/permissions.rb +6 -0
  78. data/spec/factories/roles.rb +5 -0
  79. data/spec/factories/subjects.rb +24 -0
  80. data/spec/gumboot/api_constraints_spec.rb +18 -0
  81. data/spec/gumboot/api_controller_spec.rb +7 -0
  82. data/spec/gumboot/api_subjects_spec.rb +7 -0
  83. data/spec/gumboot/application_controller_spec.rb +7 -0
  84. data/spec/gumboot/foreign_keys_spec.rb +7 -0
  85. data/spec/gumboot/permissions_spec.rb +7 -0
  86. data/spec/gumboot/roles_spec.rb +7 -0
  87. data/spec/gumboot/subjects_spec.rb +7 -0
  88. data/spec/lib/gumboot/strap_spec.rb +330 -0
  89. data/spec/spec_helper.rb +45 -0
  90. metadata +387 -0
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: [:spec, :rubocop]
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gumboot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'aaf-gumboot'
8
+ spec.version = Gumboot::VERSION
9
+ spec.authors = ['Bradley Beddoes']
10
+ spec.email = ['bradleybeddoes@aaf.edu.au']
11
+ spec.summary = 'Kick off subject and API structure for AAF applications'
12
+ spec.description = 'Provides a set of shared specs and base generators to' \
13
+ ' ensure that all AAF ruby applications follow the' \
14
+ ' same basic structure and implementation for' \
15
+ ' subjects, RESTful APIs and access control.'
16
+ spec.homepage = 'http://www.aaf.edu.au'
17
+ spec.license = 'Apache-2.0'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+
21
+ spec.add_dependency 'accession', '~> 1.0'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'mysql2', '~> 0.3.20'
26
+ spec.add_development_dependency 'sqlite3'
27
+ spec.add_development_dependency 'valhammer'
28
+ spec.add_development_dependency 'factory_girl'
29
+ spec.add_development_dependency 'faker'
30
+ spec.add_development_dependency 'rspec-rails'
31
+ spec.add_development_dependency 'rubocop'
32
+ spec.add_development_dependency 'simplecov'
33
+ spec.add_development_dependency 'codeclimate-test-reporter'
34
+ spec.add_development_dependency 'rails-controller-testing'
35
+
36
+ spec.add_development_dependency 'guard'
37
+ spec.add_development_dependency 'guard-rspec'
38
+ spec.add_development_dependency 'guard-rubocop'
39
+ spec.add_development_dependency 'guard-bundler'
40
+
41
+ spec.add_development_dependency 'rails', '~> 4.2.6'
42
+ end
@@ -0,0 +1 @@
1
+ require 'gumboot'
data/lib/gumboot.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Gumboot
2
+ # Your code goes here...
3
+ end
4
+
5
+ require 'gumboot/version'
@@ -0,0 +1,17 @@
1
+ RSpec.shared_examples 'Anon controller' do
2
+ controller(described_class) do
3
+ def an_action
4
+ check_access!('required:permission')
5
+ head :ok
6
+ end
7
+
8
+ def bad_action
9
+ head :ok
10
+ end
11
+
12
+ def public
13
+ public_action
14
+ head :ok
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ RSpec.shared_examples 'API constraints' do
2
+ context 'AAF shared implementation' do
3
+ context '#matches?' do
4
+ context 'with default: false' do
5
+ subject { described_class.new(version: '1', default: false) }
6
+
7
+ it 'is true for a valid request' do
8
+ expect(subject.matches?(matching_request)).to be_truthy
9
+ end
10
+
11
+ it 'is false for a non-matching request' do
12
+ expect(subject.matches?(non_matching_request)).to be_falsey
13
+ end
14
+ end
15
+
16
+ context 'with default: true' do
17
+ subject { described_class.new(version: '1', default: true) }
18
+
19
+ it 'is true for a valid request' do
20
+ expect(subject.matches?(matching_request)).to be_truthy
21
+ end
22
+
23
+ it 'is true for a non-matching request' do
24
+ expect(subject.matches?(non_matching_request)).to be_truthy
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,206 @@
1
+ require 'gumboot/shared_examples/anonymous_controller'
2
+
3
+ RSpec.shared_examples 'API base controller' do
4
+ context 'AAF shared implementation' do
5
+ include_examples 'Anon controller'
6
+
7
+ before do
8
+ @routes.draw do
9
+ get '/anonymous/an_action' => 'api/api#an_action'
10
+ get '/anonymous/bad_action' => 'api/api#bad_action'
11
+ get '/anonymous/public' => 'api/api#public'
12
+ end
13
+ end
14
+
15
+ it { is_expected.to respond_to(:subject) }
16
+
17
+ context '#ensure_authenticated as before_action' do
18
+ subject { response }
19
+ let(:json) { JSON.parse(subject.body) }
20
+
21
+ context 'no x509 header set by nginx' do
22
+ before { get :an_action }
23
+
24
+ it { is_expected.to have_http_status(:unauthorized) }
25
+
26
+ context 'json within response' do
27
+ it 'has a message' do
28
+ expect(json['message']).to eq('SSL client failure.')
29
+ end
30
+ it 'has an error' do
31
+ expect(json['error']).to eq('Subject DN')
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'x509 header set to "(null)"' do
37
+ before do
38
+ request.env['HTTP_X509_DN'] = '(null)'
39
+ get :an_action
40
+ end
41
+
42
+ it { is_expected.to have_http_status(:unauthorized) }
43
+ context 'json within response' do
44
+ it 'has a message' do
45
+ expect(json['message']).to eq('SSL client failure.')
46
+ end
47
+ it 'has an error' do
48
+ expect(json['error']).to eq('Subject DN')
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'invalid x509 header set by nginx' do
54
+ before do
55
+ request.env['HTTP_X509_DN'] = "Z=#{Faker::Lorem.word}"
56
+ get :an_action
57
+ end
58
+
59
+ it { is_expected.to have_http_status(:unauthorized) }
60
+ context 'json within response' do
61
+ it 'has a message' do
62
+ expect(json['message']).to eq('SSL client failure.')
63
+ end
64
+ it 'has an error' do
65
+ expect(json['error']).to eq('Subject DN invalid')
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'without a CN component to DN' do
71
+ before do
72
+ request.env['HTTP_X509_DN'] = "O=#{Faker::Lorem.word}"
73
+ get :an_action
74
+ end
75
+
76
+ it { is_expected.to have_http_status(:unauthorized) }
77
+ context 'json within response' do
78
+ it 'has a message' do
79
+ expect(json['message']).to eq('SSL client failure.')
80
+ end
81
+ it 'has an error' do
82
+ expect(json['error']).to eq('Subject CN invalid')
83
+ end
84
+ end
85
+ end
86
+
87
+ context 'with a CN that does not represent an APISubject' do
88
+ before do
89
+ request.env['HTTP_X509_DN'] = "CN=#{Faker::Lorem.word}/" \
90
+ "O=#{Faker::Lorem.word}"
91
+ get :an_action
92
+ end
93
+
94
+ it { is_expected.to have_http_status(:unauthorized) }
95
+ context 'json within response' do
96
+ it 'has a message' do
97
+ expect(json['message']).to eq('SSL client failure.')
98
+ end
99
+ it 'has an error' do
100
+ expect(json['error']).to eq('Subject invalid')
101
+ end
102
+ end
103
+ end
104
+
105
+ context 'with an APISubject that is not functioning' do
106
+ let(:api_subject) { create :api_subject, enabled: false }
107
+
108
+ before do
109
+ request.env['HTTP_X509_DN'] = "CN=#{api_subject.x509_cn}/" \
110
+ "O=#{Faker::Lorem.word}"
111
+ get :an_action
112
+ end
113
+
114
+ it { is_expected.to have_http_status(:unauthorized) }
115
+ context 'json within response' do
116
+ it 'has a message' do
117
+ expect(json['message']).to eq('SSL client failure.')
118
+ end
119
+ it 'has an error' do
120
+ expect(json['error']).to eq('Subject not functional')
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ context '#ensure_access_checked as after_action' do
127
+ subject(:api_subject) { create :api_subject }
128
+ let(:json) { JSON.parse(response.body) }
129
+
130
+ before do
131
+ request.env['HTTP_X509_DN'] = "CN=#{api_subject.x509_cn}/DC=example"
132
+ end
133
+
134
+ RSpec.shared_examples 'APIController base state' do
135
+ it 'fails request to incorrectly implemented action' do
136
+ msg = 'No access control performed by API::APIController#bad_action'
137
+ expect { get :bad_action }.to raise_error(msg)
138
+ end
139
+
140
+ it 'completes request to a public action' do
141
+ get :public
142
+ expect(response).to have_http_status(:ok)
143
+ end
144
+ end
145
+
146
+ context 'subject without permissions' do
147
+ include_examples 'APIController base state'
148
+
149
+ it 'has no permissions' do
150
+ expect(api_subject.permissions).to eq([])
151
+ end
152
+
153
+ context 'the request does not complete' do
154
+ before { get :an_action }
155
+ it 'should respond with status code :forbidden (403)' do
156
+ expect(response).to have_http_status(:forbidden)
157
+ end
158
+ it 'recieves a json message' do
159
+ expect(json['message'])
160
+ .to eq('The request was understood but explicitly denied.')
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'subject with invalid permissions' do
166
+ subject(:api_subject) do
167
+ create :api_subject, :authorized, permission: 'invalid:permission'
168
+ end
169
+
170
+ include_examples 'APIController base state'
171
+
172
+ it 'has an invalid permission' do
173
+ expect(api_subject.permissions).to eq(['invalid:permission'])
174
+ end
175
+
176
+ context 'the request does not complete' do
177
+ before { get :an_action }
178
+ it 'should respond with status code :forbidden (403)' do
179
+ expect(response).to have_http_status(:forbidden)
180
+ end
181
+ it 'recieves a json message' do
182
+ expect(json['message'])
183
+ .to eq('The request was understood but explicitly denied.')
184
+ end
185
+ end
186
+ end
187
+
188
+ context 'subject with valid permission' do
189
+ subject(:api_subject) do
190
+ create :api_subject, :authorized, permission: 'required:permission'
191
+ end
192
+
193
+ include_examples 'APIController base state'
194
+
195
+ it 'has a valid permission' do
196
+ expect(api_subject.permissions).to eq(['required:permission'])
197
+ end
198
+
199
+ it 'completes request after permissions checked' do
200
+ get :an_action
201
+ expect(response).to have_http_status(:ok)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,44 @@
1
+ RSpec.shared_examples 'API Subjects' do
2
+ context 'AAF shared implementation' do
3
+ subject { build :api_subject }
4
+
5
+ it { is_expected.to be_valid }
6
+ it { is_expected.to be_an(Accession::Principal) }
7
+ it { is_expected.to respond_to(:roles) }
8
+ it { is_expected.to respond_to(:permissions) }
9
+ it { is_expected.to respond_to(:permits?) }
10
+ it { is_expected.to respond_to(:functioning?) }
11
+
12
+ it 'is invalid without an x509_cn' do
13
+ subject.x509_cn = nil
14
+ expect(subject).not_to be_valid
15
+ end
16
+ it 'is invalid if an x509 value is not in the correct format' do
17
+ subject.x509_cn += '%^%&*'
18
+ expect(subject).not_to be_valid
19
+ end
20
+ it 'is valid if an x509 value is in the correct format' do
21
+ expect(subject).to be_valid
22
+ end
23
+ it 'is invalid if an x509 value is not unique' do
24
+ create(:api_subject, x509_cn: subject.x509_cn)
25
+ expect(subject).not_to be_valid
26
+ end
27
+ it 'is invalid without a description' do
28
+ subject.description = nil
29
+ expect(subject).not_to be_valid
30
+ end
31
+ it 'is invalid without a contact name' do
32
+ subject.contact_name = nil
33
+ expect(subject).not_to be_valid
34
+ end
35
+ it 'is invalid without a contact mail address' do
36
+ subject.contact_mail = nil
37
+ expect(subject).not_to be_valid
38
+ end
39
+ it 'is invalid without an enabled state' do
40
+ subject.enabled = nil
41
+ expect(subject).not_to be_valid
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,223 @@
1
+ require 'gumboot/shared_examples/anonymous_controller'
2
+
3
+ RSpec.shared_examples 'Application controller' do
4
+ context 'AAF shared implementation' do
5
+ include_examples 'Anon controller'
6
+
7
+ before do
8
+ @routes.draw do
9
+ get '/anonymous/an_action' => 'anonymous#an_action'
10
+ get '/anonymous/bad_action' => 'anonymous#bad_action'
11
+ get '/anonymous/public' => 'anonymous#public'
12
+ end
13
+ end
14
+
15
+ context '#subject' do
16
+ context 'No subject ID is set in session' do
17
+ before do
18
+ session[:subject_id] = nil
19
+ end
20
+
21
+ it 'returns nil' do
22
+ expect(subject.subject).to be_nil
23
+ end
24
+ end
25
+
26
+ context 'session has subject_id that does not represent a Subject' do
27
+ before do
28
+ session[:subject_id] = -1
29
+ end
30
+
31
+ it 'returns nil' do
32
+ expect(subject.subject).to be_nil
33
+ end
34
+ end
35
+
36
+ context 'Subject that is not functioning' do
37
+ let(:current_subject) { create :subject, enabled: false }
38
+
39
+ before do
40
+ session[:subject_id] = current_subject.id
41
+ end
42
+
43
+ it 'returns nil' do
44
+ expect(subject.subject).to be_nil
45
+ end
46
+ end
47
+
48
+ context 'Subject is valid' do
49
+ let(:current_subject) { create :subject }
50
+
51
+ before do
52
+ session[:subject_id] = current_subject.id
53
+ end
54
+
55
+ it 'returns subject' do
56
+ expect(subject.subject).to eq(current_subject)
57
+ end
58
+ end
59
+ end
60
+
61
+ context '#ensure_authenticated as before_action' do
62
+ subject { response }
63
+
64
+ context 'No subject ID is set in session' do
65
+ before do
66
+ session[:subject_id] = nil
67
+ get :an_action
68
+ end
69
+
70
+ it { is_expected.to have_http_status(:redirect) }
71
+ it { is_expected.to redirect_to('/auth/login') }
72
+ end
73
+
74
+ context 'session has subject_id that does not represent a Subject' do
75
+ before do
76
+ session[:subject_id] = -1
77
+ get :an_action
78
+ end
79
+
80
+ it { is_expected.to have_http_status(:unauthorized) }
81
+
82
+ it do
83
+ is_expected.to render_template(
84
+ 'dynamic_errors/unauthorized',
85
+ 'layouts/application'
86
+ )
87
+ end
88
+
89
+ it 'resets the session' do
90
+ expect(session[:subject_id]).to be_nil
91
+ end
92
+ end
93
+
94
+ context 'Subject that is not functioning' do
95
+ let(:current_subject) { create :subject, enabled: false }
96
+
97
+ before do
98
+ session[:subject_id] = current_subject.id
99
+ get :an_action
100
+ end
101
+
102
+ it { is_expected.to have_http_status(:unauthorized) }
103
+
104
+ it do
105
+ is_expected.to render_template(
106
+ 'dynamic_errors/unauthorized',
107
+ 'layouts/application'
108
+ )
109
+ end
110
+
111
+ it 'resets the session' do
112
+ expect(session[:subject_id]).to be_nil
113
+ end
114
+ end
115
+
116
+ context 'when request is session' do
117
+ it 'POST request should not create a uri session' do
118
+ post :an_action
119
+ expect(session).not_to include(:return_url)
120
+ end
121
+
122
+ it 'GET request should not create a uri session' do
123
+ get :an_action
124
+ uri = URI.parse(session[:return_url])
125
+ expect(uri.path).to eq('/anonymous/an_action')
126
+ expect(uri.query).to be_blank
127
+ expect(uri.fragment).to be_blank
128
+ end
129
+
130
+ it 'GET request should create a uri session including fragments' do
131
+ get :an_action, params: { time: 1000 }
132
+ uri = URI.parse(session[:return_url])
133
+
134
+ expect(uri.path).to eq('/anonymous/an_action')
135
+ expect(uri.query).to eq('time=1000')
136
+ expect(uri.fragment).to be_blank
137
+ end
138
+ end
139
+ end
140
+
141
+ context '#ensure_access_checked as after_action' do
142
+ before { session[:subject_id] = subject.id }
143
+
144
+ RSpec.shared_examples 'ApplicationController base state' do
145
+ it 'fails request to incorrectly implemented action' do
146
+ msg = 'No access control performed by AnonymousController#bad_action'
147
+ expect { get :bad_action }.to raise_error(msg)
148
+ end
149
+
150
+ it 'completes request to a public action' do
151
+ get :public
152
+ expect(response).to have_http_status(:ok)
153
+ end
154
+ end
155
+
156
+ context 'subject without permissions' do
157
+ subject(:subject) { create :subject }
158
+
159
+ include_examples 'ApplicationController base state'
160
+
161
+ it 'has no permissions' do
162
+ expect(subject.permissions).to eq([])
163
+ end
164
+
165
+ context 'the request does not complete' do
166
+ before { get :an_action }
167
+ it 'should respond with status code :forbidden (403)' do
168
+ expect(response).to have_http_status(:forbidden)
169
+ end
170
+
171
+ it 'renders forbidden template' do
172
+ expect(response).to render_template(
173
+ 'dynamic_errors/forbidden',
174
+ 'layouts/application'
175
+ )
176
+ end
177
+ end
178
+ end
179
+
180
+ context 'subject with invalid permissions' do
181
+ subject(:subject) do
182
+ create :subject, :authorized, permission: 'invalid:permission'
183
+ end
184
+
185
+ include_examples 'ApplicationController base state'
186
+
187
+ it 'has an invalid permission' do
188
+ expect(subject.permissions).to eq(['invalid:permission'])
189
+ end
190
+
191
+ context 'the request does not complete' do
192
+ before { get :an_action }
193
+ it 'should respond with status code :forbidden (403)' do
194
+ expect(response).to have_http_status(:forbidden)
195
+ end
196
+ it 'renders forbidden template' do
197
+ expect(response).to render_template(
198
+ 'dynamic_errors/forbidden',
199
+ 'layouts/application'
200
+ )
201
+ end
202
+ end
203
+ end
204
+
205
+ context 'subject with valid permission' do
206
+ subject(:subject) do
207
+ create :subject, :authorized, permission: 'required:permission'
208
+ end
209
+
210
+ include_examples 'ApplicationController base state'
211
+
212
+ it 'has a valid permission' do
213
+ expect(subject.permissions).to eq(['required:permission'])
214
+ end
215
+
216
+ it 'completes request after permissions checked' do
217
+ get :an_action
218
+ expect(response).to have_http_status(:ok)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end