and-son 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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