treequel 1.2.2 → 1.3.0pre384

Sign up to get free protection for your applications and to get access to all the features.
@@ -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