treequel 1.0.4 → 1.1.0

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