Skip to content

Commit db7bdf5

Browse files
committed
Merge pull request #133 from ruby-ldap/open-integration-tests
[CI] Add integration tests for Net::LDAP#open behavior
2 parents ea124f2 + 0536b42 commit db7bdf5

File tree

3 files changed

+114
-7
lines changed

3 files changed

+114
-7
lines changed

lib/net/ldap/connection.rb

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,45 @@ def close
111111
@conn = nil
112112
end
113113

114+
# Internal: Reads messages by ID from a queue, falling back to reading from
115+
# the connected socket until a message matching the ID is read. Any messages
116+
# with mismatched IDs gets queued for subsequent reads by the origin of that
117+
# message ID.
118+
#
119+
# Returns a Net::LDAP::PDU object or nil.
120+
def queued_read(message_id)
121+
if pdu = message_queue[message_id].shift
122+
return pdu
123+
end
124+
125+
# read messages until we have a match for the given message_id
126+
while pdu = read
127+
if pdu.message_id == message_id
128+
return pdu
129+
else
130+
message_queue[pdu.message_id].push pdu
131+
next
132+
end
133+
end
134+
135+
pdu
136+
end
137+
138+
# Internal: The internal queue of messages, read from the socket, grouped by
139+
# message ID.
140+
#
141+
# Used by `queued_read` to return messages sent by the server with the given
142+
# ID. If no messages are queued for that ID, `queued_read` will `read` from
143+
# the socket and queue messages that don't match the given ID for other
144+
# readers.
145+
#
146+
# Returns the message queue Hash.
147+
def message_queue
148+
@message_queue ||= Hash.new do |hash, key|
149+
hash[key] = []
150+
end
151+
end
152+
114153
# Internal: Reads and parses data from the configured connection.
115154
#
116155
# - syntax: the BER syntax to use to parse the read data with
@@ -146,9 +185,9 @@ def read(syntax = Net::LDAP::AsnSyntax)
146185
#
147186
# Returns the return value from writing to the connection, which in some
148187
# cases is the Integer number of bytes written to the socket.
149-
def write(request, controls = nil)
188+
def write(request, controls = nil, message_id = next_msgid)
150189
instrument "write.net_ldap_connection" do |payload|
151-
packet = [next_msgid.to_ber, request, controls].compact.to_ber_sequence
190+
packet = [message_id.to_ber, request, controls].compact.to_ber_sequence
152191
payload[:content_length] = @conn.write(packet)
153192
end
154193
end
@@ -375,7 +414,10 @@ def search(args = nil)
375414
result_pdu = nil
376415
n_results = 0
377416

417+
message_id = next_msgid
418+
378419
instrument "search.net_ldap_connection",
420+
message_id: message_id,
379421
filter: filter,
380422
base: base,
381423
scope: scope,
@@ -422,12 +464,12 @@ def search(args = nil)
422464
controls << ber_sort if ber_sort
423465
controls = controls.empty? ? nil : controls.to_ber_contextspecific(0)
424466

425-
write(request, controls)
467+
write(request, controls, message_id)
426468

427469
result_pdu = nil
428470
controls = []
429471

430-
while pdu = read
472+
while pdu = queued_read(message_id)
431473
case pdu.app_tag
432474
when Net::LDAP::PDU::SearchReturnedData
433475
n_results += 1
@@ -495,6 +537,16 @@ def search(args = nil)
495537

496538
result_pdu || OpenStruct.new(:status => :failure, :result_code => 1, :message => "Invalid search")
497539
end # instrument
540+
ensure
541+
# clean up message queue for this search
542+
messages = message_queue.delete(message_id)
543+
544+
# in the exceptional case some messages were *not* consumed from the queue,
545+
# instrument the event but do not fail.
546+
unless messages.empty?
547+
instrument "search_messages_unread.net_ldap_connection",
548+
message_id: message_id, messages: messages
549+
end
498550
end
499551

500552
MODIFY_OPERATIONS = { #:nodoc:

test/integration/test_open.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require_relative '../test_helper'
2+
3+
class TestBindIntegration < LDAPIntegrationTestCase
4+
def test_binds_without_open
5+
events = @service.subscribe "bind.net_ldap_connection"
6+
7+
@ldap.search(filter: "uid=user1", base: "ou=People,dc=rubyldap,dc=com", ignore_server_caps: true)
8+
@ldap.search(filter: "uid=user1", base: "ou=People,dc=rubyldap,dc=com", ignore_server_caps: true)
9+
10+
assert_equal 2, events.size
11+
end
12+
13+
def test_binds_with_open
14+
events = @service.subscribe "bind.net_ldap_connection"
15+
16+
@ldap.open do
17+
@ldap.search(filter: "uid=user1", base: "ou=People,dc=rubyldap,dc=com", ignore_server_caps: true)
18+
@ldap.search(filter: "uid=user1", base: "ou=People,dc=rubyldap,dc=com", ignore_server_caps: true)
19+
end
20+
21+
assert_equal 1, events.size
22+
end
23+
24+
def test_nested_search_without_open
25+
entries = []
26+
nested_entry = nil
27+
28+
@ldap.search(filter: "(|(uid=user1)(uid=user2))", base: "ou=People,dc=rubyldap,dc=com") do |entry|
29+
entries << entry.uid.first
30+
nested_entry ||= @ldap.search(filter: "uid=user3", base: "ou=People,dc=rubyldap,dc=com").first
31+
end
32+
33+
assert_equal "user3", nested_entry.uid.first
34+
assert_equal %w(user1 user2), entries
35+
end
36+
37+
def test_nested_search_with_open
38+
entries = []
39+
nested_entry = nil
40+
41+
@ldap.open do
42+
@ldap.search(filter: "(|(uid=user1)(uid=user2))", base: "ou=People,dc=rubyldap,dc=com") do |entry|
43+
entries << entry.uid.first
44+
nested_entry ||= @ldap.search(filter: "uid=user3", base: "ou=People,dc=rubyldap,dc=com").first
45+
end
46+
end
47+
48+
assert_equal "user3", nested_entry.uid.first
49+
assert_equal %w(user1 user2), entries
50+
end
51+
end

test/test_ldap_connection.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,20 +185,21 @@ def test_bind_net_ldap_connection_event
185185

186186
def test_search_net_ldap_connection_event
187187
# search data
188-
search_data_ber = Net::BER::BerIdentifiedArray.new([2, [
188+
search_data_ber = Net::BER::BerIdentifiedArray.new([1, [
189189
"uid=user1,ou=People,dc=rubyldap,dc=com",
190190
[ ["uid", ["user1"]] ]
191191
]])
192192
search_data_ber.ber_identifier = Net::LDAP::PDU::SearchReturnedData
193-
search_data = [2, search_data_ber]
193+
search_data = [1, search_data_ber]
194194
# search result (end of results)
195195
search_result_ber = Net::BER::BerIdentifiedArray.new([0, "", ""])
196196
search_result_ber.ber_identifier = Net::LDAP::PDU::SearchResult
197-
search_result = [2, search_result_ber]
197+
search_result = [1, search_result_ber]
198198
@tcp_socket.should_receive(:read_ber).and_return(search_data).
199199
and_return(search_result)
200200

201201
events = @service.subscribe "search.net_ldap_connection"
202+
unread = @service.subscribe "search_messages_unread.net_ldap_connection"
202203

203204
result = @connection.search(filter: "(uid=user1)", base: "ou=People,dc=rubyldap,dc=com")
204205
assert result.success?, "should be success"
@@ -209,5 +210,8 @@ def test_search_net_ldap_connection_event
209210
assert payload.has_key?(:filter)
210211
assert_equal "(uid=user1)", payload[:filter].to_s
211212
assert result
213+
214+
# ensure no unread
215+
assert unread.empty?, "should not have any leftover unread messages"
212216
end
213217
end

0 commit comments

Comments
 (0)