treequel 1.0.1 → 1.0.4

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 (73) hide show
  1. data/ChangeLog +176 -14
  2. data/LICENSE +1 -1
  3. data/Rakefile +61 -45
  4. data/Rakefile.local +20 -0
  5. data/bin/treequel +502 -269
  6. data/examples/ldap-rack-auth.rb +2 -0
  7. data/lib/treequel.rb +221 -18
  8. data/lib/treequel/branch.rb +410 -201
  9. data/lib/treequel/branchcollection.rb +25 -13
  10. data/lib/treequel/branchset.rb +42 -40
  11. data/lib/treequel/constants.rb +233 -3
  12. data/lib/treequel/control.rb +95 -0
  13. data/lib/treequel/controls/contentsync.rb +138 -0
  14. data/lib/treequel/controls/pagedresults.rb +162 -0
  15. data/lib/treequel/controls/sortedresults.rb +216 -0
  16. data/lib/treequel/directory.rb +212 -65
  17. data/lib/treequel/exceptions.rb +11 -12
  18. data/lib/treequel/filter.rb +1 -12
  19. data/lib/treequel/mixins.rb +83 -47
  20. data/lib/treequel/monkeypatches.rb +29 -0
  21. data/lib/treequel/schema.rb +23 -19
  22. data/lib/treequel/schema/attributetype.rb +33 -3
  23. data/lib/treequel/schema/ldapsyntax.rb +0 -11
  24. data/lib/treequel/schema/matchingrule.rb +0 -11
  25. data/lib/treequel/schema/matchingruleuse.rb +0 -11
  26. data/lib/treequel/schema/objectclass.rb +36 -10
  27. data/lib/treequel/schema/table.rb +159 -0
  28. data/lib/treequel/sequel_integration.rb +7 -7
  29. data/lib/treequel/utils.rb +4 -66
  30. data/rake/documentation.rb +89 -0
  31. data/rake/helpers.rb +375 -307
  32. data/rake/hg.rb +16 -2
  33. data/rake/manual.rb +11 -6
  34. data/rake/packaging.rb +20 -35
  35. data/rake/publishing.rb +22 -62
  36. data/spec/lib/constants.rb +20 -0
  37. data/spec/lib/control_behavior.rb +44 -0
  38. data/spec/lib/matchers.rb +51 -0
  39. data/spec/treequel/branch_spec.rb +88 -29
  40. data/spec/treequel/branchcollection_spec.rb +24 -1
  41. data/spec/treequel/branchset_spec.rb +123 -51
  42. data/spec/treequel/control_spec.rb +48 -0
  43. data/spec/treequel/controls/contentsync_spec.rb +38 -0
  44. data/spec/treequel/controls/pagedresults_spec.rb +138 -0
  45. data/spec/treequel/controls/sortedresults_spec.rb +171 -0
  46. data/spec/treequel/directory_spec.rb +186 -16
  47. data/spec/treequel/mixins_spec.rb +42 -3
  48. data/spec/treequel/schema/attributetype_spec.rb +22 -20
  49. data/spec/treequel/schema/objectclass_spec.rb +67 -46
  50. data/spec/treequel/schema/table_spec.rb +134 -0
  51. data/spec/treequel_spec.rb +277 -15
  52. metadata +89 -108
  53. data/bin/treequel.orig +0 -963
  54. data/examples/ldap-monitor.rb +0 -143
  55. data/examples/ldap-monitor/public/css/master.css +0 -328
  56. data/examples/ldap-monitor/public/images/card_small.png +0 -0
  57. data/examples/ldap-monitor/public/images/chain_small.png +0 -0
  58. data/examples/ldap-monitor/public/images/globe_small.png +0 -0
  59. data/examples/ldap-monitor/public/images/globe_small_green.png +0 -0
  60. data/examples/ldap-monitor/public/images/plug.png +0 -0
  61. data/examples/ldap-monitor/public/images/shadows/large-30-down.png +0 -0
  62. data/examples/ldap-monitor/public/images/tick.png +0 -0
  63. data/examples/ldap-monitor/public/images/tick_circle.png +0 -0
  64. data/examples/ldap-monitor/public/images/treequel-favicon.png +0 -0
  65. data/examples/ldap-monitor/views/backends.erb +0 -41
  66. data/examples/ldap-monitor/views/connections.erb +0 -74
  67. data/examples/ldap-monitor/views/databases.erb +0 -39
  68. data/examples/ldap-monitor/views/dump_subsystem.erb +0 -14
  69. data/examples/ldap-monitor/views/index.erb +0 -14
  70. data/examples/ldap-monitor/views/layout.erb +0 -35
  71. data/examples/ldap-monitor/views/listeners.erb +0 -30
  72. data/rake/rdoc.rb +0 -30
  73. data/rake/win32.rb +0 -190
data/rake/hg.rb CHANGED
@@ -22,6 +22,8 @@ unless defined?( HG_DOTDIR )
22
22
  ###
23
23
 
24
24
  module MercurialHelpers
25
+ require './helpers.rb' unless defined?( RakefileHelpers )
26
+ include RakefileHelpers
25
27
 
26
28
  ###############
27
29
  module_function
@@ -83,7 +85,7 @@ unless defined?( HG_DOTDIR )
83
85
 
84
86
  ### Return the list of files which are of status 'unknown'
85
87
  def get_unknown_files
86
- list = read_command_output( 'hg', 'status', '-un', '--no-color' )
88
+ list = read_command_output( 'hg', 'status', '-un', '--color', 'never' )
87
89
  list = list.split( /\n/ )
88
90
 
89
91
  trace "New files: %p" % [ list ]
@@ -208,8 +210,20 @@ unless defined?( HG_DOTDIR )
208
210
  task :add => :newfiles
209
211
 
210
212
 
213
+ desc "Pull and update from the default repo"
214
+ task :pull do
215
+ paths = get_repo_paths()
216
+ if origin_url = paths['default']
217
+ ask_for_confirmation( "Pull and update from '#{origin_url}'?", false ) do
218
+ run 'hg', 'pull', '-u'
219
+ end
220
+ else
221
+ trace "Skipping pull: No 'default' path."
222
+ end
223
+ end
224
+
211
225
  desc "Check the current code in if tests pass"
212
- task :checkin => ['hg:newfiles', 'test', COMMIT_MSG_FILE] do
226
+ task :checkin => ['hg:pull', 'hg:newfiles', 'test', COMMIT_MSG_FILE] do
213
227
  targets = get_target_args()
214
228
  $stderr.puts '---', File.read( COMMIT_MSG_FILE ), '---'
215
229
  ask_for_confirmation( "Continue with checkin?" ) do
data/rake/manual.rb CHANGED
@@ -147,9 +147,14 @@ module Manual
147
147
 
148
148
  layout = self.config['layout'].sub( /\.page$/, '' )
149
149
  templatepath = @layouts_dir + "#{layout}.page"
150
- template = ERB.new( templatepath.read )
151
- page = self
150
+ template = nil
151
+ if Object.const_defined?( :Encoding )
152
+ template = ERB.new( templatepath.read(:encoding => 'UTF-8') )
153
+ else
154
+ template = ERB.new( templatepath.read )
155
+ end
152
156
 
157
+ page = self
153
158
  html = template.result( binding() )
154
159
 
155
160
  # Use Tidy to clean up the html if 'cleanup' is turned on, but remove the Tidy
@@ -565,7 +570,7 @@ module Manual
565
570
  manual_pages = setup_page_conversion_tasks( sourcedir, outputdir, catalog )
566
571
 
567
572
  desc "Build the manual"
568
- task :build => [ :rdoc, :copy_resources, :copy_apidocs, :generate_pages ]
573
+ task :build => [ :apidocs, :copy_resources, :copy_apidocs, :generate_pages ]
569
574
 
570
575
  task :clobber do
571
576
  RakeFileUtils.verbose( $verbose ) do
@@ -686,8 +691,8 @@ module Manual
686
691
  end
687
692
 
688
693
  desc "Copy API documentation to the manual output directory"
689
- task :copy_apidocs => :rdoc do
690
- cp_r( RDOCDIR, outputdir )
694
+ task :copy_apidocs => :apidocs do
695
+ cp_r( API_DOCSDIR, outputdir )
691
696
  end
692
697
 
693
698
  # Now group all the resource file tasks into a containing task
@@ -713,7 +718,7 @@ if MANUALDIR.exist?
713
718
 
714
719
  Manual::GenTask.new do |manual|
715
720
  manual.metadata.version = PKG_VERSION
716
- manual.metadata.api_dir = RDOCDIR
721
+ manual.metadata.api_dir = API_DOCSDIR
717
722
  manual.output_dir = MANUALOUTPUTDIR
718
723
  manual.base_dir = MANUALDIR
719
724
  manual.source_dir = 'src'
data/rake/packaging.rb CHANGED
@@ -1,17 +1,27 @@
1
- #
2
- # Packaging Rake Tasks
3
-
4
- #
1
+ #####################################################################
2
+ ### P A C K A G I N G T A S K S
3
+ #####################################################################
5
4
 
6
5
  require 'rbconfig'
7
6
  require 'pathname'
8
- require 'rake/packagetask'
9
- require 'rake/gempackagetask'
7
+ require 'rubygems/package_task'
10
8
 
11
- require Pathname( __FILE__ ).dirname.expand_path + 'hg.rb'
12
9
 
13
10
  include Config
14
11
 
12
+ ### Task: prerelease
13
+ desc "Append the package build number to package versions"
14
+ task :prerelease do
15
+ GEMSPEC.version.version << ".#{PKG_BUILD}"
16
+ Rake::Task[:gem].clear
17
+
18
+ Gem::PackageTask.new( GEMSPEC ) do |pkg|
19
+ pkg.need_zip = true
20
+ pkg.need_tar = true
21
+ end
22
+ end
23
+
24
+
15
25
  ### Task: gem
16
26
  ### Task: package
17
27
  Rake::PackageTask.new( PKG_NAME, PKG_VERSION ) do |task|
@@ -24,34 +34,9 @@ end
24
34
  task :package => [:gem]
25
35
 
26
36
 
27
- ### Task: gem
28
- gempath = PKGDIR + GEM_FILE_NAME
29
-
30
- desc "Build a RubyGem package (#{GEM_FILE_NAME})"
31
- task :gem => gempath.to_s
32
- file gempath.to_s => [PKGDIR.to_s] + GEMSPEC.files do
33
- when_writing( "Creating GEM" ) do
34
- Gem::Builder.new( GEMSPEC ).build
35
- verbose( true ) do
36
- mv GEM_FILE_NAME, gempath
37
- end
38
- end
39
- end
40
-
41
-
42
- prerelease_gempath = PKGDIR + SNAPSHOT_GEM_NAME
43
-
44
- desc "Build a pre-release RubyGem package"
45
- task :prerelease_gem => prerelease_gempath.to_s
46
- file prerelease_gempath.to_s => [PKGDIR.to_s] + GEMSPEC.files do
47
- when_writing( "Creating prerelease GEM" ) do
48
- gemspec = GEMSPEC.clone
49
- gemspec.version = Gem::Version.create( "%s.%s" % [GEMSPEC.version, PKG_BUILD] )
50
- Gem::Builder.new( gemspec ).build
51
- verbose( true ) do
52
- mv SNAPSHOT_GEM_NAME, prerelease_gempath
53
- end
54
- end
37
+ Gem::PackageTask.new( GEMSPEC ) do |pkg|
38
+ pkg.need_zip = true
39
+ pkg.need_tar = true
55
40
  end
56
41
 
57
42
 
data/rake/publishing.rb CHANGED
@@ -34,7 +34,13 @@ class Net::SMTP
34
34
 
35
35
  def do_ssl_start( helodomain, user, secret, authtype )
36
36
  raise IOError, 'SMTP session already started' if @started
37
- check_auth_args user, secret if user or secret
37
+ if user or secret
38
+ if self.method( :check_auth_args ).arity == 3
39
+ check_auth_args( user, secret, authtype )
40
+ else
41
+ check_auth_args( user, secret )
42
+ end
43
+ end
38
44
 
39
45
  # Open the connection
40
46
  @debug_output << "opening connection to #{@address}...\n" if @debug_output
@@ -85,7 +91,6 @@ begin
85
91
  require 'tmail'
86
92
  require 'net/smtp'
87
93
  require 'etc'
88
- require 'rubyforge'
89
94
  require 'socket'
90
95
  require 'text/format'
91
96
 
@@ -134,11 +139,11 @@ begin
134
139
  task :project => :upload # the old name
135
140
 
136
141
  desc "Publish the project docs to #{PROJECT_HOST}"
137
- task :upload_docs => [ :rdoc ] do
142
+ task :upload_docs => [ :apidocs ] do
138
143
  when_writing( "Publishing docs to #{PROJECT_SCPDOCURL}" ) do
139
144
  log "Uploading API documentation to %s:%s" % [ PROJECT_HOST, PROJECT_DOCDIR ]
140
145
  run 'ssh', PROJECT_HOST, "rm -rf #{PROJECT_DOCDIR}"
141
- run 'scp', '-qCr', RDOCDIR, PROJECT_SCPDOCURL
146
+ run 'scp', '-qCr', API_DOCSDIR, PROJECT_SCPDOCURL
142
147
  end
143
148
  end
144
149
 
@@ -195,15 +200,20 @@ begin
195
200
  desc 'Send out a release announcement'
196
201
  task :announce => [RELEASE_ANNOUNCE_FILE] do
197
202
  email = TMail::Mail.new
198
- if $publish_privately
203
+
204
+ if $publish_privately || RELEASE_ANNOUNCE_ADDRESSES.empty?
199
205
  trace "Sending private announce mail"
200
206
  email.to = 'rubymage@gmail.com'
201
207
  else
202
208
  trace "Sending public announce mail"
203
- email.to = 'Ruby-Talk List <ruby-talk@ruby-lang.org>'
209
+ email.to = RELEASE_ANNOUNCE_ADDRESSES
204
210
  email.bcc = 'rubymage@gmail.com'
205
211
  end
206
- email.from = GEMSPEC.email
212
+
213
+ from = prompt_with_default( "Send announcement as:",
214
+ 'Michael Granger <ged@FaerieMUD.org>' ) or fail
215
+
216
+ email.from = from
207
217
  email.subject = "[ANN] #{PKG_NAME} #{PKG_VERSION}"
208
218
  email.body = File.read( RELEASE_ANNOUNCE_FILE )
209
219
  email.date = Time.new
@@ -236,61 +246,11 @@ begin
236
246
  end
237
247
 
238
248
 
239
- desc 'Publish the new release to RubyForge'
240
- task :publish => [:clean, :package, :notes] do |task|
241
- project = RUBYFORGE_PROJECT
242
-
243
- if $publish_privately
244
- log "Skipping push of release files to RubyForge"
245
- else
246
- rf = RubyForge.new
247
- log "Loading RubyForge config"
248
- rf.configure
249
-
250
- group_id = rf.autoconfig['group_ids'][RUBYFORGE_GROUP] or
251
- fail "Your configuration doesn't have a group id for '#{RUBYFORGE_GROUP}'"
252
-
253
- # If this project doesn't yet exist, create it
254
- unless rf.autoconfig['package_ids'].key?( project )
255
- ask_for_confirmation( "Package '#{project}' doesn't exist on RubyForge. Create it?" ) do
256
- log "Creating new package '#{project}'"
257
- rf.create_package( group_id, project )
258
- end
259
- end
260
-
261
- package_id = rf.autoconfig['package_ids'][ project ]
262
-
263
- # Make sure this release doesn't already exist
264
- releases = rf.autoconfig['release_ids']
265
- if releases.key?( GEMSPEC.name ) && releases[ GEMSPEC.name ].key?( PKG_VERSION )
266
- log "Rubyforge seems to already have #{ PKG_FILE_NAME }"
267
- else
268
- config = rf.userconfig or
269
- fail "You apparently haven't set up your RubyForge credentials on this machine."
270
- config['release_notes'] = GEMSPEC.description
271
- config['release_changes'] = File.read( RELEASE_NOTES_FILE )
272
-
273
- files = FileList[ PKGDIR + GEM_FILE_NAME ]
274
- files.include PKGDIR + "#{PKG_FILE_NAME}.tar.gz"
275
- files.include PKGDIR + "#{PKG_FILE_NAME}.tar.bz2"
276
- files.include PKGDIR + "#{PKG_FILE_NAME}.zip"
277
-
278
- log "Releasing #{PKG_FILE_NAME}"
279
- when_writing do
280
- log "Publishing to RubyForge: \n",
281
- "\tproject: #{RUBYFORGE_GROUP}\n",
282
- "\tpackage: #{PKG_NAME.downcase}\n",
283
- "\tpackage version: #{PKG_VERSION}\n",
284
- "\tfiles: " + files.collect {|f| f.to_s }.join(', ') + "\n"
285
-
286
- ask_for_confirmation( "Publish to RubyForge?" ) do
287
- log 'Logging in...'
288
- rf.login
289
- log "Adding the new release to the '#{project}' project"
290
- rf.add_release( group_id, package_id, PKG_VERSION, *files )
291
- end
292
- end
293
- end
249
+ desc 'Publish the new release to Gemcutter'
250
+ task :publish => [:clean, :gem, :notes] do |task|
251
+ ask_for_confirmation( "Publish #{GEM_FILE_NAME} to Gemcutter?", false ) do
252
+ gempath = PKGDIR + GEM_FILE_NAME
253
+ sh 'gem', 'push', gempath
294
254
  end
295
255
  end
296
256
  end
@@ -68,6 +68,11 @@ module Treequel::TestConstants # :nodoc:all
68
68
  TEST_SUBHOSTS_RDN = "#{TEST_HOSTS_DN_ATTR}=#{TEST_HOSTS_DN_VALUE}"
69
69
  TEST_SUBHOSTS_DN = "#{TEST_HOSTS_RDN},#{TEST_SUBDOMAIN_DN}"
70
70
 
71
+ TEST_SUBHOST_DN_ATTR = 'cn'
72
+ TEST_SUBHOST_DN_VALUE = 'ronky'
73
+ TEST_SUBHOST_RDN = "#{TEST_SUBHOST_DN_ATTR}=#{TEST_SUBHOST_DN_VALUE}"
74
+ TEST_SUBHOST_DN = "#{TEST_SUBHOST_RDN},#{TEST_SUBHOSTS_DN}"
75
+
71
76
  TEST_PEOPLE_DN_ATTR = 'ou'
72
77
  TEST_PEOPLE_DN_VALUE = 'People'
73
78
  TEST_PEOPLE_RDN = "#{TEST_PEOPLE_DN_ATTR}=#{TEST_PEOPLE_DN_VALUE}"
@@ -83,6 +88,21 @@ module Treequel::TestConstants # :nodoc:all
83
88
  TEST_PERSON2_RDN = "#{TEST_PERSON2_DN_ATTR}=#{TEST_PERSON2_DN_VALUE}"
84
89
  TEST_PERSON2_DN = "#{TEST_PERSON2_RDN},#{TEST_PEOPLE_DN}"
85
90
 
91
+ TEST_PHONES_DN_ATTR = 'ou'
92
+ TEST_PHONES_DN_VALUE = 'Phones'
93
+ TEST_PHONES_RDN = "#{TEST_PHONES_DN_ATTR}=#{TEST_PHONES_DN_VALUE}"
94
+ TEST_PHONES_DN = "#{TEST_PHONES_RDN},#{TEST_BASE_DN}"
95
+
96
+ TEST_ROOMS_DN_ATTR = 'ou'
97
+ TEST_ROOMS_DN_VALUE = 'Rooms'
98
+ TEST_ROOMS_RDN = "#{TEST_ROOMS_DN_ATTR}=#{TEST_ROOMS_DN_VALUE}"
99
+ TEST_ROOMS_DN = "#{TEST_ROOMS_RDN},#{TEST_BASE_DN}"
100
+
101
+ TEST_ROOM_DN_ATTR = 'cn'
102
+ TEST_ROOM_DN_VALUE = 'broomcloset'
103
+ TEST_ROOM_RDN = "#{TEST_ROOM_DN_ATTR}=#{TEST_ROOM_DN_VALUE}"
104
+ TEST_ROOM_DN = "#{TEST_ROOM_RDN},#{TEST_ROOMS_DN}"
105
+
86
106
  constants.each do |cname|
87
107
  const_get(cname).freeze
88
108
  end
@@ -0,0 +1,44 @@
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 'spec'
13
+ require 'spec/lib/constants'
14
+ require 'spec/lib/helpers'
15
+
16
+ require 'treequel'
17
+ require 'treequel/control'
18
+
19
+ include Treequel::TestConstants
20
+ include Treequel::Constants
21
+
22
+ #####################################################################
23
+ ### C O N T E X T S
24
+ #####################################################################
25
+ describe "A Treequel::Control", :shared => true do
26
+ include Treequel::SpecHelpers
27
+
28
+ before( :each ) do
29
+ raise "Spec doesn't set @control before the Control shared behavior" unless @control
30
+ end
31
+
32
+ it "implements one of either #get_client_controls or #get_server_controls" do
33
+ methods = [
34
+ 'get_client_controls', # 1.8.x
35
+ 'get_server_controls',
36
+ :get_client_controls, # 1.9.x
37
+ :get_server_controls
38
+ ]
39
+ (@control.instance_methods( false ) | methods).should_not be_empty()
40
+ end
41
+
42
+ end
43
+
44
+
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ basedir = Pathname.new( __FILE__ ).dirname.parent
7
+
8
+ libdir = basedir + "lib"
9
+
10
+ $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
11
+ }
12
+
13
+ require 'yaml'
14
+ require 'treequel'
15
+
16
+ require 'spec/lib/constants'
17
+
18
+ ### RSpec matchers
19
+ module Treequel::Matchers
20
+
21
+ class ArrayIncludingMatcher
22
+ def initialize( expected )
23
+ @expected = expected
24
+ end
25
+
26
+ def ==( actual )
27
+ @expected.each do |value|
28
+ return false unless actual.include?( value )
29
+ end
30
+ true
31
+ rescue NoMethodError => ex
32
+ return false
33
+ end
34
+
35
+ def description
36
+ "array_including(#{ @expected.inspect.sub(/^\[|\]$/,"") })"
37
+ end
38
+ end
39
+
40
+
41
+ ###############
42
+ module_function
43
+ ###############
44
+
45
+ ### Return true if the actual value includes the specified +objects+.
46
+ def array_including( *objects )
47
+ ArrayIncludingMatcher.new( objects )
48
+ end
49
+
50
+ end # module Treequel::Matchers
51
+
@@ -9,22 +9,14 @@ BEGIN {
9
9
  $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
10
10
  }
11
11
 
12
- begin
13
- require 'spec'
14
- require 'spec/lib/constants'
15
- require 'spec/lib/helpers'
16
-
17
- require 'treequel/branch'
18
- require 'treequel/branchset'
19
- require 'treequel/branchcollection'
20
- rescue LoadError
21
- unless Object.const_defined?( :Gem )
22
- require 'rubygems'
23
- retry
24
- end
25
- raise
26
- end
12
+ require 'spec'
13
+ require 'spec/lib/constants'
14
+ require 'spec/lib/helpers'
15
+ require 'spec/lib/matchers'
27
16
 
17
+ require 'treequel/branch'
18
+ require 'treequel/branchset'
19
+ require 'treequel/branchcollection'
28
20
 
29
21
  include Treequel::TestConstants
30
22
  include Treequel::Constants
@@ -34,7 +26,8 @@ include Treequel::Constants
34
26
  #####################################################################
35
27
 
36
28
  describe Treequel::Branch do
37
- include Treequel::SpecHelpers
29
+ include Treequel::SpecHelpers,
30
+ Treequel::Matchers
38
31
 
39
32
  before( :all ) do
40
33
  setup_logging( :fatal )
@@ -150,6 +143,22 @@ describe Treequel::Branch do
150
143
  @branch.entry.should == :the_extended_entry
151
144
  end
152
145
 
146
+ it "can search its directory for values using itself as a base" do
147
+ @directory.should_receive( :search ).with( @branch, :one, '(objectClass=*)', {} ).
148
+ and_return( :entries )
149
+ @branch.search( :one, '(objectClass=*)' ).should == :entries
150
+ end
151
+
152
+ it "can search its directory for values with a block" do
153
+ @directory.should_receive( :search ).with( @branch, :one, '(objectClass=*)', {} ).
154
+ and_yield( :an_entry )
155
+ yielded_val = nil
156
+ @branch.search( :one, '(objectClass=*)' ) do |val|
157
+ yielded_val = val
158
+ end
159
+ yielded_val.should == :an_entry
160
+ end
161
+
153
162
  it "clears any cached values if its include_operational_attrs attribute is changed" do
154
163
  @directory.should_receive( :get_entry ).with( @branch ).exactly( :once ).
155
164
  and_return( :the_entry )
@@ -201,13 +210,20 @@ describe Treequel::Branch do
201
210
 
202
211
  lambda {
203
212
  @branch.facelart( 'sbc' )
204
- }.should raise_error( NoMethodError )
213
+ }.should raise_exception( NoMethodError, /undefined method.*facelart/i )
205
214
  end
206
215
 
216
+ it "don't create sub-branches for multi-value RDNs with an invalid attribute" do
217
+ @schema.should_receive( :attribute_types ).
218
+ and_return({ :cn => :a_value, :ou => :a_value })
219
+
220
+ lambda {
221
+ @branch.cn( 'benchlicker', :facelart => 'sbc' )
222
+ }.should raise_exception( NoMethodError, /invalid secondary attribute.*facelart/i )
223
+ end
207
224
 
208
225
  it "can return all of its immediate children as Branches" do
209
- @directory.should_receive( :search ).
210
- with( @branch, :one, '(objectClass=*)' ).
226
+ @directory.should_receive( :search ).with( @branch, :one, '(objectClass=*)', {} ).
211
227
  and_return([ :the_children ])
212
228
  @branch.children.should == [ :the_children ]
213
229
  end
@@ -350,13 +366,14 @@ describe Treequel::Branch do
350
366
  end
351
367
 
352
368
  it "knows if a attribute is valid given its objectClasses" do
353
- attrs = mock( "Attribute list", :null_object => true )
369
+ attrtype = mock( "attribute type object" )
354
370
 
355
371
  @branch.should_receive( :valid_attribute_types ).
356
372
  twice.
357
- and_return([ attrs ])
373
+ and_return([ attrtype ])
358
374
 
359
- attrs.should_receive( :names ).twice.and_return([ :cn, :l, :uid ])
375
+ attrtype.should_receive( :valid_name? ).with( :uid ).and_return( true )
376
+ attrtype.should_receive( :valid_name? ).with( :rubberChicken ).and_return( false )
360
377
 
361
378
  @branch.valid_attribute?( :uid ).should be_true()
362
379
  @branch.valid_attribute?( :rubberChicken ).should be_false()
@@ -364,7 +381,7 @@ describe Treequel::Branch do
364
381
 
365
382
  it "can be moved to a new location within the directory" do
366
383
  newdn = "ou=hosts,dc=admin,#{TEST_BASE_DN}"
367
- @directory.should_receive( :move ).with( @branch, newdn, {} )
384
+ @directory.should_receive( :move ).with( @branch, newdn )
368
385
  @branch.move( newdn )
369
386
  end
370
387
 
@@ -379,12 +396,6 @@ describe Treequel::Branch do
379
396
  end
380
397
 
381
398
 
382
- it "can be deleted from the directory" do
383
- @directory.should_receive( :delete ).with( @branch )
384
- @branch.delete
385
- end
386
-
387
-
388
399
  it "can create children under itself" do
389
400
  newattrs = {
390
401
  :ipHostNumber => '127.0.0.1',
@@ -436,6 +447,46 @@ describe Treequel::Branch do
436
447
  end
437
448
 
438
449
 
450
+ it "can delete all values of one of its entry's individual attributes" do
451
+ LDAP::Mod.should_receive( :new ).with( LDAP::LDAP_MOD_DELETE, 'displayName', [] ).
452
+ and_return( :mod_delete )
453
+ @directory.should_receive( :modify ).with( @branch, [:mod_delete] )
454
+
455
+ @branch.delete( :displayName )
456
+ end
457
+
458
+ it "can delete all values of more than one of its entry's individual attributes" do
459
+ LDAP::Mod.should_receive( :new ).with( LDAP::LDAP_MOD_DELETE, 'displayName', [] ).
460
+ and_return( :first_mod_delete )
461
+ LDAP::Mod.should_receive( :new ).with( LDAP::LDAP_MOD_DELETE, 'gecos', [] ).
462
+ and_return( :second_mod_delete )
463
+ @directory.should_receive( :modify ).
464
+ with( @branch, [:first_mod_delete, :second_mod_delete] )
465
+
466
+ @branch.delete( :displayName, :gecos )
467
+ end
468
+
469
+ it "can delete one particular value of its entry's individual attributes" do
470
+ LDAP::Mod.should_receive( :new ).
471
+ with( LDAP::LDAP_MOD_DELETE, 'objectClass', ['apple-user'] ).
472
+ and_return( :mod_delete )
473
+ @directory.should_receive( :modify ).with( @branch, [:mod_delete] )
474
+
475
+ @branch.delete( :objectClass => 'apple-user' )
476
+ end
477
+
478
+ it "can delete particular values of more than one of its entry's individual attributes" do
479
+ LDAP::Mod.should_receive( :new ).
480
+ with( LDAP::LDAP_MOD_DELETE, 'objectClass', ['apple-user', 'inetOrgPerson'] ).
481
+ and_return( :first_mod_delete )
482
+ LDAP::Mod.should_receive( :new ).
483
+ with( LDAP::LDAP_MOD_DELETE, 'cn', [] ).and_return( :second_mod_delete )
484
+ @directory.should_receive( :modify ).
485
+ with( @branch, array_including(:first_mod_delete, :second_mod_delete) )
486
+
487
+ @branch.delete( :objectClass => ['apple-user',:inetOrgPerson], :cn => [] )
488
+ end
489
+
439
490
  it "knows how to represent its DN as an RFC1781-style UFN" do
440
491
  @branch.to_ufn.should =~ /Hosts, acme\.com/i
441
492
  end
@@ -561,6 +612,14 @@ describe Treequel::Branch do
561
612
  end
562
613
  end
563
614
 
615
+ it "can fetch multiple values via #values_at" do
616
+ @branch.should_receive( :[] ).with( :cn ).and_return( :cn_value )
617
+ @branch.should_receive( :[] ).with( :desc ).and_return( :desc_value )
618
+ @branch.should_receive( :[] ).with( :l ).and_return( :l_value )
619
+
620
+ @branch.values_at( :cn, :desc, :l ).should == [ :cn_value, :desc_value, :l_value ]
621
+ end
622
+
564
623
  end
565
624
 
566
625
  end