Skip to content

Commit 763f211

Browse files
committed
Move Test.observe into Observe module. Add more tests.
1 parent 6f332b6 commit 763f211

File tree

7 files changed

+250
-20
lines changed

7 files changed

+250
-20
lines changed

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ group :test, :development do
1212
gem 'bundler'
1313
gem 'minitest'
1414
gem 'rack-test'
15+
gem 'rails'
1516
gem 'rake'
1617
gem 'rspec'
1718
gem 'rubocop'
1819
gem 'rubocop-performance'
1920
gem 'simplecov'
21+
gem 'sinatra'
2022
end

Gemfile.lock

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ PATH
1010
GEM
1111
remote: https://rubygems.org/
1212
specs:
13+
actioncable (8.0.2)
14+
actionpack (= 8.0.2)
15+
activesupport (= 8.0.2)
16+
nio4r (~> 2.0)
17+
websocket-driver (>= 0.6.1)
18+
zeitwerk (~> 2.6)
19+
actionmailbox (8.0.2)
20+
actionpack (= 8.0.2)
21+
activejob (= 8.0.2)
22+
activerecord (= 8.0.2)
23+
activestorage (= 8.0.2)
24+
activesupport (= 8.0.2)
25+
mail (>= 2.8.0)
26+
actionmailer (8.0.2)
27+
actionpack (= 8.0.2)
28+
actionview (= 8.0.2)
29+
activejob (= 8.0.2)
30+
activesupport (= 8.0.2)
31+
mail (>= 2.8.0)
32+
rails-dom-testing (~> 2.2)
1333
actionpack (8.0.2)
1434
actionview (= 8.0.2)
1535
activesupport (= 8.0.2)
@@ -20,12 +40,34 @@ GEM
2040
rails-dom-testing (~> 2.2)
2141
rails-html-sanitizer (~> 1.6)
2242
useragent (~> 0.16)
43+
actiontext (8.0.2)
44+
actionpack (= 8.0.2)
45+
activerecord (= 8.0.2)
46+
activestorage (= 8.0.2)
47+
activesupport (= 8.0.2)
48+
globalid (>= 0.6.0)
49+
nokogiri (>= 1.8.5)
2350
actionview (8.0.2)
2451
activesupport (= 8.0.2)
2552
builder (~> 3.1)
2653
erubi (~> 1.11)
2754
rails-dom-testing (~> 2.2)
2855
rails-html-sanitizer (~> 1.6)
56+
activejob (8.0.2)
57+
activesupport (= 8.0.2)
58+
globalid (>= 0.3.6)
59+
activemodel (8.0.2)
60+
activesupport (= 8.0.2)
61+
activerecord (8.0.2)
62+
activemodel (= 8.0.2)
63+
activesupport (= 8.0.2)
64+
timeout (>= 0.4.0)
65+
activestorage (8.0.2)
66+
actionpack (= 8.0.2)
67+
activejob (= 8.0.2)
68+
activerecord (= 8.0.2)
69+
activesupport (= 8.0.2)
70+
marcel (~> 1.0)
2971
activesupport (8.0.2)
3072
base64
3173
benchmark (>= 0.3)
@@ -47,13 +89,22 @@ GEM
4789
concurrent-ruby (1.3.5)
4890
connection_pool (2.5.3)
4991
crass (1.0.6)
92+
date (3.4.1)
5093
diff-lcs (1.6.2)
5194
docile (1.4.1)
5295
drb (2.2.3)
96+
erb (5.0.1)
5397
erubi (1.13.1)
98+
globalid (1.2.1)
99+
activesupport (>= 6.1)
54100
hana (1.3.7)
55101
i18n (1.14.7)
56102
concurrent-ruby (~> 1.0)
103+
io-console (0.8.0)
104+
irb (1.15.2)
105+
pp (>= 0.6.0)
106+
rdoc (>= 4.0.0)
107+
reline (>= 0.4.2)
57108
json (2.12.2)
58109
json_schemer (2.4.0)
59110
bigdecimal
@@ -66,7 +117,26 @@ GEM
66117
loofah (2.24.1)
67118
crass (~> 1.0.2)
68119
nokogiri (>= 1.12.0)
120+
mail (2.8.1)
121+
mini_mime (>= 0.1.1)
122+
net-imap
123+
net-pop
124+
net-smtp
125+
marcel (1.0.4)
126+
mini_mime (1.1.5)
69127
minitest (5.25.5)
128+
mustermann (3.0.3)
129+
ruby2_keywords (~> 0.0.1)
130+
net-imap (0.5.9)
131+
date
132+
net-protocol
133+
net-pop (0.1.2)
134+
net-protocol
135+
net-protocol (0.2.2)
136+
timeout
137+
net-smtp (0.5.1)
138+
net-protocol
139+
nio4r (2.7.4)
70140
nokogiri (1.18.8-arm64-darwin)
71141
racc (~> 1.4)
72142
nokogiri (1.18.8-x86_64-linux-gnu)
@@ -77,26 +147,65 @@ GEM
77147
parser (3.3.8.0)
78148
ast (~> 2.4.1)
79149
racc
150+
pp (0.6.2)
151+
prettyprint
152+
prettyprint (0.2.0)
80153
prism (1.4.0)
154+
psych (5.2.6)
155+
date
156+
stringio
81157
racc (1.8.1)
82158
rack (3.1.16)
159+
rack-protection (4.1.1)
160+
base64 (>= 0.1.0)
161+
logger (>= 1.6.0)
162+
rack (>= 3.0.0, < 4)
83163
rack-session (2.1.1)
84164
base64 (>= 0.1.0)
85165
rack (>= 3.0.0)
86166
rack-test (2.2.0)
87167
rack (>= 1.3)
88168
rackup (2.2.1)
89169
rack (>= 3)
170+
rails (8.0.2)
171+
actioncable (= 8.0.2)
172+
actionmailbox (= 8.0.2)
173+
actionmailer (= 8.0.2)
174+
actionpack (= 8.0.2)
175+
actiontext (= 8.0.2)
176+
actionview (= 8.0.2)
177+
activejob (= 8.0.2)
178+
activemodel (= 8.0.2)
179+
activerecord (= 8.0.2)
180+
activestorage (= 8.0.2)
181+
activesupport (= 8.0.2)
182+
bundler (>= 1.15.0)
183+
railties (= 8.0.2)
90184
rails-dom-testing (2.3.0)
91185
activesupport (>= 5.0.0)
92186
minitest
93187
nokogiri (>= 1.6)
94188
rails-html-sanitizer (1.6.2)
95189
loofah (~> 2.21)
96190
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
191+
railties (8.0.2)
192+
actionpack (= 8.0.2)
193+
activesupport (= 8.0.2)
194+
irb (~> 1.13)
195+
rackup (>= 1.0.0)
196+
rake (>= 12.2)
197+
thor (~> 1.0, >= 1.2.2)
198+
zeitwerk (~> 2.6)
97199
rainbow (3.1.1)
98200
rake (13.3.0)
201+
rdoc (6.14.1)
202+
erb
203+
psych (>= 4.0.0)
99204
regexp_parser (2.10.0)
205+
reline (0.6.1)
206+
io-console (~> 0.5)
207+
roda (3.93.0)
208+
rack
100209
rspec (3.13.1)
101210
rspec-core (~> 3.13.0)
102211
rspec-expectations (~> 3.13.0)
@@ -129,6 +238,7 @@ GEM
129238
rubocop (>= 1.75.0, < 2.0)
130239
rubocop-ast (>= 1.38.0, < 2.0)
131240
ruby-progressbar (1.13.0)
241+
ruby2_keywords (0.0.5)
132242
securerandom (0.4.1)
133243
simplecov (0.22.0)
134244
docile (~> 1.1)
@@ -137,13 +247,29 @@ GEM
137247
simplecov-html (0.13.1)
138248
simplecov_json_formatter (0.1.4)
139249
simpleidn (0.2.3)
250+
sinatra (4.1.1)
251+
logger (>= 1.6.0)
252+
mustermann (~> 3.0)
253+
rack (>= 3.0.0, < 4)
254+
rack-protection (= 4.1.1)
255+
rack-session (>= 2.0.0, < 3)
256+
tilt (~> 2.0)
257+
stringio (3.1.7)
258+
thor (1.3.2)
259+
tilt (2.6.0)
260+
timeout (0.4.3)
140261
tzinfo (2.0.6)
141262
concurrent-ruby (~> 1.0)
142263
unicode-display_width (3.1.4)
143264
unicode-emoji (~> 4.0, >= 4.0.4)
144265
unicode-emoji (4.0.4)
145266
uri (1.0.3)
146267
useragent (0.16.11)
268+
websocket-driver (0.8.0)
269+
base64
270+
websocket-extensions (>= 0.1.0)
271+
websocket-extensions (0.1.5)
272+
zeitwerk (2.7.3)
147273

148274
PLATFORMS
149275
arm64-darwin-21
@@ -159,11 +285,14 @@ DEPENDENCIES
159285
rack (>= 3.0.0)
160286
rack-test
161287
rackup
288+
rails
162289
rake
290+
roda
163291
rspec
164292
rubocop
165293
rubocop-performance
166294
simplecov
295+
sinatra
167296

168297
BUNDLED WITH
169298
2.3.10

lib/openapi_first/test.rb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@ module Test
99
autoload :Coverage, 'openapi_first/test/coverage'
1010
autoload :Methods, 'openapi_first/test/methods'
1111
autoload :Callable, 'openapi_first/test/callable'
12+
autoload :Observe, 'openapi_first/test/observe'
1213
extend Registry
1314

1415
class CoverageError < Error; end
1516

16-
# @visible private
17-
module Observed; end
18-
1917
# Inject request/response validation in a rack app class
2018
def self.observe(app, api: :default)
21-
mod = Callable[api:]
22-
app.prepend(mod) unless app.include?(Observed)
23-
app.include(Observed)
19+
Observe.observe(app, api:)
2420
end
2521

2622
def self.minitest?(base)

lib/openapi_first/test/callable.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ module Test
77
module Callable
88
# Returns a Module with a `call(env)` method that wraps super inside silent request/response validation
99
# You can use this like `Application.prepend(OpenapiFirst::Test.app_module)` to monitor your app during testing.
10-
def self.[](api: :default)
11-
definition = OpenapiFirst::Test[api]
10+
def self.[](definition)
1211
Module.new.tap do |mod|
1312
mod.define_method(:call) do |env|
1413
request = Rack::Request.new(env)
14+
1515
definition.validate_request(request, raise_error: false)
1616
response = super(env)
1717
status, headers, body = response

lib/openapi_first/test/observe.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# frozen_string_literal: true
2+
3+
module OpenapiFirst
4+
module Test
5+
class ObserveError < Error; end
6+
7+
# @visible private
8+
module Observed; end
9+
10+
# Inject silent request/response validation to observe rack apps during testing
11+
module Observe
12+
def self.observe(app, api: :default)
13+
unless app.instance_methods.include?(:call)
14+
raise ObserveError, "Don't know how to observe #{app}, because it has no call instance method."
15+
end
16+
17+
return if app.include?(Observed)
18+
19+
definition = OpenapiFirst::Test[api]
20+
mod = OpenapiFirst::Test::Callable[definition]
21+
app.prepend(mod)
22+
app.include(Observed)
23+
end
24+
end
25+
end
26+
end

spec/test/observe_spec.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe OpenapiFirst::Test::Observe do
4+
RSpec.shared_examples 'an observed app' do
5+
it 'injects request/response validation' do
6+
described_class.observe(app)
7+
8+
expect(definition).to receive(:validate_request)
9+
expect(definition).to receive(:validate_response)
10+
11+
callable.call(Rack::MockRequest.env_for('/'))
12+
end
13+
end
14+
15+
let(:definition) { OpenapiFirst.load('./examples/openapi.yaml') }
16+
let(:callable) { app }
17+
18+
before do
19+
OpenapiFirst::Test.register(definition)
20+
end
21+
22+
context 'with a simple class' do
23+
let(:app) do
24+
Class.new do
25+
def call(_env)
26+
Rack::Response.new.finish
27+
end
28+
end
29+
end
30+
31+
let(:callable) { app.new }
32+
33+
it_behaves_like 'an observed app'
34+
35+
it 'injects request/response validation only once' do
36+
2.times { described_class.observe(app) }
37+
38+
expect(definition).to receive(:validate_request).once
39+
expect(definition).to receive(:validate_response).once
40+
41+
callable.call({})
42+
end
43+
end
44+
45+
context 'with a class that has no call method' do
46+
let(:app) do
47+
Class.new
48+
end
49+
50+
it 'raises an error when trying to observe' do
51+
expect do
52+
described_class.observe(app)
53+
end.to raise_error OpenapiFirst::Test::ObserveError
54+
55+
app.instance_methods.include?(:call)
56+
end
57+
end
58+
59+
context 'with a Sinatra app' do
60+
require 'sinatra/base'
61+
62+
let(:app) do
63+
Class.new(Sinatra::Base)
64+
end
65+
it_behaves_like 'an observed app'
66+
67+
context 'when using the class method' do
68+
let(:callable) { app }
69+
70+
it_behaves_like 'an observed app'
71+
end
72+
end
73+
74+
context 'with a Rails app' do
75+
require 'rails'
76+
require 'action_controller/railtie'
77+
78+
before do
79+
Rails.logger = Logger.new(StringIO.new)
80+
end
81+
82+
let(:app) do
83+
Class.new(Rails::Application)
84+
end
85+
86+
it_behaves_like 'an observed app'
87+
end
88+
end

0 commit comments

Comments
 (0)