Skip to content

Commit

Permalink
Add support mounted rack app for rails and hanami (#213)
Browse files Browse the repository at this point in the history
* Add support mounted rack app for rails and hanami

* Add end line

* Test with minitest

---------

Co-authored-by: Aleksei <[email protected]>
Co-authored-by: exoego <[email protected]>
  • Loading branch information
3 people committed Apr 17, 2024
1 parent dc14c65 commit 493cde9
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 12 deletions.
6 changes: 4 additions & 2 deletions lib/rspec/openapi/extractors/hanami.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class << RSpec::OpenAPI::Extractors::Hanami = Object.new
# @param [RSpec::Core::Example] example
# @return Array
def request_attributes(request, example)
route = Hanami.app.router.recognize(request.path, method: request.method)

return RSpec::OpenAPI::Extractors::Rack.request_attributes(request, example) unless route.routable?

metadata = example.metadata[:openapi] || {}
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
Expand All @@ -62,8 +66,6 @@ def request_attributes(request, example)
deprecated = metadata[:deprecated]
path = request.path

route = Hanami.app.router.recognize(request.path, method: request.method)

raw_path_params = route.params.filter { |_key, value| number_or_nil(value) }

result = InspectorAnalyzer.call(request.method, add_id(path, route))
Expand Down
43 changes: 33 additions & 10 deletions lib/rspec/openapi/extractors/rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ class << RSpec::OpenAPI::Extractors::Rails = Object.new
# @param [RSpec::Core::Example] example
# @return Array
def request_attributes(request, example)
# Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
fixed_request = request.dup
fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?

route, path = find_rails_route(fixed_request)

raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?

return RSpec::OpenAPI::Extractors::Rack.request_attributes(request, example) unless path

metadata = example.metadata[:openapi] || {}
summary = metadata[:summary] || RSpec::OpenAPI.summary_builder.call(example)
tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example)
Expand All @@ -16,14 +26,6 @@ def request_attributes(request, example)
deprecated = metadata[:deprecated]
raw_path_params = request.path_parameters

# Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
fixed_request = request.dup
fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?

route, path = find_rails_route(fixed_request)
raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?

path = path.delete_suffix('(.:format)')
summary ||= route.requirements[:action]
tags ||= [route.requirements[:controller]&.classify].compact
# :controller and :action always exist. :format is added when routes is configured as such.
Expand All @@ -42,17 +44,38 @@ def request_response(context)

# @param [ActionDispatch::Request] request
def find_rails_route(request, app: Rails.application, path_prefix: '')
app.routes.router.recognize(request) do |route|
path = route.path.spec.to_s
app.routes.router.recognize(request) do |route, parameters|
path = route.path.spec.to_s.delete_suffix('(.:format)')

if route.app.matches?(request)
if route.app.engine?
route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
next if route.nil?
elsif path_prefix + path == add_id(request.path, parameters)
return [route, path_prefix + path]
else
return [route, nil]
end
return [route, path_prefix + path]
end
end

nil
end

def add_id(path, parameters)
parameters.each_pair do |key, value|
next unless number_or_nil(value)

path = path.sub("/#{value}", "/:#{key}")
end

path
end

def number_or_nil(string)
Integer(string || '')
rescue ArgumentError
nil
end
end
3 changes: 3 additions & 0 deletions spec/apps/hanami/config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true
require 'rack_test/app'

module HanamiTest
class Routes < Hanami::Routes
Expand Down Expand Up @@ -35,5 +36,7 @@ class Routes < Hanami::Routes
post '/extensions', to: 'extensions.create'
end
end

use RackTest::App
end
end
36 changes: 36 additions & 0 deletions spec/apps/hanami/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,42 @@
}
}
},
"/rack/bar": {
"get": {
"summary": "GET /rack/bar",
"responses": {
"200": {
"description": "returns some content bar",
"content": {
"text/plain": {
"schema": {
"type": "string"
},
"example": "A RACK BAR"
}
}
}
}
}
},
"/rack/foo": {
"get": {
"summary": "GET /rack/foo",
"responses": {
"200": {
"description": "returns some content foo",
"content": {
"text/plain": {
"schema": {
"type": "string"
},
"example": "A RACK FOO"
}
}
}
}
}
},
"/secret_items": {
"get": {
"summary": "index",
Expand Down
22 changes: 22 additions & 0 deletions spec/apps/hanami/doc/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,28 @@ paths:
schema:
type: string
example: ANOTHER TEST
"/rack/bar":
get:
summary: GET /rack/bar
responses:
'200':
description: returns some content bar
content:
text/plain:
schema:
type: string
example: A RACK BAR
"/rack/foo":
get:
summary: GET /rack/foo
responses:
'200':
description: returns some content foo
content:
text/plain:
schema:
type: string
example: A RACK FOO
"/secret_items":
get:
summary: index
Expand Down
23 changes: 23 additions & 0 deletions spec/apps/hanami/lib/rack_test/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module RackTest
class App
def initialize(app)
@app = app
end

def call(env)
req = Rack::Request.new(env)
path = req.path_info

case path
when "/rack/foo"
[200, { 'Content-Type' => 'text/plain' }, ['A RACK FOO']]
when "/rack/bar"
[200, { 'Content-Type' => 'text/plain' }, ['A RACK BAR']]
else
return @app.call(env)
end
end
end
end
1 change: 1 addition & 0 deletions spec/apps/rails/config/initializers/rack_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require Rails.root.join('lib/rack_test/app')
1 change: 1 addition & 0 deletions spec/apps/rails/config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Rails.application.routes.draw do
mount ::MyEngine::Engine => '/my_engine'
mount ::RackTest::App.new, at: '/rack'

get '/my_engine/test' => ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['ANOTHER TEST']] }

Expand Down
36 changes: 36 additions & 0 deletions spec/apps/rails/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,42 @@
}
}
},
"/rack/bar": {
"get": {
"summary": "GET /rack/bar",
"responses": {
"200": {
"description": "returns some content bar",
"content": {
"text/plain": {
"schema": {
"type": "string"
},
"example": "A RACK BAR"
}
}
}
}
}
},
"/rack/foo": {
"get": {
"summary": "GET /rack/foo",
"responses": {
"200": {
"description": "returns some content foo",
"content": {
"text/plain": {
"schema": {
"type": "string"
},
"example": "A RACK FOO"
}
}
}
}
}
},
"/secret_items": {
"get": {
"summary": "index",
Expand Down
22 changes: 22 additions & 0 deletions spec/apps/rails/doc/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,28 @@ paths:
schema:
type: string
example: ANOTHER TEST
"/rack/bar":
get:
summary: GET /rack/bar
responses:
'200':
description: returns some content bar
content:
text/plain:
schema:
type: string
example: A RACK BAR
"/rack/foo":
get:
summary: GET /rack/foo
responses:
'200':
description: returns some content foo
content:
text/plain:
schema:
type: string
example: A RACK FOO
"/secret_items":
get:
summary: index
Expand Down
17 changes: 17 additions & 0 deletions spec/apps/rails/lib/rack_test/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module RackTest
class App
def call(env)
req = Rack::Request.new(env)
path = req.path_info

case path
when "/foo"
[200, { 'Content-Type' => 'text/plain' }, ['A RACK FOO']]
when "/bar"
[200, { 'Content-Type' => 'text/plain' }, ['A RACK BAR']]
end
end
end
end
15 changes: 15 additions & 0 deletions spec/integration_tests/rails_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,18 @@ class NamespaceTest < ActionDispatch::IntegrationTest
assert_response 200
end
end

class RackAppTest < ActionDispatch::IntegrationTest
i_suck_and_my_tests_are_order_dependent!
openapi!

test 'returns some content foo' do
get '/rack/foo/'
assert_response 200
end

test 'returns some content bar' do
get '/rack/bar'
assert_response 200
end
end
16 changes: 16 additions & 0 deletions spec/requests/hanami_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,19 @@
end
end
end

RSpec.describe 'Rack app test', type: :request do
describe '/rack/foo' do
it 'returns some content foo' do
get '/rack/foo'
expect(last_response.status).to eq(200)
end
end

describe '/rack/bar' do
it 'returns some content bar' do
get '/rack/bar'
expect(last_response.status).to eq(200)
end
end
end
16 changes: 16 additions & 0 deletions spec/requests/rails_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,19 @@
end
end
end

RSpec.describe 'Rack app test', type: :request do
describe '/rack/foo' do
it 'returns some content foo' do
get '/rack/foo'
expect(response.status).to eq(200)
end
end

describe '/rack/bar' do
it 'returns some content bar' do
get '/rack/bar'
expect(response.status).to eq(200)
end
end
end

0 comments on commit 493cde9

Please sign in to comment.