diff --git a/lib/rspec/openapi/record_builder.rb b/lib/rspec/openapi/record_builder.rb index 54f5fd93..8d3f1962 100644 --- a/lib/rspec/openapi/record_builder.rb +++ b/lib/rspec/openapi/record_builder.rb @@ -69,8 +69,14 @@ def extract_request_attributes(request, example) raw_path_params = request.path_parameters path = request.path if rails? - route = find_rails_route(request) - path = route.path.spec.to_s.delete_suffix('(.:format)') + # 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. @@ -102,21 +108,18 @@ def rack_test?(context) end # @param [ActionDispatch::Request] request - def find_rails_route(request, app: Rails.application, fix_path: true) - # 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 - if fix_path && !request.script_name.empty? - request = request.dup - request.path_info = File.join(request.script_name, request.path_info) - end - + def find_rails_route(request, app: Rails.application, path_prefix: '') app.routes.router.recognize(request) do |route| + path = route.path.spec.to_s if route.app.matches?(request) - return find_rails_route(request, app: route.app.app, fix_path: false) if route.app.engine? - - return route + if route.app.engine? + route, path = find_rails_route(request, app: route.app.app, path_prefix: path) + next if route.nil? + end + return [route, path_prefix + path] end end - raise "No route matched for #{request.request_method} #{request.path_info}" + nil end # workaround to get real request parameters diff --git a/spec/integration_tests/rails_test.rb b/spec/integration_tests/rails_test.rb index 65109cd1..043b0a80 100644 --- a/spec/integration_tests/rails_test.rb +++ b/spec/integration_tests/rails_test.rb @@ -189,3 +189,12 @@ class EngineTest < ActionDispatch::IntegrationTest assert_response 200 end end + +class EngineExtraRoutesTest < ActionDispatch::IntegrationTest + openapi! + + test 'returns the block content' do + get '/my_engine/test' + assert_response 200 + end +end diff --git a/spec/rails/config/routes.rb b/spec/rails/config/routes.rb index 4acd108b..24e09746 100644 --- a/spec/rails/config/routes.rb +++ b/spec/rails/config/routes.rb @@ -1,6 +1,8 @@ Rails.application.routes.draw do mount ::MyEngine::Engine => '/my_engine' + get '/my_engine/test' => ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['ANOTHER TEST']] } + defaults format: 'json' do resources :tables, only: [:index, :show, :create, :update, :destroy] resources :images, only: [:index, :show] do diff --git a/spec/rails/doc/openapi.json b/spec/rails/doc/openapi.json index e6518ace..07f98407 100644 --- a/spec/rails/doc/openapi.json +++ b/spec/rails/doc/openapi.json @@ -720,9 +720,9 @@ } } }, - "/eng_route": { + "/my_engine/eng_route": { "get": { - "summary": "GET /eng_route", + "summary": "GET /my_engine/eng_route", "tags": [ ], @@ -741,6 +741,27 @@ } } }, + "/my_engine/test": { + "get": { + "summary": "GET /my_engine/test", + "tags": [ + + ], + "responses": { + "200": { + "description": "returns the block content", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "ANOTHER TEST" + } + } + } + } + } + }, "/images": { "get": { "summary": "index", diff --git a/spec/rails/doc/openapi.yaml b/spec/rails/doc/openapi.yaml index 7e7eb55d..3ea218c6 100644 --- a/spec/rails/doc/openapi.yaml +++ b/spec/rails/doc/openapi.yaml @@ -488,9 +488,9 @@ paths: schema: type: string example: A TEST - "/eng_route": + "/my_engine/eng_route": get: - summary: GET /eng_route + summary: GET /my_engine/eng_route tags: [] responses: '200': @@ -526,6 +526,18 @@ paths: example: - name: file.png tags: [] + "/my_engine/test": + get: + summary: GET /my_engine/test + tags: [] + responses: + '200': + description: returns the block content + content: + text/plain: + schema: + type: string + example: ANOTHER TEST "/images/upload": post: summary: upload diff --git a/spec/requests/rails_spec.rb b/spec/requests/rails_spec.rb index d5febe6c..c6b61558 100644 --- a/spec/requests/rails_spec.rb +++ b/spec/requests/rails_spec.rb @@ -197,3 +197,12 @@ end end end + +RSpec.describe 'Engine extra routes', type: :request do + describe '#test' do + it 'returns the block content' do + get '/my_engine/test' + expect(response.status).to eq(200) + end + end +end