sanford-protocol 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +145 -0
- data/Rakefile +4 -0
- data/lib/sanford-protocol/connection.rb +54 -0
- data/lib/sanford-protocol/msg_data.rb +61 -0
- data/lib/sanford-protocol/request.rb +48 -0
- data/lib/sanford-protocol/response.rb +45 -0
- data/lib/sanford-protocol/response_status.rb +44 -0
- data/lib/sanford-protocol/test/fake_socket.rb +51 -0
- data/lib/sanford-protocol/test/helpers.rb +36 -0
- data/lib/sanford-protocol/version.rb +5 -0
- data/lib/sanford-protocol.rb +52 -0
- data/sanford-protocol.gemspec +24 -0
- data/test/helper.rb +32 -0
- data/test/unit/connection_tests.rb +27 -0
- data/test/unit/fake_socket_tests.rb +85 -0
- data/test/unit/msg_data_tests.rb +121 -0
- data/test/unit/protocol_tests.rb +51 -0
- data/test/unit/request_tests.rb +71 -0
- data/test/unit/response_status_tests.rb +44 -0
- data/test/unit/response_tests.rb +74 -0
- data/test/unit/test_helpers_tests.rb +35 -0
- metadata +143 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 jcredding
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# Sanford Protocol
|
2
|
+
|
3
|
+
Ruby implementation of the Sanford communication protocol.
|
4
|
+
|
5
|
+
## The Protocol
|
6
|
+
|
7
|
+
**Version**: `1`
|
8
|
+
|
9
|
+
Sanford communicates using binary encoded messages. Sanford messages are two headers and a body:
|
10
|
+
|
11
|
+
```
|
12
|
+
|------ 1B -------|------ 4B -------|---- (Body Size)B ----|
|
13
|
+
| (packed header) | (packed header) | (BSON binary string) |
|
14
|
+
| Version | Body Size | Body |
|
15
|
+
|-----------------|-----------------|----------------------|
|
16
|
+
```
|
17
|
+
|
18
|
+
### Version
|
19
|
+
|
20
|
+
The first header represents the protocol version in use. It is a 1 byte unsigned integer and exists to ensure both the client and the server are talking the same protocol.
|
21
|
+
|
22
|
+
### Body Size
|
23
|
+
|
24
|
+
The second header represents the size of the message's body. It is a 4 byte unsigned integer and tells the receiver how many bytes to read to receive the body.
|
25
|
+
|
26
|
+
### Body
|
27
|
+
|
28
|
+
The Body is the content of the message. It is a [BSON](http://bsonspec.org/) encoded binary string that decodes to a ruby hash. Since the size of the body is encoded as a 4 byte (32 bit) unsigned integer, there is a size limit for body data (`(2 ** 32) - 1` or `4,294,967,295` or `~4GB`).
|
29
|
+
|
30
|
+
## Request
|
31
|
+
|
32
|
+
A request is made up of 3 required parts: the version, the name, and the params.
|
33
|
+
|
34
|
+
* **version** - (string) version of the requested API.
|
35
|
+
* **name** - (string) name of the requested API service.
|
36
|
+
* **params** - (object) data for the service call - can be any BSON serializable object.
|
37
|
+
|
38
|
+
Requests are encoded as BSON hashes when transmitted in messages.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
{ 'version': 'v1',
|
42
|
+
'name': 'some_service',
|
43
|
+
'params': 'something'
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
## Response
|
48
|
+
|
49
|
+
A response is made up of 2 parts: the status and the result.
|
50
|
+
|
51
|
+
* **status** - (tuple, required) A code and message describing the result of the service call.
|
52
|
+
* **result** - (object, optional) Result of calling the service. This can be any BSON serializable object. Typically won't be set if the request is not successful.
|
53
|
+
|
54
|
+
Responses are encoded as BSON hashes when transmitted in messages.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
{ 'status': [ 200, 'The request was successful.' ]
|
58
|
+
'result': true
|
59
|
+
}
|
60
|
+
```
|
61
|
+
|
62
|
+
### Status Codes
|
63
|
+
|
64
|
+
This is the list of defined status codes.
|
65
|
+
|
66
|
+
* `200` - `ok` - The request was successful.
|
67
|
+
* `400` - `bad_request` - The request couldn't be read. This is usually because it was not formed correctly.
|
68
|
+
* `404` - `not_found` - The server couldn't find something requested.
|
69
|
+
* `500` - `error` - The server errored responding to the request.
|
70
|
+
|
71
|
+
In addition to these, a service can return custom status codes, but they should use a number greater than or equal to `600` to avoid collisions with Sanford's defined status codes.
|
72
|
+
|
73
|
+
## Usage
|
74
|
+
|
75
|
+
The `Sanford::Protocol` module defines helper methods for encoding and decoding messages.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# encode a message
|
79
|
+
data = { 'something' => true }
|
80
|
+
msg_body = Sanford::Protocol.msg_body.encode(data)
|
81
|
+
msg_size = Sanford::Protocol.msg_size.encode(msg_body.bytesize)
|
82
|
+
msg = [Sanford::Protocol.msg_version, msg_size, msg_body].join
|
83
|
+
```
|
84
|
+
|
85
|
+
### Connection
|
86
|
+
|
87
|
+
If you are sending and receiving messages using a tcp socket, use `Sanford::Protocol::Connection`.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
connection = Sanford::Protocol::Connection.new(tcp_socket)
|
91
|
+
incoming_data = connection.read
|
92
|
+
connection.write(outgoing_data)
|
93
|
+
```
|
94
|
+
|
95
|
+
For incoming messages, it reads them off the socket, validate them, and return the decoded body data. For outgoing messages, it encodes the message body from given data, adds the appropiate message headers, and writes the message to the socket.
|
96
|
+
|
97
|
+
### Requests And Responses
|
98
|
+
|
99
|
+
Request and response objects have helpers for sending and receiving data using a connection.
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# For a server...
|
103
|
+
# use Request#parse to build an incoming request
|
104
|
+
data_hash = server_connection.read
|
105
|
+
incoming_request = Sanford::Protocol::Request.parse(data_hash)
|
106
|
+
# use Response#to_hash to send a response
|
107
|
+
outgoing_response = Sanford::Protocol::Response.new(status, result)
|
108
|
+
server_connection.write(outgoing_response.to_hash)
|
109
|
+
|
110
|
+
# For a client...
|
111
|
+
# use Request#to_hash to send a request
|
112
|
+
outgoing_request = Sanford::Protocol::Request.new(name, version, params)
|
113
|
+
client_connection.write(outgoing_request.to_hash)
|
114
|
+
# use Response#parse to build an incoming response
|
115
|
+
data_hash = client_connection.read
|
116
|
+
incoming_response = Sanford::Protocol::Response.parse(data_hash)
|
117
|
+
```
|
118
|
+
|
119
|
+
## Test Helpers
|
120
|
+
|
121
|
+
A `FakeSocket` helper class and an associated `Test::Helpers` module are provided to help test receiving and sending Sanford::Protocol messages without using real sockets.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# fake a socket with some incoming binary
|
125
|
+
socket = FakeSocket.new(msg_binary_string)
|
126
|
+
connection = Sanford::Protocol::Connection.new(socket)
|
127
|
+
msg_data = connection.read
|
128
|
+
|
129
|
+
# write some binary to that fake socket and verify it
|
130
|
+
connection.write(msg_data)
|
131
|
+
puts socket.out # => msg binary string
|
132
|
+
|
133
|
+
# create a socket with an incoming request and verify the request
|
134
|
+
socket = FakeSocket.with_request(*request_params)
|
135
|
+
msg_data = Sanford::Protocol::Connection.new(socket).read
|
136
|
+
request = Sanford::Protocol::Request.parse(msg_data)
|
137
|
+
```
|
138
|
+
|
139
|
+
## Contributing
|
140
|
+
|
141
|
+
1. Fork it
|
142
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
143
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
144
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
145
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'sanford-protocol/msg_data'
|
2
|
+
|
3
|
+
module Sanford::Protocol
|
4
|
+
|
5
|
+
# Sanford Protocol's connection class wraps a socket and provides a `read` and
|
6
|
+
# `write` method for using Sanford's message protocol. Mixes in the protocol
|
7
|
+
# to get the `msg_version`, `msg_size`, and `msg_body` handler methods.
|
8
|
+
|
9
|
+
class Connection
|
10
|
+
include Sanford::Protocol
|
11
|
+
|
12
|
+
def initialize(tcp_socket)
|
13
|
+
@socket = Socket.new(tcp_socket)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Message format (see sanford-protocal.rb):
|
17
|
+
# |------ 1B -------|------ 4B -------|-- (msg body size)B --|
|
18
|
+
# | (packed header) | (packed header) | (BSON binary string) |
|
19
|
+
# | msg version | msg body size | msg body |
|
20
|
+
# |-----------------|-----------------|----------------------|
|
21
|
+
|
22
|
+
def read
|
23
|
+
MsgVersion.new{ @socket.read msg_version.bytesize }.validate!
|
24
|
+
size = MsgSize.new{ @socket.decode msg_size, msg_size.bytes }.validate!.value
|
25
|
+
return MsgBody.new{ @socket.decode msg_body, size }.validate!.value
|
26
|
+
end
|
27
|
+
|
28
|
+
def write(data)
|
29
|
+
body = @socket.encode msg_body, data
|
30
|
+
size = @socket.encode msg_size, body.bytesize
|
31
|
+
|
32
|
+
@socket.write(msg_version, size, body)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Socket < Struct.new(:tcp_socket)
|
36
|
+
def decode(handler, num_bytes)
|
37
|
+
handler.decode(read(num_bytes))
|
38
|
+
end
|
39
|
+
|
40
|
+
def encode(handler, data)
|
41
|
+
handler.encode data
|
42
|
+
end
|
43
|
+
|
44
|
+
def read(number_of_bytes)
|
45
|
+
tcp_socket.recvfrom(number_of_bytes).first
|
46
|
+
end
|
47
|
+
|
48
|
+
def write(*binary_strings)
|
49
|
+
tcp_socket.send(binary_strings.join, 0)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Sanford::Protocol
|
2
|
+
|
3
|
+
class BadMessageError < RuntimeError
|
4
|
+
def initialize(message, bt=nil)
|
5
|
+
super(message)
|
6
|
+
set_backtrace(bt || caller)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class MsgData
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
def initialize(called_from=nil, &get_value)
|
14
|
+
@called_from = called_from || caller
|
15
|
+
|
16
|
+
# By default, any exceptions from getting the value are "hidden" behind a
|
17
|
+
# more general `BadMessageError`. In non-debug scenarios this is ideal and
|
18
|
+
# allows you to rescue from a common exception and respond in a standard way.
|
19
|
+
# In debug scenarios, however, go ahead and raise the real exception.
|
20
|
+
|
21
|
+
begin
|
22
|
+
@value = get_value.call
|
23
|
+
rescue Exception => err
|
24
|
+
ENV['SANFORD_PROTOCOL_DEBUG'] ? raise(err) : self.error!(self.get_value_error)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_value_error; "Error reading the message"; end
|
29
|
+
def validate!; self; end
|
30
|
+
|
31
|
+
def error!(message)
|
32
|
+
raise BadMessageError.new(message, @called_from)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
class MsgSize < MsgData
|
38
|
+
def get_value_error; "Error reading message body size."; end
|
39
|
+
def validate!
|
40
|
+
error!("Empty message size") if self.value.nil?
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class MsgVersion < MsgData
|
46
|
+
def get_value_error; "Error reading message protocol version"; end
|
47
|
+
def validate!
|
48
|
+
error!("Protocol version mismatch") if version_mismatch?
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
def version_mismatch?
|
53
|
+
self.value != Sanford::Protocol.msg_version
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class MsgBody < MsgData
|
58
|
+
def get_value_error; "Error reading message body."; end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# The Request class models a specific type of Sanford message body and provides
|
2
|
+
# a defined structure for it. A request requires a message body to contain a
|
3
|
+
# version, name and params. It provides methods for working with message bodies
|
4
|
+
# (hashes) with `parse` and `to_hash`. In addition to this, a request has a
|
5
|
+
# `valid?` method, that returns whether it is valid and if it isn't why.
|
6
|
+
|
7
|
+
module Sanford::Protocol
|
8
|
+
|
9
|
+
class Request
|
10
|
+
|
11
|
+
def self.parse(body)
|
12
|
+
self.new(body['version'], body['name'], body['params'])
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :version, :name, :params
|
16
|
+
|
17
|
+
def initialize(version, name, params)
|
18
|
+
@version, @name, @params = version, name, params
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
{ 'version' => @version,
|
23
|
+
'name' => @name,
|
24
|
+
'params' => @params
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
if !@version
|
30
|
+
[ false, "The request doesn't contain a version." ]
|
31
|
+
elsif !@name
|
32
|
+
[ false, "The request doesn't contain a name." ]
|
33
|
+
else
|
34
|
+
[ true ]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
reference = '0x0%x' % (self.object_id << 1)
|
40
|
+
"#<#{self.class}:#{reference}"\
|
41
|
+
" @version=#{@version.inspect}"\
|
42
|
+
" @name=#{@name.inspect}"\
|
43
|
+
" @params=#{@params.inspect}>"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'sanford-protocol/response_status'
|
2
|
+
|
3
|
+
# The Response class models a specific type of Sanford message body and provides
|
4
|
+
# a defined structure for it. A response requires a message body to contain a
|
5
|
+
# status (which is a code and optional message) and a result. It provides
|
6
|
+
# methods for working with message bodies (hashes) with `parse` and `to_hash`.
|
7
|
+
|
8
|
+
module Sanford::Protocol
|
9
|
+
|
10
|
+
class Response
|
11
|
+
|
12
|
+
def self.parse(hash)
|
13
|
+
self.new(hash['status'], hash['result'])
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :status, :result
|
17
|
+
|
18
|
+
def initialize(status, result = nil)
|
19
|
+
@status, @result = build_status(status), result
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{ 'status' => [ @status.code, @status.message ],
|
24
|
+
'result' => @result
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
reference = '0x0%x' % (self.object_id << 1)
|
30
|
+
"#<#{self.class}:#{reference}"\
|
31
|
+
" @status=#{@status.inspect}"\
|
32
|
+
" @result=#{@result.inspect}>"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def build_status(status)
|
38
|
+
return status if status.kind_of?(ResponseStatus)
|
39
|
+
|
40
|
+
ResponseStatus.new(*status)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# The Response Status class models a code and optional message. This makes up
|
2
|
+
# part of a response and provides methods for building and displaying statuses.
|
3
|
+
|
4
|
+
module Sanford::Protocol
|
5
|
+
class ResponseStatus < Struct.new(:code_obj, :message)
|
6
|
+
|
7
|
+
def initialize(code, message = nil)
|
8
|
+
super(Code.new(code), message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def code; code_obj.number; end
|
12
|
+
def name; code_obj.name; end
|
13
|
+
def to_s; code_obj.to_s; end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
reference = '0x0%x' % (self.object_id << 1)
|
17
|
+
"#<#{self.class}:#{reference}"\
|
18
|
+
" @code=#{code_obj}"\
|
19
|
+
" @message=#{message.inspect}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
class Code < Struct.new(:number, :name)
|
23
|
+
NUMBERS = {
|
24
|
+
'ok' => 200,
|
25
|
+
'bad_request' => 400,
|
26
|
+
'not_found' => 404,
|
27
|
+
'error' => 500
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
def initialize(key)
|
31
|
+
num = NUMBERS[key.to_s] || key.to_i
|
32
|
+
name = NUMBERS.index(num) || NoName
|
33
|
+
super(num, name.upcase)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s; "[#{[number, name].compact.join(', ')}]"; end
|
37
|
+
|
38
|
+
class NoName
|
39
|
+
def self.upcase; nil; end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# The FakeSocket class can be used to work with Sanford Protocol in a test
|
2
|
+
# environment. Instead of passing a real socket, pass an instance of this class.
|
3
|
+
# It mimics the socket API that sanford is concerned with.
|
4
|
+
|
5
|
+
require 'sanford-protocol'
|
6
|
+
require 'sanford-protocol/request'
|
7
|
+
|
8
|
+
module Sanford::Protocol::Test
|
9
|
+
class FakeSocket
|
10
|
+
|
11
|
+
def self.with_request(*request_params)
|
12
|
+
request = Sanford::Protocol::Request.new(*request_params)
|
13
|
+
self.with_msg_body(request.to_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.with_msg_body(body, size=nil, encoded_version=nil)
|
17
|
+
encoded_body = Sanford::Protocol.msg_body.encode(body)
|
18
|
+
self.with_encoded_msg_body(encoded_body, size, encoded_version)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.with_encoded_msg_body(encoded_body, size=nil, encoded_version=nil)
|
22
|
+
encoded_size = Sanford::Protocol.msg_size.encode(size || encoded_body.bytesize)
|
23
|
+
encoded_version ||= Sanford::Protocol.msg_version
|
24
|
+
self.new(encoded_version, encoded_size, encoded_body)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(*bytes)
|
28
|
+
@out = StringIO.new
|
29
|
+
@in = StringIO.new
|
30
|
+
reset(*bytes)
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset(*new_bytes)
|
34
|
+
@in << new_bytes.join; @in.rewind;
|
35
|
+
end
|
36
|
+
|
37
|
+
def in; @in.string; end
|
38
|
+
def out; @out.string; end
|
39
|
+
|
40
|
+
# Socket methods -- requied by Sanford::Protocol
|
41
|
+
|
42
|
+
def recvfrom(number_of_bytes)
|
43
|
+
[ @in.read(number_of_bytes.to_i) ]
|
44
|
+
end
|
45
|
+
|
46
|
+
def send(bytes, flag)
|
47
|
+
@out << bytes
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'sanford-protocol/test/fake_socket'
|
2
|
+
require 'sanford-protocol/response'
|
3
|
+
|
4
|
+
module Sanford::Protocol::Test
|
5
|
+
|
6
|
+
module Helpers
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def fake_socket_with_request(*args)
|
10
|
+
FakeSocket.with_request(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fake_socket_with_msg_body(*args)
|
14
|
+
FakeSocket.with_msg_body(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fake_socket_with_encoded_msg_body(*args)
|
18
|
+
FakeSocket.with_encoded_msg_body(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fake_socket_with(*args)
|
22
|
+
FakeSocket.new(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_response_from_fake_socket(from_fake_socket)
|
26
|
+
data = Sanford::Protocol::Connection.new(from_fake_socket).read
|
27
|
+
Sanford::Protocol::Response.parse(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
def read_written_response_from_fake_socket(from_fake_socket)
|
31
|
+
read_response_from_fake_socket(FakeSocket.new(from_fake_socket.out))
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'sanford-protocol/connection'
|
2
|
+
require 'sanford-protocol/request'
|
3
|
+
require 'sanford-protocol/response'
|
4
|
+
|
5
|
+
module Sanford
|
6
|
+
module Protocol
|
7
|
+
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# If anything changes in this file, the VERSION number should be
|
11
|
+
# incremented. This is used by clients and servers to ensure they are
|
12
|
+
# working under the same assumptions. In addition to incrementing this, the
|
13
|
+
# README needs to be updated to display the current version and needs to
|
14
|
+
# describe everything in this file.
|
15
|
+
|
16
|
+
VERSION = 1
|
17
|
+
|
18
|
+
# The message version is the 1B encoding of the `VERSION` above. It is
|
19
|
+
# encoded using Array#pack 'C' (8-bit unsigned integer). The max value it
|
20
|
+
# can encode is 255 (`(2 ** 8) - 1`).
|
21
|
+
|
22
|
+
def msg_version; @msg_version ||= PackedHeader.new(1, 'C').encode(VERSION); end
|
23
|
+
|
24
|
+
# The message size is encoded using Array#pack 'N'. This encoding represents
|
25
|
+
# a 32-bit (4 byte) unsigned integer. The max value that can be encoded in
|
26
|
+
# 4 bytes is 4,294,967,295 (`(2 ** 32) - 1`) or a size of ~4GB.
|
27
|
+
|
28
|
+
def msg_size; @msg_size ||= PackedHeader.new(4, 'N'); end
|
29
|
+
|
30
|
+
# THe message body is encoded using BSON.
|
31
|
+
|
32
|
+
def msg_body; @msg_body ||= BsonBody.new; end
|
33
|
+
|
34
|
+
class PackedHeader < Struct.new(:bytes, :directive)
|
35
|
+
def encode(data); [*data].pack(directive); end
|
36
|
+
def decode(binary); binary.to_s.unpack(directive).first; end
|
37
|
+
end
|
38
|
+
|
39
|
+
class BsonBody
|
40
|
+
require 'bson'
|
41
|
+
|
42
|
+
# BSON returns a byte buffer when serializing. This doesn't always behave
|
43
|
+
# like a string, so convert it to one.
|
44
|
+
def encode(data); ::BSON.serialize(data).to_s; end
|
45
|
+
|
46
|
+
# BSON returns an ordered hash when deserializing. This should be
|
47
|
+
# functionally equivalent to a regular hash.
|
48
|
+
def decode(binary); ::BSON.deserialize(binary); end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sanford-protocol/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "sanford-protocol"
|
8
|
+
gem.version = Sanford::Protocol::GEM_VERSION
|
9
|
+
gem.authors = ["Collin Redding", "Kelly Redding"]
|
10
|
+
gem.email = ["collin.redding@me.com", "kelly@kellyredding.com"]
|
11
|
+
gem.description = "Ruby implementation of Sanford's communication protocol."
|
12
|
+
gem.summary = "Ruby implementation of Sanford's communication protocol."
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency("bson", ["~>1.7"])
|
21
|
+
|
22
|
+
gem.add_development_dependency("assert", ["~> 0.8"])
|
23
|
+
gem.add_development_dependency("assert-mocha", ["~> 0.1"])
|
24
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
ROOT = File.expand_path('../..', __FILE__)
|
2
|
+
|
3
|
+
ENV['SANFORD_PROTOCOL_DEBUG'] = 'yes'
|
4
|
+
|
5
|
+
require 'sanford-protocol/test/fake_socket'
|
6
|
+
FakeSocket = Sanford::Protocol::Test::FakeSocket
|
7
|
+
|
8
|
+
require 'assert-mocha' if defined?(Assert)
|
9
|
+
|
10
|
+
class Assert::Context
|
11
|
+
|
12
|
+
def setup_some_msg_data(data=nil)
|
13
|
+
@data = data || { 'something' => true }
|
14
|
+
@encoded_body = Sanford::Protocol.msg_body.encode(@data)
|
15
|
+
@encoded_size = Sanford::Protocol.msg_size.encode(@encoded_body.bytesize)
|
16
|
+
@encoded_version = Sanford::Protocol.msg_version
|
17
|
+
@msg = [@encoded_version, @encoded_size, @encoded_body].join
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup_some_request_data
|
21
|
+
@request_params = ['1', 'a_service', {:some => 'data'}]
|
22
|
+
@request = Sanford::Protocol::Request.new(*@request_params)
|
23
|
+
setup_some_msg_data(@request.to_hash)
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_some_response_data
|
27
|
+
@response_params = [200, 'in testing all is well']
|
28
|
+
@response = Sanford::Protocol::Response.new(*@response_params)
|
29
|
+
setup_some_msg_data(@response.to_hash)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'sanford-protocol/connection'
|
3
|
+
|
4
|
+
class Sanford::Protocol::Connection
|
5
|
+
|
6
|
+
class BaseTests < Assert::Context
|
7
|
+
desc "Sanford::Protocol::Connection"
|
8
|
+
setup do
|
9
|
+
setup_some_msg_data
|
10
|
+
@socket = FakeSocket.new(@msg)
|
11
|
+
@connection = Sanford::Protocol::Connection.new(@socket)
|
12
|
+
end
|
13
|
+
subject{ @connection }
|
14
|
+
|
15
|
+
should have_instance_methods :read, :write
|
16
|
+
|
17
|
+
should "read messages off the socket with #read" do
|
18
|
+
assert_equal @data, subject.read
|
19
|
+
end
|
20
|
+
|
21
|
+
should "write messages to the socket with #write" do
|
22
|
+
subject.write(@data)
|
23
|
+
assert_equal @msg, @socket.out
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'sanford-protocol/test/fake_socket'
|
3
|
+
require 'sanford-protocol/request'
|
4
|
+
|
5
|
+
class Sanford::Protocol::Test::FakeSocket
|
6
|
+
|
7
|
+
class BaseTests < Assert::Context
|
8
|
+
desc "a FakeSocket"
|
9
|
+
setup do
|
10
|
+
@fs = FakeSocket.new
|
11
|
+
end
|
12
|
+
subject { @fs }
|
13
|
+
|
14
|
+
should have_cmeths :with_request, :with_msg_body, :with_encoded_msg_body
|
15
|
+
should have_imeths :in, :out, :reset
|
16
|
+
should have_imeths :recvfrom, :send
|
17
|
+
|
18
|
+
should "have no `in` or `out` data by default" do
|
19
|
+
assert_empty subject.in
|
20
|
+
assert_empty subject.out
|
21
|
+
end
|
22
|
+
|
23
|
+
should "push `out` data using #send" do
|
24
|
+
subject.send('some out data', 0)
|
25
|
+
assert_equal 'some out data', subject.out
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class WithInDataTests < BaseTests
|
31
|
+
desc "created given some data"
|
32
|
+
setup do
|
33
|
+
@in_data = 'some in data'
|
34
|
+
@fs = FakeSocket.new(@in_data)
|
35
|
+
end
|
36
|
+
|
37
|
+
should "add the data as `in` data" do
|
38
|
+
assert_equal @in_data, subject.in
|
39
|
+
end
|
40
|
+
|
41
|
+
should "pull `in` data using #recvfrom" do
|
42
|
+
recvfrom_data = subject.recvfrom(@in_data.bytesize)
|
43
|
+
|
44
|
+
assert_kind_of ::Array, recvfrom_data
|
45
|
+
assert_equal @in_data, recvfrom_data.first
|
46
|
+
end
|
47
|
+
|
48
|
+
should "reset its `in` data using #reset" do
|
49
|
+
subject.reset('some different in data')
|
50
|
+
assert_equal 'some different in data', subject.in
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class EncodedMessageTests < BaseTests
|
56
|
+
desc "with encoded msg data"
|
57
|
+
setup do
|
58
|
+
setup_some_msg_data
|
59
|
+
end
|
60
|
+
|
61
|
+
should "build with the msg as `in` data given the encoded msg body" do
|
62
|
+
s = FakeSocket.with_encoded_msg_body(@encoded_body)
|
63
|
+
assert_equal @msg, s.in
|
64
|
+
end
|
65
|
+
|
66
|
+
should "build with the msg as `in` data given the unencoded msg body" do
|
67
|
+
s = FakeSocket.with_msg_body(@data)
|
68
|
+
assert_equal @msg, s.in
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class RequestTests < BaseTests
|
74
|
+
desc "that is a request"
|
75
|
+
setup do
|
76
|
+
setup_some_request_data
|
77
|
+
end
|
78
|
+
|
79
|
+
should "build with the request msg as `in` data given the request" do
|
80
|
+
s = FakeSocket.with_request(*@request_params)
|
81
|
+
assert_equal @msg, s.in
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'sanford-protocol/msg_data'
|
3
|
+
require 'sanford-protocol'
|
4
|
+
|
5
|
+
class Sanford::Protocol::MsgData
|
6
|
+
|
7
|
+
class BadMessageTests < Assert::Context
|
8
|
+
desc "reading data on a bad message"
|
9
|
+
setup do
|
10
|
+
@debug = ENV['SANFORD_PROTOCOL_DEBUG']
|
11
|
+
ENV.delete('SANFORD_PROTOCOL_DEBUG')
|
12
|
+
|
13
|
+
@connection = Sanford::Protocol::Connection.new(FakeSocket.new)
|
14
|
+
@socket = @connection.instance_variable_get "@socket"
|
15
|
+
end
|
16
|
+
teardown do
|
17
|
+
ENV['SANFORD_PROTOCOL_DEBUG'] = @debug
|
18
|
+
end
|
19
|
+
subject{ @connection }
|
20
|
+
|
21
|
+
def assert_bad_message(expected_msg)
|
22
|
+
exception = begin; subject.read; rescue Exception => err; err; end
|
23
|
+
assert_instance_of Sanford::Protocol::BadMessageError, exception
|
24
|
+
assert_equal expected_msg, exception.message
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class BadSizeTests < BadMessageTests
|
30
|
+
desc "that errors when reading the version part"
|
31
|
+
setup do
|
32
|
+
@socket.stubs(:read). # when reading the version, fail
|
33
|
+
with(Sanford::Protocol.msg_version.bytesize).
|
34
|
+
raises("simulated socket read error!")
|
35
|
+
end
|
36
|
+
teardown do
|
37
|
+
@socket.unstub(:read)
|
38
|
+
end
|
39
|
+
|
40
|
+
should "raise a BadMessageError with a relevant message" do
|
41
|
+
assert_bad_message "Error reading message protocol version"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class BadVersionTests < BadMessageTests
|
46
|
+
desc "that errors when reading the size part"
|
47
|
+
setup do
|
48
|
+
@socket.stubs(:read). # when reading the version, succeed
|
49
|
+
with(Sanford::Protocol.msg_version.bytesize).
|
50
|
+
returns(Sanford::Protocol.msg_version)
|
51
|
+
@socket.stubs(:read). # when reading the size, fail
|
52
|
+
with(Sanford::Protocol.msg_size.bytes).
|
53
|
+
raises("simulated socket read error!")
|
54
|
+
end
|
55
|
+
teardown do
|
56
|
+
@socket.unstub(:read)
|
57
|
+
end
|
58
|
+
|
59
|
+
should "raise a BadMessageError with a relevant message" do
|
60
|
+
assert_bad_message "Error reading message body size."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class BadBodyTests < BadMessageTests
|
65
|
+
desc "that errors when reading the body part"
|
66
|
+
setup do
|
67
|
+
@socket.stubs(:read). # when reading the version, succeed
|
68
|
+
with(Sanford::Protocol.msg_version.bytesize).
|
69
|
+
returns(Sanford::Protocol.msg_version)
|
70
|
+
@socket.stubs(:read). # when reading the size, succeed
|
71
|
+
with(Sanford::Protocol.msg_size.bytes).
|
72
|
+
returns(Sanford::Protocol.msg_size.encode(50))
|
73
|
+
@socket.stubs(:read). # when reading the body, fail
|
74
|
+
with(50).
|
75
|
+
raises("simulated socket read error!")
|
76
|
+
end
|
77
|
+
teardown do
|
78
|
+
Sanford::Protocol.unstub(:read)
|
79
|
+
end
|
80
|
+
|
81
|
+
should "raise a BadMessageError with a relevant message" do
|
82
|
+
assert_bad_message "Error reading message body."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class NilSizeTests < BadMessageTests
|
87
|
+
desc 'that reads a nil size'
|
88
|
+
setup do
|
89
|
+
@socket.stubs(:read). # when reading the version, succeed
|
90
|
+
with(Sanford::Protocol.msg_version.bytesize).
|
91
|
+
returns(Sanford::Protocol.msg_version)
|
92
|
+
@socket.stubs(:read). # when reading the size, return nil
|
93
|
+
with(Sanford::Protocol.msg_size.bytes).
|
94
|
+
returns(nil)
|
95
|
+
end
|
96
|
+
teardown do
|
97
|
+
@socket.unstub(:read)
|
98
|
+
end
|
99
|
+
|
100
|
+
should "raise a BadMessageError with a relevant message" do
|
101
|
+
assert_bad_message "Empty message size"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class VersionMismatchTests < BadMessageTests
|
106
|
+
desc "with a mismatched protocol version"
|
107
|
+
setup do
|
108
|
+
@socket.stubs(:read). # when reading the version, encode wrong number
|
109
|
+
with(Sanford::Protocol.msg_version.bytesize).
|
110
|
+
returns(Sanford::Protocol::PackedHeader.new(1, 'C').encode(0))
|
111
|
+
end
|
112
|
+
teardown do
|
113
|
+
@socket.unstub(:read)
|
114
|
+
end
|
115
|
+
|
116
|
+
should "raise a BadMessageError with a relevant message" do
|
117
|
+
assert_bad_message "Protocol version mismatch"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# These tests are intended to be brittle and make sure the protocol conforms to
|
2
|
+
# an expected spec. If any of these tests fail, you probably need to modify the
|
3
|
+
# protocol's version constant.
|
4
|
+
|
5
|
+
require 'assert'
|
6
|
+
require 'sanford-protocol'
|
7
|
+
|
8
|
+
module Sanford::Protocol
|
9
|
+
class BaseTests < Assert::Context
|
10
|
+
desc "Sanford::Protocol"
|
11
|
+
subject{ Sanford::Protocol }
|
12
|
+
|
13
|
+
should have_instance_methods :msg_version, :msg_size, :msg_body
|
14
|
+
|
15
|
+
should "define the protocol version" do
|
16
|
+
assert_equal 1, subject::VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
should "encode the protocol version to a 1B binary string" do
|
20
|
+
assert_equal 1, subject.msg_version.bytesize
|
21
|
+
|
22
|
+
expected = [ subject::VERSION ].pack('C')
|
23
|
+
assert_equal expected, subject.msg_version
|
24
|
+
end
|
25
|
+
|
26
|
+
should "encode/decode the size to/from a 4B binary string" do
|
27
|
+
assert_equal 4, subject.msg_size.bytes
|
28
|
+
|
29
|
+
size = (2 ** 32) - 1 # the max number it supports
|
30
|
+
binary = [ size ].pack('N')
|
31
|
+
assert_equal binary, subject.msg_size.encode(size)
|
32
|
+
assert_equal size, subject.msg_size.decode(binary)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "encode the body to BSON" do
|
36
|
+
data = {
|
37
|
+
'string' => 'test',
|
38
|
+
'int' => 1,
|
39
|
+
'float' => 2.1,
|
40
|
+
'boolean' => true,
|
41
|
+
'array' => [ 1, 2, 3 ],
|
42
|
+
'hash' => { 'something' => 'else' }
|
43
|
+
}
|
44
|
+
binary = ::BSON.serialize(data).to_s
|
45
|
+
|
46
|
+
assert_equal binary, subject.msg_body.encode(data)
|
47
|
+
assert_equal data, subject.msg_body.decode(binary)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'sanford-protocol/request'
|
3
|
+
|
4
|
+
class Sanford::Protocol::Request
|
5
|
+
|
6
|
+
class BaseTests < Assert::Context
|
7
|
+
desc "Sanford::Protocol::Request"
|
8
|
+
setup do
|
9
|
+
@request = Sanford::Protocol::Request.new('v1', 'some_service', [ true ])
|
10
|
+
end
|
11
|
+
subject{ @request }
|
12
|
+
|
13
|
+
should have_instance_methods :name, :version, :params, :to_hash, :valid?
|
14
|
+
should have_class_methods :parse
|
15
|
+
|
16
|
+
should "return an instance of a Sanford::Protocol::Request given a hash using #parse" do
|
17
|
+
# using BSON messages are hashes
|
18
|
+
hash = {
|
19
|
+
'name' => 'service_name',
|
20
|
+
'version' => 'service_version',
|
21
|
+
'params' => 'service_params'
|
22
|
+
}
|
23
|
+
request = Sanford::Protocol::Request.parse(hash)
|
24
|
+
|
25
|
+
assert_instance_of Sanford::Protocol::Request, request
|
26
|
+
assert_equal hash['name'], request.name
|
27
|
+
assert_equal hash['version'], request.version
|
28
|
+
assert_equal hash['params'], request.params
|
29
|
+
end
|
30
|
+
|
31
|
+
should "return the request as a hash with #to_hash" do
|
32
|
+
# using BSON messages are hashes
|
33
|
+
expected = {
|
34
|
+
'version' => 'v1',
|
35
|
+
'name' => 'some_service',
|
36
|
+
'params' => [ true ]
|
37
|
+
}
|
38
|
+
|
39
|
+
assert_equal expected, subject.to_hash
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class ValidTests < BaseTests
|
44
|
+
desc "valid?"
|
45
|
+
|
46
|
+
should "return true and no message with a valid request" do
|
47
|
+
request = Sanford::Protocol::Request.new('name', 'v1', {})
|
48
|
+
is_valid, message = request.valid?
|
49
|
+
|
50
|
+
assert_equal true, is_valid
|
51
|
+
assert_equal nil, message
|
52
|
+
end
|
53
|
+
|
54
|
+
should "return false and a message when there isn't a name" do
|
55
|
+
request = Sanford::Protocol::Request.new('v1', nil, {})
|
56
|
+
is_valid, message = request.valid?
|
57
|
+
|
58
|
+
assert_equal false, is_valid
|
59
|
+
assert_equal "The request doesn't contain a name.", message
|
60
|
+
end
|
61
|
+
|
62
|
+
should "return false and a message when there isn't a version" do
|
63
|
+
request = Sanford::Protocol::Request.new(nil, 'name', {})
|
64
|
+
is_valid, message = request.valid?
|
65
|
+
|
66
|
+
assert_equal false, is_valid
|
67
|
+
assert_equal "The request doesn't contain a version.", message
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'sanford-protocol/response_status'
|
3
|
+
|
4
|
+
class Sanford::Protocol::ResponseStatus
|
5
|
+
|
6
|
+
class BaseTests < Assert::Context
|
7
|
+
desc "Sanford::Protocol::ResponseStatus"
|
8
|
+
setup do
|
9
|
+
@status = Sanford::Protocol::ResponseStatus.new(200, "OK")
|
10
|
+
end
|
11
|
+
subject{ @status }
|
12
|
+
|
13
|
+
should have_readers :code_obj, :message
|
14
|
+
should have_instance_methods :code, :name
|
15
|
+
|
16
|
+
should "know it's code name" do
|
17
|
+
named = Sanford::Protocol::ResponseStatus.new(200)
|
18
|
+
unamed = Sanford::Protocol::ResponseStatus.new(999)
|
19
|
+
|
20
|
+
assert_equal "OK", named.name
|
21
|
+
assert_equal nil, unamed.name
|
22
|
+
end
|
23
|
+
|
24
|
+
should "know it's code numbers" do
|
25
|
+
Code::NUMBERS.each do |name, value|
|
26
|
+
status = Sanford::Protocol::ResponseStatus.new(name)
|
27
|
+
assert_equal value, status.code
|
28
|
+
end
|
29
|
+
|
30
|
+
unamed = Sanford::Protocol::ResponseStatus.new('unamed')
|
31
|
+
assert_equal 0, unamed.code
|
32
|
+
end
|
33
|
+
|
34
|
+
should "return it's code number and code name with #to_s" do
|
35
|
+
named = Sanford::Protocol::ResponseStatus.new(200)
|
36
|
+
unamed = Sanford::Protocol::ResponseStatus.new(999)
|
37
|
+
|
38
|
+
assert_equal "[#{named.code}, #{named.name}]", named.to_s
|
39
|
+
assert_equal "[#{unamed.code}]", unamed.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'sanford-protocol/response'
|
3
|
+
|
4
|
+
class Sanford::Protocol::Response
|
5
|
+
|
6
|
+
class BaseTests < Assert::Context
|
7
|
+
desc "Sanford::Protocol::Response"
|
8
|
+
setup do
|
9
|
+
@response = Sanford::Protocol::Response.new([ 672, 'YAR!' ], { 'something' => true })
|
10
|
+
end
|
11
|
+
subject{ @response }
|
12
|
+
|
13
|
+
should have_instance_methods :status, :result, :to_hash
|
14
|
+
should have_class_methods :parse
|
15
|
+
|
16
|
+
should "return an instance of a Sanford::Protocol::Response given a hash using #parse" do
|
17
|
+
# using BSON messages are hashes
|
18
|
+
hash = {
|
19
|
+
'status' => [ 200, 'OK' ],
|
20
|
+
'result' => 'yes'
|
21
|
+
}
|
22
|
+
request = Sanford::Protocol::Response.parse(hash)
|
23
|
+
|
24
|
+
assert_instance_of Sanford::Protocol::Response, request
|
25
|
+
assert_equal hash['status'].first, request.status.code
|
26
|
+
assert_equal hash['status'].last, request.status.message
|
27
|
+
assert_equal hash['result'], request.result
|
28
|
+
end
|
29
|
+
|
30
|
+
should "return the request as a hash with #to_hash" do
|
31
|
+
# using BSON messages are hashes
|
32
|
+
expected = {
|
33
|
+
'status' => [ 672, 'YAR!' ],
|
34
|
+
'result' => { 'something' => true }
|
35
|
+
}
|
36
|
+
|
37
|
+
assert_equal expected, subject.to_hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Somewhat of a system test, want to make sure if Response is passed some
|
42
|
+
# "fuzzy" args that it will build it's status object as expected
|
43
|
+
class StatusBuildingTests < BaseTests
|
44
|
+
|
45
|
+
should "build a status with it's code set, given an integer" do
|
46
|
+
response = Sanford::Protocol::Response.new(574)
|
47
|
+
|
48
|
+
assert_equal 574, response.status.code
|
49
|
+
assert_equal nil, response.status.message
|
50
|
+
end
|
51
|
+
|
52
|
+
should "build a status with it's code set, given a name" do
|
53
|
+
response = Sanford::Protocol::Response.new('ok')
|
54
|
+
|
55
|
+
assert_equal 200, response.status.code
|
56
|
+
assert_equal nil, response.status.message
|
57
|
+
end
|
58
|
+
|
59
|
+
should "use a status object, if given one" do
|
60
|
+
status = Sanford::Protocol::ResponseStatus.new(200, "OK")
|
61
|
+
response = Sanford::Protocol::Response.new(status)
|
62
|
+
|
63
|
+
assert_same status, response.status
|
64
|
+
end
|
65
|
+
|
66
|
+
should "build a status with a code and message set, when given both" do
|
67
|
+
response = Sanford::Protocol::Response.new([ 348, "my message" ])
|
68
|
+
|
69
|
+
assert_equal 348, response.status.code
|
70
|
+
assert_equal "my message", response.status.message
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'assert'
|
2
|
+
require 'sanford-protocol/test/helpers'
|
3
|
+
|
4
|
+
module Sanford::Protocol::Test::Helpers
|
5
|
+
|
6
|
+
class BaseTests < Assert::Context
|
7
|
+
desc "the test helpers"
|
8
|
+
subject { Sanford::Protocol::Test::Helpers }
|
9
|
+
|
10
|
+
should have_imeths :fake_socket_with_request, :fake_socket_with_msg_body
|
11
|
+
should have_imeths :fake_socket_with_encoded_msg_body, :fake_socket_with
|
12
|
+
should have_imeths :read_response_from_fake_socket
|
13
|
+
should have_imeths :read_written_response_from_fake_socket
|
14
|
+
|
15
|
+
should "be able to read responses given a fake socket" do
|
16
|
+
setup_some_response_data
|
17
|
+
fs = FakeSocket.new(@msg)
|
18
|
+
response = subject.read_response_from_fake_socket(fs)
|
19
|
+
|
20
|
+
assert_kind_of Sanford::Protocol::Response, response
|
21
|
+
assert_equal @data, response.to_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
should "be able to read responses written to a fake socket" do
|
25
|
+
setup_some_response_data
|
26
|
+
fs = FakeSocket.new; fs.send(@msg, 0)
|
27
|
+
response = subject.read_written_response_from_fake_socket(fs)
|
28
|
+
|
29
|
+
assert_kind_of Sanford::Protocol::Response, response
|
30
|
+
assert_equal @data, response.to_hash
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sanford-protocol
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Collin Redding
|
14
|
+
- Kelly Redding
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2012-11-14 00:00:00 Z
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bson
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 1
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 7
|
32
|
+
version: "1.7"
|
33
|
+
type: :runtime
|
34
|
+
requirement: *id001
|
35
|
+
prerelease: false
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: assert
|
38
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 27
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
- 8
|
47
|
+
version: "0.8"
|
48
|
+
type: :development
|
49
|
+
requirement: *id002
|
50
|
+
prerelease: false
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: assert-mocha
|
53
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 9
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
- 1
|
62
|
+
version: "0.1"
|
63
|
+
type: :development
|
64
|
+
requirement: *id003
|
65
|
+
prerelease: false
|
66
|
+
description: Ruby implementation of Sanford's communication protocol.
|
67
|
+
email:
|
68
|
+
- collin.redding@me.com
|
69
|
+
- kelly@kellyredding.com
|
70
|
+
executables: []
|
71
|
+
|
72
|
+
extensions: []
|
73
|
+
|
74
|
+
extra_rdoc_files: []
|
75
|
+
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lib/sanford-protocol.rb
|
83
|
+
- lib/sanford-protocol/connection.rb
|
84
|
+
- lib/sanford-protocol/msg_data.rb
|
85
|
+
- lib/sanford-protocol/request.rb
|
86
|
+
- lib/sanford-protocol/response.rb
|
87
|
+
- lib/sanford-protocol/response_status.rb
|
88
|
+
- lib/sanford-protocol/test/fake_socket.rb
|
89
|
+
- lib/sanford-protocol/test/helpers.rb
|
90
|
+
- lib/sanford-protocol/version.rb
|
91
|
+
- sanford-protocol.gemspec
|
92
|
+
- test/helper.rb
|
93
|
+
- test/unit/connection_tests.rb
|
94
|
+
- test/unit/fake_socket_tests.rb
|
95
|
+
- test/unit/msg_data_tests.rb
|
96
|
+
- test/unit/protocol_tests.rb
|
97
|
+
- test/unit/request_tests.rb
|
98
|
+
- test/unit/response_status_tests.rb
|
99
|
+
- test/unit/response_tests.rb
|
100
|
+
- test/unit/test_helpers_tests.rb
|
101
|
+
homepage: ""
|
102
|
+
licenses: []
|
103
|
+
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
hash: 3
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
version: "0"
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
hash: 3
|
124
|
+
segments:
|
125
|
+
- 0
|
126
|
+
version: "0"
|
127
|
+
requirements: []
|
128
|
+
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 1.8.24
|
131
|
+
signing_key:
|
132
|
+
specification_version: 3
|
133
|
+
summary: Ruby implementation of Sanford's communication protocol.
|
134
|
+
test_files:
|
135
|
+
- test/helper.rb
|
136
|
+
- test/unit/connection_tests.rb
|
137
|
+
- test/unit/fake_socket_tests.rb
|
138
|
+
- test/unit/msg_data_tests.rb
|
139
|
+
- test/unit/protocol_tests.rb
|
140
|
+
- test/unit/request_tests.rb
|
141
|
+
- test/unit/response_status_tests.rb
|
142
|
+
- test/unit/response_tests.rb
|
143
|
+
- test/unit/test_helpers_tests.rb
|