treequel 1.2.2 → 1.3.0pre384
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|