treequel 1.2.2 → 1.3.0pre384
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.tar.gz.sig +0 -0
- data/ChangeLog +3374 -0
- data/History.md +39 -0
- data/LICENSE +27 -0
- data/README.md +25 -2
- data/Rakefile +64 -29
- data/bin/treequel +16 -25
- data/bin/treewhat +318 -0
- data/lib/treequel.rb +13 -3
- data/lib/treequel/behavior/control.rb +40 -0
- data/lib/treequel/branch.rb +56 -28
- data/lib/treequel/branchset.rb +10 -2
- data/lib/treequel/controls/pagedresults.rb +4 -2
- data/lib/treequel/directory.rb +40 -50
- data/lib/treequel/exceptions.rb +18 -0
- data/lib/treequel/mixins.rb +44 -14
- data/lib/treequel/model.rb +338 -21
- data/lib/treequel/model/errors.rb +79 -0
- data/lib/treequel/model/objectclass.rb +26 -2
- data/lib/treequel/model/schemavalidations.rb +69 -0
- data/lib/treequel/monkeypatches.rb +99 -17
- data/lib/treequel/schema.rb +19 -5
- data/spec/lib/constants.rb +20 -2
- data/spec/lib/helpers.rb +25 -8
- data/spec/treequel/branch_spec.rb +73 -10
- data/spec/treequel/controls/contentsync_spec.rb +2 -11
- data/spec/treequel/controls/pagedresults_spec.rb +25 -9
- data/spec/treequel/controls/sortedresults_spec.rb +8 -10
- data/spec/treequel/directory_spec.rb +74 -63
- data/spec/treequel/model/errors_spec.rb +77 -0
- data/spec/treequel/model/objectclass_spec.rb +107 -35
- data/spec/treequel/model/schemavalidations_spec.rb +112 -0
- data/spec/treequel/model_spec.rb +294 -81
- data/spec/treequel/monkeypatches_spec.rb +49 -3
- metadata +28 -16
- metadata.gz.sig +0 -0
- data/spec/lib/control_behavior.rb +0 -47
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent.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 'rspec'
|
14
|
+
|
15
|
+
require 'spec/lib/helpers'
|
16
|
+
|
17
|
+
require 'treequel/model'
|
18
|
+
require 'treequel/model/errors'
|
19
|
+
require 'treequel/branchset'
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
#####################################################################
|
24
|
+
### C O N T E X T S
|
25
|
+
#####################################################################
|
26
|
+
|
27
|
+
describe Treequel::Model::Errors do
|
28
|
+
|
29
|
+
before( :all ) do
|
30
|
+
setup_logging( :fatal )
|
31
|
+
end
|
32
|
+
|
33
|
+
before( :each ) do
|
34
|
+
@errors = Treequel::Model::Errors.new
|
35
|
+
end
|
36
|
+
|
37
|
+
after( :all ) do
|
38
|
+
reset_logging()
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
it "allows the addition of errors" do
|
43
|
+
@errors.add( :cn, "Not a common name." )
|
44
|
+
@errors[:cn].should have( 1 ).member
|
45
|
+
@errors[:cn].should include( "Not a common name." )
|
46
|
+
end
|
47
|
+
|
48
|
+
it "knows how many errors there are" do
|
49
|
+
@errors.add( :l, "is not valid" )
|
50
|
+
@errors.add( :description, "must " )
|
51
|
+
@errors.add( :description, "must have at least one description" )
|
52
|
+
|
53
|
+
@errors.count.should == 3
|
54
|
+
end
|
55
|
+
|
56
|
+
it "is empty if there haven't been any errors registered" do
|
57
|
+
@errors.should be_empty()
|
58
|
+
end
|
59
|
+
|
60
|
+
it "isn't empty if there have been errors registered" do
|
61
|
+
@errors.add( :uid, 'duplicate value' )
|
62
|
+
@errors.should_not be_empty()
|
63
|
+
end
|
64
|
+
|
65
|
+
it "can build an array of error messages" do
|
66
|
+
@errors.add( :l, "is not a valid location" )
|
67
|
+
@errors.add( [:givenName, :sn, :displayName], "must be unique" )
|
68
|
+
|
69
|
+
@errors.full_messages.should have( 2 ).members
|
70
|
+
@errors.full_messages.should include( "givenName and sn and displayName must be unique" )
|
71
|
+
@errors.full_messages.should include( "l is not a valid location" )
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# vim: set nosta noet ts=4 sw=4:
|
@@ -12,34 +12,29 @@ BEGIN {
|
|
12
12
|
|
13
13
|
require 'rspec'
|
14
14
|
|
15
|
-
require 'spec/lib/constants'
|
16
15
|
require 'spec/lib/helpers'
|
17
|
-
require 'spec/lib/matchers'
|
18
16
|
|
19
17
|
require 'treequel/model'
|
20
18
|
require 'treequel/model/objectclass'
|
21
19
|
require 'treequel/branchset'
|
22
20
|
|
23
21
|
|
24
|
-
include Treequel::TestConstants
|
25
|
-
include Treequel::Constants
|
26
22
|
|
27
23
|
#####################################################################
|
28
24
|
### C O N T E X T S
|
29
25
|
#####################################################################
|
30
26
|
|
31
27
|
describe Treequel::Model::ObjectClass do
|
32
|
-
include Treequel::SpecHelpers,
|
33
|
-
Treequel::Matchers
|
34
|
-
|
35
|
-
class << self
|
36
|
-
alias_method :they, :it
|
37
|
-
end
|
38
28
|
|
39
29
|
before( :all ) do
|
40
30
|
setup_logging( :fatal )
|
41
31
|
end
|
42
32
|
|
33
|
+
after( :each ) do
|
34
|
+
Treequel::Model.objectclass_registry.clear
|
35
|
+
Treequel::Model.base_registry.clear
|
36
|
+
end
|
37
|
+
|
43
38
|
after( :all ) do
|
44
39
|
reset_logging()
|
45
40
|
end
|
@@ -54,14 +49,9 @@ describe Treequel::Model::ObjectClass do
|
|
54
49
|
end
|
55
50
|
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
after( :each ) do
|
60
|
-
Treequel::Model.objectclass_registry.clear
|
61
|
-
Treequel::Model.base_registry.clear
|
62
|
-
end
|
52
|
+
context "extended module" do
|
63
53
|
|
64
|
-
|
54
|
+
it "can declare a required objectClass" do
|
65
55
|
mixin = Module.new do
|
66
56
|
extend Treequel::Model::ObjectClass
|
67
57
|
model_objectclasses :inetOrgPerson
|
@@ -70,7 +60,7 @@ describe Treequel::Model::ObjectClass do
|
|
70
60
|
mixin.model_objectclasses.should == [:inetOrgPerson]
|
71
61
|
end
|
72
62
|
|
73
|
-
|
63
|
+
it "can declare a required objectClass as a String" do
|
74
64
|
mixin = Module.new do
|
75
65
|
extend Treequel::Model::ObjectClass
|
76
66
|
model_objectclasses 'apple-computer-list'
|
@@ -79,7 +69,7 @@ describe Treequel::Model::ObjectClass do
|
|
79
69
|
mixin.model_objectclasses.should == [:'apple-computer-list']
|
80
70
|
end
|
81
71
|
|
82
|
-
|
72
|
+
it "can declare multiple required objectClasses" do
|
83
73
|
mixin = Module.new do
|
84
74
|
extend Treequel::Model::ObjectClass
|
85
75
|
model_objectclasses :inetOrgPerson, :acmeAccount
|
@@ -88,9 +78,10 @@ describe Treequel::Model::ObjectClass do
|
|
88
78
|
mixin.model_objectclasses.should == [ :inetOrgPerson, :acmeAccount ]
|
89
79
|
end
|
90
80
|
|
91
|
-
|
81
|
+
it "can declare a single base" do
|
92
82
|
mixin = Module.new do
|
93
|
-
extend Treequel::Model::ObjectClass
|
83
|
+
extend Treequel::Model::ObjectClass,
|
84
|
+
Treequel::TestConstants
|
94
85
|
model_objectclasses :device
|
95
86
|
model_bases TEST_PHONES_DN
|
96
87
|
end
|
@@ -98,7 +89,7 @@ describe Treequel::Model::ObjectClass do
|
|
98
89
|
mixin.model_bases.should == [TEST_PHONES_DN]
|
99
90
|
end
|
100
91
|
|
101
|
-
|
92
|
+
it "can declare a base with spaces" do
|
102
93
|
mixin = Module.new do
|
103
94
|
extend Treequel::Model::ObjectClass
|
104
95
|
model_objectclasses :device
|
@@ -108,9 +99,10 @@ describe Treequel::Model::ObjectClass do
|
|
108
99
|
mixin.model_bases.should == ['ou=phones,dc=acme,dc=com']
|
109
100
|
end
|
110
101
|
|
111
|
-
|
102
|
+
it "can declare multiple bases" do
|
112
103
|
mixin = Module.new do
|
113
|
-
extend Treequel::Model::ObjectClass
|
104
|
+
extend Treequel::Model::ObjectClass,
|
105
|
+
Treequel::TestConstants
|
114
106
|
model_objectclasses :ipHost
|
115
107
|
model_bases TEST_HOSTS_DN,
|
116
108
|
TEST_SUBHOSTS_DN
|
@@ -119,7 +111,7 @@ describe Treequel::Model::ObjectClass do
|
|
119
111
|
mixin.model_bases.should include( TEST_HOSTS_DN, TEST_SUBHOSTS_DN )
|
120
112
|
end
|
121
113
|
|
122
|
-
|
114
|
+
it "raises an exception when creating a search for a mixin that hasn't declared " +
|
123
115
|
"at least one objectClass or base" do
|
124
116
|
mixin = Module.new do
|
125
117
|
extend Treequel::Model::ObjectClass
|
@@ -130,7 +122,7 @@ describe Treequel::Model::ObjectClass do
|
|
130
122
|
}.to raise_exception( Treequel::ModelError, /has no search criteria defined/ )
|
131
123
|
end
|
132
124
|
|
133
|
-
|
125
|
+
it "defaults to using Treequel::Model as its model class" do
|
134
126
|
mixin = Module.new do
|
135
127
|
extend Treequel::Model::ObjectClass
|
136
128
|
end
|
@@ -138,7 +130,7 @@ describe Treequel::Model::ObjectClass do
|
|
138
130
|
mixin.model_class.should == Treequel::Model
|
139
131
|
end
|
140
132
|
|
141
|
-
|
133
|
+
it "can declare a model class other than Treequel::Model" do
|
142
134
|
class MyModel < Treequel::Model; end
|
143
135
|
mixin = Module.new do
|
144
136
|
extend Treequel::Model::ObjectClass
|
@@ -148,7 +140,7 @@ describe Treequel::Model::ObjectClass do
|
|
148
140
|
mixin.model_class.should == MyModel
|
149
141
|
end
|
150
142
|
|
151
|
-
|
143
|
+
it "re-registers objectClasses that have already been declared when declaring a " +
|
152
144
|
"new model class" do
|
153
145
|
class MyModel < Treequel::Model; end
|
154
146
|
|
@@ -162,7 +154,7 @@ describe Treequel::Model::ObjectClass do
|
|
162
154
|
MyModel.objectclass_registry[:inetOrgPerson].should include( mixin )
|
163
155
|
end
|
164
156
|
|
165
|
-
|
157
|
+
it "re-registers bases that have already been declared when declaring a " +
|
166
158
|
"new model class" do
|
167
159
|
class MyModel < Treequel::Model; end
|
168
160
|
|
@@ -175,10 +167,88 @@ describe Treequel::Model::ObjectClass do
|
|
175
167
|
Treequel::Model.base_registry['ou=people,dc=acme,dc=com'].should_not include( mixin )
|
176
168
|
MyModel.base_registry['ou=people,dc=acme,dc=com'].should include( mixin )
|
177
169
|
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "model instantiation" do
|
173
|
+
|
174
|
+
before( :each ) do
|
175
|
+
@conn = mock( "ldap connection object" )
|
176
|
+
@directory = get_fixtured_directory( @conn )
|
177
|
+
end
|
178
|
+
|
179
|
+
it "can instantiate a new model object with its declared objectClasses" do
|
180
|
+
mixin = Module.new do
|
181
|
+
extend Treequel::Model::ObjectClass
|
182
|
+
model_objectclasses :inetOrgPerson
|
183
|
+
end
|
184
|
+
|
185
|
+
result = mixin.create( @directory, TEST_PERSON_DN )
|
186
|
+
result.should be_a( Treequel::Model )
|
187
|
+
result[:objectClass].should include( 'inetOrgPerson' )
|
188
|
+
result[TEST_PERSON_DN_ATTR].should include( TEST_PERSON_DN_VALUE )
|
189
|
+
end
|
190
|
+
|
191
|
+
it "merges objectClasses passed to the creation method" do
|
192
|
+
mixin = Module.new do
|
193
|
+
extend Treequel::Model::ObjectClass
|
194
|
+
model_objectclasses :inetOrgPerson
|
195
|
+
end
|
196
|
+
|
197
|
+
result = mixin.create( @directory, TEST_PERSON_DN,
|
198
|
+
:objectClass => [:person, :inetOrgPerson] )
|
199
|
+
result.should be_a( Treequel::Model )
|
200
|
+
result[:objectClass].should have( 2 ).members
|
201
|
+
result[:objectClass].should include( 'inetOrgPerson', 'person' )
|
202
|
+
result[TEST_PERSON_DN_ATTR].should include( TEST_PERSON_DN_VALUE )
|
203
|
+
end
|
204
|
+
|
205
|
+
it "handles the creation of objects with multi-value DNs" do
|
206
|
+
mixin = Module.new do
|
207
|
+
extend Treequel::Model::ObjectClass
|
208
|
+
model_objectclasses :ipHost, :ieee802Device, :device
|
209
|
+
end
|
210
|
+
|
211
|
+
result = mixin.create( @directory, TEST_HOST_MULTIVALUE_DN )
|
212
|
+
result.should be_a( Treequel::Model )
|
213
|
+
result[:objectClass].should have( 3 ).members
|
214
|
+
result[:objectClass].should include( 'ipHost', 'ieee802Device', 'device' )
|
215
|
+
result[TEST_HOST_MULTIVALUE_DN_ATTR1].should include( TEST_HOST_MULTIVALUE_DN_VALUE1 )
|
216
|
+
result[TEST_HOST_MULTIVALUE_DN_ATTR2].should include( TEST_HOST_MULTIVALUE_DN_VALUE2 )
|
217
|
+
end
|
218
|
+
|
219
|
+
it "can instantiate a new model object with its declared objectClasses" do
|
220
|
+
conn = mock( "ldap connection object" )
|
221
|
+
directory = get_fixtured_directory( conn )
|
222
|
+
|
223
|
+
mixin = Module.new do
|
224
|
+
extend Treequel::Model::ObjectClass
|
225
|
+
model_objectclasses :inetOrgPerson
|
226
|
+
end
|
227
|
+
|
228
|
+
result = mixin.create( directory, TEST_PERSON_DN )
|
229
|
+
result.should be_a( Treequel::Model )
|
230
|
+
result[:objectClass].should include( 'inetOrgPerson' )
|
231
|
+
end
|
232
|
+
|
233
|
+
it "merges objectClasses passed to the creation method" do
|
234
|
+
conn = mock( "ldap connection object" )
|
235
|
+
directory = get_fixtured_directory( conn )
|
236
|
+
|
237
|
+
mixin = Module.new do
|
238
|
+
extend Treequel::Model::ObjectClass
|
239
|
+
model_objectclasses :inetOrgPerson
|
240
|
+
end
|
241
|
+
|
242
|
+
result = mixin.create( directory, TEST_PERSON_DN,
|
243
|
+
:objectClass => [:person, :inetOrgPerson] )
|
244
|
+
result.should be_a( Treequel::Model )
|
245
|
+
result[:objectClass].should have( 2 ).members
|
246
|
+
result[:objectClass].should include( 'inetOrgPerson', 'person' )
|
247
|
+
end
|
178
248
|
|
179
249
|
end
|
180
250
|
|
181
|
-
|
251
|
+
context "module that has one required objectClass declared" do
|
182
252
|
|
183
253
|
before( :each ) do
|
184
254
|
@mixin = Module.new do
|
@@ -216,7 +286,7 @@ describe Treequel::Model::ObjectClass do
|
|
216
286
|
|
217
287
|
end
|
218
288
|
|
219
|
-
|
289
|
+
context "module that has more than one required objectClass declared" do
|
220
290
|
|
221
291
|
before( :each ) do
|
222
292
|
@mixin = Module.new do
|
@@ -256,10 +326,11 @@ describe Treequel::Model::ObjectClass do
|
|
256
326
|
|
257
327
|
end
|
258
328
|
|
259
|
-
|
329
|
+
context "module that has one base declared" do
|
260
330
|
before( :each ) do
|
261
331
|
@mixin = Module.new do
|
262
|
-
extend Treequel::Model::ObjectClass
|
332
|
+
extend Treequel::Model::ObjectClass,
|
333
|
+
Treequel::TestConstants
|
263
334
|
model_bases TEST_PEOPLE_DN
|
264
335
|
end
|
265
336
|
end
|
@@ -293,10 +364,11 @@ describe Treequel::Model::ObjectClass do
|
|
293
364
|
|
294
365
|
end
|
295
366
|
|
296
|
-
|
367
|
+
context "module that has more than one base declared" do
|
297
368
|
before( :each ) do
|
298
369
|
@mixin = Module.new do
|
299
|
-
extend Treequel::Model::ObjectClass
|
370
|
+
extend Treequel::Model::ObjectClass,
|
371
|
+
Treequel::TestConstants
|
300
372
|
model_bases TEST_HOSTS_DN,
|
301
373
|
TEST_SUBHOSTS_DN
|
302
374
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent.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 'rspec'
|
14
|
+
|
15
|
+
require 'spec/lib/helpers'
|
16
|
+
|
17
|
+
require 'treequel/model'
|
18
|
+
require 'treequel/model/schemavalidations'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
#####################################################################
|
23
|
+
### C O N T E X T S
|
24
|
+
#####################################################################
|
25
|
+
|
26
|
+
describe Treequel::Model::SchemaValidations do
|
27
|
+
|
28
|
+
before( :all ) do
|
29
|
+
setup_logging( :fatal )
|
30
|
+
end
|
31
|
+
|
32
|
+
before( :each ) do
|
33
|
+
@conn = double( "LDAP connection", :bound? => false )
|
34
|
+
@directory = get_fixtured_directory( @conn )
|
35
|
+
@modelobj = Treequel::Model.new( @directory, TEST_PERSON_DN )
|
36
|
+
end
|
37
|
+
|
38
|
+
after( :all ) do
|
39
|
+
reset_logging()
|
40
|
+
end
|
41
|
+
|
42
|
+
# StructuralObjectClass ( 2.5.6.6 NAME 'person'
|
43
|
+
# DESC 'RFC2256: a person'
|
44
|
+
# SUP top STRUCTURAL
|
45
|
+
# MUST ( sn $ cn )
|
46
|
+
# MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
|
47
|
+
|
48
|
+
it "adds an error if the object doesn't have at least one value for all of its MUST attributes" do
|
49
|
+
@conn.stub( :search_ext2 ).and_return( [] )
|
50
|
+
@modelobj.object_class = 'person'
|
51
|
+
@modelobj.validate
|
52
|
+
@modelobj.errors.should have( 2 ).members
|
53
|
+
@modelobj.errors.full_messages.should include( 'cn MUST have at least one value' )
|
54
|
+
@modelobj.errors.full_messages.should include( 'sn MUST have at least one value' )
|
55
|
+
end
|
56
|
+
|
57
|
+
it "adds an error if the object has a value for an attribute that isn't in one of its MAY attributes" do
|
58
|
+
# First set the object classes to include one which MAY have a 'displayName'
|
59
|
+
entry = {
|
60
|
+
'objectClass' => ['person', 'inetOrgPerson'],
|
61
|
+
'cn' => ['J. Random'],
|
62
|
+
'sn' => ['Hacker'],
|
63
|
+
'displayName' => ['Trinket the Trivial']
|
64
|
+
}
|
65
|
+
@conn.stub( :search_ext2 ).and_return([ entry ])
|
66
|
+
|
67
|
+
# ..then remove the objectclass that grants it and validate
|
68
|
+
@modelobj.object_class -= ['inetOrgPerson']
|
69
|
+
@modelobj.validate
|
70
|
+
|
71
|
+
@modelobj.errors.full_messages.
|
72
|
+
should == [%{displayName is not allowed by entry's objectClasses}]
|
73
|
+
end
|
74
|
+
|
75
|
+
# AuxiliaryObjectClass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
|
76
|
+
# DESC 'Abstraction of an account with POSIX attributes'
|
77
|
+
# SUP top AUXILIARY
|
78
|
+
# MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
|
79
|
+
# MAY ( userPassword $ loginShell $ gecos $ description ) )
|
80
|
+
|
81
|
+
it "adds an error if the object has a value for an attribute that doesn't match the " +
|
82
|
+
"attribute's syntax" do
|
83
|
+
@conn.stub( :search_ext2 ).and_return( [] )
|
84
|
+
|
85
|
+
@modelobj.object_class = ['inetOrgPerson', 'posixAccount']
|
86
|
+
@modelobj.cn = 'J. Random'
|
87
|
+
@modelobj.sn = 'Hacker'
|
88
|
+
@modelobj.uid = 'jrandom'
|
89
|
+
@modelobj.home_directory = '/users/j/jrandom'
|
90
|
+
|
91
|
+
# Set the 'Integer' attributes to values that can't be cast to integers
|
92
|
+
@modelobj.uid_number = "something that's not a number"
|
93
|
+
@modelobj.gid_number = "also not a number"
|
94
|
+
|
95
|
+
@modelobj.validate
|
96
|
+
|
97
|
+
@modelobj.errors.should have( 2 ).members
|
98
|
+
@modelobj.errors.full_messages.should include( "uidNumber isn't a valid Integer value" )
|
99
|
+
@modelobj.errors.full_messages.should include( "gidNumber isn't a valid Integer value" )
|
100
|
+
end
|
101
|
+
|
102
|
+
it "does nothing if :with_schema => false is passed to #validate" do
|
103
|
+
@conn.stub( :search_ext2 ).and_return( [] )
|
104
|
+
@modelobj.object_class = 'person' # MUST ( sn $ cn )
|
105
|
+
@modelobj.validate( :with_schema => false )
|
106
|
+
@modelobj.errors.should be_empty()
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
# vim: set nosta noet ts=4 sw=4:
|