From ce35f18b47c36ff13761d50175d1db63c81b9c39 Mon Sep 17 00:00:00 2001 From: Aleksei <9ceb2990-3744-4629-82f3-19bc5c80b3a2@mackevich.addymail.com> Date: Tue, 16 Apr 2024 21:48:58 +0500 Subject: [PATCH 1/3] Add support mounted rack app for rails and hanami --- lib/rspec/openapi/extractors/hanami.rb | 6 ++- lib/rspec/openapi/extractors/rails.rb | 43 ++++++++++++++----- spec/apps/hanami/config/routes.rb | 3 ++ spec/apps/hanami/doc/openapi.json | 36 ++++++++++++++++ spec/apps/hanami/doc/openapi.yaml | 22 ++++++++++ spec/apps/hanami/lib/rack_test/app.rb | 23 ++++++++++ .../rails/config/initializers/rack_test.rb | 1 + spec/apps/rails/config/routes.rb | 1 + spec/apps/rails/doc/openapi.json | 36 ++++++++++++++++ spec/apps/rails/doc/openapi.yaml | 22 ++++++++++ spec/apps/rails/lib/rack_test/app.rb | 17 ++++++++ spec/requests/hanami_spec.rb | 16 +++++++ spec/requests/rails_spec.rb | 16 +++++++ 13 files changed, 230 insertions(+), 12 deletions(-) create mode 100644 spec/apps/hanami/lib/rack_test/app.rb create mode 100644 spec/apps/rails/config/initializers/rack_test.rb create mode 100644 spec/apps/rails/lib/rack_test/app.rb diff --git a/lib/rspec/openapi/extractors/hanami.rb b/lib/rspec/openapi/extractors/hanami.rb index ef3a9fd5..8eab32f6 100644 --- a/lib/rspec/openapi/extractors/hanami.rb +++ b/lib/rspec/openapi/extractors/hanami.rb @@ -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) @@ -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)) diff --git a/lib/rspec/openapi/extractors/rails.rb b/lib/rspec/openapi/extractors/rails.rb index ef269fbe..063f00af 100644 --- a/lib/rspec/openapi/extractors/rails.rb +++ b/lib/rspec/openapi/extractors/rails.rb @@ -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) @@ -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. @@ -42,12 +44,17 @@ 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 @@ -55,4 +62,20 @@ def find_rails_route(request, app: Rails.application, path_prefix: '') 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 diff --git a/spec/apps/hanami/config/routes.rb b/spec/apps/hanami/config/routes.rb index 6d6caf52..1e281448 100644 --- a/spec/apps/hanami/config/routes.rb +++ b/spec/apps/hanami/config/routes.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +require 'rack_test/app' module HanamiTest class Routes < Hanami::Routes @@ -35,5 +36,7 @@ class Routes < Hanami::Routes post '/extensions', to: 'extensions.create' end end + + use RackTest::App end end diff --git a/spec/apps/hanami/doc/openapi.json b/spec/apps/hanami/doc/openapi.json index cf4e3269..46d95fe6 100644 --- a/spec/apps/hanami/doc/openapi.json +++ b/spec/apps/hanami/doc/openapi.json @@ -341,6 +341,42 @@ } } }, + "/rack/bar": { + "get": { + "summary": "GET /rack/bar", + "responses": { + "200": { + "description": "returns some content", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "A RACK BAR" + } + } + } + } + } + }, + "/rack/foo": { + "get": { + "summary": "GET /rack/foo", + "responses": { + "200": { + "description": "returns some content", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "A RACK FOO" + } + } + } + } + } + }, "/secret_items": { "get": { "summary": "index", diff --git a/spec/apps/hanami/doc/openapi.yaml b/spec/apps/hanami/doc/openapi.yaml index 8042346e..be536643 100644 --- a/spec/apps/hanami/doc/openapi.yaml +++ b/spec/apps/hanami/doc/openapi.yaml @@ -212,6 +212,28 @@ paths: schema: type: string example: ANOTHER TEST + "/rack/bar": + get: + summary: GET /rack/bar + responses: + '200': + description: returns some content + content: + text/plain: + schema: + type: string + example: A RACK BAR + "/rack/foo": + get: + summary: GET /rack/foo + responses: + '200': + description: returns some content + content: + text/plain: + schema: + type: string + example: A RACK FOO "/secret_items": get: summary: index diff --git a/spec/apps/hanami/lib/rack_test/app.rb b/spec/apps/hanami/lib/rack_test/app.rb new file mode 100644 index 00000000..2cc41c28 --- /dev/null +++ b/spec/apps/hanami/lib/rack_test/app.rb @@ -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 \ No newline at end of file diff --git a/spec/apps/rails/config/initializers/rack_test.rb b/spec/apps/rails/config/initializers/rack_test.rb new file mode 100644 index 00000000..7cef0a59 --- /dev/null +++ b/spec/apps/rails/config/initializers/rack_test.rb @@ -0,0 +1 @@ +require Rails.root.join('lib/rack_test/app') \ No newline at end of file diff --git a/spec/apps/rails/config/routes.rb b/spec/apps/rails/config/routes.rb index 889043dd..1c6ff973 100644 --- a/spec/apps/rails/config/routes.rb +++ b/spec/apps/rails/config/routes.rb @@ -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']] } diff --git a/spec/apps/rails/doc/openapi.json b/spec/apps/rails/doc/openapi.json index e048b2bf..6d6142b1 100644 --- a/spec/apps/rails/doc/openapi.json +++ b/spec/apps/rails/doc/openapi.json @@ -384,6 +384,42 @@ } } }, + "/rack/bar": { + "get": { + "summary": "GET /rack/bar", + "responses": { + "200": { + "description": "returns some content", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "A RACK BAR" + } + } + } + } + } + }, + "/rack/foo": { + "get": { + "summary": "GET /rack/foo", + "responses": { + "200": { + "description": "returns some content", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "A RACK FOO" + } + } + } + } + } + }, "/secret_items": { "get": { "summary": "index", diff --git a/spec/apps/rails/doc/openapi.yaml b/spec/apps/rails/doc/openapi.yaml index dbafbec8..c9aef615 100644 --- a/spec/apps/rails/doc/openapi.yaml +++ b/spec/apps/rails/doc/openapi.yaml @@ -239,6 +239,28 @@ paths: schema: type: string example: ANOTHER TEST + "/rack/bar": + get: + summary: GET /rack/bar + responses: + '200': + description: returns some content + content: + text/plain: + schema: + type: string + example: A RACK BAR + "/rack/foo": + get: + summary: GET /rack/foo + responses: + '200': + description: returns some content + content: + text/plain: + schema: + type: string + example: A RACK FOO "/secret_items": get: summary: index diff --git a/spec/apps/rails/lib/rack_test/app.rb b/spec/apps/rails/lib/rack_test/app.rb new file mode 100644 index 00000000..95a71d47 --- /dev/null +++ b/spec/apps/rails/lib/rack_test/app.rb @@ -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 \ No newline at end of file diff --git a/spec/requests/hanami_spec.rb b/spec/requests/hanami_spec.rb index 144332c0..fb82fbd1 100644 --- a/spec/requests/hanami_spec.rb +++ b/spec/requests/hanami_spec.rb @@ -281,3 +281,19 @@ end end end + +RSpec.describe 'Rack app test', type: :request do + describe '/rack/foo' do + it 'returns some content' do + get '/rack/foo' + expect(last_response.status).to eq(200) + end + end + + describe '/rack/bar' do + it 'returns some content' do + get '/rack/bar' + expect(last_response.status).to eq(200) + end + end +end diff --git a/spec/requests/rails_spec.rb b/spec/requests/rails_spec.rb index c4e9ca5e..550a307a 100644 --- a/spec/requests/rails_spec.rb +++ b/spec/requests/rails_spec.rb @@ -270,3 +270,19 @@ end end end + +RSpec.describe 'Rack app test', type: :request do + describe '/rack/foo' do + it 'returns some content' do + get '/rack/foo' + expect(response.status).to eq(200) + end + end + + describe '/rack/bar' do + it 'returns some content' do + get '/rack/bar' + expect(response.status).to eq(200) + end + end +end From a711523f7ea63311ece3ce9ce72918a27b3fb09b Mon Sep 17 00:00:00 2001 From: Aleksei <9ceb2990-3744-4629-82f3-19bc5c80b3a2@mackevich.addymail.com> Date: Tue, 16 Apr 2024 21:51:35 +0500 Subject: [PATCH 2/3] Add end line --- spec/apps/hanami/lib/rack_test/app.rb | 2 +- spec/apps/rails/config/initializers/rack_test.rb | 2 +- spec/apps/rails/lib/rack_test/app.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/apps/hanami/lib/rack_test/app.rb b/spec/apps/hanami/lib/rack_test/app.rb index 2cc41c28..7d2cea7f 100644 --- a/spec/apps/hanami/lib/rack_test/app.rb +++ b/spec/apps/hanami/lib/rack_test/app.rb @@ -20,4 +20,4 @@ def call(env) end end end -end \ No newline at end of file +end diff --git a/spec/apps/rails/config/initializers/rack_test.rb b/spec/apps/rails/config/initializers/rack_test.rb index 7cef0a59..b539c24c 100644 --- a/spec/apps/rails/config/initializers/rack_test.rb +++ b/spec/apps/rails/config/initializers/rack_test.rb @@ -1 +1 @@ -require Rails.root.join('lib/rack_test/app') \ No newline at end of file +require Rails.root.join('lib/rack_test/app') diff --git a/spec/apps/rails/lib/rack_test/app.rb b/spec/apps/rails/lib/rack_test/app.rb index 95a71d47..886688df 100644 --- a/spec/apps/rails/lib/rack_test/app.rb +++ b/spec/apps/rails/lib/rack_test/app.rb @@ -14,4 +14,4 @@ def call(env) end end end -end \ No newline at end of file +end From 6a1094de64dcb12dcfb3e03c03a3b4db8acaf50f Mon Sep 17 00:00:00 2001 From: exoego Date: Wed, 17 Apr 2024 15:41:31 +0900 Subject: [PATCH 3/3] Test with minitest --- spec/apps/hanami/doc/openapi.json | 4 ++-- spec/apps/hanami/doc/openapi.yaml | 4 ++-- spec/apps/rails/doc/openapi.json | 4 ++-- spec/apps/rails/doc/openapi.yaml | 4 ++-- spec/integration_tests/rails_test.rb | 15 +++++++++++++++ spec/requests/hanami_spec.rb | 4 ++-- spec/requests/rails_spec.rb | 4 ++-- 7 files changed, 27 insertions(+), 12 deletions(-) diff --git a/spec/apps/hanami/doc/openapi.json b/spec/apps/hanami/doc/openapi.json index 46d95fe6..10ef5fbf 100644 --- a/spec/apps/hanami/doc/openapi.json +++ b/spec/apps/hanami/doc/openapi.json @@ -346,7 +346,7 @@ "summary": "GET /rack/bar", "responses": { "200": { - "description": "returns some content", + "description": "returns some content bar", "content": { "text/plain": { "schema": { @@ -364,7 +364,7 @@ "summary": "GET /rack/foo", "responses": { "200": { - "description": "returns some content", + "description": "returns some content foo", "content": { "text/plain": { "schema": { diff --git a/spec/apps/hanami/doc/openapi.yaml b/spec/apps/hanami/doc/openapi.yaml index be536643..b64e6c1b 100644 --- a/spec/apps/hanami/doc/openapi.yaml +++ b/spec/apps/hanami/doc/openapi.yaml @@ -217,7 +217,7 @@ paths: summary: GET /rack/bar responses: '200': - description: returns some content + description: returns some content bar content: text/plain: schema: @@ -228,7 +228,7 @@ paths: summary: GET /rack/foo responses: '200': - description: returns some content + description: returns some content foo content: text/plain: schema: diff --git a/spec/apps/rails/doc/openapi.json b/spec/apps/rails/doc/openapi.json index 6d6142b1..8f7e3e8d 100644 --- a/spec/apps/rails/doc/openapi.json +++ b/spec/apps/rails/doc/openapi.json @@ -389,7 +389,7 @@ "summary": "GET /rack/bar", "responses": { "200": { - "description": "returns some content", + "description": "returns some content bar", "content": { "text/plain": { "schema": { @@ -407,7 +407,7 @@ "summary": "GET /rack/foo", "responses": { "200": { - "description": "returns some content", + "description": "returns some content foo", "content": { "text/plain": { "schema": { diff --git a/spec/apps/rails/doc/openapi.yaml b/spec/apps/rails/doc/openapi.yaml index c9aef615..f18c52e2 100644 --- a/spec/apps/rails/doc/openapi.yaml +++ b/spec/apps/rails/doc/openapi.yaml @@ -244,7 +244,7 @@ paths: summary: GET /rack/bar responses: '200': - description: returns some content + description: returns some content bar content: text/plain: schema: @@ -255,7 +255,7 @@ paths: summary: GET /rack/foo responses: '200': - description: returns some content + description: returns some content foo content: text/plain: schema: diff --git a/spec/integration_tests/rails_test.rb b/spec/integration_tests/rails_test.rb index 27793de3..ef65946f 100644 --- a/spec/integration_tests/rails_test.rb +++ b/spec/integration_tests/rails_test.rb @@ -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 diff --git a/spec/requests/hanami_spec.rb b/spec/requests/hanami_spec.rb index fb82fbd1..a1951186 100644 --- a/spec/requests/hanami_spec.rb +++ b/spec/requests/hanami_spec.rb @@ -284,14 +284,14 @@ RSpec.describe 'Rack app test', type: :request do describe '/rack/foo' do - it 'returns some content' 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' do + it 'returns some content bar' do get '/rack/bar' expect(last_response.status).to eq(200) end diff --git a/spec/requests/rails_spec.rb b/spec/requests/rails_spec.rb index 550a307a..b41c93f3 100644 --- a/spec/requests/rails_spec.rb +++ b/spec/requests/rails_spec.rb @@ -273,14 +273,14 @@ RSpec.describe 'Rack app test', type: :request do describe '/rack/foo' do - it 'returns some content' 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' do + it 'returns some content bar' do get '/rack/bar' expect(response.status).to eq(200) end