and-son 0.6.1 → 0.7.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.
@@ -0,0 +1,287 @@
1
+ require 'assert'
2
+ require 'and-son/call_runner'
3
+
4
+ require 'sanford-protocol/fake_connection'
5
+
6
+ class AndSon::CallRunner
7
+
8
+ class UnitTests < Assert::Context
9
+ desc "AndSon::CallRunner"
10
+ setup do
11
+ @host = Factory.string
12
+ @port = Factory.integer
13
+
14
+ @call_runner_class = AndSon::CallRunner
15
+ end
16
+ subject{ @call_runner_class }
17
+
18
+ should "include the call runner instance methods" do
19
+ assert_includes AndSon::CallRunner::InstanceMethods, subject
20
+ end
21
+
22
+ end
23
+
24
+ class InitTests < UnitTests
25
+ desc "when init"
26
+ setup do
27
+ @call_runner = @call_runner_class.new(@host, @port)
28
+ end
29
+ subject{ @call_runner }
30
+
31
+ should have_readers :host, :port
32
+ should have_accessors :params_value, :timeout_value, :logger_value
33
+ should have_imeths :call_runner
34
+
35
+ should "know its host and port" do
36
+ assert_equal @host, subject.host
37
+ assert_equal @port, subject.port
38
+ end
39
+
40
+ should "default its params, timeout and logger" do
41
+ assert_equal({}, subject.params_value)
42
+ assert_equal 60, subject.timeout_value
43
+ assert_instance_of NullLogger, subject.logger_value
44
+ end
45
+
46
+ should "return itself using `call_runner`" do
47
+ assert_same subject, subject.call_runner
48
+ end
49
+
50
+ should "be comparable" do
51
+ matching = @call_runner_class.new(@host, @port)
52
+ assert_equal matching, subject
53
+
54
+ not_matching = @call_runner_class.new(Factory.string, @port)
55
+ assert_not_equal not_matching, subject
56
+ not_matching = @call_runner_class.new(@host, Factory.integer)
57
+ assert_not_equal not_matching, subject
58
+ params = { Factory.string => Factory.string }
59
+ not_matching = @call_runner_class.new(@host, @port).params(params)
60
+ assert_not_equal not_matching, subject
61
+ not_matching = @call_runner_class.new(@host, @port).logger(Factory.string)
62
+ assert_not_equal not_matching, subject
63
+ not_matching = @call_runner_class.new(@host, @port).timeout(Factory.integer)
64
+ assert_not_equal not_matching, subject
65
+ end
66
+
67
+ should "be hash comparable" do
68
+ assert_equal subject.call_runner.hash, subject.hash
69
+
70
+ matching = @call_runner_class.new(@host, @port)
71
+ assert_true subject.eql?(matching)
72
+ not_matching = @call_runner_class.new(Factory.string, Factory.integer)
73
+ assert_false subject.eql?(not_matching)
74
+ end
75
+
76
+ end
77
+
78
+ class InitWithTimeoutEnvVarTests < UnitTests
79
+ desc "when init with the timeout env var set"
80
+ setup do
81
+ @current_timeout = ENV['ANDSON_TIMEOUT']
82
+ ENV['ANDSON_TIMEOUT'] = Factory.integer.to_s
83
+
84
+ @call_runner = @call_runner_class.new(@host, @port)
85
+ end
86
+ teardown do
87
+ ENV['ANDSON_TIMEOUT'] = @current_timeout
88
+ end
89
+ subject{ @call_runner }
90
+
91
+ should "set its timeout value using the env var" do
92
+ assert_equal ENV['ANDSON_TIMEOUT'].to_f, subject.timeout_value
93
+ end
94
+
95
+ end
96
+
97
+ class InstanceMethodsTests < InitTests
98
+
99
+ should have_imeths :timeout, :params, :logger
100
+
101
+ should "set its timeout value and return its call runner using `timeout`" do
102
+ timeout_value = Factory.integer
103
+ result = subject.timeout(timeout_value)
104
+ assert_equal timeout_value, subject.timeout_value
105
+ assert_same subject, result
106
+ end
107
+
108
+ should "update its params value and return its call runner using `params`" do
109
+ params_value = { Factory.string => Factory.string }
110
+ result = subject.params(params_value)
111
+ assert_equal params_value, subject.params_value
112
+ assert_same subject, result
113
+
114
+ new_key = Factory.string
115
+ new_value = Factory.string
116
+ subject.params({ new_key => new_value })
117
+ assert_equal 2, subject.params_value.keys.size
118
+ assert_equal new_value, subject.params_value[new_key]
119
+ end
120
+
121
+ should "stringify any values passed to `params`" do
122
+ key = Factory.string
123
+ value = Factory.string
124
+ subject.params({ key.to_sym => value })
125
+ assert_equal({ key => value }, subject.params_value)
126
+ end
127
+
128
+ should "raise an argument error when `params` is not passed a hash" do
129
+ assert_raises(ArgumentError){ subject.params(Factory.string) }
130
+ end
131
+
132
+ should "set its logger value and return its call runner using `logger`" do
133
+ logger_value = Factory.string
134
+ result = subject.logger(logger_value)
135
+ assert_equal logger_value, subject.logger_value
136
+ assert_same subject, result
137
+ end
138
+
139
+ end
140
+
141
+ class CallSetupTests < InitTests
142
+ setup do
143
+ @name = Factory.string
144
+ @params = { Factory.string => Factory.string }
145
+
146
+ @logger_spy = LoggerSpy.new
147
+ @call_runner.logger(@logger_spy)
148
+
149
+ @protocol_response = Sanford::Protocol::Response.new(
150
+ [200, Factory.string],
151
+ Factory.string
152
+ )
153
+ @response = AndSon::Response.new(@protocol_response)
154
+ end
155
+
156
+ end
157
+
158
+ class CallTests < CallSetupTests
159
+ desc "call method"
160
+ setup do
161
+ @call_bang_name = nil
162
+ @call_bang_params = nil
163
+ @call_bang_called = false
164
+ Assert.stub(@call_runner, :call!) do |name, params|
165
+ @call_bang_name = name
166
+ @call_bang_params = params
167
+ @call_bang_called = true
168
+ @response
169
+ end
170
+ end
171
+
172
+ should "call `call!`" do
173
+ assert_false @call_bang_called
174
+ subject.call(@name, @params)
175
+ assert_equal @name, @call_bang_name
176
+ assert_equal @params, @call_bang_params
177
+ assert_true @call_bang_called
178
+ end
179
+
180
+ should "return the response data when a block isn't provided" do
181
+ result = subject.call(@name, @params)
182
+ assert_equal @response.data, result
183
+ end
184
+
185
+ should "yield the protocol response when a block is provided" do
186
+ yielded = nil
187
+ subject.call(@name, @params){ |response| yielded = response }
188
+ assert_equal @protocol_response, yielded
189
+ end
190
+
191
+ should "default its params when they aren't provided" do
192
+ subject.call(@name)
193
+ assert_equal({}, @call_bang_params)
194
+ end
195
+
196
+ should "log a summary line of the call" do
197
+ subject.call(@name, @params)
198
+ assert_match /\A\[AndSon\]/, @logger_spy.output
199
+ assert_match /time=\d+.\d+/, @logger_spy.output
200
+ assert_match /status=#{@protocol_response.code}/, @logger_spy.output
201
+ host_and_port = "#{@host}:#{@port}"
202
+ assert_match /host=#{host_and_port.inspect}/, @logger_spy.output
203
+ assert_match /service=#{@name.inspect}/, @logger_spy.output
204
+ regex = Regexp.new(Regexp.escape("params=#{@params.inspect}"))
205
+ assert_match regex, @logger_spy.output
206
+ end
207
+
208
+ should "raise an argument error when not passed a hash for params" do
209
+ assert_raises(ArgumentError){ subject.call(@name, Factory.string) }
210
+ end
211
+
212
+ end
213
+
214
+ class CallBangTests < CallSetupTests
215
+ desc "the call! method"
216
+ setup do
217
+ @call_runner.params({ Factory.string => Factory.string })
218
+
219
+ @fake_connection = FakeConnection.new
220
+ @fake_connection.peek_data = Factory.string(1)
221
+ @fake_connection.read_data = @protocol_response.to_hash
222
+ Assert.stub(AndSon::Connection, :new).with(@host, @port){ @fake_connection }
223
+ end
224
+
225
+ should "open a connection, write a request and close the write stream" do
226
+ subject.call!(@name, @params)
227
+
228
+ protocol_connection = @fake_connection.protocol_connection
229
+ params = @call_runner.params_value.merge(@params)
230
+ expected = Sanford::Protocol::Request.new(@name, params).to_hash
231
+ assert_equal expected, protocol_connection.write_data
232
+ assert_true protocol_connection.closed_write
233
+ end
234
+
235
+ should "build a response from reading the server response on the connection" do
236
+ response = subject.call!(@name, @params)
237
+
238
+ protocol_connection = @fake_connection.protocol_connection
239
+ assert_equal @call_runner.timeout_value, protocol_connection.peek_timeout
240
+ assert_equal @call_runner.timeout_value, protocol_connection.read_timeout
241
+ assert_equal @response, response
242
+ end
243
+
244
+ should "raise a connection closed error if the server doesn't write a response" do
245
+ @fake_connection.peek_data = "" # simulate the server not writing a response
246
+ assert_raise(AndSon::ConnectionClosedError) do
247
+ subject.call!(@name, @params)
248
+ end
249
+ end
250
+
251
+ end
252
+
253
+ class FakeConnection
254
+ attr_reader :protocol_connection
255
+
256
+ def initialize
257
+ @protocol_connection = Sanford::Protocol::FakeConnection.new
258
+ end
259
+
260
+ def open
261
+ yield @protocol_connection if block_given?
262
+ ensure
263
+ @protocol_connection.close if @protocol_connection
264
+ end
265
+
266
+ def peek_data=(value)
267
+ @protocol_connection.peek_data = value
268
+ end
269
+
270
+ def read_data=(value)
271
+ @protocol_connection.read_data = value
272
+ end
273
+ end
274
+
275
+ class LoggerSpy
276
+ attr_reader :output
277
+
278
+ def initialize
279
+ @output = ""
280
+ end
281
+
282
+ def info(message)
283
+ @output += "#{message}\n"
284
+ end
285
+ end
286
+
287
+ end
@@ -1,135 +1,270 @@
1
1
  require 'assert'
2
2
  require 'and-son/client'
3
3
 
4
- require 'and-son/stored_responses'
5
- require 'test/support/fake_connection'
6
- require 'test/support/fake_server'
7
-
8
- class AndSon::Client
4
+ module AndSon::Client
9
5
 
10
6
  class UnitTests < Assert::Context
11
- include FakeServer::Helper
12
-
13
7
  desc "AndSon::Client"
14
8
  setup do
15
- @host, @port = '0.0.0.0', 8000
16
- @client = AndSon::Client.new(@host, @port)
9
+ @current_timeout = ENV['ANDSON_TEST_MODE']
10
+ ENV['ANDSON_TEST_MODE'] = 'yes'
11
+
12
+ @host = Factory.string
13
+ @port = Factory.integer
17
14
  end
18
- subject{ @client }
15
+ teardown do
16
+ ENV['ANDSON_TEST_MODE'] = @current_timeout
17
+ end
18
+ subject{ AndSon::Client }
19
19
 
20
- should have_imeths :host, :port, :responses
21
- should have_imeths :call_runner, :call, :timeout, :logger, :params
20
+ should have_imeths :new
22
21
 
23
- should "know its default call runner" do
24
- default_runner = subject.call_runner
22
+ should "return an and-son client using `new`" do
23
+ ENV.delete('ANDSON_TEST_MODE')
24
+ client = subject.new(@host, @port)
25
+ assert_instance_of AndSon::AndSonClient, client
26
+ end
25
27
 
26
- assert_equal @host, default_runner.host
27
- assert_equal @port, default_runner.port
28
- assert_equal 60.0, default_runner.timeout_value
29
- assert_instance_of AndSon::NullLogger, default_runner.logger_value
28
+ should "return a test client using `new` in test mode" do
29
+ client = subject.new(@host, @port)
30
+ assert_instance_of AndSon::TestClient, client
30
31
  end
31
32
 
32
- should "override the default call runner timeout with an env var" do
33
- prev = ENV['ANDSON_TIMEOUT']
34
- ENV['ANDSON_TIMEOUT'] = '20'
33
+ end
35
34
 
36
- assert_equal 20.0, subject.call_runner.timeout_value
35
+ class MixinTests < UnitTests
36
+ desc "as a mixin"
37
+ setup do
38
+ @client_class = Class.new do
39
+ include AndSon::Client
40
+ end
41
+ end
42
+ subject{ @client_class }
37
43
 
38
- ENV['ANDSON_TIMEOUT'] = prev
44
+ should "include the call runner instance methods" do
45
+ assert_includes AndSon::CallRunner::InstanceMethods, subject
39
46
  end
40
47
 
41
- should "return a CallRunner with a timeout value set #timeout" do
42
- runner = subject.timeout(10)
48
+ end
43
49
 
44
- assert_kind_of AndSon::CallRunner, runner
45
- assert_respond_to :call, runner
46
- assert_equal 10.0, runner.timeout_value
50
+ class InitTests < MixinTests
51
+ desc "when init"
52
+ setup do
53
+ @client = @client_class.new(@host, @port)
47
54
  end
55
+ subject{ @client }
48
56
 
49
- should "return a CallRunner with params_value set using #params and stringify " \
50
- "the params hash" do
51
- runner = subject.params({ :api_key => 12345 })
57
+ should have_readers :host, :port
52
58
 
53
- assert_kind_of AndSon::CallRunner, runner
54
- assert_respond_to :call, runner
55
- assert_equal({ "api_key" => 12345 }, runner.params_value)
59
+ should "know its host and port" do
60
+ assert_equal @host, subject.host
61
+ assert_equal @port, subject.port
56
62
  end
57
63
 
58
- should "return a CallRunner with a logger value set #logger" do
59
- runner = subject.logger(logger = Logger.new(STDOUT))
64
+ end
60
65
 
61
- assert_kind_of AndSon::CallRunner, runner
62
- assert_respond_to :call, runner
63
- assert_equal logger, runner.logger_value
66
+ class AndSonClientTests < UnitTests
67
+ desc "AndSonClient"
68
+ setup do
69
+ @client = AndSon::AndSonClient.new(@host, @port)
64
70
  end
71
+ subject{ @client }
65
72
 
66
- should "raise an ArgumentError when #params is not passed a Hash" do
67
- assert_raises(ArgumentError) do
68
- subject.params('test')
69
- end
73
+ should have_imeths :call, :call_runner
74
+
75
+ should "know its call runner" do
76
+ runner = subject.call_runner
77
+ assert_instance_of AndSon::CallRunner, runner
78
+ assert_equal subject.host, runner.host
79
+ assert_equal subject.port, runner.port
80
+ assert_not_same runner, subject.call_runner
81
+ end
82
+
83
+ should "be comparable" do
84
+ matching = AndSon::AndSonClient.new(@host, @port)
85
+ assert_equal matching, subject
86
+
87
+ not_matching = AndSon::AndSonClient.new(Factory.string, Factory.integer)
88
+ assert_not_equal not_matching, subject
70
89
  end
71
- should "track its stored responses" do
72
- assert_kind_of AndSon::StoredResponses, subject.responses
90
+
91
+ should "be hash comparable" do
92
+ assert_equal subject.call_runner.hash, subject.hash
93
+
94
+ matching = AndSon::AndSonClient.new(@host, @port)
95
+ assert_true subject.eql?(matching)
96
+ not_matching = AndSon::AndSonClient.new(Factory.string, Factory.integer)
97
+ assert_false subject.eql?(not_matching)
73
98
  end
74
99
 
75
100
  end
76
101
 
77
- class CallTest < UnitTests
78
- desc "call"
102
+ class AndSonClientCallTests < AndSonClientTests
103
+ desc "call method"
79
104
  setup do
80
- @connection = AndSon::Connection.new('localhost', 12001)
81
- @response = AndSon::Response.parse({ 'status' => [200] })
82
- @fake_connection = FakeConnection.new
83
- Assert.stub(AndSon::Connection, :new){ @connection }
105
+ @call_runner_spy = CallRunnerSpy.new
106
+ Assert.stub(AndSon::CallRunner, :new){ @call_runner_spy }
107
+
108
+ @service_name = Factory.string
109
+ @service_params = { Factory.string => Factory.string }
110
+ @response_block = proc{ Factory.string }
111
+
112
+ @response = subject.call(@service_name, @service_params, &@response_block)
84
113
  end
85
114
 
86
- should "write a request to the connection" do
87
- Assert.stub(@connection, :open) do |&block|
88
- block.call(@fake_connection)
89
- @response
90
- end
115
+ should "call `call` on its call runner and return its response" do
116
+ assert_equal [@service_name, @service_params], @call_runner_spy.call_args
117
+ assert_equal @response_block, @call_runner_spy.call_block
118
+ assert_equal @call_runner_spy.call_response, @response
119
+ end
120
+
121
+ end
91
122
 
92
- client = AndSon::Client.new('localhost', 12001).call('echo', {
93
- :message => 'test'
94
- })
123
+ class TestClientTests < UnitTests
124
+ desc "TestClient"
125
+ setup do
126
+ @name = Factory.string
127
+ @params = { Factory.string => Factory.string }
95
128
 
96
- request_data = @fake_connection.written.first
97
- assert_equal 'echo', request_data['name']
98
- assert_equal({ 'message' => 'test' }, request_data['params'])
129
+ @client = AndSon::TestClient.new(@host, @port)
130
+ data = Factory.string
131
+ @client.add_response(@name, @params){ data }
132
+ @response = @client.responses.get(@name, @params)
99
133
  end
134
+ subject{ @client }
100
135
 
101
- should "close the write stream" do
102
- Assert.stub(@connection, :open) do |&block|
103
- block.call(@fake_connection)
104
- @response
105
- end
136
+ should have_accessors :timeout_value, :params_value, :logger_value
137
+ should have_readers :calls, :responses
138
+ should have_imeths :add_response, :remove_response, :reset
139
+
140
+ should "default its params value" do
141
+ assert_equal({}, subject.params_value)
142
+ end
106
143
 
107
- client = AndSon::Client.new('localhost', 12001).call('echo', {
108
- :message => 'test'
109
- })
144
+ should "know its stored responses" do
145
+ assert_instance_of AndSon::StoredResponses, subject.responses
146
+ end
110
147
 
111
- assert @fake_connection.write_stream_closed?
148
+ should "know its call runner" do
149
+ subject
112
150
  end
113
151
 
114
- should "raise an ArgumentError when #call is not passed a Hash for params" do
115
- client = AndSon::Client.new('localhost', 12001)
116
- runner = client.timeout(0.1) # in case it actually tries to make the request
152
+ should "store each call made in its `calls`" do
153
+ assert_equal [], subject.calls
154
+ subject.call(@name, @params)
155
+ assert_equal 1, subject.calls.size
117
156
 
118
- assert_raises(ArgumentError) do
119
- runner.call('something', 'test')
120
- end
157
+ call = subject.calls.last
158
+ assert_instance_of AndSon::TestClient::Call, call
159
+ assert_equal @name, call.request_name
160
+ assert_equal @params, call.request_params
161
+ assert_equal @response.protocol_response, call.response
121
162
  end
122
163
 
123
- should "raise a ConnectionClosedError when the server closes the connection" do
124
- self.start_closing_server(12001) do
125
- client = AndSon::Client.new('localhost', 12001)
164
+ should "return a stored response using `call`" do
165
+ assert_equal @response.data, subject.call(@name, @params)
166
+ end
126
167
 
127
- assert_raises(AndSon::ConnectionClosedError) do
128
- client.call('anything')
129
- end
130
- end
168
+ should "yield a stored response using `call` with a block" do
169
+ yielded = nil
170
+ subject.call(@name, @params){ |response| yielded = response }
171
+ assert_equal @response.protocol_response, yielded
172
+ end
173
+
174
+ should "allow adding/removing stored responses" do
175
+ data = Factory.string
176
+ subject.add_response(@name, @params){ data }
177
+ response = subject.responses.get(@name, @params)
178
+ assert_equal data, response.data
179
+
180
+ subject.remove_response(@name, @params)
181
+ response = subject.responses.get(@name, @params)
182
+ assert_not_equal data, response.data
183
+ end
184
+
185
+ should "clear its calls and remove all its configured responses using `reset`" do
186
+ subject.call(@name, @params)
187
+ assert_not_equal [], subject.calls
188
+ assert_equal @response, subject.responses.get(@name, @params)
189
+
190
+ subject.reset
191
+ assert_equal [], subject.calls
192
+ assert_not_equal @response, subject.responses.get(@name, @params)
193
+ end
194
+
195
+ should "be comparable" do
196
+ matching = AndSon::TestClient.new(@host, @port)
197
+ assert_equal matching, subject
198
+
199
+ not_matching = AndSon::TestClient.new(Factory.string, @port)
200
+ assert_not_equal not_matching, subject
201
+ not_matching = AndSon::TestClient.new(@host, Factory.integer)
202
+ assert_not_equal not_matching, subject
203
+ params = { Factory.string => Factory.string }
204
+ not_matching = AndSon::TestClient.new(@host, @port).params(params)
205
+ assert_not_equal not_matching, subject
206
+ not_matching = AndSon::TestClient.new(@host, @port).logger(Factory.string)
207
+ assert_not_equal not_matching, subject
208
+ not_matching = AndSon::TestClient.new(@host, @port).timeout(Factory.integer)
209
+ assert_not_equal not_matching, subject
210
+ end
211
+
212
+ should "be hash comparable" do
213
+ assert_equal subject.call_runner.hash, subject.hash
214
+
215
+ matching = AndSon::TestClient.new(@host, @port)
216
+ assert_true subject.eql?(matching)
217
+ not_matching = AndSon::TestClient.new(Factory.string, Factory.integer)
218
+ assert_false subject.eql?(not_matching)
131
219
  end
132
220
 
133
221
  end
134
222
 
223
+ class TestClientInstanceMethodsTests < TestClientTests
224
+
225
+ should "set its timeout value and return its call runner using `timeout`" do
226
+ timeout_value = Factory.integer
227
+ result = subject.timeout(timeout_value)
228
+ assert_equal timeout_value, subject.timeout_value
229
+ assert_same subject, result
230
+ end
231
+
232
+ should "update its params value and return its call runner using `params`" do
233
+ params_value = { Factory.string => Factory.string }
234
+ result = subject.params(params_value)
235
+ assert_equal params_value, subject.params_value
236
+ assert_same subject, result
237
+
238
+ new_key = Factory.string
239
+ new_value = Factory.string
240
+ subject.params({ new_key => new_value })
241
+ assert_equal 2, subject.params_value.keys.size
242
+ assert_equal new_value, subject.params_value[new_key]
243
+ end
244
+
245
+ should "set its logger value and return its call runner using `logger`" do
246
+ logger_value = Factory.string
247
+ result = subject.logger(logger_value)
248
+ assert_equal logger_value, subject.logger_value
249
+ assert_same subject, result
250
+ end
251
+
252
+ end
253
+
254
+ class CallRunnerSpy
255
+ attr_reader :call_args, :call_block, :call_response
256
+
257
+ def initialize
258
+ @call_args = []
259
+ @call_block = nil
260
+ @call_response = Factory.string
261
+ end
262
+
263
+ def call(*args, &block)
264
+ @call_args = args
265
+ @call_block = block
266
+ @call_response
267
+ end
268
+ end
269
+
135
270
  end