treequel 1.2.2 → 1.3.0pre384

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.
@@ -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 created from entries" do
305
+ describe "objects loaded from entries" do
248
306
 
249
307
  before( :all ) do
250
308
  @entry = {
251
- 'dn' => ['uid=slappy,ou=people,dc=acme,dc=com'],
252
- 'uid' => ['slappy'],
253
- 'cn' => ['Slappy the Frog'],
254
- 'givenName' => ['Slappy'],
255
- 'sn' => ['Frog'],
256
- 'l' => ['a forest in England'],
257
- 'title' => ['Forest Fire Prevention Advocate'],
258
- 'displayName' => ['Slappy the Frog'],
259
- 'logonTime' => 'a time string',
260
- 'objectClass' => %w[
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 "normalizes underbarred readers for camelCased attributes" do
287
- attrtype = stub( "Treequel attributeType object", :name => :givenName )
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.should_not_receive( :valid_attribute_type )
298
- @obj.should_not_receive( :[] )
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.should_receive( :[] ).with( :uid ).and_return( ['slappy'] )
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
- attrtype = stub( "Treequel attributeType object", :name => :logonTime, :single? => true )
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
- attrtype = stub( "Treequel attributeType object", :name => :activated, :single? => true )
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
- attrtype = stub( "Treequel attributeType object", :name => :deactivated, :single? => true )
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
- attrtype = stub( "Treequel attributeType object", :name => :description, :single? => false )
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
- @obj.should have_description()
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
- attrtype = stub( "Treequel attributeType object", :name => :l, :single? => false )
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
- @directory.stub( :convert_to_object ).and_return {|oid,str| str }
383
- @directory.should_receive( :search ).
384
- with( @obj, :scope, :filter, :selectattrs => ['cn', 'objectClass'] )
385
- @obj.search( :scope, :filter, :selectattrs => ['cn'] )
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
- @directory.stub( :convert_to_object ).and_return {|oid,str| str }
391
- @directory.should_receive( :search ).
392
- with( @obj, :scope, :filter, :selectattrs => [] )
393
- @obj.search( :scope, :filter, :selectattrs => [] )
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