treequel 1.2.1 → 1.2.2

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