treequel 1.2.2 → 1.3.0pre384
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/ChangeLog +3374 -0
- data/History.md +39 -0
- data/LICENSE +27 -0
- data/README.md +25 -2
- data/Rakefile +64 -29
- data/bin/treequel +16 -25
- data/bin/treewhat +318 -0
- data/lib/treequel.rb +13 -3
- data/lib/treequel/behavior/control.rb +40 -0
- data/lib/treequel/branch.rb +56 -28
- data/lib/treequel/branchset.rb +10 -2
- data/lib/treequel/controls/pagedresults.rb +4 -2
- data/lib/treequel/directory.rb +40 -50
- data/lib/treequel/exceptions.rb +18 -0
- data/lib/treequel/mixins.rb +44 -14
- data/lib/treequel/model.rb +338 -21
- data/lib/treequel/model/errors.rb +79 -0
- data/lib/treequel/model/objectclass.rb +26 -2
- data/lib/treequel/model/schemavalidations.rb +69 -0
- data/lib/treequel/monkeypatches.rb +99 -17
- data/lib/treequel/schema.rb +19 -5
- data/spec/lib/constants.rb +20 -2
- data/spec/lib/helpers.rb +25 -8
- data/spec/treequel/branch_spec.rb +73 -10
- data/spec/treequel/controls/contentsync_spec.rb +2 -11
- data/spec/treequel/controls/pagedresults_spec.rb +25 -9
- data/spec/treequel/controls/sortedresults_spec.rb +8 -10
- data/spec/treequel/directory_spec.rb +74 -63
- data/spec/treequel/model/errors_spec.rb +77 -0
- data/spec/treequel/model/objectclass_spec.rb +107 -35
- data/spec/treequel/model/schemavalidations_spec.rb +112 -0
- data/spec/treequel/model_spec.rb +294 -81
- data/spec/treequel/monkeypatches_spec.rb +49 -3
- metadata +28 -16
- metadata.gz.sig +0 -0
- data/spec/lib/control_behavior.rb +0 -47
data/spec/treequel/model_spec.rb
CHANGED
@@ -59,6 +59,7 @@ describe Treequel::Model do
|
|
59
59
|
and_return( [:inetOrgPerson] )
|
60
60
|
mixin.should_receive( :model_bases ).at_least( :once ).
|
61
61
|
and_return( [] )
|
62
|
+
|
62
63
|
Treequel::Model.register_mixin( mixin )
|
63
64
|
Treequel::Model.mixins_for_objectclasses( :inetOrgPerson ).should include( mixin )
|
64
65
|
end
|
@@ -69,6 +70,7 @@ describe Treequel::Model do
|
|
69
70
|
and_return( [:inetOrgPerson, :organizationalPerson] )
|
70
71
|
mixin.should_receive( :model_bases ).at_least( :once ).
|
71
72
|
and_return( [] )
|
73
|
+
|
72
74
|
Treequel::Model.register_mixin( mixin )
|
73
75
|
Treequel::Model.mixins_for_objectclasses( :inetOrgPerson, :organizationalPerson ).
|
74
76
|
should include( mixin )
|
@@ -80,6 +82,7 @@ describe Treequel::Model do
|
|
80
82
|
and_return( [] )
|
81
83
|
mixin.should_receive( :model_bases ).at_least( :once ).
|
82
84
|
and_return( [TEST_PEOPLE_DN] )
|
85
|
+
|
83
86
|
Treequel::Model.register_mixin( mixin )
|
84
87
|
Treequel::Model.mixins_for_dn( TEST_PEOPLE_DN ).should include( mixin )
|
85
88
|
end
|
@@ -90,6 +93,7 @@ describe Treequel::Model do
|
|
90
93
|
and_return( [] )
|
91
94
|
mixin.should_receive( :model_bases ).at_least( :once ).
|
92
95
|
and_return( [TEST_PEOPLE_DN] )
|
96
|
+
|
93
97
|
Treequel::Model.register_mixin( mixin )
|
94
98
|
Treequel::Model.mixins_for_dn( TEST_PERSON_DN ).should include( mixin )
|
95
99
|
end
|
@@ -101,7 +105,6 @@ describe Treequel::Model do
|
|
101
105
|
mixin.should_receive( :model_bases ).at_least( :once ).and_return( [] )
|
102
106
|
|
103
107
|
Treequel::Model.register_mixin( mixin )
|
104
|
-
|
105
108
|
Treequel::Model.mixins_for_dn( TEST_PERSON_DN ).should include( mixin )
|
106
109
|
end
|
107
110
|
|
@@ -142,6 +145,29 @@ describe Treequel::Model do
|
|
142
145
|
obj.should_not be_a( mixin3 )
|
143
146
|
end
|
144
147
|
|
148
|
+
it "extends dups of new instances with registered mixins" do
|
149
|
+
mixin1 = Module.new do
|
150
|
+
extend Treequel::Model::ObjectClass
|
151
|
+
model_bases TEST_HOSTS_DN, TEST_SUBHOSTS_DN
|
152
|
+
model_objectclasses :ipHost
|
153
|
+
end
|
154
|
+
mixin2 = Module.new do
|
155
|
+
extend Treequel::Model::ObjectClass
|
156
|
+
model_bases TEST_HOSTS_DN
|
157
|
+
model_objectclasses :device
|
158
|
+
end
|
159
|
+
mixin3 = Module.new do
|
160
|
+
extend Treequel::Model::ObjectClass
|
161
|
+
model_objectclasses :person
|
162
|
+
end
|
163
|
+
|
164
|
+
obj = Treequel::Model.new( @directory, TEST_SUBHOST_DN, @simple_entry ).dup
|
165
|
+
|
166
|
+
obj.should be_a( mixin1 )
|
167
|
+
obj.should_not be_a( mixin2 )
|
168
|
+
obj.should_not be_a( mixin3 )
|
169
|
+
end
|
170
|
+
|
145
171
|
it "extends new instances with mixins that are implied by objectClass SUP attributes, too" do
|
146
172
|
inherited_mixin = Module.new do
|
147
173
|
extend Treequel::Model::ObjectClass
|
@@ -184,6 +210,37 @@ describe Treequel::Model do
|
|
184
210
|
obj.should_not be_a( mixin3 )
|
185
211
|
end
|
186
212
|
|
213
|
+
it "applies applicable mixins to instances which have objectClasses added to them" do
|
214
|
+
mixin1 = Module.new do
|
215
|
+
extend Treequel::Model::ObjectClass
|
216
|
+
model_bases TEST_HOSTS_DN, TEST_SUBHOSTS_DN
|
217
|
+
model_objectclasses :ipHost
|
218
|
+
end
|
219
|
+
mixin2 = Module.new do
|
220
|
+
extend Treequel::Model::ObjectClass
|
221
|
+
model_bases TEST_HOSTS_DN
|
222
|
+
model_objectclasses :device
|
223
|
+
end
|
224
|
+
|
225
|
+
@conn.stub( :search_ext2 ).
|
226
|
+
with( TEST_HOST_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
|
227
|
+
and_return( [] )
|
228
|
+
obj = Treequel::Model.new( @directory, TEST_HOST_DN )
|
229
|
+
|
230
|
+
obj.extensions.should be_empty()
|
231
|
+
|
232
|
+
obj.object_class += [ :ipHost ]
|
233
|
+
obj.extensions.should have( 1 ).member
|
234
|
+
obj.should be_a( mixin1 )
|
235
|
+
obj.should_not be_a( mixin2 )
|
236
|
+
|
237
|
+
obj.object_class += [ :device ]
|
238
|
+
obj.extensions.should have( 2 ).members
|
239
|
+
obj.should be_a( mixin1 )
|
240
|
+
obj.should be_a( mixin2 )
|
241
|
+
|
242
|
+
end
|
243
|
+
|
187
244
|
it "doesn't try to apply objectclasses to non-existant entries" do
|
188
245
|
mixin1 = Module.new do
|
189
246
|
extend Treequel::Model::ObjectClass
|
@@ -241,23 +298,31 @@ describe Treequel::Model do
|
|
241
298
|
@directory.should_receive( :get_entry ).with( @obj ).and_return( @entry )
|
242
299
|
@obj.cn.should == ['Slappy the Frog']
|
243
300
|
end
|
301
|
+
|
244
302
|
end
|
245
303
|
|
246
304
|
|
247
|
-
describe "objects
|
305
|
+
describe "objects loaded from entries" do
|
248
306
|
|
249
307
|
before( :all ) do
|
250
308
|
@entry = {
|
251
|
-
'dn'
|
252
|
-
'uid'
|
253
|
-
'cn'
|
254
|
-
'givenName'
|
255
|
-
'sn'
|
256
|
-
'l'
|
257
|
-
'title'
|
258
|
-
'displayName'
|
259
|
-
'logonTime'
|
260
|
-
'
|
309
|
+
'dn' => ['uid=slappy,ou=people,dc=acme,dc=com'],
|
310
|
+
'uid' => ['slappy'],
|
311
|
+
'cn' => ['Slappy the Frog'],
|
312
|
+
'givenName' => ['Slappy'],
|
313
|
+
'sn' => ['Frog'],
|
314
|
+
'l' => ['a forest in England'],
|
315
|
+
'title' => ['Forest Fire Prevention Advocate'],
|
316
|
+
'displayName' => ['Slappy the Frog'],
|
317
|
+
'logonTime' => ['1293167318'],
|
318
|
+
'uidNumber' => ['1121'],
|
319
|
+
'gidNumber' => ['200'],
|
320
|
+
'homeDirectory' => ['/u/j/jrandom'],
|
321
|
+
'description' => [
|
322
|
+
'Smokey the Bear is much more intense in person.',
|
323
|
+
'Alright.'
|
324
|
+
],
|
325
|
+
'objectClass' => %w[
|
261
326
|
top
|
262
327
|
person
|
263
328
|
organizationalPerson
|
@@ -275,103 +340,63 @@ describe Treequel::Model do
|
|
275
340
|
|
276
341
|
|
277
342
|
it "provides readers for valid attributes" do
|
278
|
-
attrtype = stub( "Treequel attributeType object", :name => :uid )
|
279
|
-
|
280
|
-
@obj.should_receive( :valid_attribute_type ).with( :uid ).and_return( attrtype )
|
281
|
-
@obj.should_receive( :[] ).with( :uid ).and_return( ['slappy'] )
|
282
|
-
|
283
343
|
@obj.uid.should == ['slappy']
|
284
344
|
end
|
285
345
|
|
286
|
-
it "
|
287
|
-
|
288
|
-
|
289
|
-
@obj.should_receive( :valid_attribute_type ).with( :given_name ).and_return( nil )
|
290
|
-
@obj.should_receive( :valid_attribute_type ).with( :givenName ).and_return( attrtype )
|
291
|
-
@obj.should_receive( :[] ).with( :givenName ).and_return( ['Slappy'] )
|
346
|
+
it "provides readers for single-letter attributes" do
|
347
|
+
@obj.l.should == ['a forest in England']
|
348
|
+
end
|
292
349
|
|
350
|
+
it "normalizes underbarred readers for camelCased attributes" do
|
293
351
|
@obj.given_name.should == ['Slappy']
|
294
352
|
end
|
295
353
|
|
296
354
|
it "falls through to branch-traversal for a reader with arguments" do
|
297
|
-
@obj.
|
298
|
-
|
299
|
-
|
300
|
-
@obj.should_receive( :traverse_branch ).
|
301
|
-
with( :dc, :admin, {} ).and_return( :a_child_branch )
|
302
|
-
|
303
|
-
@obj.dc( :admin ).should == :a_child_branch
|
355
|
+
result = @obj.dc( :admin )
|
356
|
+
result.should be_a( Treequel::Model )
|
357
|
+
result.dn.should == "dc=admin,#{@entry['dn'].first}"
|
304
358
|
end
|
305
359
|
|
306
360
|
it "accommodates branch-traversal from its auto-generated readers" do
|
307
|
-
@obj.
|
308
|
-
@obj.uid.should == ['slappy']
|
309
|
-
|
361
|
+
@obj.uid # Generate the reader, which should then do traversal, too
|
310
362
|
@obj.uid( :slappy ).should be_a( Treequel::Model )
|
311
363
|
end
|
312
364
|
|
313
365
|
it "provides writers for valid singular attributes" do
|
314
|
-
|
315
|
-
|
316
|
-
@obj.should_receive( :valid_attribute_type ).with( :logonTime ).and_return( attrtype )
|
317
|
-
@obj.should_receive( :[]= ).with( :logonTime, 'stampley' )
|
318
|
-
|
319
|
-
@obj.logonTime = 'stampley'
|
366
|
+
@obj.logonTime.should equal( 1293167318 )
|
320
367
|
end
|
321
368
|
|
322
369
|
it "provides writers for valid non-singular attributes that accept a non-array" do
|
323
|
-
attrtype = stub( "Treequel attributeType object", :name => :uid, :single? => false )
|
324
|
-
|
325
|
-
@obj.should_receive( :valid_attribute_type ).with( :uid ).and_return( attrtype )
|
326
|
-
@obj.should_receive( :[]= ).with( :uid, ['stampley'] )
|
327
|
-
|
328
370
|
@obj.uid = 'stampley'
|
371
|
+
@obj.uid.should == ['stampley']
|
329
372
|
end
|
330
373
|
|
331
374
|
it "provides a predicate that tests true for valid singular attributes that are set" do
|
332
|
-
|
333
|
-
|
334
|
-
@obj.should_receive( :valid_attribute_type ).with( :activated ).and_return( attrtype )
|
335
|
-
@obj.should_receive( :[] ).with( :activated ).and_return( :a_time_object )
|
336
|
-
|
337
|
-
@obj.should be_activated()
|
375
|
+
@obj.display_name?.should be_true()
|
338
376
|
end
|
339
377
|
|
340
378
|
it "provides a predicate that tests false for valid singular attributes that are not set" do
|
341
|
-
|
342
|
-
|
343
|
-
@obj.should_receive( :valid_attribute_type ).with( :deactivated ).and_return( attrtype )
|
344
|
-
@obj.should_receive( :[] ).with( :deactivated ).and_return( nil )
|
345
|
-
|
346
|
-
@obj.should_not be_deactivated()
|
379
|
+
@obj.delete( :displayName )
|
380
|
+
@obj.display_name?.should be_false()
|
347
381
|
end
|
348
382
|
|
349
383
|
it "provides a predicate that tests true for valid non-singular attributes that have " +
|
350
384
|
"at least one value" do
|
351
|
-
|
352
|
-
|
353
|
-
@obj.should_receive( :valid_attribute_type ).with( :description ).and_return( attrtype )
|
354
|
-
@obj.should_receive( :[] ).with( :description ).
|
355
|
-
and_return([ 'Racoon City', 'St-Michael Clock Tower' ])
|
385
|
+
@obj.should have_given_name()
|
386
|
+
end
|
356
387
|
|
357
|
-
|
388
|
+
it "provides a predicate that tests true for single-letter non-singular attributes " +
|
389
|
+
"that have at least one value" do
|
390
|
+
@obj.should have_l()
|
358
391
|
end
|
359
392
|
|
360
393
|
it "provides a predicate that tests false for valid non-singular attributes that don't " +
|
361
394
|
"have at least one value" do
|
362
|
-
|
363
|
-
|
364
|
-
@obj.should_receive( :valid_attribute_type ).with( :locality_name ).and_return( attrtype )
|
365
|
-
@obj.should_receive( :[] ).with( :l ).
|
366
|
-
and_return([])
|
367
|
-
|
368
|
-
@obj.should_not have_locality_name()
|
395
|
+
@obj.delete( :givenName )
|
396
|
+
@obj.should_not have_given_name()
|
369
397
|
end
|
370
398
|
|
371
399
|
it "falls through to the default proxy method for invalid attributes" do
|
372
|
-
@obj.stub( :valid_attribute_type ).and_return( nil )
|
373
|
-
@entry.should_not_receive( :[] )
|
374
|
-
|
375
400
|
expect {
|
376
401
|
@obj.nonexistant
|
377
402
|
}.to raise_exception( NoMethodError, /undefined method/i )
|
@@ -379,26 +404,214 @@ describe Treequel::Model do
|
|
379
404
|
|
380
405
|
it "adds the objectClass attribute to the attribute list when executing a search that " +
|
381
406
|
"contains a select" do
|
382
|
-
@
|
383
|
-
|
384
|
-
|
385
|
-
|
407
|
+
@conn.should_receive( :search_ext2 ).
|
408
|
+
with( @entry['dn'].first, LDAP::LDAP_SCOPE_ONELEVEL, "(cn=magnelion)",
|
409
|
+
["cn", "objectClass"], false, nil, nil, 0, 0, 0, "", nil ).
|
410
|
+
and_return( [] )
|
411
|
+
@obj.search( :one, '(cn=magnelion)', :selectattrs => ['cn'] )
|
386
412
|
end
|
387
413
|
|
388
414
|
it "doesn't add the objectClass attribute to the attribute list when the search " +
|
389
415
|
"doesn't contain a select" do
|
390
|
-
@
|
391
|
-
|
392
|
-
|
393
|
-
|
416
|
+
@conn.should_receive( :search_ext2 ).
|
417
|
+
with( @entry['dn'].first, LDAP::LDAP_SCOPE_ONELEVEL, "(cn=ephelion)",
|
418
|
+
['*'], false, nil, nil, 0, 0, 0, "", nil ).
|
419
|
+
and_return( [] )
|
420
|
+
@obj.search( :one, '(cn=ephelion)' )
|
394
421
|
end
|
395
422
|
|
396
423
|
it "knows which attribute methods it responds to" do
|
397
|
-
@directory.stub( :convert_to_object ).and_return {|oid,str| str }
|
398
424
|
@obj.should respond_to( :cn )
|
399
425
|
@obj.should_not respond_to( :humpsize )
|
400
426
|
end
|
401
427
|
|
428
|
+
it "defers writing modifications via #[]= back to the directory" do
|
429
|
+
@conn.should_not_receive( :modify )
|
430
|
+
@obj[ :uid ] = 'slippy'
|
431
|
+
@obj.uid.should == ['slippy']
|
432
|
+
end
|
433
|
+
|
434
|
+
it "defers writing modifications via #merge back to the directory" do
|
435
|
+
@conn.should_not_receive( :modify )
|
436
|
+
@obj.merge( :uid => 'slippy', :givenName => 'Slippy' )
|
437
|
+
@obj.uid.should == ['slippy']
|
438
|
+
@obj.given_name.should == ['Slippy']
|
439
|
+
end
|
440
|
+
|
441
|
+
it "defers writing modifications via #delete back to the directory" do
|
442
|
+
@conn.should_not_receive( :modify )
|
443
|
+
@obj.delete( :uid, :givenName )
|
444
|
+
@obj.uid.should == []
|
445
|
+
@obj.given_name.should == []
|
446
|
+
end
|
447
|
+
|
448
|
+
it "knows if any validation errors have been encountered" do
|
449
|
+
@obj.errors.should be_a( Hash )
|
450
|
+
end
|
451
|
+
|
452
|
+
|
453
|
+
context "with no modified attributes" do
|
454
|
+
|
455
|
+
it "knows that it hasn't been modified" do
|
456
|
+
@obj.should_not be_modified()
|
457
|
+
end
|
458
|
+
|
459
|
+
it "doesn't write anything to the directory when its #save method is called" do
|
460
|
+
@conn.should_not_receive( :modify )
|
461
|
+
@obj.save
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|
465
|
+
|
466
|
+
|
467
|
+
context "with a single modified attribute" do
|
468
|
+
|
469
|
+
before( :each ) do
|
470
|
+
@obj.uid = 'slippy'
|
471
|
+
end
|
472
|
+
|
473
|
+
it "knows that is has been modified" do
|
474
|
+
@obj.should be_modified()
|
475
|
+
end
|
476
|
+
|
477
|
+
it "can return the modification as a list of LDAP::Mod objects" do
|
478
|
+
result = @obj.modifications
|
479
|
+
|
480
|
+
result.should be_an( Array )
|
481
|
+
result.should have( 2 ).members
|
482
|
+
result.should include( ldap_mod_add 'uid', 'slippy' )
|
483
|
+
result.should include( ldap_mod_delete 'uid', 'slappy' )
|
484
|
+
end
|
485
|
+
|
486
|
+
it "reverts the attribute if its #revert method is called" do
|
487
|
+
@conn.should_receive( :search_ext2 ).
|
488
|
+
with( @entry['dn'].first, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
|
489
|
+
and_return([ @entry ])
|
490
|
+
|
491
|
+
@obj.revert
|
492
|
+
|
493
|
+
@obj.uid.should == @entry['uid']
|
494
|
+
@obj.should_not be_modified()
|
495
|
+
end
|
496
|
+
|
497
|
+
it "updates the modified attribute when saved" do
|
498
|
+
@conn.should_receive( :modify ).
|
499
|
+
with( "uid=slappy,ou=people,dc=acme,dc=com",
|
500
|
+
[ldap_mod_delete(:uid, 'slappy'), ldap_mod_add(:uid, 'slippy')] )
|
501
|
+
@obj.save
|
502
|
+
end
|
503
|
+
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
context "with several modified attributes" do
|
508
|
+
|
509
|
+
before( :each ) do
|
510
|
+
@obj.uid = 'fappy'
|
511
|
+
@obj.given_name = 'Fappy'
|
512
|
+
@obj.display_name = 'Fappy the Bear'
|
513
|
+
@obj.delete( :l )
|
514
|
+
@obj.delete( :description => 'Alright.' )
|
515
|
+
@obj.description << "The new mascot."
|
516
|
+
end
|
517
|
+
|
518
|
+
it "knows that is has been modified" do
|
519
|
+
@obj.should be_modified()
|
520
|
+
end
|
521
|
+
|
522
|
+
it "returns the modified values via its accessors" do
|
523
|
+
@obj.uid.should == ['fappy']
|
524
|
+
@obj.given_name.should == ['Fappy']
|
525
|
+
@obj.display_name.should == 'Fappy the Bear' # SINGLE
|
526
|
+
@obj.l.should == []
|
527
|
+
@obj.description.should have( 2 ).members
|
528
|
+
@obj.description.should include(
|
529
|
+
"Smokey the Bear is much more intense in person.",
|
530
|
+
"The new mascot."
|
531
|
+
)
|
532
|
+
end
|
533
|
+
|
534
|
+
it "can return the modifications as a list of LDAP::Mod objects" do
|
535
|
+
result = @obj.modifications
|
536
|
+
|
537
|
+
result.should be_an( Array )
|
538
|
+
result.should have( 9 ).members
|
539
|
+
result.should include( ldap_mod_delete :uid, 'slappy' )
|
540
|
+
result.should include( ldap_mod_add :uid, 'fappy' )
|
541
|
+
result.should include( ldap_mod_delete :givenName, 'Slappy' )
|
542
|
+
result.should include( ldap_mod_add :givenName, 'Fappy' )
|
543
|
+
result.should include( ldap_mod_delete :displayName, 'Slappy the Frog' )
|
544
|
+
result.should include( ldap_mod_add :displayName, 'Fappy the Bear' )
|
545
|
+
result.should include( ldap_mod_add :description, 'The new mascot.' )
|
546
|
+
result.should include( ldap_mod_delete :description, 'Alright.' )
|
547
|
+
result.should include( ldap_mod_delete :l, 'a forest in England' )
|
548
|
+
|
549
|
+
end
|
550
|
+
|
551
|
+
it "reverts all of the attributes if its #revert method is called" do
|
552
|
+
@conn.should_receive( :search_ext2 ).
|
553
|
+
with( @entry['dn'].first, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
|
554
|
+
and_return([ @entry ])
|
555
|
+
|
556
|
+
@obj.revert
|
557
|
+
|
558
|
+
@obj.uid.should == @entry['uid']
|
559
|
+
@obj.given_name.should == @entry['givenName']
|
560
|
+
@obj.display_name.should == @entry['displayName'].first # SINGLE
|
561
|
+
@obj.l.should == @entry['l']
|
562
|
+
@obj.should_not be_modified()
|
563
|
+
end
|
564
|
+
|
565
|
+
end
|
566
|
+
|
567
|
+
end
|
568
|
+
|
569
|
+
describe "objects created in memory" do
|
570
|
+
|
571
|
+
before( :all ) do
|
572
|
+
@entry = {
|
573
|
+
'uid' => ['jrandom'],
|
574
|
+
'cn' => ['James'],
|
575
|
+
'sn' => ['Hacker'],
|
576
|
+
'l' => ['a forest in England'],
|
577
|
+
'displayName' => ['J. Random Hacker'],
|
578
|
+
'uidNumber' => ['1121'],
|
579
|
+
'gidNumber' => ['200'],
|
580
|
+
'homeDirectory' => ['/u/j/jrandom'],
|
581
|
+
'objectClass' => %w[
|
582
|
+
person
|
583
|
+
inetOrgPerson
|
584
|
+
posixAccount
|
585
|
+
],
|
586
|
+
}
|
587
|
+
end
|
588
|
+
|
589
|
+
before( :each ) do
|
590
|
+
@obj = Treequel::Model.new( @directory, TEST_PERSON_DN, @entry )
|
591
|
+
end
|
592
|
+
|
593
|
+
|
594
|
+
it "creates the entry in the directory when saved" do
|
595
|
+
@conn.stub( :search_ext2 ).
|
596
|
+
with( TEST_PERSON_DN, LDAP::LDAP_SCOPE_BASE, '(objectClass=*)').
|
597
|
+
and_return( [] )
|
598
|
+
|
599
|
+
@conn.should_receive( :add ).
|
600
|
+
with( TEST_PERSON_DN, [
|
601
|
+
ldap_mod_add("cn", "James"),
|
602
|
+
ldap_mod_add("displayName", "J. Random Hacker"),
|
603
|
+
ldap_mod_add("gidNumber", "200"),
|
604
|
+
ldap_mod_add("homeDirectory", "/u/j/jrandom"),
|
605
|
+
ldap_mod_add("l", "a forest in England"),
|
606
|
+
ldap_mod_add("objectClass", "inetOrgPerson", "person", "posixAccount"),
|
607
|
+
ldap_mod_add("sn", "Hacker"),
|
608
|
+
ldap_mod_add("uid", "jrandom"),
|
609
|
+
ldap_mod_add("uidNumber", "1121")
|
610
|
+
] )
|
611
|
+
|
612
|
+
@obj.save
|
613
|
+
end
|
614
|
+
|
402
615
|
end
|
403
616
|
|
404
617
|
end
|