treequel 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. data/ChangeLog +354 -0
  2. data/LICENSE +27 -0
  3. data/README +66 -0
  4. data/Rakefile +345 -0
  5. data/Rakefile.local +43 -0
  6. data/bin/treeirb +14 -0
  7. data/bin/treequel +229 -0
  8. data/examples/company-directory.rb +112 -0
  9. data/examples/ldap-monitor.rb +143 -0
  10. data/examples/ldap-monitor/public/css/master.css +328 -0
  11. data/examples/ldap-monitor/public/images/card_small.png +0 -0
  12. data/examples/ldap-monitor/public/images/chain_small.png +0 -0
  13. data/examples/ldap-monitor/public/images/globe_small.png +0 -0
  14. data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
  15. data/examples/ldap-monitor/public/images/plug.png +0 -0
  16. data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
  17. data/examples/ldap-monitor/public/images/tick.png +0 -0
  18. data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
  19. data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
  20. data/examples/ldap-monitor/views/backends.erb +41 -0
  21. data/examples/ldap-monitor/views/connections.erb +74 -0
  22. data/examples/ldap-monitor/views/databases.erb +39 -0
  23. data/examples/ldap-monitor/views/dump_subsystem.erb +14 -0
  24. data/examples/ldap-monitor/views/index.erb +14 -0
  25. data/examples/ldap-monitor/views/layout.erb +35 -0
  26. data/examples/ldap-monitor/views/listeners.erb +30 -0
  27. data/examples/ldap_state.rb +62 -0
  28. data/lib/treequel.rb +145 -0
  29. data/lib/treequel/branch.rb +589 -0
  30. data/lib/treequel/branchcollection.rb +204 -0
  31. data/lib/treequel/branchset.rb +360 -0
  32. data/lib/treequel/constants.rb +604 -0
  33. data/lib/treequel/directory.rb +541 -0
  34. data/lib/treequel/exceptions.rb +32 -0
  35. data/lib/treequel/filter.rb +704 -0
  36. data/lib/treequel/mixins.rb +325 -0
  37. data/lib/treequel/schema.rb +245 -0
  38. data/lib/treequel/schema/attributetype.rb +252 -0
  39. data/lib/treequel/schema/ldapsyntax.rb +96 -0
  40. data/lib/treequel/schema/matchingrule.rb +124 -0
  41. data/lib/treequel/schema/matchingruleuse.rb +124 -0
  42. data/lib/treequel/schema/objectclass.rb +289 -0
  43. data/lib/treequel/sequel_integration.rb +26 -0
  44. data/lib/treequel/utils.rb +169 -0
  45. data/rake/191_compat.rb +26 -0
  46. data/rake/dependencies.rb +76 -0
  47. data/rake/helpers.rb +434 -0
  48. data/rake/hg.rb +261 -0
  49. data/rake/manual.rb +782 -0
  50. data/rake/packaging.rb +135 -0
  51. data/rake/publishing.rb +318 -0
  52. data/rake/rdoc.rb +30 -0
  53. data/rake/style.rb +62 -0
  54. data/rake/svn.rb +668 -0
  55. data/rake/testing.rb +187 -0
  56. data/rake/verifytask.rb +64 -0
  57. data/rake/win32.rb +190 -0
  58. data/spec/lib/constants.rb +93 -0
  59. data/spec/lib/helpers.rb +100 -0
  60. data/spec/treequel/branch_spec.rb +569 -0
  61. data/spec/treequel/branchcollection_spec.rb +213 -0
  62. data/spec/treequel/branchset_spec.rb +376 -0
  63. data/spec/treequel/directory_spec.rb +487 -0
  64. data/spec/treequel/filter_spec.rb +482 -0
  65. data/spec/treequel/mixins_spec.rb +330 -0
  66. data/spec/treequel/schema/attributetype_spec.rb +237 -0
  67. data/spec/treequel/schema/ldapsyntax_spec.rb +83 -0
  68. data/spec/treequel/schema/matchingrule_spec.rb +158 -0
  69. data/spec/treequel/schema/matchingruleuse_spec.rb +137 -0
  70. data/spec/treequel/schema/objectclass_spec.rb +262 -0
  71. data/spec/treequel/schema_spec.rb +118 -0
  72. data/spec/treequel/utils_spec.rb +49 -0
  73. data/spec/treequel_spec.rb +179 -0
  74. metadata +169 -0
@@ -0,0 +1,482 @@
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 'rubygems'
13
+ gem 'rspec', '>= 1.2.6'
14
+
15
+ require 'spec'
16
+ require 'spec/lib/constants'
17
+ require 'spec/lib/helpers'
18
+
19
+ require 'treequel/filter'
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::Filter do
30
+ include Treequel::SpecHelpers
31
+
32
+ before( :all ) do
33
+ setup_logging( :fatal )
34
+ end
35
+
36
+ after( :all ) do
37
+ reset_logging()
38
+ end
39
+
40
+
41
+ it "knows that it is promiscuous (will match any entry) if its component is promiscuous" do
42
+ Treequel::Filter.new.should be_promiscuous()
43
+ end
44
+
45
+ it "knows that it isn't promiscuous if its component isn't promiscuous" do
46
+ Treequel::Filter.new( :uid, 'batgirl' ).should_not be_promiscuous()
47
+ end
48
+
49
+
50
+ it "defaults to selecting everything" do
51
+ Treequel::Filter.new.to_s.should == '(objectClass=*)'
52
+ end
53
+
54
+ it "can be created from a string literal" do
55
+ Treequel::Filter.new( '(uid=bargrab)' ).to_s.should == '(uid=bargrab)'
56
+ end
57
+
58
+ it "wraps string literal instances in parens if it requires them" do
59
+ Treequel::Filter.new( 'uid=bargrab' ).to_s.should == '(uid=bargrab)'
60
+ end
61
+
62
+ it "parses a single Symbol argument as a presence filter" do
63
+ Treequel::Filter.new( :uid ).to_s.should == '(uid=*)'
64
+ end
65
+
66
+ it "parses a single-element Array with a Symbol as a presence filter" do
67
+ Treequel::Filter.new( [:uid] ).to_s.should == '(uid=*)'
68
+ end
69
+
70
+ it "parses a Symbol+value pair as a simple item equal filter" do
71
+ Treequel::Filter.new( :uid, 'bigthung' ).to_s.should == '(uid=bigthung)'
72
+ end
73
+
74
+ it "parses a single-item Symbol+value hash as a simple item equal filter" do
75
+ Treequel::Filter.new({ :uidNumber => 3036 }).to_s.should == '(uidNumber=3036)'
76
+ end
77
+
78
+ it "parses a Symbol+value pair in an Array as a simple item equal filter" do
79
+ Treequel::Filter.new( [:uid, 'bigthung'] ).to_s.should == '(uid=bigthung)'
80
+ end
81
+
82
+ it "parses a multi-value Hash as an ANDed collection of simple item equals filters" do
83
+ expr = Treequel::Filter.new( :givenName => 'Michael', :sn => 'Granger' )
84
+ gnpat = Regexp.quote( '(givenName=Michael)' )
85
+ snpat = Regexp.quote( '(sn=Granger)' )
86
+
87
+ expr.to_s.should =~ /\(&(#{gnpat}#{snpat}|#{snpat}#{gnpat})\)/i
88
+ end
89
+
90
+ it "parses an AND expression with only a single clause" do
91
+ Treequel::Filter.new( [:&, [:uid, 'kunglung']] ).to_s.should == '(&(uid=kunglung))'
92
+ end
93
+
94
+ it "parses an AND expression with multiple clauses" do
95
+ Treequel::Filter.new( [:and, [:uid, 'kunglung'], [:name, 'chunger']] ).to_s.
96
+ should == '(&(uid=kunglung)(name=chunger))'
97
+ end
98
+
99
+ it "parses an OR expression with only a single clause" do
100
+ Treequel::Filter.new( [:|, [:uid, 'kunglung']] ).to_s.should == '(|(uid=kunglung))'
101
+ end
102
+
103
+ it "parses an OR expression with multiple clauses" do
104
+ Treequel::Filter.new( [:or, [:uid, 'kunglung'], [:name, 'chunger']] ).to_s.
105
+ should == '(|(uid=kunglung)(name=chunger))'
106
+ end
107
+
108
+ it "parses an OR expression with String literal clauses" do
109
+ Treequel::Filter.new( :or, ['cn~=facet', 'cn=structure', 'cn=envision'] ).to_s.
110
+ should == '(|(cn~=facet)(cn=structure)(cn=envision))'
111
+ end
112
+
113
+ it "infers the OR-hash form if the expression is Symbol => Array" do
114
+ Treequel::Filter.new( :uid => %w[lar bin fon guh] ).to_s.
115
+ should == '(|(uid=lar)(uid=bin)(uid=fon)(uid=guh))'
116
+ end
117
+
118
+ it "correctly includes OR subfilters in a Hash if the value is an Array" do
119
+ fstr = Treequel::Filter.new( :objectClass => 'inetOrgPerson', :uid => %w[lar bin fon guh] ).to_s
120
+
121
+ fstr.should include('(|(uid=lar)(uid=bin)(uid=fon)(uid=guh))')
122
+ fstr.should include('(objectClass=inetOrgPerson)')
123
+ fstr.should =~ /^\(&/
124
+ end
125
+
126
+ it "parses a NOT expression with only a single clause" do
127
+ Treequel::Filter.new( [:'!', [:uid, 'kunglung']] ).to_s.should == '(!(uid=kunglung))'
128
+ end
129
+
130
+ it "parses a Range item as a boolean ANDed expression" do
131
+ filter = Treequel::Filter.new( :uid, 200..1000 ).to_s.should == '(&(uid>=200)(uid<=1000))'
132
+ end
133
+
134
+ it "parses a exclusive Range correctly" do
135
+ filter = Treequel::Filter.new( :uid, 200...1000 ).to_s.should == '(&(uid>=200)(uid<=999))'
136
+ end
137
+
138
+ it "parses a Range item with non-numeric components" do
139
+ filter = Treequel::Filter.new( :lastName => 'Dale'..'Darby' ).to_s.
140
+ should == '(&(lastName>=Dale)(lastName<=Darby))'
141
+ end
142
+
143
+ it "raises an exception with a NOT expression that contains more than one clause" do
144
+ expect {
145
+ Treequel::Filter.new( :not, [:uid, 'kunglung'], [:name, 'chunger'] )
146
+ }.to raise_error( ArgumentError )
147
+ end
148
+
149
+
150
+ it "parses a Substring item from a filter that includes an asterisk" do
151
+ filter = Treequel::Filter.new( :portrait, "\\ff\\d8\\ff\\e0*" )
152
+ filter.component.class.should == Treequel::Filter::SubstringItemComponent
153
+ end
154
+
155
+ it "parses a Present item from a filter that is only an asterisk" do
156
+ filter = Treequel::Filter.new( :disabled, "*" )
157
+ filter.component.class.should == Treequel::Filter::PresentItemComponent
158
+ end
159
+
160
+ it "raises an error when an extensible item filter is given" do
161
+ expect {
162
+ Treequel::Filter.new( :'cn:1.2.3.4.5:', 'Fred Flintstone' )
163
+ }.to raise_error( NotImplementedError, /extensible.*supported/i )
164
+ end
165
+
166
+
167
+ it "parses a complex nested expression" do
168
+ Treequel::Filter.new(
169
+ [:and,
170
+ [:or,
171
+ [:and, [:chungability,'fantagulous'], [:l, 'the moon']],
172
+ [:chungability, '*grunt*'],
173
+ [:hunker]],
174
+ [:not, [:description, 'mediocre']] ]
175
+ ).to_s.should == '(&(|(&(chungability=fantagulous)(l=the moon))' +
176
+ '(chungability=*grunt*)(hunker=*))(!(description=mediocre)))'
177
+ end
178
+
179
+
180
+ ### Operators
181
+ describe "operator methods" do
182
+
183
+ before( :each ) do
184
+ @filter1 = Treequel::Filter.new( :uid, :buckrogers )
185
+ @filter2 = Treequel::Filter.new( :l, :mars )
186
+ end
187
+
188
+ it "compares as equal with another filter if their components are equal" do
189
+ otherfilter = mock( "other filter" )
190
+ otherfilter.should_receive( :component ).and_return( :componentobj )
191
+ @filter1.component = :componentobj
192
+
193
+ @filter1.should == otherfilter
194
+ end
195
+
196
+ it "creates a new AND filter out of two filters that are added together" do
197
+ result = @filter1 + @filter2
198
+ result.should be_a( Treequel::Filter )
199
+ end
200
+
201
+ it "creates a new AND filter out of two filters that are bitwise-ANDed together" do
202
+ result = @filter1 & @filter2
203
+ result.should be_a( Treequel::Filter )
204
+ end
205
+
206
+ it "doesn't include the left operand in an AND filter if it is promiscuous" do
207
+ pfilter = Treequel::Filter.new
208
+ result = pfilter & @filter2
209
+
210
+ result.should == @filter2
211
+ end
212
+
213
+ it "doesn't include the right operand in an AND filter if it is promiscuous" do
214
+ pfilter = Treequel::Filter.new
215
+ result = @filter1 & pfilter
216
+
217
+ result.should == @filter1
218
+ end
219
+
220
+ end
221
+
222
+ describe "components:" do
223
+
224
+ before( :each ) do
225
+ @filter1 = stub( "filter1", :to_s => '(filter1)' )
226
+ @filter2 = stub( "filter2", :to_s => '(filter2)' )
227
+ end
228
+
229
+
230
+ describe Treequel::Filter::FilterList do
231
+ it "stringifies by joining its stringified members" do
232
+ Treequel::Filter::FilterList.new( @filter1, @filter2 ).to_s.
233
+ should == '(filter1)(filter2)'
234
+ end
235
+ end
236
+
237
+ describe Treequel::Filter::Component do
238
+ it "is an abstract class" do
239
+ expect {
240
+ Treequel::Filter::Component.new
241
+ }.to raise_error( NoMethodError )
242
+ end
243
+
244
+ it "is non-promiscuous by default" do
245
+ Class.new( Treequel::Filter::Component ).new.should_not be_promiscuous()
246
+ end
247
+
248
+ end
249
+
250
+
251
+ describe Treequel::Filter::SimpleItemComponent do
252
+ before( :each ) do
253
+ @component = Treequel::Filter::SimpleItemComponent.new( :uid, 'schlange' )
254
+ end
255
+
256
+ it "can parse a component object from a string literal" do
257
+ comp = Treequel::Filter::SimpleItemComponent.parse_from_string( 'description=screamer' )
258
+ comp.filtertype.should == :equal
259
+ comp.filtertype_op.should == '='
260
+ comp.attribute.should == 'description'
261
+ comp.value.should == 'screamer'
262
+ end
263
+
264
+ it "raises an ExpressionError if it can't parse a string literal" do
265
+ expect { Treequel::Filter::SimpleItemComponent.parse_from_string( 'whatev!' ) }.
266
+ to raise_error( Treequel::ExpressionError, /unable to parse/i )
267
+ end
268
+
269
+ it "uses the 'equal' operator if none is specified" do
270
+ @component.filtertype.should == :equal
271
+ end
272
+
273
+ it "knows what the appropriate operator is for its filtertype" do
274
+ @component.filtertype_op.should == '='
275
+ end
276
+
277
+ it "knows what the appropriate operator is for its filtertype even if it's set to a string" do
278
+ @component.filtertype = 'greater'
279
+ @component.filtertype_op.should == '>='
280
+ end
281
+
282
+ it "stringifies as <attribute><operator><value>" do
283
+ @component.to_s.should == 'uid=schlange'
284
+ end
285
+
286
+ it "uses the '~=' operator if its filtertype is 'approx'" do
287
+ @component.filtertype = :approx
288
+ @component.filtertype_op.should == '~='
289
+ end
290
+
291
+ it "uses the '>=' operator if its filtertype is 'greater'" do
292
+ @component.filtertype = :greater
293
+ @component.filtertype_op.should == '>='
294
+ end
295
+
296
+ it "uses the '<=' operator if its filtertype is 'less'" do
297
+ @component.filtertype = :less
298
+ @component.filtertype_op.should == '<='
299
+ end
300
+
301
+ it "raises an error if it's created with an unknown filtertype" do
302
+ expect {
303
+ Treequel::Filter::SimpleItemComponent.new( :uid, 'schlange', :fork )
304
+ }.to raise_error( Treequel::ExpressionError, /invalid/i )
305
+
306
+ end
307
+
308
+ end
309
+
310
+
311
+ describe Treequel::Filter::SubstringItemComponent do
312
+
313
+ before( :each ) do
314
+ @component = Treequel::Filter::SubstringItemComponent.new( :description, '*basecamp*' )
315
+ end
316
+
317
+
318
+ it "can parse a component object from a string literal" do
319
+ comp = Treequel::Filter::SubstringItemComponent.parse_from_string( 'description=*basecamp*' )
320
+ comp.attribute.should == 'description'
321
+ comp.options.should == ''
322
+ comp.pattern.should == '*basecamp*'
323
+ end
324
+
325
+ it "can parse a component object from a string literal with attribute options" do
326
+ jpeg_portraits = Treequel::Filter::SubstringItemComponent.
327
+ parse_from_string( "portrait;binary=\\xff\\xd8\\xff\\xe0*" )
328
+ jpeg_portraits.attribute.should == 'portrait'
329
+ jpeg_portraits.options.should == ';binary'
330
+ jpeg_portraits.pattern.should == "\\xff\\xd8\\xff\\xe0*"
331
+ end
332
+
333
+ it "raises an ExpressionError if it can't parse a string literal" do
334
+ expect { Treequel::Filter::SubstringItemComponent.parse_from_string( 'whatev>=1' ) }.
335
+ to raise_error( Treequel::ExpressionError, /unable to parse/i )
336
+ end
337
+
338
+ end
339
+
340
+
341
+ describe Treequel::Filter::AndComponent do
342
+ it "stringifies as its filters ANDed together" do
343
+ Treequel::Filter::AndComponent.new( @filter1, @filter2 ).to_s.
344
+ should == '&(filter1)(filter2)'
345
+ end
346
+
347
+ it "allows a single filter" do
348
+ Treequel::Filter::AndComponent.new( @filter1 ).to_s.
349
+ should == '&(filter1)'
350
+ end
351
+ end
352
+
353
+ describe Treequel::Filter::OrComponent do
354
+ it "stringifies as its filters ORed together" do
355
+ Treequel::Filter::OrComponent.new( @filter1, @filter2 ).to_s.
356
+ should == '|(filter1)(filter2)'
357
+ end
358
+
359
+ it "allows a single filter" do
360
+ Treequel::Filter::OrComponent.new( @filter1 ).to_s.
361
+ should == '|(filter1)'
362
+ end
363
+
364
+ end
365
+
366
+ describe Treequel::Filter::NotComponent do
367
+ it "stringifies as the negation of its filter" do
368
+ Treequel::Filter::NotComponent.new( @filter1 ).to_s.
369
+ should == '!(filter1)'
370
+ end
371
+
372
+ it "can't be created with multiple filters" do
373
+ expect {
374
+ Treequel::Filter::NotComponent.new( @filter1, @filter2 )
375
+ }.to raise_error( ArgumentError, /2 for 1/i )
376
+ end
377
+ end
378
+ end
379
+
380
+ describe "support for Sequel expressions" do
381
+
382
+ before( :each ) do
383
+ pending "requires the 'sequel' library" unless Sequel.const_defined?( :Model )
384
+ end
385
+
386
+ it "supports the boolean expression syntax" do
387
+ pending( "inequality operators don't work under 1.9.1" ) if
388
+ vvec( RUBY_VERSION ) > vvec( '1.8.7' )
389
+ filter = Treequel::Filter.new( :uid >= 2000 )
390
+ filter.should be_a( Treequel::Filter )
391
+ filter.to_s.should == '(uid>=2000)'
392
+ end
393
+
394
+ it "supports Sequel expressions in ANDed subexpressions" do
395
+ pending( "inequality operators don't work under 1.9.1" ) if
396
+ vvec( RUBY_VERSION ) > vvec( '1.8.7' )
397
+ filter = Treequel::Filter.new( :and, [:uid >= 1024], [:uid <= 65535] )
398
+ filter.should be_a( Treequel::Filter )
399
+ filter.to_s.should == '(&(uid>=1024)(uid<=65535))'
400
+ end
401
+
402
+ it "advises user to use '>=' instead of '>' in expressions" do
403
+ pending( "inequality operators don't work under 1.9.1" ) if
404
+ vvec( RUBY_VERSION ) > vvec( '1.8.7' )
405
+ expect {
406
+ Treequel::Filter.new( :uid > 1024 )
407
+ }.to raise_error( Treequel::ExpressionError, /greater-than-or-equal/i )
408
+ end
409
+
410
+ it "advises user to use '<=' instead of '<' in expressions" do
411
+ pending( "inequality operators don't work under 1.9.1" ) if
412
+ vvec( RUBY_VERSION ) > vvec( '1.8.7' )
413
+ expect {
414
+ Treequel::Filter.new( :activated < Time.now )
415
+ }.to raise_error( Treequel::ExpressionError, /less-than-or-equal/i )
416
+ end
417
+
418
+ it "supports the 'LIKE' expression syntax with a single string argument" do
419
+ filter = Treequel::Filter.new( :cn.like('mar*n') )
420
+ filter.should be_a( Treequel::Filter )
421
+ filter.to_s.should == '(cn=mar*n)'
422
+ end
423
+
424
+ it "treats a LIKE expression with no asterisks as an 'approx' filter" do
425
+ filter = Treequel::Filter.new( :cn.like('maylin') )
426
+ filter.should be_a( Treequel::Filter )
427
+ filter.to_s.should == '(cn~=maylin)'
428
+ end
429
+
430
+ it "supports the 'LIKE' expression syntax with multiple string arguments" do
431
+ filter = Treequel::Filter.new( :cn.like('may*', 'mah*') )
432
+ filter.should be_a( Treequel::Filter )
433
+ filter.to_s.should == '(|(cn=may*)(cn=mah*))'
434
+ end
435
+
436
+ it "raises an exception when given a 'LIKE' expression with a regex argument" do
437
+ expect {
438
+ Treequel::Filter.new( :cn.like(/^ma.*/) )
439
+ }.to raise_error( Treequel::ExpressionError, /regex/i )
440
+ end
441
+
442
+ it "raises an exception when given a 'LIKE' expression with a regex argument with flags" do
443
+ expect {
444
+ Treequel::Filter.new( :cn.like(/^ma.*/i) )
445
+ }.to raise_error( Treequel::ExpressionError, /regex/i )
446
+ end
447
+
448
+ it "raises an exception when given a 'LIKE' expression with a mix of regex and string " +
449
+ "arguments" do
450
+ expect {
451
+ Treequel::Filter.new( :cn.like('maylin', /^mi.*/i) )
452
+ }.to raise_error( Treequel::ExpressionError, /regex/i )
453
+ end
454
+
455
+ it "supports negation of a 'exists' expression via the Sequel ~ syntax" do
456
+ filter = Treequel::Filter.new( ~:cn )
457
+ filter.should be_a( Treequel::Filter )
458
+ filter.to_s.should == '(!(cn=*))'
459
+ end
460
+
461
+ it "supports negation of a simple equality expression via the Sequel ~ syntax" do
462
+ filter = Treequel::Filter.new( ~{ :l => 'anoos' } )
463
+ filter.should be_a( Treequel::Filter )
464
+ filter.to_s.should == '(!(l=anoos))'
465
+ end
466
+
467
+ it "supports negation of an approximate-match expression via the Sequel ~ syntax" do
468
+ filter = Treequel::Filter.new( ~:cn.like('maylin') )
469
+ filter.should be_a( Treequel::Filter )
470
+ filter.to_s.should == '(!(cn~=maylin))'
471
+ end
472
+
473
+ it "supports negation of a matching expression via the Sequel ~ syntax" do
474
+ filter = Treequel::Filter.new( ~:cn.like('may*i*') )
475
+ filter.should be_a( Treequel::Filter )
476
+ filter.to_s.should == '(!(cn=may*i*))'
477
+ end
478
+ end
479
+ end
480
+
481
+
482
+ # vim: set nosta noet ts=4 sw=4: