and-son 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,4 +1 @@
1
1
  require "bundler/gem_tasks"
2
-
3
- require "assert/rake_tasks"
4
- Assert::RakeTasks.install
data/and-son.gemspec CHANGED
@@ -19,6 +19,6 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency("sanford-protocol", ["~>0.5"])
21
21
 
22
- gem.add_development_dependency("assert", ["~>1.0"])
22
+ gem.add_development_dependency("assert", ["~>2.0"])
23
23
  gem.add_development_dependency("assert-mocha", ["~>1.0"])
24
24
  end
@@ -1,3 +1,5 @@
1
+ require 'benchmark'
2
+ require 'logger'
1
3
  require 'ostruct'
2
4
  require 'sanford-protocol'
3
5
  require 'and-son/connection'
@@ -14,7 +16,7 @@ module AndSon
14
16
  # called on a client, but returns the chained instance if called on a runner
15
17
 
16
18
  def timeout(seconds)
17
- self.call_runner.tap{|r| r.timeout_value = seconds.to_f}
19
+ self.call_runner.tap{|r| r.timeout_value = seconds.to_f }
18
20
  end
19
21
 
20
22
  def params(hash = nil)
@@ -24,6 +26,10 @@ module AndSon
24
26
  self.call_runner.tap{|r| r.params_value.merge!(self.stringify_keys(hash)) }
25
27
  end
26
28
 
29
+ def logger(passed_logger)
30
+ self.call_runner.tap{|r| r.logger_value = passed_logger }
31
+ end
32
+
27
33
  protected
28
34
 
29
35
  def stringify_keys(hash)
@@ -55,13 +61,15 @@ module AndSon
55
61
  :version => version,
56
62
  :timeout_value => (ENV['ANDSON_TIMEOUT'] || DEFAULT_TIMEOUT).to_f,
57
63
  :params_value => {},
64
+ :logger_value => NullLogger.new,
58
65
  :responses => @responses,
59
66
  })
60
67
  end
61
68
  end
62
69
 
63
70
  class CallRunner < OpenStruct
64
- # {:host, :port, :version, :timeout_value, :params_value, :responses}
71
+ # { :host, :port, :version, :timeout_value, :params_value, :logger_value,
72
+ # :responses }
65
73
  include CallRunnerMethods
66
74
 
67
75
  # chain runner methods by returning itself
@@ -72,9 +80,13 @@ module AndSon
72
80
  if !params.kind_of?(Hash)
73
81
  raise ArgumentError, "expected params to be a Hash instead of a #{params.class}"
74
82
  end
75
- client_response = self.responses.find(name, params) if ENV['ANDSON_TEST_MODE']
76
- client_response ||= self.call!(name, params)
83
+ client_response = nil
84
+ benchmark = Benchmark.measure do
85
+ client_response = self.responses.find(name, params) if ENV['ANDSON_TEST_MODE']
86
+ client_response ||= self.call!(name, params)
87
+ end
77
88
 
89
+ self.logger_value.info("[AndSon] #{summary_line(name, params, benchmark, client_response)}")
78
90
  if block_given?
79
91
  yield client_response.protocol_response
80
92
  else
@@ -86,10 +98,64 @@ module AndSon
86
98
  call_params = self.params_value.merge(params)
87
99
  AndSon::Connection.new(host, port).open do |connection|
88
100
  connection.write(Sanford::Protocol::Request.new(version, name, call_params).to_hash)
89
- AndSon::Response.parse(connection.read(timeout_value))
101
+ if !connection.peek(timeout_value).empty?
102
+ AndSon::Response.parse(connection.read(timeout_value))
103
+ else
104
+ raise AndSon::ConnectionClosedError.new
105
+ end
90
106
  end
91
107
  end
92
108
 
109
+ protected
110
+
111
+ def summary_line(name, params, benchmark, client_response)
112
+ response = client_response.protocol_response
113
+ SummaryLine.new.tap do |line|
114
+ line.add 'host', "#{self.host}:#{self.port}"
115
+ line.add 'version', self.version
116
+ line.add 'service', name
117
+ line.add 'params', params
118
+ line.add 'status', response.code
119
+ line.add 'duration', self.round_time(benchmark.real)
120
+ end
121
+ end
122
+
123
+ ROUND_PRECISION = 2
124
+ ROUND_MODIFIER = 10 ** ROUND_PRECISION
125
+ def round_time(time_in_seconds)
126
+ (time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
127
+ end
128
+
129
+ end
130
+
131
+ class SummaryLine
132
+
133
+ def initialize
134
+ @hash = {}
135
+ end
136
+
137
+ def add(key, value)
138
+ @hash[key] = value.inspect if value
139
+ end
140
+
141
+ def to_s
142
+ [ 'host', 'version', 'service', 'status', 'duration', 'params' ].map do |key|
143
+ "#{key}=#{@hash[key]}" if @hash[key]
144
+ end.compact.join(" ")
145
+ end
146
+
147
+ end
148
+
149
+ class ConnectionClosedError < RuntimeError
150
+ def initialize
151
+ super "The server closed the connection, no response was written."
152
+ end
153
+ end
154
+
155
+ class NullLogger
156
+ ::Logger::Severity.constants.each do |name|
157
+ define_method(name.downcase){|*args| } # no-op
158
+ end
93
159
  end
94
160
 
95
161
  end
@@ -1,7 +1,5 @@
1
1
  require 'sanford-protocol'
2
2
 
3
- require 'and-son/exceptions'
4
-
5
3
  module AndSon
6
4
 
7
5
  class Response < Struct.new(:protocol_response)
@@ -41,4 +39,19 @@ module AndSon
41
39
 
42
40
  end
43
41
 
42
+ class RequestError < RuntimeError
43
+ attr_reader :response
44
+
45
+ def initialize(protocol_response)
46
+ super(protocol_response.status.message)
47
+ @response = protocol_response
48
+ end
49
+ end
50
+
51
+ ClientError = Class.new(RequestError)
52
+ BadRequestError = Class.new(ClientError)
53
+ NotFoundError = Class.new(ClientError)
54
+
55
+ ServerError = Class.new(RequestError)
56
+
44
57
  end
@@ -1,3 +1,3 @@
1
1
  module AndSon
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,8 +1,11 @@
1
1
  class FakeServer
2
2
 
3
- def initialize(port)
3
+ def initialize(port, options = nil)
4
+ options ||= {}
4
5
  @port = port
5
6
  @handlers = {}
7
+
8
+ @closing_server = !!options[:closing_server]
6
9
  end
7
10
 
8
11
  def add_handler(version, name, &block)
@@ -13,7 +16,13 @@ class FakeServer
13
16
  server = TCPServer.new("localhost", @port)
14
17
  socket = server.accept
15
18
 
16
- serve(socket)
19
+ if @closing_server
20
+ sleep 0.1 # ensure the connection isn't closed before a client can run
21
+ # IO.select
22
+ socket.close
23
+ else
24
+ serve(socket)
25
+ end
17
26
 
18
27
  server.close rescue false
19
28
  end
@@ -37,21 +46,22 @@ class FakeServer
37
46
 
38
47
  def run_fake_server(server, &block)
39
48
  begin
40
- pid = fork do
41
- trap("TERM"){ exit }
42
- server.run
43
- end
44
-
45
- sleep 0.3 # Give time for the socket to start listening.
49
+ thread = Thread.new{ server.run }
46
50
  yield
47
51
  ensure
48
- if pid
49
- Process.kill("TERM", pid)
50
- Process.wait(pid)
52
+ begin
53
+ TCPSocket.open("localhost", server.instance_variable_get("@port"))
54
+ rescue Exception
51
55
  end
56
+ thread.join
52
57
  end
53
58
  end
54
59
 
60
+ def start_closing_server(port, &block)
61
+ server = FakeServer.new(port, :closing_server => true)
62
+ run_fake_server(server, &block)
63
+ end
64
+
55
65
  end
56
66
 
57
67
  end
@@ -1,8 +1,11 @@
1
1
  require 'assert'
2
+ require 'and-son/stored_responses'
2
3
 
3
4
  class AndSon::Client
4
5
 
5
6
  class BaseTest < Assert::Context
7
+ include FakeServer::Helper
8
+
6
9
  desc "AndSon::Client"
7
10
  setup do
8
11
  @host, @port, @version = '0.0.0.0', 8000, "v1"
@@ -10,8 +13,8 @@ class AndSon::Client
10
13
  end
11
14
  subject{ @client }
12
15
 
13
- should have_imeths :host, :port, :version
14
- should have_imeths :call_runner, :call, :timeout
16
+ should have_imeths :host, :port, :version, :responses
17
+ should have_imeths :call_runner, :call, :timeout, :logger, :params
15
18
 
16
19
  should "know its default call runner" do
17
20
  default_runner = subject.call_runner
@@ -20,6 +23,7 @@ class AndSon::Client
20
23
  assert_equal @port, default_runner.port
21
24
  assert_equal @version, default_runner.version
22
25
  assert_equal 60.0, default_runner.timeout_value
26
+ assert_instance_of AndSon::NullLogger, default_runner.logger_value
23
27
  end
24
28
 
25
29
  should "override the default call runner timeout with an env var" do
@@ -48,6 +52,14 @@ class AndSon::Client
48
52
  assert_equal({ "api_key" => 12345 }, runner.params_value)
49
53
  end
50
54
 
55
+ should "return a CallRunner with a logger value set #logger" do
56
+ runner = subject.logger(logger = Logger.new(STDOUT))
57
+
58
+ assert_kind_of AndSon::CallRunner, runner
59
+ assert_respond_to :call, runner
60
+ assert_equal logger, runner.logger_value
61
+ end
62
+
51
63
  should "raise an ArgumentError when #params is not passed a Hash" do
52
64
  assert_raises(ArgumentError) do
53
65
  subject.params('test')
@@ -61,6 +73,21 @@ class AndSon::Client
61
73
  runner.call('something', 'test')
62
74
  end
63
75
  end
76
+
77
+ should "track its stored responses" do
78
+ assert_kind_of AndSon::StoredResponses, subject.responses
79
+ end
80
+
81
+ should "raise a ConnectionClosedError when the server closes the connection" do
82
+ self.start_closing_server(12001) do
83
+ client = AndSon::Client.new('localhost', 12001, 'v1')
84
+
85
+ assert_raises(AndSon::ConnectionClosedError) do
86
+ client.call('anything')
87
+ end
88
+ end
89
+ end
90
+
64
91
  end
65
92
 
66
93
  # the `call` method is tested in the file test/system/making_requests_test.rb,
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: and-son
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 1
10
- version: 0.2.1
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Collin Redding
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2013-02-05 00:00:00 Z
19
+ date: 2013-03-07 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  prerelease: false
@@ -40,11 +40,11 @@ dependencies:
40
40
  requirements:
41
41
  - - ~>
42
42
  - !ruby/object:Gem::Version
43
- hash: 15
43
+ hash: 3
44
44
  segments:
45
- - 1
45
+ - 2
46
46
  - 0
47
- version: "1.0"
47
+ version: "2.0"
48
48
  requirement: *id002
49
49
  name: assert
50
50
  type: :development
@@ -83,7 +83,6 @@ files:
83
83
  - lib/and-son.rb
84
84
  - lib/and-son/client.rb
85
85
  - lib/and-son/connection.rb
86
- - lib/and-son/exceptions.rb
87
86
  - lib/and-son/response.rb
88
87
  - lib/and-son/stored_responses.rb
89
88
  - lib/and-son/version.rb
@@ -1,19 +0,0 @@
1
- module AndSon
2
-
3
- class RequestError < RuntimeError
4
- attr_reader :response
5
-
6
- def initialize(protocol_response)
7
- super(protocol_response.status.message)
8
- @response = protocol_response
9
- end
10
- end
11
-
12
- ClientError = Class.new(RequestError)
13
-
14
- BadRequestError = Class.new(ClientError)
15
- NotFoundError = Class.new(ClientError)
16
-
17
- ServerError = Class.new(RequestError)
18
-
19
- end