sanford 0.1.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,72 @@
1
+ require 'logger'
2
+
3
+ class DummyHost
4
+ include Sanford::Host
5
+
6
+ ip 'localhost'
7
+ port 12000
8
+ pid_dir File.expand_path('../../../tmp/', __FILE__)
9
+
10
+ logger = Logger.new(File.expand_path("../../../log/test.log", __FILE__))
11
+ logger.level = Logger::DEBUG
12
+
13
+ version 'v1' do
14
+ service_handler_ns 'DummyHost'
15
+
16
+ service 'echo', 'Echo'
17
+ service 'bad', 'Bad'
18
+ service 'multiply', 'Multiply'
19
+ service 'halt_it', '::DummyHost::HaltIt'
20
+ service 'authorized', 'Authorized'
21
+ end
22
+
23
+ class Echo
24
+ include Sanford::ServiceHandler
25
+
26
+ def run!
27
+ params['message']
28
+ end
29
+
30
+ end
31
+
32
+ class Bad
33
+ include Sanford::ServiceHandler
34
+
35
+ def run!
36
+ raise "hahaha"
37
+ end
38
+ end
39
+
40
+ class Multiply
41
+ include Sanford::ServiceHandler
42
+
43
+ def init!
44
+ @number = params['number'] || 1
45
+ end
46
+
47
+ def run!
48
+ @number * 2
49
+ end
50
+ end
51
+
52
+ class HaltIt
53
+ include Sanford::ServiceHandler
54
+
55
+ def run!
56
+ halt 728, {
57
+ :message => "I do what I want",
58
+ :data => [ 1, true, 'yes' ]
59
+ }
60
+ end
61
+ end
62
+
63
+ class Authorized
64
+ include Sanford::ServiceHandler
65
+
66
+ def before_run
67
+ halt 401, :message => "Not authorized"
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,55 @@
1
+ require 'sanford-protocol/test/fake_socket'
2
+
3
+ class SimpleClient
4
+
5
+ def self.call_with_request(service_host, version, name, params)
6
+ self.new(service_host).call_with_request(version, name, params)
7
+ end
8
+
9
+ def self.call_with_msg_body(service_host, *args)
10
+ self.new(service_host).call_with_msg_body(*args)
11
+ end
12
+
13
+ def self.call_with_encoded_msg_body(service_host, *args)
14
+ self.new(service_host).call_with_encoded_msg_body(*args)
15
+ end
16
+
17
+ def self.call_with(service_host, bytes)
18
+ self.new(service_host).call(bytes)
19
+ end
20
+
21
+ def initialize(service_host, options = {})
22
+ @host, @port = service_host.ip, service_host.port
23
+ @delay = options[:with_delay]
24
+ end
25
+
26
+ def call_with_request(*args)
27
+ self.call_using_fake_socket(:with_request, *args)
28
+ end
29
+
30
+ def call_with_msg_body(*args)
31
+ self.call_using_fake_socket(:with_msg_body, *args)
32
+ end
33
+
34
+ def call_with_encoded_msg_body(*args)
35
+ self.call_using_fake_socket(:with_encoded_msg_body, *args)
36
+ end
37
+
38
+ def call(bytes)
39
+ socket = TCPSocket.new(@host, @port)
40
+ socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
41
+ connection = Sanford::Protocol::Connection.new(socket)
42
+ sleep(@delay) if @delay
43
+ socket.send(bytes, 0)
44
+ Sanford::Protocol::Response.parse(connection.read)
45
+ ensure
46
+ socket.close rescue false
47
+ end
48
+
49
+ protected
50
+
51
+ def call_using_fake_socket(method, *args)
52
+ self.call(Sanford::Protocol::Test::FakeSocket.send(method, *args).in)
53
+ end
54
+
55
+ end
@@ -0,0 +1,105 @@
1
+ require 'assert'
2
+
3
+ class ManagingTest < Assert::Context
4
+ desc "Using Sanford's manager"
5
+ setup do
6
+ # preserve the global service hosts configuration, no matter how we
7
+ # manipulate it
8
+ Test::Environment.store_and_clear_hosts
9
+ end
10
+ teardown do
11
+ Test::Environment.restore_hosts
12
+ end
13
+
14
+ class CallTest < ManagingTest
15
+ include Test::ForkManagerHelper
16
+
17
+ setup do
18
+ Sanford.config.hosts.add(DummyHost)
19
+ end
20
+ end
21
+
22
+ class RunTest < CallTest
23
+ desc "to run a service host"
24
+
25
+ should "start a sanford server for the only service host that is configured" do
26
+ host = Sanford.config.hosts.first
27
+
28
+ self.call_sanford_manager(:run) do
29
+ assert_nothing_raised{ self.open_socket(host.config.ip, host.config.port) }
30
+ assert File.exists?(self.expected_pid_file(host, host.config.ip, host.config.port))
31
+ end
32
+ end
33
+ end
34
+
35
+ class RunWithOptionsTest < CallTest
36
+ desc "to run a service host and passing options"
37
+ setup do
38
+ # make sure that DummyHost isn't the only 'host'
39
+ Sanford.config.hosts.add(Class.new)
40
+ end
41
+
42
+ should "start a sanford server for the specified service host and " \
43
+ "use the passed options to override it's configuration" do
44
+ host = Sanford.config.find_host('DummyHost')
45
+
46
+ self.call_sanford_manager(:run, { :host => 'DummyHost', :port => 12345 }) do
47
+ assert_nothing_raised{ self.open_socket(host.config.ip, 12345) }
48
+ assert File.exists?(self.expected_pid_file(host, host.config.ip, 12345))
49
+ end
50
+ end
51
+ end
52
+
53
+ class RunWithEnvVarsTest < CallTest
54
+ desc "to run a service host and setting env vars"
55
+ setup do
56
+ @current = ENV.delete('SANFORD_HOST'), ENV.delete('SANFORD_IP'), ENV.delete('SANFORD_PORT')
57
+ ENV['SANFORD_HOST'] = 'DummyHost'
58
+ ENV['SANFORD_IP'], ENV['SANFORD_PORT'] = 'localhost', '54321'
59
+ # make sure that DummyHost isn't the only 'host'
60
+ Sanford.config.hosts.add(Class.new)
61
+ end
62
+ teardown do
63
+ ENV['SANFORD_HOST'], ENV['SANFORD_IP'], ENV['SANFORD_PORT'] = @current
64
+ end
65
+
66
+ should "start a sanford server for the specified service host and " \
67
+ "use the env vars to override it's configuration" do
68
+ host = Sanford.config.find_host(ENV['SANFORD_HOST'])
69
+ port = ENV['SANFORD_PORT'].to_i
70
+
71
+ self.call_sanford_manager(:run) do
72
+ assert_nothing_raised{ self.open_socket(ENV['SANFORD_IP'], port) }
73
+ assert File.exists?(self.expected_pid_file(host, ENV['SANFORD_IP'], port))
74
+ end
75
+ end
76
+ end
77
+
78
+ class BadHostTest < ManagingTest
79
+ desc "with a bad host name"
80
+ setup do
81
+ Sanford.config.hosts.clear
82
+ Sanford.config.hosts.add(Class.new)
83
+ end
84
+
85
+ should "raise an exception when a service host can't be found" do
86
+ assert_raises(Sanford::NoHostError) do
87
+ Sanford::Manager.call(:run, :host => 'not_a_real_host')
88
+ end
89
+ end
90
+ end
91
+
92
+ class NoHostsTest < ManagingTest
93
+ desc "with no hosts"
94
+ setup do
95
+ Sanford.config.hosts.clear
96
+ end
97
+
98
+ should "raise an exception when there aren't any service hosts" do
99
+ assert_raises(Sanford::NoHostError) do
100
+ Sanford::Manager.call(:run)
101
+ end
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,202 @@
1
+ # This test is intended as a high level test against Sanford's server. This will
2
+ # use multiple request scenarios to test out Sanford's behavior and how it
3
+ # responds.
4
+ #
5
+ require 'assert'
6
+
7
+ class RequestHandlingTest < Assert::Context
8
+ include Test::ForkServerHelper
9
+
10
+ desc "Sanford's handling of requests"
11
+ setup do
12
+ @service_host = DummyHost.new
13
+ @server = Sanford::Server.new(@service_host, { :ready_timeout => 0 })
14
+ end
15
+
16
+ # Simple service test that echos back the params sent to it
17
+ class EchoTest < RequestHandlingTest
18
+ desc "when hitting an echo service"
19
+
20
+ should "return a successful response and echo the params sent to it" do
21
+ self.start_server(@server) do
22
+ response = SimpleClient.call_with_request(@service_host, 'v1', 'echo', {
23
+ :message => 'test'
24
+ })
25
+
26
+ assert_equal 200, response.status.code
27
+ assert_equal nil, response.status.message
28
+ assert_equal 'test', response.data
29
+ end
30
+ end
31
+ end
32
+
33
+ class ErroringRequestTest < RequestHandlingTest
34
+ setup do
35
+ @env_sanford_protocol_debug = ENV['SANFORD_PROTOCOL_DEBUG']
36
+ ENV.delete('SANFORD_PROTOCOL_DEBUG')
37
+ end
38
+ teardown do
39
+ ENV['SANFORD_PROTOCOL_DEBUG'] = @env_sanford_protocol_debug
40
+ end
41
+ end
42
+
43
+ # Sending the server a completely wrong stream of bytes
44
+ class BadMessageTest < ErroringRequestTest
45
+ desc "when sent a invalid request stream"
46
+
47
+ should "return a bad request response with an error message" do
48
+ self.start_server(@server) do
49
+ bytes = [ Sanford::Protocol.msg_version, "\000" ].join
50
+ response = SimpleClient.call_with(@service_host, bytes)
51
+
52
+ assert_equal 400, response.status.code
53
+ assert_match "size", response.status.message
54
+ assert_equal nil, response.data
55
+ end
56
+ end
57
+ end
58
+
59
+ # Sending the server a protocol version that doesn't match it's version
60
+ class WrongProtocolVersionTest < ErroringRequestTest
61
+ desc "when sent a request with a wrong protocol version"
62
+
63
+ should "return a bad request response with an error message" do
64
+ self.start_server(@server) do
65
+ bytes = [ Sanford::Protocol.msg_version, "\000" ].join
66
+ response = SimpleClient.call_with_msg_body(@service_host, {}, nil, "\000")
67
+
68
+ assert_equal 400, response.status.code
69
+ assert_match "Protocol version", response.status.message
70
+ assert_equal nil, response.data
71
+ end
72
+ end
73
+ end
74
+
75
+ # Sending the server a body that it can't parse
76
+ class BadBodyTest < ErroringRequestTest
77
+ desc "when sent a request with an invalid body"
78
+ should "return a bad request response with an error message" do
79
+ self.start_server(@server) do
80
+ response = SimpleClient.call_with_encoded_msg_body(@service_host, "\000\001\010\011" * 2)
81
+
82
+ assert_equal 400, response.status.code
83
+ assert_match "body", response.status.message
84
+ assert_equal nil, response.data
85
+ end
86
+ end
87
+ end
88
+
89
+ class MissingServiceNameTest < ErroringRequestTest
90
+ desc "when sent a request with no service name"
91
+
92
+ should "return a bad request response" do
93
+ self.start_server(@server) do
94
+ request_hash = Sanford::Protocol::Request.new('v1', 'what', {}).to_hash
95
+ request_hash.delete('name')
96
+ response = SimpleClient.call_with_msg_body(@service_host, request_hash)
97
+
98
+ assert_equal 400, response.status.code
99
+ assert_match "request", response.status.message
100
+ assert_match "name", response.status.message
101
+ assert_equal nil, response.data
102
+ end
103
+ end
104
+ end
105
+
106
+ class MissingServiceVersionTest < ErroringRequestTest
107
+ desc "when sent a request with no service version"
108
+
109
+ should "return a bad request response" do
110
+ self.start_server(@server) do
111
+ request_hash = Sanford::Protocol::Request.new('v1', 'what', {}).to_hash
112
+ request_hash.delete('version')
113
+ response = SimpleClient.call_with_msg_body(@service_host, request_hash)
114
+
115
+ assert_equal 400, response.status.code
116
+ assert_match "request", response.status.message
117
+ assert_match "version", response.status.message
118
+ assert_equal nil, response.data
119
+ end
120
+ end
121
+ end
122
+
123
+ # Requesting a service that is not defined
124
+ class NotFoundServiceTest < ErroringRequestTest
125
+ desc "when sent a request with no matching service name"
126
+
127
+ should "return a bad request response" do
128
+ self.start_server(@server) do
129
+ response = SimpleClient.call_with_request(@service_host, 'v1', 'what', {})
130
+
131
+ assert_equal 404, response.status.code
132
+ assert_equal nil, response.status.message
133
+ assert_equal nil, response.data
134
+ end
135
+ end
136
+ end
137
+
138
+ # Hitting a service that throws an exception
139
+ class ErrorServiceTest < ErroringRequestTest
140
+ desc "when sent a request that errors on the server"
141
+
142
+ should "return a bad request response" do
143
+ self.start_server(@server) do
144
+ response = SimpleClient.call_with_request(@service_host, 'v1', 'bad', {})
145
+
146
+ assert_equal 500, response.status.code
147
+ assert_match "error", response.status.message
148
+ assert_equal nil, response.data
149
+ end
150
+ end
151
+ end
152
+
153
+ class HaltTest < RequestHandlingTest
154
+ desc "when sent a request that halts"
155
+
156
+ should "return the response that was halted" do
157
+ self.start_server(@server) do
158
+ response = SimpleClient.call_with_request(@service_host, 'v1', 'halt_it', {})
159
+
160
+ assert_equal 728, response.status.code
161
+ assert_equal "I do what I want", response.status.message
162
+ assert_equal [ 1, true, 'yes' ], response.data
163
+ end
164
+ end
165
+ end
166
+
167
+ class AuthorizeRequestTest < RequestHandlingTest
168
+ desc "when sent a request that halts in a callback"
169
+
170
+ should "return the response that was halted" do
171
+ self.start_server(@server) do
172
+ response = SimpleClient.call_with_request(@service_host, 'v1', 'authorized', {})
173
+
174
+ assert_equal 401, response.status.code
175
+ assert_equal "Not authorized", response.status.message
176
+ assert_equal nil, response.data
177
+ end
178
+ end
179
+ end
180
+
181
+ class HangingRequestTest < ErroringRequestTest
182
+ desc "when a client connects but doesn't send anything"
183
+ setup do
184
+ ENV['SANFORD_TIMEOUT'] = '0.1'
185
+ end
186
+ teardown do
187
+ ENV.delete('SANFORD_TIMEOUT')
188
+ end
189
+
190
+ should "timeout" do
191
+ self.start_server(@server) do
192
+ client = SimpleClient.new(@service_host, :with_delay => 0.2)
193
+ response = client.call_with_request('v1', 'echo', { :message => 'test' })
194
+
195
+ assert_equal 408, response.status.code
196
+ assert_equal nil, response.status.message
197
+ assert_equal nil, response.data
198
+ end
199
+ end
200
+ end
201
+
202
+ end
@@ -0,0 +1,54 @@
1
+ require 'assert'
2
+
3
+ module Sanford::Config
4
+
5
+ class BaseTest < Assert::Context
6
+ desc "Sanford::Config"
7
+ subject{ Sanford::Config }
8
+
9
+ should have_instance_methods :hosts, :services_config, :find_host
10
+ end
11
+
12
+ class FindHostTest < BaseTest
13
+ desc "find_host"
14
+ setup do
15
+ Test::Environment.store_and_clear_hosts
16
+ Sanford::Config.hosts.add(NotNamedHost)
17
+ Sanford::Config.hosts.add(NamedHost)
18
+ Sanford::Config.hosts.add(BadlyNamedHost)
19
+ end
20
+ teardown do
21
+ Test::Environment.restore_hosts
22
+ end
23
+
24
+ should "allow finding hosts by their class name or configured name" do
25
+ assert_includes NotNamedHost, subject.hosts
26
+ assert_includes NamedHost, subject.hosts
27
+ assert_equal NotNamedHost, subject.find_host('NotNamedHost')
28
+ assert_equal NamedHost, subject.find_host('NamedHost')
29
+ assert_equal NamedHost, subject.find_host('named_host')
30
+ end
31
+ should "check class name before configured name" do
32
+ assert_includes BadlyNamedHost, subject.hosts
33
+ assert_equal NotNamedHost, subject.find_host('NotNamedHost')
34
+ end
35
+ end
36
+
37
+ # Using this syntax because these classes need to be defined as top-level
38
+ # constants for ease in using their class names in the tests
39
+
40
+ ::NotNamedHost = Class.new do
41
+ include Sanford::Host
42
+ end
43
+
44
+ ::NamedHost = Class.new do
45
+ include Sanford::Host
46
+ name 'named_host'
47
+ end
48
+
49
+ ::BadlyNamedHost = Class.new do
50
+ include Sanford::Host
51
+ name 'NotNamedHost'
52
+ end
53
+
54
+ end
@@ -0,0 +1,23 @@
1
+ require 'assert'
2
+
3
+ require 'sanford-protocol/test/helpers'
4
+
5
+ class Sanford::Connection
6
+
7
+ class BaseTest < Assert::Context
8
+ include Sanford::Protocol::Test::Helpers
9
+
10
+ desc "Sanford::Connection"
11
+ setup do
12
+ @fake_socket = self.fake_socket_with_request('v1', 'echo', { :message => 'test' })
13
+ @connection = Sanford::Connection.new(DummyHost.new, @fake_socket)
14
+ end
15
+ subject{ @connection }
16
+
17
+ should have_instance_methods :service_host, :exception_handler, :logger, :process, :timeout
18
+ end
19
+
20
+ # The system test `test/system/request_handling_test.rb`, covers all the
21
+ # special requests that can occur when given all sorts of invalid requests.
22
+
23
+ end
@@ -0,0 +1,69 @@
1
+ require 'assert'
2
+
3
+ class Sanford::ExceptionHandler
4
+
5
+ class BaseTest < Assert::Context
6
+ desc "Sanford::Server::ExceptionHandler"
7
+ setup do
8
+ @exception = nil
9
+ begin
10
+ raise "test"
11
+ rescue Exception => @exception
12
+ end
13
+ @logger = Sanford::NullLogger.new
14
+ @exception_handler = Sanford::ExceptionHandler.new(@exception, @logger)
15
+ end
16
+ subject{ @exception_handler }
17
+
18
+ should have_instance_methods :exception
19
+
20
+ should "have built a 500 Sanford::Response" do
21
+ response = subject.response
22
+
23
+ assert_instance_of Sanford::Protocol::Response, response
24
+ assert_equal 500, response.status.code
25
+ assert_equal "An unexpected error occurred.", response.status.message
26
+ end
27
+ end
28
+
29
+ class BadRequestTest < BaseTest
30
+ desc "with a Sanford::BadRequest exception"
31
+ setup do
32
+ @exception = nil
33
+ begin
34
+ raise Sanford::Protocol::BadMessageError, "test"
35
+ rescue Exception => @exception
36
+ end
37
+ @exception_handler = Sanford::ExceptionHandler.new(@exception, @logger)
38
+ end
39
+
40
+ should "have built a 400 Sanford::Response" do
41
+ response = subject.response
42
+
43
+ assert_instance_of Sanford::Protocol::Response, response
44
+ assert_equal 400, response.status.code
45
+ assert_equal "test", response.status.message
46
+ end
47
+ end
48
+
49
+ class NotFoundTest < BaseTest
50
+ desc "with a Sanford::NotFound exception"
51
+ setup do
52
+ @exception = nil
53
+ begin
54
+ raise Sanford::NotFoundError, "test"
55
+ rescue Exception => @exception
56
+ end
57
+ @exception_handler = Sanford::ExceptionHandler.new(@exception, @logger)
58
+ end
59
+
60
+ should "have built a 404 Sanford::Response" do
61
+ response = subject.response
62
+
63
+ assert_instance_of Sanford::Protocol::Response, response
64
+ assert_equal 404, response.status.code
65
+ assert_equal nil, response.status.message
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,39 @@
1
+ require 'assert'
2
+
3
+ class Sanford::Host::VersionGroup
4
+
5
+ class BaseTest < Assert::Context
6
+ desc "Sanford::Host::VersionGroup"
7
+ setup do
8
+ @version_group = Sanford::Host::VersionGroup.new('v1'){ }
9
+ end
10
+ subject{ @version_group }
11
+
12
+ should have_instance_methods :name, :services, :service, :to_hash
13
+
14
+ should "add a key-value to it's services hash with #service" do
15
+ subject.service('test', 'MyServiceHandler')
16
+
17
+ assert_equal 'MyServiceHandler', subject.services['test']
18
+ end
19
+ should "allow setting a namespace and use it when a service is added" do
20
+ subject.service_handler_ns 'MyNamespace'
21
+ subject.service('test', 'MyServiceHandler')
22
+
23
+ assert_equal 'MyNamespace::MyServiceHandler', subject.services['test']
24
+ end
25
+ should "ignore a namespace and when a service class has leading colons" do
26
+ subject.service_handler_ns 'MyNamespace'
27
+ subject.service('test', '::MyServiceHandler')
28
+
29
+ assert_equal '::MyServiceHandler', subject.services['test']
30
+ end
31
+ should "return a hash with it's name as a key and its services as the value with #to_hash" do
32
+ subject.service('test', 'MyServiceHandler')
33
+ expected = { subject.name => subject.services }
34
+
35
+ assert_equal expected, subject.to_hash
36
+ end
37
+ end
38
+
39
+ end