treequel 1.0.4 → 1.1.0

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.
Files changed (53) hide show
  1. data/ChangeLog +130 -1
  2. data/Rakefile +8 -3
  3. data/Rakefile.local +2 -0
  4. data/bin/treeirb +6 -2
  5. data/bin/treequel +5 -4
  6. data/lib/treequel/branch.rb +133 -72
  7. data/lib/treequel/branchcollection.rb +16 -5
  8. data/lib/treequel/branchset.rb +37 -6
  9. data/lib/treequel/constants.rb +12 -0
  10. data/lib/treequel/directory.rb +96 -41
  11. data/lib/treequel/filter.rb +42 -1
  12. data/lib/treequel/model/objectclass.rb +111 -0
  13. data/lib/treequel/model.rb +363 -0
  14. data/lib/treequel/monkeypatches.rb +65 -0
  15. data/lib/treequel/schema/attributetype.rb +108 -18
  16. data/lib/treequel/schema/ldapsyntax.rb +15 -0
  17. data/lib/treequel/schema/matchingrule.rb +24 -0
  18. data/lib/treequel/schema/matchingruleuse.rb +24 -0
  19. data/lib/treequel/schema/objectclass.rb +70 -5
  20. data/lib/treequel/schema/table.rb +5 -15
  21. data/lib/treequel/schema.rb +64 -1
  22. data/lib/treequel.rb +5 -5
  23. data/rake/documentation.rb +27 -0
  24. data/rake/hg.rb +14 -2
  25. data/rake/manual.rb +1 -1
  26. data/spec/lib/constants.rb +9 -7
  27. data/spec/lib/control_behavior.rb +1 -0
  28. data/spec/lib/matchers.rb +1 -0
  29. data/spec/treequel/branch_spec.rb +229 -20
  30. data/spec/treequel/branchcollection_spec.rb +73 -39
  31. data/spec/treequel/branchset_spec.rb +59 -8
  32. data/spec/treequel/control_spec.rb +1 -0
  33. data/spec/treequel/controls/contentsync_spec.rb +1 -0
  34. data/spec/treequel/controls/pagedresults_spec.rb +1 -0
  35. data/spec/treequel/controls/sortedresults_spec.rb +1 -0
  36. data/spec/treequel/directory_spec.rb +46 -10
  37. data/spec/treequel/filter_spec.rb +28 -5
  38. data/spec/treequel/mixins_spec.rb +7 -14
  39. data/spec/treequel/model/objectclass_spec.rb +330 -0
  40. data/spec/treequel/model_spec.rb +433 -0
  41. data/spec/treequel/monkeypatches_spec.rb +118 -0
  42. data/spec/treequel/schema/attributetype_spec.rb +116 -0
  43. data/spec/treequel/schema/ldapsyntax_spec.rb +8 -0
  44. data/spec/treequel/schema/matchingrule_spec.rb +6 -1
  45. data/spec/treequel/schema/matchingruleuse_spec.rb +5 -0
  46. data/spec/treequel/schema/objectclass_spec.rb +41 -3
  47. data/spec/treequel/schema/table_spec.rb +31 -18
  48. data/spec/treequel/schema_spec.rb +13 -16
  49. data/spec/treequel_spec.rb +22 -0
  50. data.tar.gz.sig +1 -0
  51. metadata +40 -7
  52. metadata.gz.sig +0 -0
  53. data/spec/treequel/utils_spec.rb +0 -49
@@ -0,0 +1,433 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
10
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
11
+ }
12
+
13
+ require 'spec'
14
+ require 'spec/lib/constants'
15
+ require 'spec/lib/helpers'
16
+ require 'spec/lib/matchers'
17
+
18
+ require 'treequel/model'
19
+
20
+ include Treequel::TestConstants
21
+ include Treequel::Constants
22
+
23
+ #####################################################################
24
+ ### C O N T E X T S
25
+ #####################################################################
26
+
27
+ describe Treequel::Model do
28
+ include Treequel::SpecHelpers,
29
+ Treequel::Matchers
30
+
31
+ before( :all ) do
32
+ setup_logging( :fatal )
33
+ end
34
+
35
+ after( :all ) do
36
+ reset_logging()
37
+ end
38
+
39
+ before( :each ) do
40
+ @top_oc = mock( "top objectClass", :name => 'top' )
41
+ @iphost_oc = mock( "ipHost objectClass", :name => 'ipHost' )
42
+ @device_oc = mock( "device objectClass", :name => 'device' )
43
+
44
+ @iphost_oc.stub!( :ancestors ).and_return([ @iphost_oc, @top_oc ])
45
+ @device_oc.stub!( :ancestors ).and_return([ @device_oc, @top_oc ])
46
+
47
+ @simple_entry = {
48
+ 'dn' => TEST_HOST_DN,
49
+ 'objectClass' => ['ipHost', 'device']
50
+ }
51
+ @simple_entry.stub!( :object_classes ).and_return([ @iphost_oc, @device_oc ])
52
+ @directory = mock( "treequel directory", :get_entry => @simple_entry )
53
+ end
54
+
55
+ after( :each ) do
56
+ Treequel::Model.objectclass_registry.clear
57
+ Treequel::Model.base_registry.clear
58
+ end
59
+
60
+
61
+ it "knows which mixins should be applied for a single objectClass" do
62
+ mixin = Module.new
63
+ mixin.should_receive( :model_objectclasses ).at_least( :once ).
64
+ and_return( [:inetOrgPerson] )
65
+ mixin.should_receive( :model_bases ).at_least( :once ).
66
+ and_return( [] )
67
+ Treequel::Model.register_mixin( mixin )
68
+ Treequel::Model.mixins_for_objectclasses( :inetOrgPerson ).should include( mixin )
69
+ end
70
+
71
+ it "knows which mixins should be applied for multiple objectClasses" do
72
+ mixin = Module.new
73
+ mixin.should_receive( :model_objectclasses ).at_least( :once ).
74
+ and_return( [:inetOrgPerson, :organizationalPerson] )
75
+ mixin.should_receive( :model_bases ).at_least( :once ).
76
+ and_return( [] )
77
+ Treequel::Model.register_mixin( mixin )
78
+ Treequel::Model.mixins_for_objectclasses( :inetOrgPerson, :organizationalPerson ).
79
+ should include( mixin )
80
+ end
81
+
82
+ it "knows which mixins should be applied for a DN that exactly matches one that's registered" do
83
+ mixin = Module.new
84
+ mixin.should_receive( :model_objectclasses ).at_least( :once ).
85
+ and_return( [] )
86
+ mixin.should_receive( :model_bases ).at_least( :once ).
87
+ and_return( [TEST_PEOPLE_DN] )
88
+ Treequel::Model.register_mixin( mixin )
89
+ Treequel::Model.mixins_for_dn( TEST_PEOPLE_DN ).should include( mixin )
90
+ end
91
+
92
+ it "knows which mixins should be applied for a DN that is a child of one that's registered" do
93
+ mixin = mock( "module" )
94
+ mixin.should_receive( :model_objectclasses ).at_least( :once ).
95
+ and_return( [] )
96
+ mixin.should_receive( :model_bases ).at_least( :once ).
97
+ and_return( [TEST_PEOPLE_DN] )
98
+ Treequel::Model.register_mixin( mixin )
99
+ Treequel::Model.mixins_for_dn( TEST_PERSON_DN ).should include( mixin )
100
+ end
101
+
102
+ it "knows that mixins that don't have a base apply to all DNs" do
103
+ mixin = mock( "module" )
104
+ mixin.should_receive( :model_objectclasses ).at_least( :once ).
105
+ and_return( [:top] )
106
+ mixin.should_receive( :model_bases ).at_least( :once ).and_return( [] )
107
+
108
+ Treequel::Model.register_mixin( mixin )
109
+
110
+ Treequel::Model.mixins_for_dn( TEST_PERSON_DN ).should include( mixin )
111
+ end
112
+
113
+ it "adds new registries to subclasses" do
114
+ subclass = Class.new( Treequel::Model )
115
+
116
+ # The registry should have the same default proc, but be a distinct Hash
117
+ subclass.objectclass_registry.default_proc.
118
+ should equal( Treequel::Model::SET_HASH.default_proc )
119
+ subclass.objectclass_registry.should_not equal( Treequel::Model.objectclass_registry )
120
+
121
+ # Same with this one
122
+ subclass.base_registry.default_proc.
123
+ should equal( Treequel::Model::SET_HASH.default_proc )
124
+ subclass.base_registry.should_not equal( Treequel::Model.base_registry )
125
+ end
126
+
127
+ it "extends new instances with registered mixins which are applicable" do
128
+ mixin1 = Module.new do
129
+ extend Treequel::Model::ObjectClass
130
+ model_bases TEST_HOSTS_DN, TEST_SUBHOSTS_DN
131
+ model_objectclasses :ipHost
132
+ end
133
+ mixin2 = Module.new do
134
+ extend Treequel::Model::ObjectClass
135
+ model_bases TEST_HOSTS_DN
136
+ model_objectclasses :device
137
+ end
138
+ mixin3 = Module.new do
139
+ extend Treequel::Model::ObjectClass
140
+ model_objectclasses :person
141
+ end
142
+
143
+ obj = Treequel::Model.new( @directory, TEST_SUBHOST_DN, @simple_entry )
144
+
145
+ obj.should be_a( mixin1 )
146
+ obj.should_not be_a( mixin2 )
147
+ obj.should_not be_a( mixin3 )
148
+ end
149
+
150
+ it "extends new instances with mixins that are implied by objectClass SUP attributes, too" do
151
+ inherited_mixin = Module.new do
152
+ extend Treequel::Model::ObjectClass
153
+ model_objectclasses :top
154
+ end
155
+ mixin1 = Module.new do
156
+ extend Treequel::Model::ObjectClass
157
+ model_bases TEST_HOSTS_DN, TEST_SUBHOSTS_DN
158
+ model_objectclasses :ipHost
159
+ end
160
+
161
+ obj = Treequel::Model.new( @directory, TEST_SUBHOST_DN, @simple_entry )
162
+
163
+ obj.should be_a( mixin1 )
164
+ obj.should be_a( inherited_mixin )
165
+ end
166
+
167
+ it "applies applicable mixins to instances created before looking up the corresponding entry" do
168
+ mixin1 = Module.new do
169
+ extend Treequel::Model::ObjectClass
170
+ model_bases TEST_HOSTS_DN, TEST_SUBHOSTS_DN
171
+ model_objectclasses :ipHost
172
+ end
173
+ mixin2 = Module.new do
174
+ extend Treequel::Model::ObjectClass
175
+ model_bases TEST_HOSTS_DN
176
+ model_objectclasses :device
177
+ end
178
+ mixin3 = Module.new do
179
+ extend Treequel::Model::ObjectClass
180
+ model_objectclasses :person
181
+ end
182
+
183
+ obj = Treequel::Model.new( @directory, TEST_HOST_DN )
184
+ obj.exists? # Trigger the lookup
185
+
186
+ obj.should be_a( mixin1 )
187
+ obj.should be_a( mixin2 )
188
+ obj.should_not be_a( mixin3 )
189
+ end
190
+
191
+ it "doesn't try to apply objectclasses to non-existant entries" do
192
+ mixin1 = Module.new do
193
+ extend Treequel::Model::ObjectClass
194
+ model_bases TEST_HOSTS_DN, TEST_SUBHOSTS_DN
195
+ model_objectclasses :ipHost
196
+ end
197
+
198
+ @directory.stub!( :get_entry ).and_return( nil )
199
+ obj = Treequel::Model.new( @directory, TEST_HOST_DN )
200
+ obj.exists? # Trigger the lookup
201
+
202
+ obj.should_not be_a( mixin1 )
203
+ end
204
+
205
+ it "allows a mixin to be unregistered" do
206
+ mixin = Module.new
207
+ mixin.should_receive( :model_objectclasses ).at_least( :once ).
208
+ and_return( [:inetOrgPerson] )
209
+ mixin.should_receive( :model_bases ).at_least( :once ).
210
+ and_return( [] )
211
+ Treequel::Model.register_mixin( mixin )
212
+ Treequel::Model.unregister_mixin( mixin )
213
+ Treequel::Model.mixins_for_objectclasses( :inetOrgPerson ).should_not include( mixin )
214
+ end
215
+
216
+
217
+ describe "created from DNs" do
218
+ before( :all ) do
219
+ @entry = {
220
+ 'dn' => [TEST_PERSON_DN],
221
+ 'cn' => ['Slappy the Frog'],
222
+ 'objectClass' => %w[
223
+ ipHost
224
+ ],
225
+ }
226
+ schema_dumpfile = Pathname( __FILE__ ).dirname.parent + 'data' + 'schema.yml'
227
+ hash = YAML.load_file( schema_dumpfile )
228
+ schemahash = LDAP::Schema.new( hash )
229
+ @schema = Treequel::Schema.new( schemahash )
230
+ end
231
+
232
+ before( :each ) do
233
+ @mixin = Module.new do
234
+ extend Treequel::Model::ObjectClass
235
+ model_objectclasses :ipHost
236
+ def fqdn; "some.home.example.com"; end
237
+ end
238
+ @directory = mock( 'Treequel Directory', :schema => @schema )
239
+ @directory.stub!( :convert_to_object ).with( Treequel::OIDS::OID_SYNTAX, 'ipHost' ).
240
+ and_return( 'ipHost' )
241
+ @directory.stub!( :convert_to_object ).
242
+ with( Treequel::OIDS::DIRECTORY_STRING_SYNTAX, 'Slappy the Frog' ).
243
+ and_return( 'Slappy the Frog' )
244
+ @obj = Treequel::Model.new( @directory, TEST_PERSON_DN )
245
+ @entry.stub!( :object_classes ).and_return([ @schema.object_classes[:ipHost] ])
246
+ end
247
+
248
+ after( :each ) do
249
+ Treequel::Model.objectclass_registry.clear
250
+ Treequel::Model.base_registry.clear
251
+ end
252
+
253
+ it "correctly dispatches to methods added via extension that are called before its " +
254
+ "entry is loaded" do
255
+ @directory.should_receive( :get_entry ).with( @obj ).and_return( @entry )
256
+ @obj.fqdn.should == 'some.home.example.com'
257
+ end
258
+
259
+ it "correctly falls through for methods not added by loading the entry" do
260
+ @directory.should_receive( :get_entry ).with( @obj ).and_return( @entry )
261
+ @obj.cn.should == ['Slappy the Frog']
262
+ end
263
+ end
264
+
265
+
266
+ describe "objects created from entries" do
267
+
268
+ before( :all ) do
269
+ @entry = {
270
+ 'dn' => ['uid=slappy,ou=people,dc=acme,dc=com'],
271
+ 'uid' => ['slappy'],
272
+ 'cn' => ['Slappy the Frog'],
273
+ 'givenName' => ['Slappy'],
274
+ 'sn' => ['Frog'],
275
+ 'l' => ['a forest in England'],
276
+ 'title' => ['Forest Fire Prevention Advocate'],
277
+ 'displayName' => ['Slappy the Frog'],
278
+ 'logonTime' => 'a time string',
279
+ 'objectClass' => %w[
280
+ top
281
+ person
282
+ organizationalPerson
283
+ inetOrgPerson
284
+ posixAccount
285
+ shadowAccount
286
+ apple-user
287
+ ],
288
+ }
289
+
290
+ schema_dumpfile = Pathname( __FILE__ ).dirname.parent + 'data' + 'schema.yml'
291
+ hash = YAML.load_file( schema_dumpfile )
292
+ schemahash = LDAP::Schema.new( hash )
293
+ @schema = Treequel::Schema.new( schemahash )
294
+ end
295
+
296
+ before( :each ) do
297
+ @directory = mock( 'Treequel Directory', :schema => @schema )
298
+ @entry.stub!( :object_classes ).and_return([ @schema.object_classes[:ipHost] ])
299
+ @obj = Treequel::Model.new_from_entry( @entry, @directory )
300
+ end
301
+
302
+
303
+ it "provides readers for valid attributes" do
304
+ attrtype = stub( "Treequel attributeType object", :name => :uid )
305
+
306
+ @obj.should_receive( :valid_attribute_type ).with( :uid ).and_return( attrtype )
307
+ @obj.should_receive( :[] ).with( :uid ).and_return( ['slappy'] )
308
+
309
+ @obj.uid.should == ['slappy']
310
+ end
311
+
312
+ it "normalizes underbarred readers for camelCased attributes" do
313
+ attrtype = stub( "Treequel attributeType object", :name => :givenName )
314
+
315
+ @obj.should_receive( :valid_attribute_type ).with( :given_name ).and_return( nil )
316
+ @obj.should_receive( :valid_attribute_type ).with( :givenName ).and_return( attrtype )
317
+ @obj.should_receive( :[] ).with( :givenName ).and_return( ['Slappy'] )
318
+
319
+ @obj.given_name.should == ['Slappy']
320
+ end
321
+
322
+ it "falls through to branch-traversal for a reader with arguments" do
323
+ @obj.should_not_receive( :valid_attribute_type )
324
+ @obj.should_not_receive( :[] )
325
+
326
+ @obj.should_receive( :traverse_branch ).
327
+ with( :dc, :admin, {} ).and_return( :a_child_branch )
328
+
329
+ @obj.dc( :admin ).should == :a_child_branch
330
+ end
331
+
332
+ it "accommodates branch-traversal from its auto-generated readers" do
333
+ @obj.should_receive( :[] ).with( :uid ).and_return( ['slappy'] )
334
+ @obj.uid.should == ['slappy']
335
+
336
+ @obj.uid( :slappy ).should be_a( Treequel::Model )
337
+ end
338
+
339
+ it "provides writers for valid singular attributes" do
340
+ attrtype = stub( "Treequel attributeType object", :name => :logonTime, :single? => true )
341
+
342
+ @obj.should_receive( :valid_attribute_type ).with( :logonTime ).and_return( attrtype )
343
+ @obj.should_receive( :[]= ).with( :logonTime, 'stampley' )
344
+
345
+ @obj.logonTime = 'stampley'
346
+ end
347
+
348
+ it "provides writers for valid non-singular attributes that accept a non-array" do
349
+ attrtype = stub( "Treequel attributeType object", :name => :uid, :single? => false )
350
+
351
+ @obj.should_receive( :valid_attribute_type ).with( :uid ).and_return( attrtype )
352
+ @obj.should_receive( :[]= ).with( :uid, ['stampley'] )
353
+
354
+ @obj.uid = 'stampley'
355
+ end
356
+
357
+ it "provides a predicate that tests true for valid singular attributes that are set" do
358
+ attrtype = stub( "Treequel attributeType object", :name => :activated, :single? => true )
359
+
360
+ @obj.should_receive( :valid_attribute_type ).with( :activated ).and_return( attrtype )
361
+ @obj.should_receive( :[] ).with( :activated ).and_return( :a_time_object )
362
+
363
+ @obj.should be_activated()
364
+ end
365
+
366
+ it "provides a predicate that tests false for valid singular attributes that are not set" do
367
+ attrtype = stub( "Treequel attributeType object", :name => :deactivated, :single? => true )
368
+
369
+ @obj.should_receive( :valid_attribute_type ).with( :deactivated ).and_return( attrtype )
370
+ @obj.should_receive( :[] ).with( :deactivated ).and_return( nil )
371
+
372
+ @obj.should_not be_deactivated()
373
+ end
374
+
375
+ it "provides a predicate that tests true for valid non-singular attributes that have " +
376
+ "at least one value" do
377
+ attrtype = stub( "Treequel attributeType object", :name => :description, :single? => false )
378
+
379
+ @obj.should_receive( :valid_attribute_type ).with( :description ).and_return( attrtype )
380
+ @obj.should_receive( :[] ).with( :description ).
381
+ and_return([ 'Racoon City', 'St-Michael Clock Tower' ])
382
+
383
+ @obj.should have_description()
384
+ end
385
+
386
+ it "provides a predicate that tests false for valid non-singular attributes that don't " +
387
+ "have at least one value" do
388
+ attrtype = stub( "Treequel attributeType object", :name => :l, :single? => false )
389
+
390
+ @obj.should_receive( :valid_attribute_type ).with( :locality_name ).and_return( attrtype )
391
+ @obj.should_receive( :[] ).with( :l ).
392
+ and_return([])
393
+
394
+ @obj.should_not have_locality_name()
395
+ end
396
+
397
+ it "falls through to the default proxy method for invalid attributes" do
398
+ @obj.stub!( :valid_attribute_type ).and_return( nil )
399
+ @entry.should_not_receive( :[] )
400
+
401
+ expect {
402
+ @obj.nonexistant
403
+ }.to raise_exception( NoMethodError, /undefined method/i )
404
+ end
405
+
406
+ it "adds the objectClass attribute to the attribute list when executing a search that " +
407
+ "contains a select" do
408
+ @directory.stub!( :convert_to_object ).and_return {|oid,str| str }
409
+ @directory.should_receive( :search ).
410
+ with( @obj, :scope, :filter, :selectattrs => ['cn', 'objectClass'] )
411
+ @obj.search( :scope, :filter, :selectattrs => ['cn'] )
412
+ end
413
+
414
+ it "doesn't add the objectClass attribute to the attribute list when the search " +
415
+ "doesn't contain a select" do
416
+ @directory.stub!( :convert_to_object ).and_return {|oid,str| str }
417
+ @directory.should_receive( :search ).
418
+ with( @obj, :scope, :filter, :selectattrs => [] )
419
+ @obj.search( :scope, :filter, :selectattrs => [] )
420
+ end
421
+
422
+ it "knows which attribute methods it responds to" do
423
+ @directory.stub!( :convert_to_object ).and_return {|oid,str| str }
424
+ @obj.should respond_to( :cn )
425
+ @obj.should_not respond_to( :humpsize )
426
+ end
427
+
428
+ end
429
+
430
+ end
431
+
432
+
433
+ # vim: set nosta noet ts=4 sw=4:
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ BEGIN {
4
+ require 'pathname'
5
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
+
7
+ libdir = basedir + "lib"
8
+
9
+ $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
10
+ }
11
+
12
+ require 'time'
13
+
14
+ require 'spec'
15
+ require 'spec/lib/constants'
16
+ require 'spec/lib/helpers'
17
+
18
+ require 'treequel'
19
+ require 'treequel/utils'
20
+
21
+
22
+ include Treequel::TestConstants
23
+ # include Treequel::Constants
24
+
25
+ #####################################################################
26
+ ### C O N T E X T S
27
+ #####################################################################
28
+
29
+ describe Treequel::LDAPControlExtensions do
30
+ describe "equality operator method" do
31
+
32
+ it "causes LDAP::Controls with the same class, OID, value, and criticality to " +
33
+ "compare as equal" do
34
+ control1 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\003", true )
35
+ control2 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\003", true )
36
+
37
+ control1.should == control2
38
+ end
39
+
40
+ it "causes LDAP::Controls with different classes to compare as inequal" do
41
+ control_subclass = Class.new( LDAP::Control )
42
+ control1 = control_subclass.new( CONTROL_OIDS[:sync], "0\003\n\001\003", true )
43
+ control2 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\003", true )
44
+
45
+ control1.should_not == control2
46
+ end
47
+
48
+ it "causes LDAP::Controls with different OIDs to compare as inequal" do
49
+ control1 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\003", true )
50
+ control2 = LDAP::Control.new( CONTROL_OIDS[:incremental_values], "0\003\n\001\003", true )
51
+
52
+ control1.should_not == control2
53
+ end
54
+
55
+ it "causes LDAP::Controls with different values to compare as inequal" do
56
+ control1 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\003", true )
57
+ control2 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\001", true )
58
+
59
+ control1.should_not == control2
60
+ end
61
+
62
+ it "causes LDAP::Controls with different criticality to compare as inequal" do
63
+ control1 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\003", true )
64
+ control2 = LDAP::Control.new( CONTROL_OIDS[:sync], "0\003\n\001\003", false )
65
+
66
+ control1.should_not == control2
67
+ end
68
+
69
+ end
70
+ end # module Treequel::LDAPControlExtensions
71
+
72
+
73
+ describe Treequel::TimeExtensions do
74
+
75
+ before( :each ) do
76
+ @time = Time.parse( "Fri Aug 20 08:21:35.1876455 -0700 2010" )
77
+ end
78
+
79
+ describe "RFC4517 LDAP Generalized Time method" do
80
+
81
+ it "returns the time in 'Generalized Time' format" do
82
+ @time.ldap_generalized.should == "20100820082135-0700"
83
+ end
84
+
85
+ it "can include fractional seconds if the optional fractional digits argument is given" do
86
+ @time.ldap_generalized( 3 ).should == "20100820082135.187-0700"
87
+ end
88
+
89
+ it "doesn't include the decimal if fractional digits is specified but zero" do
90
+ @time.ldap_generalized( 0 ).should == "20100820082135-0700"
91
+ end
92
+
93
+ it "zero-fills any digits after six in the fractional digits" do
94
+ @time.ldap_generalized( 11 ).should == "20100820082135.18764500000-0700"
95
+ end
96
+
97
+ it "uses 'Z' for the timezone of times in UTC" do
98
+ @time.utc.ldap_generalized.should == "20100820152135Z"
99
+ end
100
+
101
+ end
102
+
103
+ describe "RFC4517 UTC Time method" do
104
+
105
+ it "returns the time in 'UTC Time' format" do
106
+ @time.ldap_utc.should == "100820082135-0700"
107
+ end
108
+
109
+ it "uses 'Z' for the timezone of times in UTC" do
110
+ @time.utc.ldap_utc.should == "100820152135Z"
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+
118
+ # vim: set nosta noet ts=4 sw=4:
@@ -6,6 +6,7 @@ BEGIN {
6
6
 
7
7
  libdir = basedir + "lib"
8
8
 
9
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
9
10
  $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
10
11
  }
11
12
 
@@ -105,7 +106,29 @@ describe Treequel::Schema::AttributeType do
105
106
  @attrtype.syntax.should == :the_syntax
106
107
  end
107
108
 
109
+ it "can remake its own schema description" do
110
+ @attrtype.to_s.sub( /USAGE \w+\s*/i, '' ).should == OBJECTCLASS_ATTRTYPE
111
+ end
112
+
113
+ it "knows that it's a user application attribute type" do
114
+ @attrtype.should be_user()
115
+ end
116
+
117
+ it "knows that it's not an operational attribute type" do
118
+ @attrtype.should_not be_operational()
119
+ end
120
+
121
+ it "knows that it's not a directory operational attribute type" do
122
+ @attrtype.should_not be_directory_operational()
123
+ end
124
+
125
+ it "knows that it's not a distributed attribute type" do
126
+ @attrtype.should_not be_distributed_operational()
127
+ end
108
128
 
129
+ it "knows that it's not a DSA attribute type" do
130
+ @attrtype.should_not be_dsa_operational()
131
+ end
109
132
  end
110
133
 
111
134
 
@@ -233,6 +256,99 @@ describe Treequel::Schema::AttributeType do
233
256
 
234
257
  end
235
258
 
259
+ describe "parsed from an attributeType that has the 'directoryOperation' USAGE attribute" do
260
+
261
+ DIRECTORY_OPERATIONAL_ATTRIBUTETYPE = %{( 1.1.1.1 USAGE directoryOperation )}
262
+
263
+ before( :each ) do
264
+ @attrtype = Treequel::Schema::AttributeType.
265
+ parse( @schema, DIRECTORY_OPERATIONAL_ATTRIBUTETYPE )
266
+ end
267
+
268
+ it "knows that it's not a user-application attribute type" do
269
+ @attrtype.should_not be_user()
270
+ end
271
+
272
+ it "knows that it's an operational attribute type" do
273
+ @attrtype.should be_operational()
274
+ end
275
+
276
+ it "knows that it's a directory operational attribute type" do
277
+ @attrtype.should be_directory_operational()
278
+ end
279
+
280
+ it "knows that it's NOT a distributed operational attribute type" do
281
+ @attrtype.should_not be_distributed_operational()
282
+ end
283
+
284
+ it "knows that it's NOT a DSA-specific operational attribute type" do
285
+ @attrtype.should_not be_dsa_operational()
286
+ end
287
+
288
+ end
289
+
290
+ describe "parsed from an attributeType that has the 'distributedOperation' USAGE attribute" do
291
+
292
+ DISTRIBUTED_OPERATIONAL_ATTRIBUTETYPE = %{( 1.1.1.1 USAGE distributedOperation )}
293
+
294
+ before( :each ) do
295
+ @attrtype = Treequel::Schema::AttributeType.
296
+ parse( @schema, DISTRIBUTED_OPERATIONAL_ATTRIBUTETYPE )
297
+ end
298
+
299
+ it "knows that it's not a user-application attribute type" do
300
+ @attrtype.should_not be_user()
301
+ end
302
+
303
+ it "knows that it's an operational attribute type" do
304
+ @attrtype.should be_operational()
305
+ end
306
+
307
+ it "knows that it's NOT a directory operational attribute type" do
308
+ @attrtype.should_not be_directory_operational()
309
+ end
310
+
311
+ it "knows that it's a distributed operational attribute type" do
312
+ @attrtype.should be_distributed_operational()
313
+ end
314
+
315
+ it "knows that it's NOT a DSA-specific operational attribute type" do
316
+ @attrtype.should_not be_dsa_operational()
317
+ end
318
+
319
+ end
320
+
321
+ describe "parsed from an attributeType that has the 'dSAOperation' USAGE attribute" do
322
+
323
+ DSASPECIFIC_OPERATIONAL_ATTRIBUTETYPE = %{( 1.1.1.1 USAGE dSAOperation )}
324
+
325
+ before( :each ) do
326
+ @attrtype = Treequel::Schema::AttributeType.
327
+ parse( @schema, DSASPECIFIC_OPERATIONAL_ATTRIBUTETYPE )
328
+ end
329
+
330
+ it "knows that it's not a user-application attribute type" do
331
+ @attrtype.should_not be_user()
332
+ end
333
+
334
+ it "knows that it's an operational attribute type" do
335
+ @attrtype.should be_operational()
336
+ end
337
+
338
+ it "knows that it's NOT a directory operational attribute type" do
339
+ @attrtype.should_not be_directory_operational()
340
+ end
341
+
342
+ it "knows that it's NOT a distributed operational attribute type" do
343
+ @attrtype.should_not be_distributed_operational()
344
+ end
345
+
346
+ it "knows that it's a DSA-specific operational attribute type" do
347
+ @attrtype.should be_dsa_operational()
348
+ end
349
+
350
+ end
351
+
236
352
  end
237
353
 
238
354