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.
- data/ChangeLog +130 -1
- data/Rakefile +8 -3
- data/Rakefile.local +2 -0
- data/bin/treeirb +6 -2
- data/bin/treequel +5 -4
- data/lib/treequel/branch.rb +133 -72
- data/lib/treequel/branchcollection.rb +16 -5
- data/lib/treequel/branchset.rb +37 -6
- data/lib/treequel/constants.rb +12 -0
- data/lib/treequel/directory.rb +96 -41
- data/lib/treequel/filter.rb +42 -1
- data/lib/treequel/model/objectclass.rb +111 -0
- data/lib/treequel/model.rb +363 -0
- data/lib/treequel/monkeypatches.rb +65 -0
- data/lib/treequel/schema/attributetype.rb +108 -18
- data/lib/treequel/schema/ldapsyntax.rb +15 -0
- data/lib/treequel/schema/matchingrule.rb +24 -0
- data/lib/treequel/schema/matchingruleuse.rb +24 -0
- data/lib/treequel/schema/objectclass.rb +70 -5
- data/lib/treequel/schema/table.rb +5 -15
- data/lib/treequel/schema.rb +64 -1
- data/lib/treequel.rb +5 -5
- data/rake/documentation.rb +27 -0
- data/rake/hg.rb +14 -2
- data/rake/manual.rb +1 -1
- data/spec/lib/constants.rb +9 -7
- data/spec/lib/control_behavior.rb +1 -0
- data/spec/lib/matchers.rb +1 -0
- data/spec/treequel/branch_spec.rb +229 -20
- data/spec/treequel/branchcollection_spec.rb +73 -39
- data/spec/treequel/branchset_spec.rb +59 -8
- data/spec/treequel/control_spec.rb +1 -0
- data/spec/treequel/controls/contentsync_spec.rb +1 -0
- data/spec/treequel/controls/pagedresults_spec.rb +1 -0
- data/spec/treequel/controls/sortedresults_spec.rb +1 -0
- data/spec/treequel/directory_spec.rb +46 -10
- data/spec/treequel/filter_spec.rb +28 -5
- data/spec/treequel/mixins_spec.rb +7 -14
- data/spec/treequel/model/objectclass_spec.rb +330 -0
- data/spec/treequel/model_spec.rb +433 -0
- data/spec/treequel/monkeypatches_spec.rb +118 -0
- data/spec/treequel/schema/attributetype_spec.rb +116 -0
- data/spec/treequel/schema/ldapsyntax_spec.rb +8 -0
- data/spec/treequel/schema/matchingrule_spec.rb +6 -1
- data/spec/treequel/schema/matchingruleuse_spec.rb +5 -0
- data/spec/treequel/schema/objectclass_spec.rb +41 -3
- data/spec/treequel/schema/table_spec.rb +31 -18
- data/spec/treequel/schema_spec.rb +13 -16
- data/spec/treequel_spec.rb +22 -0
- data.tar.gz.sig +1 -0
- metadata +40 -7
- metadata.gz.sig +0 -0
- 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
|
|