twirp 0.0.4 → 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.
- checksums.yaml +4 -4
- data/README.md +111 -32
- data/example/gen/haberdasher_twirp.rb +1 -2
- data/example/main.rb +4 -3
- data/lib/twirp.rb +0 -1
- data/lib/twirp/error.rb +39 -55
- data/lib/twirp/service.rb +163 -110
- data/lib/twirp/version.rb +1 -1
- data/protoc-gen-twirp_ruby/main.go +3 -4
- data/test/error_test.rb +30 -13
- data/test/fake_services.rb +4 -12
- data/test/service_test.rb +641 -80
- metadata +2 -3
- data/lib/twirp/exception.rb +0 -21
data/lib/twirp/version.rb
CHANGED
@@ -79,17 +79,16 @@ func (g *generator) generateFile(file *descriptor.FileDescriptorProto) *plugin.C
|
|
79
79
|
}
|
80
80
|
for i, service := range file.Service {
|
81
81
|
serviceName := serviceName(service)
|
82
|
-
g.P(fmt.Sprintf("%sclass %sService < Twirp::Service", indent, serviceName))
|
83
|
-
g.P(fmt.Sprintf(`%s PATH_PREFIX = "/twirp/%s.%s"`, indent, pkgName, serviceName))
|
82
|
+
g.P(fmt.Sprintf("%sclass %sService < Twirp::Service", indent, CamelCase(serviceName)))
|
84
83
|
if pkgName != "" {
|
85
84
|
g.P(fmt.Sprintf(`%s package "%s"`, indent, pkgName))
|
86
85
|
}
|
87
|
-
g.P(fmt.Sprintf(`%s service "%s"`, indent,
|
86
|
+
g.P(fmt.Sprintf(`%s service "%s"`, indent, serviceName))
|
88
87
|
for _, method := range service.GetMethod() {
|
89
88
|
methName := methodName(method)
|
90
89
|
inputName := methodInputName(method)
|
91
90
|
outputName := methodOutputName(method)
|
92
|
-
g.P(fmt.Sprintf(`%s rpc :%s, %s, %s, handler_method
|
91
|
+
g.P(fmt.Sprintf(`%s rpc :%s, %s, %s, :handler_method => :%s`,
|
93
92
|
indent, methName, inputName, outputName, SnakeCase(methName)))
|
94
93
|
}
|
95
94
|
g.P(fmt.Sprintf(`%send`, indent))
|
data/test/error_test.rb
CHANGED
@@ -34,6 +34,29 @@ end
|
|
34
34
|
|
35
35
|
class TestTwirpError < Minitest::Test
|
36
36
|
|
37
|
+
def test_constructors # Try out some constructors
|
38
|
+
err = Twirp::Error.internal "woops"
|
39
|
+
assert_equal :internal, err.code
|
40
|
+
assert_equal "woops", err.msg
|
41
|
+
assert_equal({}, err.meta) # empty
|
42
|
+
|
43
|
+
err = Twirp::Error.not_found "not here", who: "Waldo"
|
44
|
+
assert_equal :not_found, err.code
|
45
|
+
assert_equal "not here", err.msg
|
46
|
+
assert_equal({who: "Waldo"}, err.meta)
|
47
|
+
|
48
|
+
err = Twirp::Error.invalid_argument("required", "argument" => "size")
|
49
|
+
assert_equal :invalid_argument, err.code
|
50
|
+
assert_equal "required", err.msg
|
51
|
+
assert_equal({"argument" => "size"}, err.meta) # empty
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_invalid_constructor # Make sure that only supported codes are implemented (prevent bad metaprogramming)
|
55
|
+
assert_raises NoMethodError do
|
56
|
+
Twirp::invalid_code_error "should fail"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
37
60
|
def test_new_with_valid_code_and_a_message
|
38
61
|
err = Twirp::Error.new(:internal, "woops")
|
39
62
|
assert_equal :internal, err.code
|
@@ -43,24 +66,18 @@ class TestTwirpError < Minitest::Test
|
|
43
66
|
|
44
67
|
def test_new_with_valid_metadata
|
45
68
|
err = Twirp::Error.new(:internal, "woops", "meta" => "data", "for this" => "error")
|
46
|
-
assert_equal err.meta["meta"]
|
69
|
+
assert_equal "data", err.meta["meta"]
|
47
70
|
assert_equal "error", err.meta["for this"]
|
48
71
|
assert_nil err.meta["something else"]
|
49
|
-
end
|
50
72
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
73
|
+
err = Twirp::Error.new(:internal, "woops", meta: "data")
|
74
|
+
assert_equal err.meta[:meta], "data"
|
75
|
+
assert_nil err.meta["meta"] # no symbol/string multiaccess for now
|
55
76
|
end
|
56
77
|
|
57
78
|
def test_invalid_metadata
|
58
79
|
Twirp::Error.new(:internal, "woops") # ensure the base case doesn't error
|
59
80
|
|
60
|
-
assert_raises ArgumentError do
|
61
|
-
Twirp::Error.new(:internal, "woops", non_string: "metadata")
|
62
|
-
end
|
63
|
-
|
64
81
|
assert_raises ArgumentError do
|
65
82
|
Twirp::Error.new(:internal, "woops", "string key" => :non_string_value)
|
66
83
|
end
|
@@ -70,14 +87,14 @@ class TestTwirpError < Minitest::Test
|
|
70
87
|
end
|
71
88
|
end
|
72
89
|
|
73
|
-
def
|
90
|
+
def test_to_h
|
74
91
|
# returns a hash with attributes
|
75
92
|
err = Twirp::Error.new(:internal, "err msg", "key" => "val")
|
76
|
-
assert_equal({code: :internal, msg: "err msg", meta: {"key" => "val"}}, err.
|
93
|
+
assert_equal({code: :internal, msg: "err msg", meta: {"key" => "val"}}, err.to_h)
|
77
94
|
|
78
95
|
# skips meta if not included
|
79
96
|
err = Twirp::Error.new(:internal, "err msg")
|
80
|
-
assert_equal({code: :internal, msg: "err msg"}, err.
|
97
|
+
assert_equal({code: :internal, msg: "err msg"}, err.to_h)
|
81
98
|
end
|
82
99
|
end
|
83
100
|
|
data/test/fake_services.rb
CHANGED
@@ -29,27 +29,19 @@ module Example
|
|
29
29
|
class Haberdasher < Twirp::Service
|
30
30
|
package "example"
|
31
31
|
service "Haberdasher"
|
32
|
-
|
33
|
-
rpc "MakeHat", Size, Hat, handler_method: :make_hat
|
32
|
+
rpc :MakeHat, Size, Hat, :handler_method => :make_hat
|
34
33
|
end
|
35
34
|
end
|
36
35
|
|
37
36
|
# Example service handler.
|
38
37
|
# It would be provided by the developer as implementation for the service.
|
39
38
|
class HaberdasherHandler
|
40
|
-
# This fake can optionally be initialized with a block for the make_hat implementation.
|
41
39
|
def initialize(&block)
|
42
|
-
if block_given?
|
43
|
-
@make_hat_block = block
|
44
|
-
else # default implementation
|
45
|
-
@make_hat_block = Proc.new do |size|
|
46
|
-
{inches: size.inches, color: "white"}
|
47
|
-
end
|
48
|
-
end
|
40
|
+
@block = block if block_given?
|
49
41
|
end
|
50
42
|
|
51
|
-
def make_hat(
|
52
|
-
@
|
43
|
+
def make_hat(input, env)
|
44
|
+
@block && @block.call(input, env)
|
53
45
|
end
|
54
46
|
end
|
55
47
|
|
data/test/service_test.rb
CHANGED
@@ -8,14 +8,19 @@ require_relative './fake_services'
|
|
8
8
|
|
9
9
|
class ServiceTest < Minitest::Test
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def setup
|
12
|
+
Example::Haberdasher.raise_exceptions = true # configure for testing to make debugging easier
|
13
|
+
end
|
14
|
+
|
15
|
+
# The rpc DSL should properly build the base Twirp environment for each rpc method.
|
16
|
+
def test_base_envs_accessor
|
17
|
+
assert_equal 1, Example::Haberdasher.base_envs.size
|
14
18
|
assert_equal({
|
15
|
-
|
16
|
-
|
19
|
+
rpc_method: :MakeHat,
|
20
|
+
input_class: Example::Size,
|
21
|
+
output_class: Example::Hat,
|
17
22
|
handler_method: :make_hat,
|
18
|
-
}, Example::Haberdasher.
|
23
|
+
}, Example::Haberdasher.base_envs["MakeHat"])
|
19
24
|
end
|
20
25
|
|
21
26
|
# DSL package and service define the proper data on the service
|
@@ -23,42 +28,27 @@ class ServiceTest < Minitest::Test
|
|
23
28
|
assert_equal "example", Example::Haberdasher.package_name
|
24
29
|
assert_equal "Haberdasher", Example::Haberdasher.service_name
|
25
30
|
assert_equal "example.Haberdasher", Example::Haberdasher.service_full_name
|
26
|
-
assert_equal "/twirp/example.Haberdasher", Example::Haberdasher.path_prefix
|
27
31
|
|
28
32
|
assert_equal "", EmptyService.package_name # defaults to empty string
|
29
33
|
assert_equal "EmptyService", EmptyService.service_name # defaults to class name
|
30
34
|
assert_equal "EmptyService", EmptyService.service_full_name # with no package is just the service name
|
31
|
-
assert_equal "/twirp/EmptyService", EmptyService.path_prefix
|
32
35
|
end
|
33
36
|
|
34
37
|
def test_init_service
|
35
38
|
svc = Example::Haberdasher.new(HaberdasherHandler.new)
|
36
39
|
assert svc.respond_to?(:call) # so it is a Proc that can be used as Rack middleware
|
37
|
-
assert_equal "example.Haberdasher", svc.
|
38
|
-
assert_equal "/twirp/example.Haberdasher", svc.path_prefix
|
40
|
+
assert_equal "example.Haberdasher", svc.full_name
|
39
41
|
end
|
40
42
|
|
41
43
|
def test_init_empty_service
|
42
44
|
empty_svc = EmptyService.new(nil) # an empty service does not need a handler
|
43
45
|
assert empty_svc.respond_to?(:call)
|
44
|
-
assert_equal "EmptyService", empty_svc.
|
45
|
-
assert_equal "/twirp/EmptyService", empty_svc.path_prefix
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_init_failures
|
49
|
-
assert_raises ArgumentError do
|
50
|
-
Example::Haberdasher.new() # handler is mandatory
|
51
|
-
end
|
52
|
-
|
53
|
-
err = assert_raises ArgumentError do
|
54
|
-
Example::Haberdasher.new("fake handler")
|
55
|
-
end
|
56
|
-
assert_equal "Handler must respond to .make_hat(input) in order to handle the message MakeHat.", err.message
|
46
|
+
assert_equal "EmptyService", empty_svc.full_name
|
57
47
|
end
|
58
48
|
|
59
49
|
def test_successful_json_request
|
60
|
-
|
61
|
-
status, headers, body = haberdasher_service.call(
|
50
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
51
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
62
52
|
|
63
53
|
assert_equal 200, status
|
64
54
|
assert_equal 'application/json', headers['Content-Type']
|
@@ -66,8 +56,8 @@ class ServiceTest < Minitest::Test
|
|
66
56
|
end
|
67
57
|
|
68
58
|
def test_successful_proto_request
|
69
|
-
|
70
|
-
status, headers, body = haberdasher_service.call(
|
59
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new(inches: 10)
|
60
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
71
61
|
|
72
62
|
assert_equal 200, status
|
73
63
|
assert_equal 'application/protobuf', headers['Content-Type']
|
@@ -75,80 +65,142 @@ class ServiceTest < Minitest::Test
|
|
75
65
|
end
|
76
66
|
|
77
67
|
def test_bad_route_with_wrong_rpc_method
|
78
|
-
|
79
|
-
status, headers, body = haberdasher_service.call(
|
68
|
+
rack_env = json_req "/twirp/example.Haberdasher/MakeUnicorns", and_rainbows: true
|
69
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
80
70
|
|
81
71
|
assert_equal 404, status
|
82
72
|
assert_equal 'application/json', headers['Content-Type']
|
83
73
|
assert_equal({
|
84
74
|
"code" => 'bad_route',
|
85
|
-
"msg"
|
86
|
-
"meta"=> {"twirp_invalid_route" => "POST /twirp/example.Haberdasher/MakeUnicorns"},
|
75
|
+
"msg" => 'Invalid rpc method "MakeUnicorns"',
|
76
|
+
"meta" => {"twirp_invalid_route" => "POST /twirp/example.Haberdasher/MakeUnicorns"},
|
87
77
|
}, JSON.parse(body[0]))
|
88
78
|
end
|
89
79
|
|
90
80
|
def test_bad_route_with_wrong_http_method
|
91
|
-
|
81
|
+
rack_env = Rack::MockRequest.env_for "example.Haberdasher/MakeHat",
|
92
82
|
method: "GET", input: '{"inches": 10}', "CONTENT_TYPE" => "application/json"
|
93
|
-
status, headers, body = haberdasher_service.call(
|
83
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
94
84
|
|
95
85
|
assert_equal 404, status
|
96
86
|
assert_equal 'application/json', headers['Content-Type']
|
97
87
|
assert_equal({
|
98
88
|
"code" => 'bad_route',
|
99
|
-
"msg"
|
100
|
-
"meta"=> {"twirp_invalid_route" => "GET /
|
89
|
+
"msg" => 'HTTP request method must be POST',
|
90
|
+
"meta" => {"twirp_invalid_route" => "GET /example.Haberdasher/MakeHat"},
|
101
91
|
}, JSON.parse(body[0]))
|
102
92
|
end
|
103
93
|
|
104
94
|
def test_bad_route_with_wrong_content_type
|
105
|
-
|
95
|
+
rack_env = Rack::MockRequest.env_for "example.Haberdasher/MakeHat",
|
106
96
|
method: "POST", input: 'free text', "CONTENT_TYPE" => "text/plain"
|
107
|
-
status, headers, body = haberdasher_service.call(
|
97
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
108
98
|
|
109
99
|
assert_equal 404, status
|
110
100
|
assert_equal 'application/json', headers['Content-Type']
|
111
101
|
assert_equal({
|
112
102
|
"code" => 'bad_route',
|
113
|
-
"msg"
|
114
|
-
"meta"=> {"twirp_invalid_route" => "POST /
|
103
|
+
"msg" => 'unexpected Content-Type: "text/plain". Content-Type header must be one of application/json or application/protobuf',
|
104
|
+
"meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
|
115
105
|
}, JSON.parse(body[0]))
|
116
106
|
end
|
117
107
|
|
118
108
|
def test_bad_route_with_wrong_path_json
|
119
|
-
|
120
|
-
status, headers, body = haberdasher_service.call(
|
109
|
+
rack_env = json_req "/wrongpath", {}
|
110
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
121
111
|
|
122
112
|
assert_equal 404, status
|
123
113
|
assert_equal 'application/json', headers['Content-Type']
|
124
114
|
assert_equal({
|
125
115
|
"code" => 'bad_route',
|
126
|
-
"msg"
|
127
|
-
"meta"=> {"twirp_invalid_route" => "POST /wrongpath"},
|
116
|
+
"msg" => 'Invalid route. Expected format: POST {BaseURL}/example.Haberdasher/{Method}',
|
117
|
+
"meta" => {"twirp_invalid_route" => "POST /wrongpath"},
|
128
118
|
}, JSON.parse(body[0]))
|
129
119
|
end
|
130
120
|
|
131
121
|
def test_bad_route_with_wrong_path_protobuf
|
132
|
-
|
133
|
-
status, headers, body = haberdasher_service.call(
|
122
|
+
rack_env = proto_req "/another/wrong.Path/MakeHat", Example::Empty.new()
|
123
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
134
124
|
|
135
125
|
assert_equal 404, status
|
136
126
|
assert_equal 'application/json', headers['Content-Type'] # error responses are always JSON, even for Protobuf requests
|
137
127
|
assert_equal({
|
138
128
|
"code" => 'bad_route',
|
139
|
-
"msg"
|
140
|
-
"meta"=> {"twirp_invalid_route" => "POST /another/wrong.Path/MakeHat"},
|
129
|
+
"msg" => 'Invalid route. Expected format: POST {BaseURL}/example.Haberdasher/{Method}',
|
130
|
+
"meta" => {"twirp_invalid_route" => "POST /another/wrong.Path/MakeHat"},
|
131
|
+
}, JSON.parse(body[0]))
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_bad_route_with_wrong_json_body
|
135
|
+
rack_env = Rack::MockRequest.env_for "example.Haberdasher/MakeHat",
|
136
|
+
method: "POST", input: 'bad json', "CONTENT_TYPE" => "application/json"
|
137
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
138
|
+
|
139
|
+
assert_equal 404, status
|
140
|
+
assert_equal 'application/json', headers['Content-Type']
|
141
|
+
assert_equal({
|
142
|
+
"code" => 'bad_route',
|
143
|
+
"msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/json',
|
144
|
+
"meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
|
141
145
|
}, JSON.parse(body[0]))
|
142
146
|
end
|
143
147
|
|
148
|
+
def test_bad_route_with_wrong_protobuf_body
|
149
|
+
rack_env = Rack::MockRequest.env_for "example.Haberdasher/MakeHat",
|
150
|
+
method: "POST", input: 'bad protobuf', "CONTENT_TYPE" => "application/protobuf"
|
151
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
152
|
+
|
153
|
+
assert_equal 404, status
|
154
|
+
assert_equal 'application/json', headers['Content-Type']
|
155
|
+
assert_equal({
|
156
|
+
"code" => 'bad_route',
|
157
|
+
"msg" => 'Invalid request body for rpc method "MakeHat" with Content-Type=application/protobuf',
|
158
|
+
"meta" => {"twirp_invalid_route" => "POST /example.Haberdasher/MakeHat"},
|
159
|
+
}, JSON.parse(body[0]))
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_bad_route_triggers_on_error_hooks
|
163
|
+
svc = haberdasher_service
|
164
|
+
|
165
|
+
before_called = false
|
166
|
+
svc.before do |rack_env, env|
|
167
|
+
before_called = true
|
168
|
+
end
|
169
|
+
|
170
|
+
success_called = false
|
171
|
+
svc.on_success do |env|
|
172
|
+
success_called = true
|
173
|
+
end
|
174
|
+
|
175
|
+
error_called = false
|
176
|
+
svc.on_error do |twerr, env|
|
177
|
+
error_called = true
|
178
|
+
end
|
179
|
+
|
180
|
+
rack_env = json_req "/example.Haberdasher/BadRouteMethod", inches: 10
|
181
|
+
status, headers, body = svc.call(rack_env)
|
182
|
+
|
183
|
+
assert_equal 404, status
|
184
|
+
refute before_called
|
185
|
+
refute success_called
|
186
|
+
assert error_called # << on_error called
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_long_base_url
|
190
|
+
rack_env = json_req "long-ass/base/url/twirp/example.Haberdasher/MakeHat", {inches: 10}
|
191
|
+
status, headers, body = haberdasher_service.call(rack_env)
|
192
|
+
|
193
|
+
assert_equal 200, status
|
194
|
+
end
|
195
|
+
|
144
196
|
# Handler should be able to return an instance of the proto message
|
145
197
|
def test_handler_returns_a_proto_message
|
146
|
-
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size|
|
198
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
147
199
|
Example::Hat.new(inches: 11)
|
148
200
|
end)
|
149
201
|
|
150
|
-
|
151
|
-
status, headers, body = svc.call(
|
202
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
203
|
+
status, headers, body = svc.call(rack_env)
|
152
204
|
|
153
205
|
assert_equal 200, status
|
154
206
|
assert_equal Example::Hat.new(inches: 11, color: ""), Example::Hat.decode(body[0])
|
@@ -156,62 +208,569 @@ class ServiceTest < Minitest::Test
|
|
156
208
|
|
157
209
|
# Handler should be able to return a hash with attributes
|
158
210
|
def test_handler_returns_hash_attributes
|
159
|
-
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size|
|
211
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
160
212
|
{inches: 11}
|
161
213
|
end)
|
162
214
|
|
163
|
-
|
164
|
-
status, headers, body = svc.call(
|
215
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
216
|
+
status, headers, body = svc.call(rack_env)
|
165
217
|
|
166
218
|
assert_equal 200, status
|
167
219
|
assert_equal Example::Hat.new(inches: 11, color: ""), Example::Hat.decode(body[0])
|
168
220
|
end
|
169
221
|
|
170
|
-
# Handler should be able to return
|
171
|
-
def
|
172
|
-
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size|
|
222
|
+
# Handler should be able to return Twirp::Error values, that will trigger error responses
|
223
|
+
def test_handler_returns_twirp_error
|
224
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
225
|
+
return Twirp::Error.invalid_argument "I don't like that size"
|
226
|
+
end)
|
227
|
+
|
228
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new(inches: 666)
|
229
|
+
status, headers, body = svc.call(rack_env)
|
230
|
+
assert_equal 400, status
|
231
|
+
assert_equal 'application/json', headers['Content-Type'] # error responses are always JSON, even for Protobuf requests
|
232
|
+
assert_equal({
|
233
|
+
"code" => 'invalid_argument',
|
234
|
+
"msg" => "I don't like that size",
|
235
|
+
}, JSON.parse(body[0]))
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_handler_method_can_set_response_headers_through_the_env
|
239
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
240
|
+
env[:http_response_headers]["Cache-Control"] = "public, max-age=60"
|
241
|
+
{}
|
242
|
+
end)
|
243
|
+
|
244
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
245
|
+
status, headers, body = svc.call(rack_env)
|
246
|
+
|
247
|
+
assert_equal 200, status
|
248
|
+
assert_equal "public, max-age=60", headers["Cache-Control"] # set by the handler
|
249
|
+
assert_equal "application/protobuf", headers["Content-Type"] # set by Twirp::Service
|
250
|
+
end
|
251
|
+
|
252
|
+
def test_handler_returns_invalid_type_nil
|
253
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
173
254
|
nil
|
174
255
|
end)
|
175
256
|
|
176
|
-
|
177
|
-
status, headers, body = svc.call(
|
257
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
258
|
+
status, headers, body = svc.call(rack_env)
|
259
|
+
|
260
|
+
assert_equal 500, status
|
261
|
+
assert_equal({
|
262
|
+
"code" => 'internal',
|
263
|
+
"msg" => "Handler method make_hat expected to return one of Example::Hat, Hash or Twirp::Error, but returned NilClass.",
|
264
|
+
}, JSON.parse(body[0]))
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_handler_returns_invalid_type_string
|
268
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
269
|
+
"bad type"
|
270
|
+
end)
|
271
|
+
|
272
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
273
|
+
status, headers, body = svc.call(rack_env)
|
274
|
+
|
275
|
+
assert_equal 500, status
|
276
|
+
assert_equal({
|
277
|
+
"code" => 'internal',
|
278
|
+
"msg" => "Handler method make_hat expected to return one of Example::Hat, Hash or Twirp::Error, but returned String.",
|
279
|
+
}, JSON.parse(body[0]))
|
280
|
+
end
|
281
|
+
|
282
|
+
def test_handler_not_implementing_method
|
283
|
+
svc = Example::Haberdasher.new("foo")
|
284
|
+
|
285
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
286
|
+
status, headers, body = svc.call(rack_env)
|
287
|
+
|
288
|
+
assert_equal 501, status
|
289
|
+
assert_equal({
|
290
|
+
"code" => 'unimplemented',
|
291
|
+
"msg" => "Handler method make_hat is not implemented.",
|
292
|
+
}, JSON.parse(body[0]))
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_handler_returns_invalid_attributes
|
296
|
+
Example::Haberdasher.raise_exceptions = false
|
297
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
298
|
+
{bad_attribute: "bad value", lol: 666}
|
299
|
+
end)
|
300
|
+
|
301
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
302
|
+
status, headers, body = svc.call(rack_env)
|
303
|
+
|
304
|
+
assert_equal 500, status
|
305
|
+
assert_equal({
|
306
|
+
"code" => 'internal',
|
307
|
+
"msg" => "Unknown field name 'bad_attribute' in initialization map entry.",
|
308
|
+
"meta" => {"cause"=>"ArgumentError"},
|
309
|
+
}, JSON.parse(body[0]))
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_handler_raises_standard_error
|
313
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
314
|
+
10 / 0 # divided by 0
|
315
|
+
end)
|
316
|
+
|
317
|
+
# check that tests can see raised errors from the handler with raise_exceptions = true
|
318
|
+
assert_raises ZeroDivisionError do
|
319
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
320
|
+
svc.call(rack_env)
|
321
|
+
end
|
322
|
+
|
323
|
+
# check that the error is properly wrapped with raise_exceptions = false
|
324
|
+
Example::Haberdasher.raise_exceptions = false
|
325
|
+
rack_env = proto_req "/example.Haberdasher/MakeHat", Example::Size.new
|
326
|
+
status, headers, body = svc.call(rack_env)
|
327
|
+
|
328
|
+
assert_equal 500, status
|
329
|
+
assert_equal({
|
330
|
+
"code" => 'internal',
|
331
|
+
"msg" => "divided by 0",
|
332
|
+
"meta" => {"cause"=>"ZeroDivisionError"},
|
333
|
+
}, JSON.parse(body[0]))
|
334
|
+
end
|
335
|
+
|
336
|
+
def test_before_simple
|
337
|
+
handler_method_called = false
|
338
|
+
handler = HaberdasherHandler.new do |size, env|
|
339
|
+
handler_method_called = true
|
340
|
+
{}
|
341
|
+
end
|
342
|
+
|
343
|
+
called_with_env = nil
|
344
|
+
called_with_rack_env = nil
|
345
|
+
svc = Example::Haberdasher.new(handler)
|
346
|
+
svc.before do |rack_env, env|
|
347
|
+
env[:raw_contet_type] = rack_env["CONTENT_TYPE"]
|
348
|
+
called_with_env = env
|
349
|
+
called_with_rack_env = rack_env
|
350
|
+
end
|
351
|
+
|
352
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
353
|
+
status, headers, body = svc.call(rack_env)
|
354
|
+
|
355
|
+
refute_nil called_with_env, "the before hook was called with a Twirp env"
|
356
|
+
assert_equal :MakeHat, called_with_env[:rpc_method]
|
357
|
+
assert_equal Example::Size.new(inches: 10), called_with_env[:input]
|
358
|
+
assert_equal "application/json", called_with_env[:raw_contet_type]
|
359
|
+
|
360
|
+
refute_nil called_with_rack_env, "the before hook was called with a Rack env"
|
361
|
+
assert_equal "POST", called_with_rack_env["REQUEST_METHOD"]
|
362
|
+
|
363
|
+
assert handler_method_called, "the handler method was called"
|
364
|
+
assert_equal 200, status, "response is successful"
|
365
|
+
end
|
366
|
+
|
367
|
+
def test_before_add_data_in_env_for_the_handler_method
|
368
|
+
val = nil
|
369
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
370
|
+
val = env[:from_the_hook]
|
371
|
+
{}
|
372
|
+
end)
|
373
|
+
svc.before do |rack_env, env|
|
374
|
+
env[:from_the_hook] = "hello handler"
|
375
|
+
end
|
376
|
+
|
377
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
378
|
+
status, headers, body = svc.call(rack_env)
|
178
379
|
|
179
380
|
assert_equal 200, status
|
180
|
-
assert_equal
|
381
|
+
assert_equal "hello handler", val
|
181
382
|
end
|
182
383
|
|
183
|
-
|
184
|
-
|
185
|
-
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size|
|
186
|
-
|
384
|
+
def test_before_returning_twirp_error_cancels_request
|
385
|
+
handler_called = false
|
386
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
387
|
+
handler_called = true
|
388
|
+
{}
|
187
389
|
end)
|
390
|
+
svc.before do |rack_env, env|
|
391
|
+
return Twirp::Error.internal "error from before hook"
|
392
|
+
end
|
188
393
|
|
189
|
-
|
190
|
-
status, headers, body = svc.call(
|
191
|
-
|
192
|
-
assert_equal
|
394
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
395
|
+
status, headers, body = svc.call(rack_env)
|
396
|
+
|
397
|
+
assert_equal 500, status
|
398
|
+
refute handler_called
|
193
399
|
assert_equal({
|
194
|
-
"code" => '
|
195
|
-
"msg"
|
400
|
+
"code" => 'intenal',
|
401
|
+
"msg" => 'error from before hook',
|
196
402
|
}, JSON.parse(body[0]))
|
197
403
|
end
|
198
404
|
|
199
|
-
|
200
|
-
|
201
|
-
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size|
|
202
|
-
|
405
|
+
def test_before_raising_exception_cancels_request
|
406
|
+
handler_called = false
|
407
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
408
|
+
handler_called = true
|
409
|
+
{}
|
203
410
|
end)
|
411
|
+
svc.before do |rack_env, env|
|
412
|
+
1 / 0 # divided by 0.
|
413
|
+
end
|
204
414
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
415
|
+
Example::Haberdasher.raise_exceptions = false
|
416
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
417
|
+
status, headers, body = svc.call(rack_env)
|
418
|
+
|
419
|
+
assert_equal 500, status
|
420
|
+
refute handler_called
|
209
421
|
assert_equal({
|
210
|
-
"code" => '
|
211
|
-
"msg"
|
422
|
+
"code" => 'internal',
|
423
|
+
"msg" => "divided by 0",
|
424
|
+
"meta" => {"cause"=>"ZeroDivisionError"},
|
425
|
+
}, JSON.parse(body[0]))
|
426
|
+
end
|
427
|
+
|
428
|
+
def test_before_multiple_run_in_order
|
429
|
+
handler_called = false
|
430
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
431
|
+
handler_called = true
|
432
|
+
refute_nil env[:from_hook_1]
|
433
|
+
refute_nil env[:from_hook_2]
|
434
|
+
{}
|
435
|
+
end)
|
436
|
+
svc.before do |rack_env, env|
|
437
|
+
assert_nil env[:from_hook_1]
|
438
|
+
assert_nil env[:from_hook_2]
|
439
|
+
env[:from_hook_1] = true
|
440
|
+
end
|
441
|
+
svc.before do |rack_env, env|
|
442
|
+
refute_nil env[:from_hook_1]
|
443
|
+
assert_nil env[:from_hook_2]
|
444
|
+
env[:from_hook_2] = true
|
445
|
+
end
|
446
|
+
|
447
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
448
|
+
status, headers, body = svc.call(rack_env)
|
449
|
+
|
450
|
+
assert_equal 200, status
|
451
|
+
assert_equal true, handler_called
|
452
|
+
end
|
453
|
+
|
454
|
+
def test_before_multiple_first_returning_error_halts_chain
|
455
|
+
hook1_called = false
|
456
|
+
hook2_called = false
|
457
|
+
handler_called = false
|
458
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
459
|
+
handler_called = true
|
460
|
+
{}
|
461
|
+
end)
|
462
|
+
svc.before do |rack_env, env|
|
463
|
+
hook1_called = true
|
464
|
+
return Twirp::Error.internal "hook1 failed"
|
465
|
+
end
|
466
|
+
svc.before do |rack_env, env|
|
467
|
+
hook2_called = true
|
468
|
+
return Twirp::Error.internal "hook2 failed"
|
469
|
+
end
|
470
|
+
|
471
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
472
|
+
status, headers, body = svc.call(rack_env)
|
473
|
+
|
474
|
+
assert_equal 500, status
|
475
|
+
assert_equal 'application/json', headers['Content-Type']
|
476
|
+
assert_equal({
|
477
|
+
"code" => 'intenal',
|
478
|
+
"msg" => 'hook1 failed',
|
479
|
+
}, JSON.parse(body[0]))
|
480
|
+
|
481
|
+
assert hook1_called
|
482
|
+
refute hook2_called
|
483
|
+
refute handler_called
|
484
|
+
end
|
485
|
+
|
486
|
+
def test_on_success_has_env_output
|
487
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |input, env|
|
488
|
+
env[:handler_called] = true
|
489
|
+
{inches: 88, color: "blue"}
|
490
|
+
end)
|
491
|
+
success_called = false
|
492
|
+
svc.on_success do |env|
|
493
|
+
success_called = true
|
494
|
+
assert env[:handler_called]
|
495
|
+
assert_equal Example::Hat.new(inches: 88, color: "blue"), env[:output] # output from hadnler
|
496
|
+
end
|
497
|
+
|
498
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
499
|
+
status, headers, body = svc.call(rack_env)
|
500
|
+
|
501
|
+
assert_equal 200, status
|
502
|
+
assert success_called
|
503
|
+
end
|
504
|
+
|
505
|
+
def test_on_success_does_not_trigger_error
|
506
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |input, env|
|
507
|
+
return Twirp::Error.internal "error from handler"
|
508
|
+
end)
|
509
|
+
success_called = false
|
510
|
+
svc.on_success do |env|
|
511
|
+
success_called = true
|
512
|
+
end
|
513
|
+
|
514
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
515
|
+
status, headers, body = svc.call(rack_env)
|
516
|
+
|
517
|
+
assert_equal 500, status
|
518
|
+
refute success_called # after hook not called
|
519
|
+
assert_equal({
|
520
|
+
"code" => 'intenal',
|
521
|
+
"msg" => 'error from handler',
|
212
522
|
}, JSON.parse(body[0]))
|
213
523
|
end
|
214
524
|
|
525
|
+
def test_on_success_multiple_run_in_order
|
526
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
527
|
+
env[:from_handler] = true
|
528
|
+
{}
|
529
|
+
end)
|
530
|
+
svc.on_success do |env|
|
531
|
+
refute_nil env[:from_handler]
|
532
|
+
assert_nil env[:from_hook_1]
|
533
|
+
assert_nil env[:from_hook_2]
|
534
|
+
env[:from_hook_1] = true
|
535
|
+
end
|
536
|
+
hook2_called = false
|
537
|
+
svc.on_success do |env|
|
538
|
+
refute_nil env[:from_handler]
|
539
|
+
refute_nil env[:from_hook_1]
|
540
|
+
assert_nil env[:from_hook_2]
|
541
|
+
env[:from_hook_2] = true
|
542
|
+
hook2_called = true
|
543
|
+
end
|
544
|
+
|
545
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
546
|
+
status, headers, body = svc.call(rack_env)
|
547
|
+
|
548
|
+
assert_equal 200, status
|
549
|
+
assert hook2_called
|
550
|
+
end
|
551
|
+
|
552
|
+
def test_on_success_raising_exception_is_handled_as_internal
|
553
|
+
handler_called = false
|
554
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
555
|
+
handler_called = true
|
556
|
+
{}
|
557
|
+
end)
|
558
|
+
svc.on_success do |env|
|
559
|
+
1 / 0 # divided by 0
|
560
|
+
end
|
561
|
+
|
562
|
+
Example::Haberdasher.raise_exceptions = false
|
563
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
564
|
+
status, headers, body = svc.call(rack_env)
|
565
|
+
|
566
|
+
assert_equal 500, status
|
567
|
+
assert handler_called
|
568
|
+
assert_equal({
|
569
|
+
"code" => 'internal',
|
570
|
+
"msg" => "divided by 0",
|
571
|
+
"meta" => {"cause"=>"ZeroDivisionError"},
|
572
|
+
}, JSON.parse(body[0]))
|
573
|
+
end
|
574
|
+
|
575
|
+
def test_on_error_after_before_fails
|
576
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
577
|
+
{}
|
578
|
+
end)
|
579
|
+
svc.before do |rack_env, env|
|
580
|
+
return Twirp::Error.internal "before failed"
|
581
|
+
end
|
582
|
+
error_called = false
|
583
|
+
svc.on_error do |twerr, env|
|
584
|
+
error_called = true
|
585
|
+
assert_equal :internal, twerr.code
|
586
|
+
assert_equal "before failed", twerr.msg
|
587
|
+
end
|
588
|
+
|
589
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
590
|
+
status, headers, body = svc.call(rack_env)
|
591
|
+
|
592
|
+
assert_equal 500, status
|
593
|
+
assert_equal({
|
594
|
+
"code" => 'intenal',
|
595
|
+
"msg" => 'before failed',
|
596
|
+
}, JSON.parse(body[0]))
|
597
|
+
assert error_called
|
598
|
+
end
|
599
|
+
|
600
|
+
def test_on_error_after_before_raises_exception
|
601
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
602
|
+
{}
|
603
|
+
end)
|
604
|
+
svc.before do |rack_env, env|
|
605
|
+
1 / 0 # divided by 0
|
606
|
+
end
|
607
|
+
Example::Haberdasher.raise_exceptions = false
|
608
|
+
|
609
|
+
error_called = false
|
610
|
+
svc.on_error do |twerr, env|
|
611
|
+
error_called = true
|
612
|
+
end
|
613
|
+
|
614
|
+
exception_raised_called = false
|
615
|
+
svc.exception_raised do |e, env|
|
616
|
+
exception_raised_called = true
|
617
|
+
assert_equal "divided by 0", e.message
|
618
|
+
end
|
619
|
+
|
620
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
621
|
+
status, headers, body = svc.call(rack_env)
|
622
|
+
|
623
|
+
assert_equal 500, status
|
624
|
+
assert_equal({
|
625
|
+
"code" => 'internal',
|
626
|
+
"msg" => "divided by 0",
|
627
|
+
"meta" => {"cause"=>"ZeroDivisionError"},
|
628
|
+
}, JSON.parse(body[0]))
|
629
|
+
assert error_called
|
630
|
+
assert exception_raised_called
|
631
|
+
end
|
632
|
+
|
633
|
+
def test_on_error_after_handler_returns_twirp_error
|
634
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
635
|
+
return Twirp::Error.internal "handler error"
|
636
|
+
end)
|
637
|
+
|
638
|
+
error_called = false
|
639
|
+
svc.on_error do |twerr, env|
|
640
|
+
error_called = true
|
641
|
+
assert_equal :internal, twerr.code
|
642
|
+
assert_equal "handler error", twerr.msg
|
643
|
+
end
|
644
|
+
|
645
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
646
|
+
status, headers, body = svc.call(rack_env)
|
647
|
+
|
648
|
+
assert_equal 500, status
|
649
|
+
assert_equal({
|
650
|
+
"code" => 'intenal',
|
651
|
+
"msg" => 'handler error',
|
652
|
+
}, JSON.parse(body[0]))
|
653
|
+
assert error_called
|
654
|
+
end
|
655
|
+
|
656
|
+
def test_on_error_after_handler_raises_exception
|
657
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
658
|
+
1 / 0 # divided by 0
|
659
|
+
end)
|
660
|
+
Example::Haberdasher.raise_exceptions = false
|
661
|
+
|
662
|
+
error_called = false
|
663
|
+
svc.on_error do |twerr, env|
|
664
|
+
error_called = true
|
665
|
+
end
|
666
|
+
|
667
|
+
exception_raised_called = false
|
668
|
+
svc.exception_raised do |e, env|
|
669
|
+
exception_raised_called = true
|
670
|
+
assert_equal "divided by 0", e.message
|
671
|
+
end
|
672
|
+
|
673
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
674
|
+
status, headers, body = svc.call(rack_env)
|
675
|
+
|
676
|
+
assert_equal 500, status
|
677
|
+
assert_equal({
|
678
|
+
"code" => 'internal',
|
679
|
+
"msg" => "divided by 0",
|
680
|
+
"meta" => {"cause"=>"ZeroDivisionError"},
|
681
|
+
}, JSON.parse(body[0]))
|
682
|
+
assert error_called
|
683
|
+
assert exception_raised_called
|
684
|
+
end
|
685
|
+
|
686
|
+
def test_on_error_is_not_called_after_success_raises_exception
|
687
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
688
|
+
{}
|
689
|
+
end)
|
690
|
+
svc.on_success do |env|
|
691
|
+
1 / 0 # divided by 0
|
692
|
+
end
|
693
|
+
Example::Haberdasher.raise_exceptions = false
|
694
|
+
|
695
|
+
error_called = false
|
696
|
+
svc.on_error do |twerr, env|
|
697
|
+
error_called = true
|
698
|
+
end
|
699
|
+
|
700
|
+
exception_raised_called = false
|
701
|
+
svc.exception_raised do |e, env|
|
702
|
+
exception_raised_called = true
|
703
|
+
assert_equal "divided by 0", e.message
|
704
|
+
end
|
705
|
+
|
706
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
707
|
+
status, headers, body = svc.call(rack_env)
|
708
|
+
|
709
|
+
assert_equal 500, status
|
710
|
+
assert_equal({
|
711
|
+
"code" => 'internal',
|
712
|
+
"msg" => "divided by 0",
|
713
|
+
"meta" => {"cause"=>"ZeroDivisionError"},
|
714
|
+
}, JSON.parse(body[0]))
|
715
|
+
refute error_called # << NOT CALLED
|
716
|
+
assert exception_raised_called
|
717
|
+
end
|
718
|
+
|
719
|
+
def test_on_error_multiple_run_in_order
|
720
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
721
|
+
env[:from_handler] = true
|
722
|
+
Twirp::Error.not_found "my friend"
|
723
|
+
end)
|
724
|
+
svc.on_error do |twerr, env|
|
725
|
+
refute_nil env[:from_handler]
|
726
|
+
assert_nil env[:from_hook_1]
|
727
|
+
assert_nil env[:from_hook_2]
|
728
|
+
env[:from_hook_1] = true
|
729
|
+
end
|
730
|
+
hook2_called = false
|
731
|
+
svc.on_error do |twerr, env|
|
732
|
+
refute_nil env[:from_handler]
|
733
|
+
refute_nil env[:from_hook_1]
|
734
|
+
assert_nil env[:from_hook_2]
|
735
|
+
env[:from_hook_2] = true
|
736
|
+
hook2_called = true
|
737
|
+
end
|
738
|
+
|
739
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
740
|
+
status, headers, body = svc.call(rack_env)
|
741
|
+
|
742
|
+
assert_equal 404, status
|
743
|
+
assert hook2_called
|
744
|
+
end
|
745
|
+
|
746
|
+
def test_on_error_raising_exception_is_handled_as_internal
|
747
|
+
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size, env|
|
748
|
+
Twirp::Error.permission_denied "blah blah"
|
749
|
+
end)
|
750
|
+
svc.on_error do |twerr, env|
|
751
|
+
1 / 0 # divided by 0
|
752
|
+
end
|
753
|
+
Example::Haberdasher.raise_exceptions = false
|
754
|
+
|
755
|
+
exception_raised_called = false
|
756
|
+
svc.exception_raised do |e, env|
|
757
|
+
exception_raised_called = true
|
758
|
+
end
|
759
|
+
|
760
|
+
rack_env = json_req "/example.Haberdasher/MakeHat", inches: 10
|
761
|
+
status, headers, body = svc.call(rack_env)
|
762
|
+
|
763
|
+
assert_equal 500, status
|
764
|
+
assert_equal({
|
765
|
+
"code" => 'internal',
|
766
|
+
"msg" => "divided by 0",
|
767
|
+
"meta" => {"cause"=>"ZeroDivisionError"},
|
768
|
+
}, JSON.parse(body[0]))
|
769
|
+
assert exception_raised_called
|
770
|
+
end
|
771
|
+
|
772
|
+
|
773
|
+
|
215
774
|
|
216
775
|
# Test Helpers
|
217
776
|
# ------------
|
@@ -229,7 +788,9 @@ class ServiceTest < Minitest::Test
|
|
229
788
|
end
|
230
789
|
|
231
790
|
def haberdasher_service
|
232
|
-
Example::Haberdasher.new(HaberdasherHandler.new
|
791
|
+
Example::Haberdasher.new(HaberdasherHandler.new do |size, _|
|
792
|
+
{inches: size.inches, color: "white"}
|
793
|
+
end)
|
233
794
|
end
|
234
795
|
end
|
235
796
|
|