treequel 1.2.1 → 1.2.2

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.
@@ -24,12 +24,12 @@ include Treequel::TestConstants
24
24
  include Treequel::Constants
25
25
 
26
26
  #####################################################################
27
- ### C O N T E X T S
27
+ ### C O N T E X T S
28
28
  #####################################################################
29
29
 
30
30
  describe Treequel::Branch do
31
31
  include Treequel::SpecHelpers,
32
- Treequel::Matchers
32
+ Treequel::Matchers
33
33
 
34
34
 
35
35
  before( :all ) do
@@ -41,7 +41,8 @@ describe Treequel::Branch do
41
41
  end
42
42
 
43
43
  before( :each ) do
44
- @directory = mock( "treequel directory", :get_entry => :an_entry_hash )
44
+ @conn = mock( "ldap connection object" )
45
+ @directory = get_fixtured_directory( @conn )
45
46
  end
46
47
 
47
48
  after( :each ) do
@@ -60,9 +61,9 @@ describe Treequel::Branch do
60
61
  }.to raise_error( ArgumentError, /invalid dn/i )
61
62
  end
62
63
 
63
- it "can be constructed from an entry returned from LDAP::Conn.search_ext2" do
64
+ it "can be constructed from an entry returned from LDAP::Conn.search_ext2" do
64
65
  entry = {
65
- 'dn' => [TEST_PERSON_DN],
66
+ 'dn' => [TEST_PERSON_DN],
66
67
  TEST_PERSON_DN_ATTR => TEST_PERSON_DN_VALUE,
67
68
  }
68
69
  branch = Treequel::Branch.new_from_entry( entry, @directory )
@@ -71,16 +72,16 @@ describe Treequel::Branch do
71
72
  branch.entry.should == entry
72
73
  end
73
74
 
74
- it "can be constructed from an entry with Symbol keys" do
75
+ it "can be constructed from an entry with Symbol keys" do
75
76
  entry = {
76
- :dn => [TEST_PERSON_DN],
77
+ :dn => [TEST_PERSON_DN],
77
78
  TEST_PERSON_DN_ATTR => TEST_PERSON_DN_VALUE,
78
79
  }
79
80
  branch = Treequel::Branch.new_from_entry( entry, @directory )
80
81
 
81
82
  branch.rdn_attributes.should == { TEST_PERSON_DN_ATTR => [TEST_PERSON_DN_VALUE] }
82
83
  branch.entry.should == {
83
- 'dn' => [TEST_PERSON_DN],
84
+ 'dn' => [TEST_PERSON_DN],
84
85
  TEST_PERSON_DN_ATTR => TEST_PERSON_DN_VALUE,
85
86
  }
86
87
  end
@@ -110,18 +111,18 @@ describe Treequel::Branch do
110
111
  describe "instances" do
111
112
 
112
113
  before( :each ) do
114
+ @entry = {
115
+ 'description' => ["A string", "another string"],
116
+ 'l' => [ 'Antartica', 'Galapagos' ],
117
+ 'objectClass' => ['organizationalUnit'],
118
+ 'rev' => ['03eca02ba232'],
119
+ 'ou' => ['Hosts'],
120
+ }
121
+ @conn.stub( :bound? ).and_return( false )
122
+ @conn.stub( :search_ext2 ).
123
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
124
+ and_return([ @entry ])
113
125
  @branch = Treequel::Branch.new( @directory, TEST_HOSTS_DN )
114
-
115
- @schema = mock( "treequel schema" )
116
- @entry = mock( "entry object" )
117
- @directory.stub( :schema ).and_return( @schema )
118
- @directory.stub( :get_entry ).and_return( @entry )
119
- @directory.stub( :base_dn ).and_return( TEST_BASE_DN )
120
- @schema.stub( :attribute_types ).
121
- and_return({ :cn => :a_value, :ou => :a_value })
122
-
123
- @syntax = stub( "attribute ldapSyntax object", :oid => OIDS::STRING_SYNTAX )
124
- @attribute_type = mock( "schema attribute type object", :syntax => @syntax )
125
126
  end
126
127
 
127
128
 
@@ -143,7 +144,6 @@ describe Treequel::Branch do
143
144
  end
144
145
 
145
146
  it "can return itself as an ldap:// URI" do
146
- @directory.should_receive( :uri ).and_return( URI.parse("ldap://#{TEST_HOST}/#{TEST_BASE_DN}") )
147
147
  @branch.uri.to_s.should == "ldap://#{TEST_HOST}/#{TEST_HOSTS_DN}?"
148
148
  end
149
149
 
@@ -173,48 +173,66 @@ describe Treequel::Branch do
173
173
  end
174
174
 
175
175
  it "knows that it exists in the directory if it can fetch its entry" do
176
- @directory.should_receive( :get_entry ).with( @branch ).exactly( :once ).
177
- and_return( :the_entry )
178
176
  @branch.exists?.should be_true()
179
177
  end
180
178
 
181
179
  it "knows that it doesn't exist in the directory if it can't fetch its entry" do
182
- @directory.should_receive( :get_entry ).with( @branch ).exactly( :once ).
183
- and_return( nil )
180
+ @conn.stub( :search_ext2 ).
181
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
182
+ and_return([])
184
183
  @branch.exists?.should be_false()
185
184
  end
186
185
 
187
186
  it "fetch their LDAP::Entry from the directory if they don't already have one" do
188
- @directory.should_receive( :get_entry ).with( @branch ).exactly( :once ).
189
- and_return( :the_entry )
187
+ @conn.should_receive( :search_ext2 ).
188
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
189
+ exactly( :once ).
190
+ and_return([ @entry ])
190
191
 
191
- @branch.entry.should == :the_entry
192
- @branch.entry.should == :the_entry # this should fetch the cached one
192
+ @branch.entry.should == @entry
193
+ @branch.entry.should == @entry # this should fetch the cached one
193
194
  end
194
195
 
195
196
  it "fetch their LDAP::Entry with operational attributes if include_operational_attrs is set" do
196
- @branch.include_operational_attrs = true
197
- @directory.should_not_receive( :get_entry )
198
- @directory.should_receive( :get_extended_entry ).with( @branch ).exactly( :once ).
199
- and_return( :the_extended_entry )
197
+ @conn.should_receive( :search_ext2 ).
198
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)", ["*", "+"] ).once.
199
+ and_return([ @entry ])
200
200
 
201
- @branch.entry.should == :the_extended_entry
201
+ @branch.include_operational_attrs = true
202
+ @branch.entry.should == @entry
202
203
  end
203
204
 
204
205
  it "can search its directory for values using itself as a base" do
205
- @directory.should_receive( :search ).with( @branch, :one, '(objectClass=*)', {} ).
206
- and_return( :entries )
207
- @branch.search( :one, '(objectClass=*)' ).should == :entries
206
+ subentry = {'objectClass' => ['ipHost'], 'dn' => [TEST_HOST_DN] }
207
+ @conn.should_receive( :search_ext2 ).
208
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_ONELEVEL, "(objectClass=*)",
209
+ ["*"], false, nil, nil, 0, 0, 0, "", nil ).
210
+ and_return([ subentry ])
211
+
212
+ branches = @branch.search( :one, '(objectClass=*)' )
213
+
214
+ branches.should have( 1 ).member
215
+ branches.first.should be_a( Treequel::Branch )
216
+ branches.first.dn.should == TEST_HOST_DN
208
217
  end
209
218
 
210
219
  it "can search its directory for values with a block" do
211
- @directory.should_receive( :search ).with( @branch, :one, '(objectClass=*)', {} ).
212
- and_yield( :an_entry )
220
+ subentry = {
221
+ 'objectClass' => ['ipHost'],
222
+ TEST_HOST_DN_ATTR => [TEST_HOST_DN_VALUE],
223
+ 'dn' => [TEST_HOST_DN],
224
+ }
225
+ @conn.should_receive( :search_ext2 ).
226
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_ONELEVEL, "(objectClass=*)",
227
+ ["*"], false, nil, nil, 0, 0, 0, "", nil ).
228
+ and_return([ subentry ])
229
+
213
230
  yielded_val = nil
214
231
  @branch.search( :one, '(objectClass=*)' ) do |val|
215
232
  yielded_val = val
216
233
  end
217
- yielded_val.should == :an_entry
234
+ yielded_val.should be_a( Treequel::Branch )
235
+ yielded_val.entry.should == subentry
218
236
  end
219
237
 
220
238
  it "clears any cached values if its include_operational_attrs attribute is changed" do
@@ -254,9 +272,6 @@ describe Treequel::Branch do
254
272
  end
255
273
 
256
274
  it "create sub-branches for messages that match valid attributeType OIDs" do
257
- @schema.should_receive( :attribute_types ).twice.
258
- and_return({ :cn => :a_value, :ou => :a_value })
259
-
260
275
  rval = @branch.cn( 'rondori' )
261
276
  rval.dn.should == "cn=rondori,#{TEST_HOSTS_DN}"
262
277
 
@@ -265,9 +280,6 @@ describe Treequel::Branch do
265
280
  end
266
281
 
267
282
  it "create sub-branches for messages with additional attribute pairs" do
268
- @schema.should_receive( :attribute_types ).
269
- and_return({ :cn => :a_value, :ou => :a_value, :l => :a_value })
270
-
271
283
  rval = @branch.cn( 'rondori', :l => 'Portland' )
272
284
  rval.dn.should == "cn=rondori+l=Portland,#{TEST_HOSTS_DN}"
273
285
 
@@ -276,21 +288,17 @@ describe Treequel::Branch do
276
288
  end
277
289
 
278
290
  it "don't create sub-branches for messages that don't match valid attributeType OIDs" do
279
- @schema.should_receive( :attribute_types ).
280
- and_return({ :cn => :a_value, :ou => :a_value })
281
-
282
- lambda {
291
+ @conn.stub( :bound? ).and_return( false )
292
+ expect {
283
293
  @branch.facelart( 'sbc' )
284
- }.should raise_exception( NoMethodError, /undefined method.*facelart/i )
294
+ }.to raise_exception( NoMethodError, /undefined method.*facelart/i )
285
295
  end
286
296
 
287
297
  it "don't create sub-branches for multi-value RDNs with an invalid attribute" do
288
- @schema.should_receive( :attribute_types ).
289
- and_return({ :cn => :a_value, :ou => :a_value })
290
-
291
- lambda {
298
+ @conn.stub( :bound? ).and_return( false )
299
+ expect {
292
300
  @branch.cn( 'benchlicker', :facelart => 'sbc' )
293
- }.should raise_exception( NoMethodError, /invalid secondary attribute.*facelart/i )
301
+ }.to raise_exception( NoMethodError, /invalid secondary attribute.*facelart/i )
294
302
  end
295
303
 
296
304
  it "can return all of its immediate children as Branches" do
@@ -300,308 +308,256 @@ describe Treequel::Branch do
300
308
  end
301
309
 
302
310
  it "can return its parent as a Branch" do
303
- parent_branch = stub( "parent branch object" )
304
- @branch.should_receive( :class ).and_return( Treequel::Branch )
305
- Treequel::Branch.should_receive( :new ).with( @directory, TEST_BASE_DN ).
306
- and_return( parent_branch )
307
- @branch.parent.should == parent_branch
311
+ @branch.parent.should be_a( Treequel::Branch )
312
+ @branch.parent.dn.should == TEST_BASE_DN
308
313
  end
309
314
 
310
315
 
311
316
  it "can construct a Treequel::Branchset that uses it as its base" do
312
- branchset = stub( "branchset" )
313
- Treequel::Branchset.should_receive( :new ).with( @branch ).
314
- and_return( branchset )
315
-
316
- @branch.branchset.should == branchset
317
+ @branch.branchset.should be_a( Treequel::Branchset )
318
+ @branch.branchset.base_dn.should == @branch.dn
317
319
  end
318
320
 
319
321
  it "can create a filtered Treequel::Branchset for itself" do
320
- branchset = mock( "filtered branchset" )
321
- Treequel::Branchset.should_receive( :new ).with( @branch ).
322
- and_return( branchset )
323
- branchset.should_receive( :filter ).with( {:cn => 'acme'} ).
324
- and_return( :a_filtered_branchset )
322
+ branchset = @branch.filter( :cn => 'acme' )
325
323
 
326
- @branch.filter( :cn => 'acme' ).should == :a_filtered_branchset
324
+ branchset.should be_a( Treequel::Branchset )
325
+ branchset.filter_string.should =~ /\(cn=acme\)/
327
326
  end
328
327
 
329
328
  it "doesn't restrict the number of arguments passed to #filter (bugfix)" do
330
- branchset = mock( "filtered branchset" )
331
- Treequel::Branchset.should_receive( :new ).with( @branch ).
332
- and_return( branchset )
333
- branchset.should_receive( :filter ).with( :uid, [:glumpy, :grumpy, :glee] ).
334
- and_return( :a_filtered_branchset )
329
+ branchset = @branch.filter( :uid => [:rev, :grumpy, :glee] )
335
330
 
336
- @branch.filter( :uid, [:glumpy, :grumpy, :glee] ).should == :a_filtered_branchset
331
+ branchset.should be_a( Treequel::Branchset )
332
+ branchset.filter_string.should =~ /\(\|\(uid=rev\)\(uid=grumpy\)\(uid=glee\)\)/i
337
333
  end
338
334
 
339
335
  it "can create a scoped Treequel::Branchset for itself" do
340
- branchset = mock( "scoped branchset" )
341
- Treequel::Branchset.should_receive( :new ).with( @branch ).
342
- and_return( branchset )
343
- branchset.should_receive( :scope ).with( :onelevel ).
344
- and_return( :a_scoped_branchset )
336
+ branchset = @branch.scope( :onelevel )
345
337
 
346
- @branch.scope( :onelevel ).should == :a_scoped_branchset
338
+ branchset.should be_a( Treequel::Branchset )
339
+ branchset.scope.should == :onelevel
347
340
  end
348
341
 
349
342
  it "can create a selective Treequel::Branchset for itself" do
350
- branchset = mock( "selective branchset" )
351
- Treequel::Branchset.should_receive( :new ).with( @branch ).
352
- and_return( branchset )
353
- branchset.should_receive( :select ).with( :uid, :l, :familyName, :givenName ).
354
- and_return( :a_selective_branchset )
343
+ branchset = @branch.select( :uid, :l, :familyName, :givenName )
355
344
 
356
- @branch.select( :uid, :l, :familyName, :givenName ).should == :a_selective_branchset
345
+ branchset.should be_a( Treequel::Branchset )
346
+ branchset.select.should == %w[uid l familyName givenName]
357
347
  end
358
348
 
359
349
  it "knows which objectClasses it has" do
360
- oc_attr = mock( "objectClass attributeType object" )
361
- string_syntax = stub( "string ldapSyntax object", :oid => OIDS::STRING_SYNTAX )
362
- @schema.should_receive( :attribute_types ).and_return({ :objectClass => oc_attr })
363
- oc_attr.should_receive( :single? ).and_return( false )
364
- oc_attr.should_receive( :syntax ).at_least( :once ).
365
- and_return( string_syntax )
366
-
367
- @entry.should_receive( :[] ).with( 'objectClass' ).at_least( :once ).
368
- and_return([ 'organizationalUnit', 'extensibleObject' ])
369
-
370
- @directory.should_receive( :convert_to_object ).
371
- with( OIDS::STRING_SYNTAX, 'organizationalUnit' ).
372
- and_return( 'organizationalUnit' )
373
- @directory.should_receive( :convert_to_object ).
374
- with( OIDS::STRING_SYNTAX, 'extensibleObject' ).
375
- and_return( 'extensibleObject' )
376
-
377
- @schema.should_receive( :object_classes ).twice.and_return({
378
- :organizationalUnit => :ou_objectclass,
379
- :extensibleObject => :extobj_objectclass,
380
- })
381
-
382
- @branch.object_classes.should == [ :ou_objectclass, :extobj_objectclass ]
350
+ @branch.object_classes.
351
+ should include( @directory.schema.object_classes[:organizationalUnit] )
383
352
  end
384
353
 
385
354
  it "knows what operational attributes it has" do
386
- op_attrs = MINIMAL_OPERATIONAL_ATTRIBUTES.inject({}) do |hash, oa|
387
- hash[ oa ] = mock("#{oa} attributeType object")
388
- hash
389
- end
390
- @schema.should_receive( :operational_attribute_types ).and_return( op_attrs.values )
355
+ op_attrs = @directory.schema.attribute_types.values.select do |attrtype|
356
+ attrtype.operational?
357
+ end.uniq
391
358
 
392
- @branch.operational_attribute_types.should == op_attrs.values
359
+ @branch.operational_attribute_types.should have( op_attrs.length ).members
360
+ @branch.operational_attribute_types.should include( *op_attrs )
393
361
  end
394
362
 
395
363
  it "knows what the OIDs of its operational attributes are" do
396
- op_attrs = MINIMAL_OPERATIONAL_ATTRIBUTES.inject({}) do |hash, oa|
397
- hash[ oa ] = stub("#{oa} attributeType object", :oid => :an_oid )
398
- hash
399
- end
400
- @schema.should_receive( :operational_attribute_types ).at_least( :once ).
401
- and_return( op_attrs.values )
364
+ op_oids = @directory.schema.attribute_types.values.select do |attrtype|
365
+ attrtype.operational?
366
+ end.uniq.map( &:oid )
402
367
 
403
- @branch.operational_attribute_oids.should have( op_attrs.length ).members
404
- @branch.operational_attribute_oids.should include( :an_oid )
368
+ @branch.operational_attribute_oids.should have( op_oids.length ).members
369
+ @branch.operational_attribute_oids.should include( *op_oids )
405
370
  end
406
371
 
407
372
  it "can return the set of all its MUST attributes' OIDs based on which objectClasses " +
408
373
  "it has" do
409
- oc1 = mock( "first objectclass" )
410
- oc2 = mock( "second objectclass" )
411
-
412
- @branch.should_receive( :object_classes ).and_return([ oc1, oc2 ])
413
- oc1.should_receive( :must_oids ).at_least( :once ).and_return([ :oid1, :oid2 ])
414
- oc2.should_receive( :must_oids ).at_least( :once ).and_return([ :oid1, :oid3 ])
374
+ @conn.stub( :search_ext2 ).
375
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
376
+ and_return([ {'objectClass' => %w[ipHost device ieee802device]} ])
415
377
 
416
378
  must_oids = @branch.must_oids
417
379
  must_oids.should have( 3 ).members
418
- must_oids.should include( :oid1, :oid2, :oid3 )
380
+ must_oids.should include( :objectClass, :cn, :ipHostNumber )
419
381
  end
420
382
 
421
383
  it "can return the set of all its MUST attributeTypes based on which objectClasses it has" do
422
- oc1 = mock( "first objectclass", :name => 'first_oc' )
423
- oc2 = mock( "second objectclass", :name => 'second_oc' )
384
+ @conn.stub( :search_ext2 ).
385
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
386
+ and_return([ {'objectClass' => %w[ipHost device ieee802device]} ])
424
387
 
425
- @branch.should_receive( :object_classes ).and_return([ oc1, oc2 ])
426
- oc1.should_receive( :must ).at_least( :once ).and_return([ :cn, :uid ])
427
- oc2.should_receive( :must ).at_least( :once ).and_return([ :cn, :l ])
388
+ expected_attrtypes = @directory.schema.attribute_types.
389
+ values_at( :objectClass, :cn, :ipHostNumber )
428
390
 
429
391
  must_attrs = @branch.must_attribute_types
430
392
  must_attrs.should have( 3 ).members
431
- must_attrs.should include( :cn, :uid, :l )
393
+ must_attrs.should include( *expected_attrtypes )
432
394
  end
433
395
 
434
396
  it "can return a Hash pre-populated with pairs that correspond to its MUST attributes" do
435
- cn_attrtype = mock( "cn attribute type", :single? => false )
436
- l_attrtype = mock( "l attribute type", :single? => true )
437
- objectClass_attrtype = mock( "objectClass attribute type", :single? => false )
438
-
439
- cn_attrtype.should_receive( :name ).at_least( :once ).and_return( :cn )
440
- l_attrtype.should_receive( :name ).at_least( :once ).and_return( :l )
441
- objectClass_attrtype.should_receive( :name ).at_least( :once ).and_return( :objectClass )
442
-
443
- @branch.should_receive( :must_attribute_types ).at_least( :once ).
444
- and_return([ cn_attrtype, l_attrtype, objectClass_attrtype ])
397
+ @conn.stub( :search_ext2 ).
398
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
399
+ and_return([ {'objectClass' => %w[ipHost device ieee802device]} ])
445
400
 
446
401
  @branch.must_attributes_hash.
447
- should == { :cn => [''], :l => '', :objectClass => [:top] }
402
+ should include({ :cn => [''], :ipHostNumber => [''], :objectClass => [:top] })
448
403
  end
449
404
 
450
405
 
451
406
  it "can return the set of all its MAY attributes' OIDs based on which objectClasses " +
452
407
  "it has" do
453
- oc1 = mock( "first objectclass" )
454
- oc2 = mock( "second objectclass" )
455
-
456
- @branch.should_receive( :object_classes ).and_return([ oc1, oc2 ])
457
- oc1.should_receive( :may_oids ).at_least( :once ).and_return([ :oid1, :oid2 ])
458
- oc2.should_receive( :may_oids ).at_least( :once ).and_return([ :oid1, :oid3 ])
408
+ @conn.stub( :search_ext2 ).
409
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
410
+ and_return([ {'objectClass' => %w[ipHost device ieee802device]} ])
459
411
 
460
412
  must_oids = @branch.may_oids
461
- must_oids.should have( 3 ).members
462
- must_oids.should include( :oid1, :oid2, :oid3 )
413
+ must_oids.should have( 9 ).members
414
+ must_oids.should include( :l, :description, :manager, :serialNumber, :seeAlso,
415
+ :owner, :ou, :o, :macAddress )
463
416
  end
464
417
 
465
418
  it "can return the set of all its MAY attributeTypes based on which objectClasses it has" do
466
- oc1 = mock( "first objectclass" )
467
- oc2 = mock( "second objectclass" )
419
+ @conn.should_receive( :search_ext2 ).
420
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
421
+ and_return([ {'objectClass' => %w[ipHost device ieee802device]} ])
468
422
 
469
- @branch.should_receive( :object_classes ).and_return([ oc1, oc2 ])
470
- oc1.should_receive( :may ).and_return([ :description, :mobilePhone ])
471
- oc2.should_receive( :may ).and_return([ :chunktype ])
423
+ expected_attrtypes = @directory.schema.attribute_types.values_at( :l, :description,
424
+ :manager, :serialNumber, :seeAlso, :owner, :ou, :o, :macAddress )
472
425
 
473
- must_attrs = @branch.may_attribute_types
474
- must_attrs.should have( 3 ).members
475
- must_attrs.should include( :description, :mobilePhone, :chunktype )
426
+ @branch.may_attribute_types.should include( *expected_attrtypes )
476
427
  end
477
428
 
478
429
  it "can return a Hash pre-populated with pairs that correspond to its MAY attributes" do
479
- cn_attrtype = mock( "cn attribute type", :single? => false )
480
- l_attrtype = mock( "l attribute type", :single? => true )
481
-
482
- cn_attrtype.should_receive( :name ).at_least( :once ).and_return( :cn )
483
- l_attrtype.should_receive( :name ).at_least( :once ).and_return( :l )
484
-
485
- @branch.should_receive( :may_attribute_types ).at_least( :once ).
486
- and_return([ cn_attrtype, l_attrtype ])
487
-
488
- @branch.may_attributes_hash.
489
- should == { :cn => [], :l => nil }
430
+ @conn.should_receive( :search_ext2 ).
431
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
432
+ and_return([ {'objectClass' => %w[ipHost device ieee802device]} ])
433
+
434
+ @branch.may_attributes_hash.should include({
435
+ :l => [],
436
+ :description => [],
437
+ :manager => [],
438
+ :serialNumber => [],
439
+ :seeAlso => [],
440
+ :owner => [],
441
+ :ou => [],
442
+ :o => [],
443
+ :macAddress => []
444
+ })
490
445
  end
491
446
 
492
447
  it "can return the set of all of its valid attributeTypes, which is a union of its " +
493
448
  "MUST and MAY attributes plus the directory's operational attributes" do
494
- @branch.should_receive( :must_attribute_types ).
495
- and_return([ :cn, :l, :uid ])
496
- @branch.should_receive( :may_attribute_types ).
497
- and_return([ :description, :mobilePhone, :chunktype ])
498
- @branch.should_receive( :operational_attribute_types ).
499
- and_return([ :createTimestamp, :creatorsName ])
500
-
501
449
  all_attrs = @branch.valid_attribute_types
502
450
 
503
- all_attrs.should have( 8 ).members
504
- all_attrs.should include( :cn, :uid, :l, :description, :mobilePhone,
505
- :chunktype, :createTimestamp, :creatorsName )
451
+ all_attrs.should have( 54 ).members
452
+ all_attrs.should include( @directory.schema.attribute_types[:ou] )
453
+ all_attrs.should include( @directory.schema.attribute_types[:l] )
506
454
  end
507
455
 
456
+
508
457
  it "can return a Hash pre-populated with pairs that correspond to all of its valid " +
509
458
  "attributes" do
510
- @branch.should_receive( :must_attributes_hash ).at_least( :once ).
511
- and_return({ :cn => [''], :l => '', :objectClass => [:top] })
512
- @branch.should_receive( :may_attributes_hash ).at_least( :once ).
513
- and_return({ :description => nil, :givenName => [], :cn => nil })
514
-
515
459
  @branch.valid_attributes_hash.should == {
516
- :cn => [''],
517
- :l => '',
518
- :objectClass => [:top],
519
- :description => nil,
520
- :givenName => [],
460
+ :objectClass => [:top],
461
+ :ou => [''],
462
+ :userPassword => [],
463
+ :searchGuide => [],
464
+ :seeAlso => [],
465
+ :businessCategory => [],
466
+ :x121Address => [],
467
+ :registeredAddress => [],
468
+ :destinationIndicator => [],
469
+ :telexNumber => [],
470
+ :teletexTerminalIdentifier => [],
471
+ :telephoneNumber => [],
472
+ :internationaliSDNNumber => [],
473
+ :facsimileTelephoneNumber => [],
474
+ :street => [],
475
+ :postOfficeBox => [],
476
+ :postalCode => [],
477
+ :postalAddress => [],
478
+ :physicalDeliveryOfficeName => [],
479
+ :st => [],
480
+ :l => [],
481
+ :description => [],
482
+ :preferredDeliveryMethod => nil,
521
483
  }
522
484
  end
523
485
 
524
486
 
525
487
  it "can return the set of all of its valid attribute OIDs, which is a union of its " +
526
488
  "MUST and MAY attribute OIDs" do
527
- @branch.should_receive( :must_oids ).
528
- and_return([ :must_oid1, :must_oid2 ])
529
- @branch.should_receive( :may_oids ).
530
- and_return([ :may_oid1, :may_oid2, :must_oid1 ])
531
-
532
489
  all_attr_oids = @branch.valid_attribute_oids
533
490
 
534
- all_attr_oids.should have( 4 ).members
535
- all_attr_oids.should include( :must_oid1, :must_oid2, :may_oid1, :may_oid2 )
491
+ all_attr_oids.should have( 23 ).members all_attr_oids.should include(
492
+ :objectClass, :ou,
493
+ :userPassword, :searchGuide, :seeAlso, :businessCategory, :x121Address,
494
+ :registeredAddress, :destinationIndicator, :telexNumber, :teletexTerminalIdentifier,
495
+ :telephoneNumber, :internationaliSDNNumber, :facsimileTelephoneNumber, :street,
496
+ :postOfficeBox, :postalCode, :postalAddress, :physicalDeliveryOfficeName, :st, :l,
497
+ :description, :preferredDeliveryMethod
498
+ )
536
499
  end
537
500
 
538
501
  it "knows if an attribute is valid given its objectClasses" do
539
- attrtype = mock( "attribute type object" )
540
-
541
- @branch.should_receive( :valid_attribute_types ).
542
- twice.
543
- and_return([ attrtype ])
544
-
545
- attrtype.should_receive( :valid_name? ).with( :uid ).and_return( true )
546
- attrtype.should_receive( :valid_name? ).with( :rubberChicken ).and_return( false )
547
-
548
- @branch.valid_attribute?( :uid ).should be_true()
502
+ @branch.valid_attribute?( :ou ).should be_true()
549
503
  @branch.valid_attribute?( :rubberChicken ).should be_false()
550
504
  end
551
505
 
552
506
  it "can be moved to a new location within the directory" do
553
- newdn = "ou=hosts,dc=admin,#{TEST_BASE_DN}"
554
- @directory.should_receive( :move ).with( @branch, newdn )
507
+ newdn = "ou=oldhosts,#{TEST_BASE_DN}"
508
+ @conn.should_receive( :modrdn ).
509
+ with( TEST_HOSTS_DN, "ou=oldhosts", true )
555
510
  @branch.move( newdn )
556
511
  end
557
512
 
558
513
 
559
514
  it "resets any cached data when its DN changes" do
560
- @directory.should_receive( :get_entry ).with( @branch ).
561
- and_return( :first_entry, :second_entry )
515
+ @conn.should_receive( :search_ext2 ).
516
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
517
+ and_return([ @entry ])
518
+ @branch.entry
562
519
 
520
+ @branch.dn = TEST_SUBHOSTS_DN
521
+
522
+ @conn.should_receive( :search_ext2 ).
523
+ with( TEST_SUBHOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
524
+ and_return([ @entry ])
563
525
  @branch.entry
564
- @branch.dn = TEST_HOSTS_DN
565
- @branch.entry.should == :second_entry
566
526
  end
567
527
 
568
528
 
569
529
  it "can create children under itself" do
570
530
  newattrs = {
571
531
  :ipHostNumber => '127.0.0.1',
572
- :objectClass => [:ipHost],
532
+ :objectClass => [:ipHost, :device],
573
533
  }
574
- @directory.should_receive( :create ).
575
- with( an_instance_of(Treequel::Branch), newattrs ).
576
- and_return( true )
577
534
 
578
- @branch.cn( :chillyt ).create( newattrs )
535
+ @conn.should_receive( :add ).
536
+ with( "cn=chillyt,#{TEST_HOSTS_DN}",
537
+ "ipHostNumber"=>["127.0.0.1"],
538
+ "objectClass"=>["ipHost", "device"],
539
+ "cn"=>["chillyt"] )
540
+
541
+ res = @branch.cn( :chillyt ).create( newattrs )
542
+ res.should be_a( Treequel::Branch )
543
+ res.dn.should == "cn=chillyt,#{TEST_HOSTS_DN}"
579
544
  end
580
545
 
581
546
 
582
547
  it "can copy itself to a sibling entry" do
583
- newbranch = stub( "copied sibling branch" )
584
- Treequel::Branch.should_receive( :new ).with( @directory, TEST_PERSON2_DN ).
585
- and_return( newbranch )
586
- @entry.should_receive( :merge ).with( {} ).and_return( :merged_attributes )
587
- @directory.should_receive( :create ).with( newbranch, :merged_attributes ).
588
- and_return( true )
548
+ @conn.should_receive( :add ).with( TEST_SUBHOSTS_DN, @entry )
589
549
 
590
- @branch.copy( TEST_PERSON2_DN ).should == newbranch
550
+ newbranch = @branch.copy( TEST_SUBHOSTS_DN )
551
+ newbranch.dn.should == TEST_SUBHOSTS_DN
591
552
  end
592
553
 
593
554
 
594
555
  it "can copy itself to a sibling entry with attribute changes" do
595
- oldattrs = { :sn => "Davies", :firstName => 'David' }
596
- newattrs = { :sn => "Michaels", :firstName => 'George' }
597
- newbranch = stub( "copied sibling branch" )
598
- Treequel::Branch.should_receive( :new ).with( @directory, TEST_PERSON2_DN ).
599
- and_return( newbranch )
600
- @entry.should_receive( :merge ).with( newattrs ).and_return( newattrs )
601
- @directory.should_receive( :create ).with( newbranch, newattrs ).
602
- and_return( true )
556
+ @conn.should_receive( :add ).
557
+ with( TEST_SUBHOSTS_DN, @entry.merge('description' => ['Hosts in a subdomain.']) )
603
558
 
604
- @branch.copy( TEST_PERSON2_DN, newattrs ).should == newbranch
559
+ newbranch = @branch.copy( TEST_SUBHOSTS_DN, :description => ['Hosts in a subdomain.'] )
560
+ newbranch.dn.should == TEST_SUBHOSTS_DN
605
561
  end
606
562
 
607
563
 
@@ -610,55 +566,47 @@ describe Treequel::Branch do
610
566
  :displayName => 'Chilly T. Penguin',
611
567
  :description => "A chilly little penguin.",
612
568
  }
613
-
614
- @directory.should_receive( :modify ).with( @branch, attributes )
569
+ @conn.should_receive( :modify ).
570
+ with( TEST_HOSTS_DN,
571
+ 'displayName' => ['Chilly T. Penguin'],
572
+ 'description' => ["A chilly little penguin."] )
615
573
 
616
574
  @branch.merge( attributes )
617
575
  end
618
576
 
619
577
 
620
578
  it "can delete all values of one of its entry's individual attributes" do
621
- LDAP::Mod.should_receive( :new ).with( LDAP::LDAP_MOD_DELETE, 'displayName', [] ).
622
- and_return( :mod_delete )
623
- @directory.should_receive( :modify ).with( @branch, [:mod_delete] )
624
-
579
+ mod = LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, 'displayName', [] )
580
+ @conn.should_receive( :modify ).with( TEST_HOSTS_DN, [mod] )
625
581
  @branch.delete( :displayName )
626
582
  end
627
583
 
628
584
  it "can delete all values of more than one of its entry's individual attributes" do
629
- LDAP::Mod.should_receive( :new ).with( LDAP::LDAP_MOD_DELETE, 'displayName', [] ).
630
- and_return( :first_mod_delete )
631
- LDAP::Mod.should_receive( :new ).with( LDAP::LDAP_MOD_DELETE, 'gecos', [] ).
632
- and_return( :second_mod_delete )
633
- @directory.should_receive( :modify ).
634
- with( @branch, [:first_mod_delete, :second_mod_delete] )
585
+ mod1 = LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, 'displayName', [] )
586
+ mod2 = LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, 'gecos', [] )
587
+ @conn.should_receive( :modify ).with( TEST_HOSTS_DN, [mod1, mod2] )
635
588
 
636
589
  @branch.delete( :displayName, :gecos )
637
590
  end
638
591
 
639
592
  it "can delete one particular value of its entry's individual attributes" do
640
- LDAP::Mod.should_receive( :new ).
641
- with( LDAP::LDAP_MOD_DELETE, 'objectClass', ['apple-user'] ).
642
- and_return( :mod_delete )
643
- @directory.should_receive( :modify ).with( @branch, [:mod_delete] )
593
+ mod = LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, 'objectClass', ['apple-user'] )
594
+ @conn.should_receive( :modify ).with( TEST_HOSTS_DN, [mod] )
644
595
 
645
596
  @branch.delete( :objectClass => 'apple-user' )
646
597
  end
647
598
 
648
599
  it "can delete particular values of more than one of its entry's individual attributes" do
649
- LDAP::Mod.should_receive( :new ).
650
- with( LDAP::LDAP_MOD_DELETE, 'objectClass', ['apple-user', 'inetOrgPerson'] ).
651
- and_return( :first_mod_delete )
652
- LDAP::Mod.should_receive( :new ).
653
- with( LDAP::LDAP_MOD_DELETE, 'cn', [] ).and_return( :second_mod_delete )
654
- @directory.should_receive( :modify ).
655
- with( @branch, array_including(:first_mod_delete, :second_mod_delete) )
600
+ mod1 = LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, 'objectClass',
601
+ ['apple-user', 'inetOrgPerson'] )
602
+ mod2 = LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, 'cn', [] )
603
+ @conn.should_receive( :modify ).with( TEST_HOSTS_DN, array_including(mod1, mod2) )
656
604
 
657
605
  @branch.delete( :objectClass => ['apple-user',:inetOrgPerson], :cn => [] )
658
606
  end
659
607
 
660
608
  it "deletes its entry entirely if no attributes are specified" do
661
- @directory.should_receive( :delete ).with( @branch )
609
+ @conn.should_receive( :delete ).with( TEST_HOSTS_DN )
662
610
  @branch.delete
663
611
  end
664
612
 
@@ -669,15 +617,13 @@ describe Treequel::Branch do
669
617
 
670
618
 
671
619
  it "knows how to represent itself as LDIF" do
672
- @entry.should_receive( :keys ).and_return([ 'description', 'l' ])
673
- @entry.should_receive( :[] ).with( 'description' ).
674
- and_return([ 'A chilly little penguin.' ])
675
- @entry.should_receive( :[] ).with( 'l' ).
676
- and_return([ 'Antartica', 'Galapagos' ])
677
-
678
620
  ldif = @branch.to_ldif
679
621
  ldif.should =~ /dn: #{TEST_HOSTS_DN_ATTR}=#{TEST_HOSTS_DN_VALUE},#{TEST_BASE_DN}/i
680
- ldif.should =~ /description: A chilly little penguin./
622
+ ldif.should =~ /description: A string/
623
+ ldif.should =~ /description: another string/
624
+ ldif.should =~ /l: Antartica/
625
+ ldif.should =~ /l: Galapagos/
626
+ ldif.should =~ /ou: Hosts/
681
627
  end
682
628
 
683
629
 
@@ -688,11 +634,13 @@ describe Treequel::Branch do
688
634
  'we see the entire universe.'
689
635
 
690
636
  it "knows how to split long lines in LDIF output" do
691
- @entry.should_receive( :keys ).and_return([ 'description', 'l' ])
692
- @entry.should_receive( :[] ).with( 'description' ).
693
- and_return([ LONG_TEST_VALUE ])
694
- @entry.should_receive( :[] ).with( 'l' ).
695
- and_return([ 'Antartica', 'Galapagos' ])
637
+ entry = {
638
+ 'description' => [LONG_TEST_VALUE],
639
+ 'l' => [ 'Antartica', 'Galapagos' ]
640
+ }
641
+ @conn.stub( :search_ext2 ).
642
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
643
+ and_return([ entry ])
696
644
 
697
645
  ldif = @branch.to_ldif( 20 )
698
646
  val = ldif[ /(description: (?:[^\n]|\n )+)/, 1 ]
@@ -724,11 +672,13 @@ describe Treequel::Branch do
724
672
  END_VALUE
725
673
 
726
674
  it "knows how to split long binary lines in LDIF output" do
727
- @entry.should_receive( :keys ).and_return([ 'description', 'l' ])
728
- @entry.should_receive( :[] ).with( 'description' ).
729
- and_return([ LONG_BINARY_TEST_VALUE ])
730
- @entry.should_receive( :[] ).with( 'l' ).
731
- and_return([ 'Antartica', 'Galapagos' ])
675
+ entry = {
676
+ 'description' => [LONG_BINARY_TEST_VALUE],
677
+ 'l' => [ 'Antartica', 'Galapagos' ]
678
+ }
679
+ @conn.stub( :search_ext2 ).
680
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
681
+ and_return([ entry ])
732
682
 
733
683
  ldif = @branch.to_ldif( 20 )
734
684
  ldif.scan( /^description/ ).length.should == 1
@@ -744,16 +694,12 @@ describe Treequel::Branch do
744
694
 
745
695
 
746
696
  it "knows how to represent itself as a Hash" do
747
- @entry.should_receive( :keys ).and_return([ 'description', 'dn', 'l' ])
748
- @entry.should_receive( :[] ).with( 'description' ).
749
- and_return([ 'A chilly little penguin.' ])
750
- @entry.should_receive( :[] ).with( 'l' ).
751
- and_return([ 'Antartica', 'Galapagos' ])
752
-
753
697
  @branch.to_hash.should == {
754
- 'description' => ['A chilly little penguin.'],
698
+ 'description' => ['A string', 'another string'],
755
699
  'l' => [ 'Antartica', 'Galapagos' ],
756
- 'dn' => TEST_HOSTS_DN,
700
+ 'objectClass' => ['organizationalUnit'],
701
+ 'ou' => ['Hosts'],
702
+ 'rev' => ['03eca02ba232']
757
703
  }
758
704
  end
759
705
 
@@ -761,12 +707,11 @@ describe Treequel::Branch do
761
707
  "Branch" do
762
708
  other_branch = Treequel::Branch.new( @directory, TEST_SUBHOSTS_DN )
763
709
 
764
- Treequel::Branchset.should_receive( :new ).with( @branch ).and_return( :branchset )
765
- Treequel::Branchset.should_receive( :new ).with( other_branch ).and_return( :other_branchset )
766
- Treequel::BranchCollection.should_receive( :new ).with( :branchset, :other_branchset ).
767
- and_return( :a_collection )
710
+ coll = (@branch + other_branch)
768
711
 
769
- (@branch + other_branch).should == :a_collection
712
+ coll.should be_a( Treequel::BranchCollection )
713
+ coll.branchsets.should have( 2 ).members
714
+ coll.branchsets.map( &:base_dn ).should include( TEST_HOSTS_DN, TEST_SUBHOSTS_DN )
770
715
  end
771
716
 
772
717
 
@@ -774,61 +719,69 @@ describe Treequel::Branch do
774
719
  describe "index fetch operator" do
775
720
 
776
721
  it "fetches a multi-value attribute as an Array of Strings" do
777
- @schema.should_receive( :attribute_types ).and_return({ :glumpy => @attribute_type })
778
- @attribute_type.should_receive( :single? ).and_return( false )
779
- @entry.should_receive( :[] ).with( 'glumpy' ).at_least( :once ).
780
- and_return([ 'glumpa1', 'glumpa2' ])
781
-
782
- @attribute_type.stub( :syntax ).and_return( @syntax )
783
- @directory.stub( :convert_to_object ).and_return {|_,str| str }
784
-
785
- @branch[ :glumpy ].should == [ 'glumpa1', 'glumpa2' ]
722
+ entry = {
723
+ 'description' => ["A string", "another string"],
724
+ 'l' => [ 'Antartica', 'Galapagos' ],
725
+ 'objectClass' => ['organizationalUnit'],
726
+ }
727
+ @conn.should_receive( :search_ext2 ).
728
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
729
+ and_return([ entry ])
730
+
731
+ @branch[ :description ].should include( 'A string', 'another string' )
732
+ @branch[ :l ].should include( 'Galapagos', 'Antartica' )
786
733
  end
787
734
 
788
735
  it "fetches a single-value attribute as a scalar String" do
789
- @schema.should_receive( :attribute_types ).and_return({ :glumpy => @attribute_type })
790
- @attribute_type.should_receive( :single? ).and_return( true )
791
- @entry.should_receive( :[] ).with( 'glumpy' ).at_least( :once ).
792
- and_return([ 'glumpa1' ])
793
-
794
- @attribute_type.stub( :syntax ).and_return( @syntax )
795
- @directory.stub( :convert_to_object ).and_return {|_,str| str }
796
-
797
- @branch[ :glumpy ].should == 'glumpa1'
736
+ test_dn = "cn=ssh,cn=www,#{TEST_HOSTS_DN}"
737
+ entry = {
738
+ 'ipServicePort' => ['22'],
739
+ 'ipServiceProtocol' => ['tcp'],
740
+ 'objectClass' => ['ipService'],
741
+ 'cn' => ['www'],
742
+ }
743
+ @conn.should_receive( :search_ext2 ).
744
+ with( test_dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
745
+ and_return([ entry ])
746
+
747
+ branch = Treequel::Branch.new( @directory, test_dn )
748
+
749
+ branch[ :ipServicePort ].should == 22
750
+ branch[ :ipServiceProtocol ].should == ['tcp']
798
751
  end
799
752
 
800
753
  it "returns the entry without conversion if there is no such attribute in the schema" do
801
- @schema.should_receive( :attribute_types ).and_return({})
802
- @entry.should_receive( :[] ).with( 'glumpy' ).at_least( :once ).
803
- and_return([ 'glumpa1' ])
804
- @branch[ :glumpy ].should == [ 'glumpa1' ]
754
+ @branch[ :rev ].should == [ '03eca02ba232' ]
805
755
  end
806
756
 
807
757
  it "returns nil if record doesn't have the attribute set" do
808
- @entry.should_receive( :[] ).with( 'glumpy' ).and_return( nil )
809
- @branch[ :glumpy ].should == nil
758
+ @branch[ :cn ].should == nil
810
759
  end
811
760
 
812
761
  it "caches the value fetched from its entry" do
813
- @schema.stub( :attribute_types ).and_return({ :glump => @attribute_type })
814
- @attribute_type.stub( :single? ).and_return( true )
815
- @attribute_type.stub( :syntax ).and_return( @syntax )
816
- @directory.stub( :convert_to_object ).and_return {|_,str| str }
817
- @entry.should_receive( :[] ).with( 'glump' ).once.and_return( [:a_value] )
818
- 2.times { @branch[ :glump ] }
762
+ @conn.should_receive( :search_ext2 ).
763
+ with( TEST_HOSTS_DN, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
764
+ exactly( :once ).
765
+ and_return([ @entry ])
766
+
767
+ 2.times { @branch[ :description ] }
819
768
  end
820
769
 
821
- it "maps attributes through its directory" do
822
- @schema.should_receive( :attribute_types ).and_return({ :bvector => @attribute_type })
823
- @attribute_type.should_receive( :single? ).and_return( true )
824
- @entry.should_receive( :[] ).with( 'bvector' ).at_least( :once ).
825
- and_return([ '010011010101B' ])
826
- @syntax.stub( :oid ).and_return( OIDS::BIT_STRING_SYNTAX )
827
- @directory.should_receive( :convert_to_object ).
828
- with( OIDS::BIT_STRING_SYNTAX, '010011010101B' ).
829
- and_return( 1237 )
830
-
831
- @branch[ :bvector ].should == 1237
770
+ it "converts objects via the conversions set in its directory" do
771
+ test_dn = "cn=ssh,cn=www,#{TEST_HOSTS_DN}"
772
+ entry = {
773
+ 'ipServicePort' => ['22'],
774
+ 'ipServiceProtocol' => ['tcp'],
775
+ 'objectClass' => ['ipService'],
776
+ 'cn' => ['www'],
777
+ }
778
+ @conn.should_receive( :search_ext2 ).
779
+ with( test_dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
780
+ and_return([ entry ])
781
+
782
+ branch = Treequel::Branch.new( @directory, test_dn )
783
+
784
+ branch[ :ipServicePort ].should be_a( Fixnum )
832
785
  end
833
786
 
834
787
  end
@@ -837,40 +790,65 @@ describe Treequel::Branch do
837
790
  describe "index set operator" do
838
791
 
839
792
  it "writes a single value attribute via its directory" do
840
- @directory.should_receive( :modify ).with( @branch, { 'glumpy' => ['gits'] } )
841
- @entry.should_receive( :[]= ).with( 'glumpy', ['gits'] )
842
- @branch[ :glumpy ] = 'gits'
793
+ test_dn = "cn=ssh,cn=www,#{TEST_HOSTS_DN}"
794
+ entry = {
795
+ 'ipServicePort' => ['22'],
796
+ 'ipServiceProtocol' => ['tcp'],
797
+ 'objectClass' => ['ipService'],
798
+ 'cn' => ['www'],
799
+ }
800
+ @conn.should_receive( :search_ext2 ).
801
+ with( test_dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
802
+ and_return([ entry ])
803
+ branch = Treequel::Branch.new( @directory, test_dn )
804
+
805
+ @conn.should_receive( :modify ).
806
+ with( test_dn, {'ipServicePort' => ['22022']} )
807
+
808
+ branch[ :ipServicePort ] = 22022
843
809
  end
844
810
 
845
- it "writes multiple attribute values via its directory" do
846
- @directory.should_receive( :modify ).with( @branch, { 'glumpy' => ['gits', 'crumps'] } )
847
- @entry.should_receive( :[]= ).with( 'glumpy', ['gits', 'crumps'] )
848
- @branch[ :glumpy ] = [ 'gits', 'crumps' ]
811
+ it "converts values for non-single attribute types to an Array" do
812
+ test_dn = "cn=laptops,ou=computerlists,ou=macosxodconfig,#{TEST_BASE_DN}"
813
+ entry = {
814
+ 'dn' => [test_dn],
815
+ 'cn' => ['laptops'],
816
+ 'objectClass' => ['apple-computer-list'],
817
+ 'apple-computers' => %w[chernobyl beetlejuice toronaga],
818
+ 'apple-generateduid' => ['3f73981a-07bb-11e0-b21a-fb56fb48ba42'],
819
+ }
820
+ @conn.should_receive( :search_ext2 ).
821
+ with( test_dn, LDAP::LDAP_SCOPE_BASE, "(objectClass=*)" ).
822
+ and_return([ entry ])
823
+ branch = Treequel::Branch.new( @directory, test_dn )
824
+
825
+ @conn.should_receive( :modify ).
826
+ with( test_dn, "apple-computers" =>
827
+ ["chernobyl", "beetlejuice", "toronaga", "glider", "creeper"] )
828
+
829
+ branch[ 'apple-computers' ] += %w[glider creeper]
849
830
  end
850
831
 
851
- it "clears the cache after a successful write" do
852
- @schema.stub( :attribute_types ).and_return({ :glorpy => @attribute_type })
853
- @attribute_type.stub( :single? ).and_return( true )
854
- @attribute_type.stub( :syntax ).and_return( @syntax )
855
- @directory.stub( :convert_to_object ).and_return {|_,val| val }
856
- @entry.should_receive( :[] ).with( 'glorpy' ).and_return( [:firstval], [:secondval] )
832
+ it "writes multiple attribute values via its directory" do
833
+ test_dn = "cn=ssh,cn=www,#{TEST_HOSTS_DN}"
834
+ branch = Treequel::Branch.new( @directory, test_dn )
857
835
 
858
- @directory.should_receive( :modify ).with( @branch, {'glorpy' => ['chunks']} )
859
- @directory.stub( :convert_to_attribute ).and_return {|_,val| val }
860
- @entry.should_receive( :[]= ).with( 'glorpy', ['chunks'] )
836
+ @conn.should_receive( :modify ).
837
+ with( test_dn,
838
+ 'ipServicePort' => ['56'],
839
+ 'ipServiceProtocol' => ['udp']
840
+ )
861
841
 
862
- @branch[ :glorpy ].should == :firstval
863
- @branch[ :glorpy ] = 'chunks'
864
- @branch[ :glorpy ].should == :secondval
842
+ branch.merge( :ipServicePort => 56, :ipServiceProtocol => 'udp' )
865
843
  end
866
844
  end
867
845
 
868
846
  it "can fetch multiple values via #values_at" do
869
- @branch.should_receive( :[] ).with( :cn ).and_return( :cn_value )
870
- @branch.should_receive( :[] ).with( :desc ).and_return( :desc_value )
871
- @branch.should_receive( :[] ).with( :l ).and_return( :l_value )
847
+ results = @branch.values_at( TEST_HOSTS_DN_ATTR, :description )
872
848
 
873
- @branch.values_at( :cn, :desc, :l ).should == [ :cn_value, :desc_value, :l_value ]
849
+ results.should have( 2 ).members
850
+ results.first.should == [TEST_HOSTS_DN_VALUE]
851
+ results.last.should == @entry['description']
874
852
  end
875
853
 
876
854
  end