rkerberos 0.2.3 → 0.3.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/spec/krb5_spec.rb CHANGED
@@ -23,7 +23,7 @@ RSpec.describe Kerberos::Krb5 do
23
23
  let(:service) { 'kadmin/admin' }
24
24
 
25
25
  it 'has the correct version constant' do
26
- expect(Kerberos::Krb5::VERSION).to eq('0.2.3')
26
+ expect(Kerberos::Krb5::VERSION).to eq('0.3.1')
27
27
  end
28
28
 
29
29
  it 'accepts a block and yields itself' do
@@ -31,6 +31,31 @@ RSpec.describe Kerberos::Krb5 do
31
31
  described_class.new { |k| expect(k).to be_a(described_class) }
32
32
  end
33
33
 
34
+ describe 'constructor' do
35
+ it 'accepts a context keyword argument' do
36
+ context = Kerberos::Krb5::Context.new
37
+ k = described_class.new(context: context)
38
+ expect(k).to be_a(described_class)
39
+ expect(k.get_default_realm).to eq(@realm)
40
+ k.close
41
+ end
42
+
43
+ it 'raises TypeError if context is not a Kerberos::Krb5::Context' do
44
+ expect { described_class.new(context: 'bad') }.to raise_error(TypeError, /context must be/)
45
+ end
46
+
47
+ it 'raises an error if the context is closed' do
48
+ context = Kerberos::Krb5::Context.new
49
+ context.close
50
+ expect { described_class.new(context: context) }.to raise_error(Kerberos::Krb5::Exception, /context is closed/)
51
+ end
52
+
53
+ it 'rejects positional option hashes and unknown keywords' do
54
+ expect { described_class.new({context: nil}) }.to raise_error(ArgumentError)
55
+ expect { described_class.new(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
56
+ end
57
+ end
58
+
34
59
  describe '#get_default_realm' do
35
60
  it 'responds to get_default_realm' do
36
61
  expect(krb5).to respond_to(:get_default_realm)
@@ -48,6 +73,17 @@ RSpec.describe Kerberos::Krb5 do
48
73
  it 'default_realm is an alias for get_default_realm' do
49
74
  expect(krb5.method(:default_realm)).to eq(krb5.method(:get_default_realm))
50
75
  end
76
+ it 'raises after the object is closed' do
77
+ krb5.close
78
+ expect { krb5.get_default_realm }.to raise_error(Kerberos::Krb5::Exception)
79
+ end
80
+ end
81
+
82
+ describe '#set_default_realm' do
83
+ it 'raises after the object is closed' do
84
+ krb5.close
85
+ expect { krb5.set_default_realm(@realm) }.to raise_error(Kerberos::Krb5::Exception)
86
+ end
51
87
  end
52
88
 
53
89
  describe '#verify_init_creds', :kadm5 do
@@ -68,53 +104,108 @@ RSpec.describe Kerberos::Krb5 do
68
104
  expect(krb5).to respond_to(:verify_init_creds)
69
105
  end
70
106
 
107
+ it 'requires get_init_creds_password arguments as keywords' do
108
+ expect { krb5.get_init_creds_password(user, 'changeme') }.to raise_error(ArgumentError)
109
+ expect {
110
+ krb5.get_init_creds_password(principal: user, password: 'changeme', unknown: true)
111
+ }.to raise_error(ArgumentError, /unknown keyword/)
112
+ end
113
+
71
114
  it 'raises when no credentials have been acquired' do
72
115
  expect { krb5.verify_init_creds }.to raise_error(Kerberos::Krb5::Exception)
73
116
  end
74
117
 
75
118
  it 'validates argument types' do
76
- expect { krb5.verify_init_creds(true) }.to raise_error(TypeError)
77
- expect { krb5.verify_init_creds(nil, true) }.to raise_error(TypeError)
78
- expect { krb5.verify_init_creds(nil, nil, true) }.to raise_error(TypeError)
119
+ expect { krb5.verify_init_creds(server: true) }.to raise_error(TypeError)
120
+ expect { krb5.verify_init_creds(keytab: true) }.to raise_error(TypeError)
121
+ expect { krb5.verify_init_creds(ccache: true) }.to raise_error(TypeError)
122
+ end
123
+
124
+ it 'accepts only documented keyword arguments' do
125
+ expect { krb5.verify_init_creds(nil) }.to raise_error(ArgumentError)
126
+ expect { krb5.verify_init_creds(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
79
127
  end
80
128
 
81
129
  it 'verifies credentials obtained via password' do
82
- krb5.get_init_creds_password(user, 'changeme')
130
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
83
131
  expect(krb5.verify_init_creds).to be true
84
132
  end
85
133
 
86
134
  it 'accepts a server principal string' do
87
- krb5.get_init_creds_password(user, 'changeme')
88
- expect(krb5.verify_init_creds("kadmin/admin@#{@realm}")).to be true
135
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
136
+ expect(krb5.verify_init_creds(server: "kadmin/admin@#{@realm}")).to be true
89
137
  end
90
138
 
91
139
  it 'accepts a Keytab object' do
92
- krb5.get_init_creds_password(user, 'changeme')
140
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
93
141
  kt = Kerberos::Krb5::Keytab.new
94
- expect(krb5.verify_init_creds(nil, kt)).to be true
142
+ expect(krb5.verify_init_creds(keytab: kt)).to be true
143
+ end
144
+
145
+ it 'rejects a closed Keytab object' do
146
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
147
+ kt = Kerberos::Krb5::Keytab.new
148
+ kt.close
149
+ expect { krb5.verify_init_creds(keytab: kt) }
150
+ .to raise_error(Kerberos::Krb5::Exception, /keytab is closed/)
95
151
  end
96
152
 
97
153
  it 'stores additional credentials in provided CredentialsCache' do
98
154
  ccache = Kerberos::Krb5::CredentialsCache.new
99
- krb5.get_init_creds_password(user, 'changeme')
100
- expect(krb5.verify_init_creds(nil, nil, ccache)).to be true
155
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
156
+ expect(krb5.verify_init_creds(ccache: ccache)).to be true
101
157
  expect(ccache.primary_principal).to be_a(String)
102
158
  expect(ccache.primary_principal).to include('@')
103
159
  end
104
160
 
161
+ it 'rejects a closed CredentialsCache' do
162
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
163
+ ccache = Kerberos::Krb5::CredentialsCache.new
164
+ ccache.close
165
+ expect { krb5.verify_init_creds(ccache: ccache) }
166
+ .to raise_error(Kerberos::Krb5::Exception, /credentials cache is closed/)
167
+ end
168
+
169
+ it 'does not acquire credentials into a closed CredentialsCache' do
170
+ ccache = Kerberos::Krb5::CredentialsCache.new
171
+ ccache.close
172
+ expect {
173
+ krb5.get_init_creds_password(principal: user, password: 'changeme', ccache: ccache)
174
+ }.to raise_error(Kerberos::Krb5::Exception, /credentials cache is closed/)
175
+ end
176
+
105
177
  it 'provides authenticate! which acquires and verifies (Zanarotti mitigation)' do
106
178
  expect(krb5).to respond_to(:authenticate!)
107
- expect(krb5.authenticate!(user, 'changeme')).to be true
179
+ expect(krb5.authenticate!(principal: user, password: 'changeme')).to be true
108
180
  expect(krb5.verify_init_creds).to be true
109
181
  end
110
182
 
111
183
  it 'accepts an optional service argument' do
112
- expect { krb5.authenticate!(user, 'changeme', 'kadmin/changepw') }.not_to raise_error
113
- expect(krb5.verify_init_creds).to be true
184
+ expect {
185
+ krb5.authenticate!(principal: user, password: 'changeme', service: 'verify/rkerberos-test')
186
+ }.not_to raise_error
187
+ end
188
+
189
+ it 'fails closed when no verification keytab is available' do
190
+ original_keytab = ENV['KRB5_KTNAME']
191
+ ENV['KRB5_KTNAME'] = "FILE:/tmp/missing_verify_keytab_#{Process.pid}"
192
+
193
+ expect {
194
+ described_class.new.authenticate!(principal: user, password: 'changeme')
195
+ }.to raise_error(Kerberos::Krb5::Exception, /krb5_verify_init_creds/)
196
+ ensure
197
+ ENV['KRB5_KTNAME'] = original_keytab
114
198
  end
115
199
 
116
200
  it 'validates argument types for authenticate!' do
117
- expect { krb5.authenticate!(true, true) }.to raise_error(TypeError)
201
+ expect { krb5.authenticate!(principal: true, password: true) }.to raise_error(TypeError)
202
+ end
203
+
204
+ it 'requires authenticate! arguments as keywords' do
205
+ expect { krb5.authenticate!(user, 'changeme') }.to raise_error(ArgumentError)
206
+ expect {
207
+ krb5.authenticate!(principal: user, password: 'changeme', unknown: true)
208
+ }.to raise_error(ArgumentError, /unknown keyword/)
118
209
  end
119
210
  end
120
211
 
@@ -154,16 +245,16 @@ RSpec.describe Kerberos::Krb5 do
154
245
  end
155
246
 
156
247
  it 'changes the password successfully' do
157
- krb5.get_init_creds_password(user, 'changeme')
248
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
158
249
  expect(krb5.change_password('changeme', 'Newpass99!')).to be true
159
250
  # Verify we can authenticate with the new password
160
251
  krb5_check = described_class.new
161
- expect(krb5_check.get_init_creds_password(user, 'Newpass99!')).to be true
252
+ expect(krb5_check.get_init_creds_password(principal: user, password: 'Newpass99!')).to be true
162
253
  krb5_check.close
163
254
  end
164
255
 
165
256
  it 'raises with a meaningful message when the old password is wrong' do
166
- krb5.get_init_creds_password(user, 'changeme')
257
+ krb5.get_init_creds_password(principal: user, password: 'changeme')
167
258
  expect {
168
259
  krb5.change_password('wrongpass', 'Newpass99!')
169
260
  }.to raise_error(Kerberos::Krb5::Exception, /krb5_(get_init_creds_password|change_password)/)
@@ -180,7 +271,7 @@ RSpec.describe Kerberos::Krb5 do
180
271
  let(:compliant_pw) { 'Changeme1!' }
181
272
 
182
273
  it 'raises an exception with the KDC rejection reason' do
183
- krb5.get_init_creds_password(policy_user, compliant_pw)
274
+ krb5.get_init_creds_password(principal: policy_user, password: compliant_pw)
184
275
  expect {
185
276
  krb5.change_password(compliant_pw, 'a')
186
277
  }.to raise_error(Kerberos::Krb5::Exception, /krb5_change_password/)
@@ -208,18 +299,92 @@ RSpec.describe Kerberos::Krb5 do
208
299
  expect(krb5).to respond_to(:get_init_creds_keytab)
209
300
  end
210
301
 
302
+ it 'accepts only documented keyword arguments' do
303
+ expect { krb5.get_init_creds_keytab(user) }.to raise_error(ArgumentError)
304
+ expect { krb5.get_init_creds_keytab(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
305
+ end
306
+
211
307
  it 'acquires credentials for a principal from a supplied keytab file' do
212
308
  kt_name = "FILE:#{@kt_file}"
213
- expect { krb5.get_init_creds_keytab(user, kt_name) }.not_to raise_error
309
+ expect { krb5.get_init_creds_keytab(principal: user, keytab: kt_name) }.not_to raise_error
214
310
  expect(krb5.verify_init_creds).to be true
215
311
  end
216
312
 
217
313
  it 'accepts a CredentialsCache to receive credentials' do
218
314
  ccache = Kerberos::Krb5::CredentialsCache.new
219
315
  kt_name = "FILE:#{@kt_file}"
220
- expect { krb5.get_init_creds_keytab(user, kt_name, nil, ccache) }.not_to raise_error
316
+ expect { krb5.get_init_creds_keytab(principal: user, keytab: kt_name, ccache: ccache) }.not_to raise_error
221
317
  expect(ccache.primary_principal).to be_a(String)
222
318
  expect(ccache.primary_principal).to include('@')
223
319
  end
320
+
321
+ it 'rejects a closed CredentialsCache' do
322
+ ccache = Kerberos::Krb5::CredentialsCache.new
323
+ ccache.close
324
+ kt_name = "FILE:#{@kt_file}"
325
+ expect {
326
+ krb5.get_init_creds_keytab(principal: user, keytab: kt_name, ccache: ccache)
327
+ }.to raise_error(Kerberos::Krb5::Exception, /credentials cache is closed/)
328
+ end
329
+ end
330
+
331
+ describe '.thread_safe?' do
332
+ it 'returns a boolean' do
333
+ result = described_class.thread_safe?
334
+ expect([true, false]).to include(result)
335
+ end
336
+
337
+ it 'is callable without an instance' do
338
+ expect(described_class).to respond_to(:thread_safe?)
339
+ end
340
+ end
341
+
342
+ describe '#get_host_realm' do
343
+ it 'responds to get_host_realm' do
344
+ expect(krb5).to respond_to(:get_host_realm)
345
+ end
346
+
347
+ it 'returns an array of realm strings' do
348
+ result = krb5.get_host_realm('localhost')
349
+ expect(result).to be_a(Array)
350
+ result.each { |r| expect(r).to be_a(String) }
351
+ end
352
+
353
+ it 'returns a non-empty array for a known hostname' do
354
+ result = krb5.get_host_realm('localhost')
355
+ expect(result).not_to be_empty
356
+ end
357
+
358
+ it 'raises TypeError for non-string argument' do
359
+ expect { krb5.get_host_realm(123) }.to raise_error(TypeError)
360
+ end
361
+
362
+ it 'raises ArgumentError when called without arguments' do
363
+ expect { krb5.get_host_realm }.to raise_error(ArgumentError)
364
+ end
365
+ end
366
+
367
+ describe '#expand_hostname' do
368
+ it 'responds to expand_hostname' do
369
+ expect(krb5).to respond_to(:expand_hostname)
370
+ end
371
+
372
+ it 'returns a string' do
373
+ result = krb5.expand_hostname('localhost')
374
+ expect(result).to be_a(String)
375
+ end
376
+
377
+ it 'returns a non-empty string' do
378
+ result = krb5.expand_hostname('localhost')
379
+ expect(result.size).to be > 0
380
+ end
381
+
382
+ it 'raises TypeError for non-string argument' do
383
+ expect { krb5.expand_hostname(123) }.to raise_error(TypeError)
384
+ end
385
+
386
+ it 'raises ArgumentError when called without arguments' do
387
+ expect { krb5.expand_hostname }.to raise_error(ArgumentError)
388
+ end
224
389
  end
225
390
  end
data/spec/policy_spec.rb CHANGED
@@ -21,6 +21,10 @@ RSpec.describe 'Kerberos::Kadm5::Policy', :kadm5 do
21
21
  it 'must be present' do
22
22
  expect { klass.new(max_life: 10000) }.to raise_error(ArgumentError)
23
23
  end
24
+ it 'rejects positional option hashes and unknown keywords' do
25
+ expect { klass.new({name: 'test'}) }.to raise_error(ArgumentError)
26
+ expect { klass.new(name: 'test', unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
27
+ end
24
28
  end
25
29
 
26
30
  describe 'min_life' do
@@ -5,20 +5,51 @@ require 'rkerberos'
5
5
 
6
6
  RSpec.describe Kerberos::Krb5::Principal do
7
7
  let(:name) { 'Jon' }
8
- subject(:princ) { described_class.new(name) }
8
+ subject(:princ) { described_class.new(name: name) }
9
9
 
10
10
  describe 'constructor' do
11
- it 'requires a string argument' do
12
- expect { described_class.new(1) }.to raise_error(TypeError)
13
- expect { described_class.new(true) }.to raise_error(TypeError)
11
+ it 'requires name to be a string' do
12
+ expect { described_class.new(name: 1) }.to raise_error(TypeError)
13
+ expect { described_class.new(name: true) }.to raise_error(TypeError)
14
14
  end
15
15
 
16
- it 'accepts an explicit nil argument' do
17
- expect{ described_class.new(nil) }.not_to raise_error
16
+ it 'accepts no arguments' do
17
+ expect{ described_class.new }.not_to raise_error
18
18
  end
19
19
 
20
- it 'works as expected with a nil argument to the constructor' do
21
- expect(described_class.new(nil).principal).to be_nil
20
+ it 'works as expected with no name argument' do
21
+ expect(described_class.new.principal).to be_nil
22
+ end
23
+
24
+ it 'rejects positional arguments' do
25
+ expect { described_class.new('Jon') }.to raise_error(ArgumentError)
26
+ end
27
+
28
+ it 'accepts a context keyword argument' do
29
+ ctx = Kerberos::Krb5::Context.new
30
+ expect { described_class.new(name: 'Jon', context: ctx) }.not_to raise_error
31
+ end
32
+
33
+ it 'rejects positional option hashes and unknown keywords' do
34
+ expect { described_class.new({name: 'Jon'}) }.to raise_error(ArgumentError)
35
+ expect { described_class.new(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
36
+ end
37
+
38
+ it 'uses the same realm when a context is provided' do
39
+ ctx = Kerberos::Krb5::Context.new
40
+ p1 = described_class.new(name: 'Jon')
41
+ p2 = described_class.new(name: 'Jon', context: ctx)
42
+ expect(p2.realm).to eq(p1.realm)
43
+ end
44
+
45
+ it 'raises TypeError for non-Context context argument' do
46
+ expect { described_class.new(name: 'Jon', context: "bad") }.to raise_error(TypeError)
47
+ end
48
+
49
+ it 'raises error for a closed context' do
50
+ ctx = Kerberos::Krb5::Context.new
51
+ ctx.close
52
+ expect { described_class.new(name: 'Jon', context: ctx) }.to raise_error(Kerberos::Krb5::Exception)
22
53
  end
23
54
  end
24
55
 
@@ -27,8 +58,34 @@ RSpec.describe Kerberos::Krb5::Principal do
27
58
  expect(subject.realm).to eq('EXAMPLE.COM')
28
59
  end
29
60
 
30
- it 'raises an error if the constructor argument was nil' do
31
- expect{ described_class.new(nil).realm }.to raise_error(Kerberos::Krb5::Exception, /no principal/)
61
+ it 'raises an error if no name was provided' do
62
+ expect{ described_class.new.realm }.to raise_error(Kerberos::Krb5::Exception, /no principal/)
63
+ end
64
+ end
65
+
66
+ describe '#close' do
67
+ it 'releases a borrowed context and prevents further native operations' do
68
+ context = Kerberos::Krb5::Context.new
69
+ principal = described_class.new(name: name, context: context)
70
+
71
+ expect(principal.close).to equal(principal)
72
+ expect { principal.realm }.to raise_error(Kerberos::Krb5::Exception)
73
+ expect { context.close }.not_to raise_error
74
+ end
75
+
76
+ it 'can be called multiple times' do
77
+ expect { 2.times { princ.close } }.not_to raise_error
78
+ end
79
+ end
80
+
81
+ describe '#==' do
82
+ it 'returns true for equivalent principals' do
83
+ expect(princ).to eq(described_class.new(name: name))
84
+ end
85
+
86
+ it 'returns false for objects of another type' do
87
+ expect(princ == name).to be false
88
+ expect(princ == nil).to be false
32
89
  end
33
90
  end
34
91
 
@@ -83,4 +140,53 @@ RSpec.describe Kerberos::Krb5::Principal do
83
140
  expect { princ.mod_date }.not_to raise_error
84
141
  end
85
142
  end
143
+
144
+ describe '#principal_type' do
145
+ it 'returns an integer for a named principal' do
146
+ p = described_class.new(name: 'user@EXAMPLE.COM')
147
+ expect(p.principal_type).to be_a(Integer)
148
+ end
149
+
150
+ it 'returns KRB5_NT_PRINCIPAL (1) for a simple name' do
151
+ p = described_class.new(name: 'user@EXAMPLE.COM')
152
+ expect(p.principal_type).to eq(1)
153
+ end
154
+
155
+ it 'raises an error when no principal has been established' do
156
+ p = described_class.new
157
+ expect { p.principal_type }.to raise_error(Kerberos::Krb5::Exception, /no principal/)
158
+ end
159
+ end
160
+
161
+ describe '#components' do
162
+ it 'responds to components' do
163
+ expect(princ).to respond_to(:components)
164
+ end
165
+
166
+ it 'returns an array of strings' do
167
+ result = princ.components
168
+ expect(result).to be_a(Array)
169
+ result.each { |c| expect(c).to be_a(String) }
170
+ end
171
+
172
+ it 'returns one component for a simple principal' do
173
+ p = described_class.new(name: 'user@EXAMPLE.COM')
174
+ expect(p.components).to eq(['user'])
175
+ end
176
+
177
+ it 'returns multiple components for a compound principal' do
178
+ p = described_class.new(name: 'admin/instance@EXAMPLE.COM')
179
+ expect(p.components).to eq(['admin', 'instance'])
180
+ end
181
+
182
+ it 'returns three components for a three-part principal' do
183
+ p = described_class.new(name: 'a/b/c@EXAMPLE.COM')
184
+ expect(p.components).to eq(['a', 'b', 'c'])
185
+ end
186
+
187
+ it 'raises an error when no principal has been established' do
188
+ p = described_class.new
189
+ expect { p.components }.to raise_error(Kerberos::Krb5::Exception, /no principal/)
190
+ end
191
+ end
86
192
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rkerberos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Berger
@@ -66,6 +66,7 @@ extensions:
66
66
  extra_rdoc_files: []
67
67
  files:
68
68
  - CHANGES.md
69
+ - EXAMPLES.md
69
70
  - Gemfile
70
71
  - LICENSE
71
72
  - MANIFEST.md
@@ -93,17 +94,17 @@ files:
93
94
  - spec/policy_spec.rb
94
95
  - spec/principal_spec.rb
95
96
  - spec/spec_helper.rb
96
- homepage: http://github.com/rkerberos/rkerberos
97
+ homepage: https://github.com/rkerberos/rkerberos
97
98
  licenses:
98
99
  - Artistic-2.0
99
100
  metadata:
100
- homepage_uri: https://github.com/rkerberos/rkerberos
101
101
  bug_tracker_uri: https://github.com/rkerberos/rkerberos/issues
102
- changelog_uri: https://github.com/rkerberos/rkerberos/blob/main/CHANGES.md
102
+ changelog_uri: https://github.com/rkerberos/rkerberos/blob/master/CHANGES.md
103
103
  documentation_uri: https://github.com/rkerberos/rkerberos/wiki
104
+ homepage_uri: https://github.com/rkerberos/rkerberos
104
105
  source_code_uri: https://github.com/rkerberos/rkerberos
105
106
  wiki_uri: https://github.com/rkerberos/rkerberos/wiki
106
- github_repo: https://github.com/djberg96/rkerberos
107
+ github_repo: https://github.com/rkerberos/rkerberos
107
108
  funding_uri: https://github.com/sponsors/rkerberos
108
109
  rubygems_mfa_required: 'true'
109
110
  rdoc_options: []
@@ -113,16 +114,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
114
  requirements:
114
115
  - - ">="
115
116
  - !ruby/object:Gem::Version
116
- version: '0'
117
+ version: '3.2'
117
118
  required_rubygems_version: !ruby/object:Gem::Requirement
118
119
  requirements:
119
120
  - - ">="
120
121
  - !ruby/object:Gem::Version
121
122
  version: '0'
122
123
  requirements: []
123
- rubygems_version: 4.0.6
124
+ rubygems_version: 4.0.18
124
125
  specification_version: 4
125
- summary: A Ruby interface for the the Kerberos library
126
+ summary: A Ruby interface for the Kerberos library
126
127
  test_files:
127
128
  - spec/config_spec.rb
128
129
  - spec/context_spec.rb