sanford 0.1.0

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