diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93077e3a..fc31f1e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ jobs: matrix: include: # Recent Rubies and Rails + - ruby-version: '3.3' - ruby-version: '3.2' - ruby-version: '3.1' - ruby-version: '3.0' diff --git a/lib/rspec_api_documentation/api_formatter.rb b/lib/rspec_api_documentation/api_formatter.rb index 7a9f97f3..117df936 100644 --- a/lib/rspec_api_documentation/api_formatter.rb +++ b/lib/rspec_api_documentation/api_formatter.rb @@ -2,7 +2,7 @@ module RspecApiDocumentation class ApiFormatter < RSpec::Core::Formatters::BaseTextFormatter - RSpec::Core::Formatters.register self, :example_passed, :example_failed, :stop + RSpec::Core::Formatters.register self, :example_passed, :example_failed, :stop, :example_group_started def initialize(output) super @@ -19,7 +19,7 @@ def start(notification) def example_group_started(notification) super - output.puts " #{@example_group.description}" + output.puts " #{notification.group.description}" end def example_passed(example_notification) diff --git a/lib/rspec_api_documentation/client_base.rb b/lib/rspec_api_documentation/client_base.rb index db0560a3..34ccbdb1 100644 --- a/lib/rspec_api_documentation/client_base.rb +++ b/lib/rspec_api_documentation/client_base.rb @@ -45,7 +45,8 @@ def process(method, path, params = {}, headers ={}) def read_request_body input = last_request.env["rack.input"] - input.rewind + return "" unless input + input.rewind if input.respond_to?(:rewind) input.read end @@ -89,6 +90,12 @@ def record_response_body(response_content_type, response_body) return nil if response_body.empty? formatter = RspecApiDocumentation.configuration.response_body_formatter + # Only force UTF-8 for text-based content types + if response_body.respond_to?(:encoding) && response_body.encoding == Encoding::ASCII_8BIT + if response_content_type && (response_content_type.include?('json') || response_content_type.include?('text')) + response_body = response_body.force_encoding(Encoding::UTF_8) + end + end formatter.call(response_content_type, response_body) end diff --git a/lib/rspec_api_documentation/headers.rb b/lib/rspec_api_documentation/headers.rb index d3041cde..465fe5cf 100644 --- a/lib/rspec_api_documentation/headers.rb +++ b/lib/rspec_api_documentation/headers.rb @@ -6,7 +6,7 @@ def env_to_headers(env) headers = {} env.each do |key, value| # HTTP_ACCEPT_CHARSET => Accept-Charset - if key =~ /^(HTTP_|CONTENT_TYPE)/ + if key =~ /^(HTTP_|CONTENT_TYPE)/ && key != "HTTP_VERSION" header = key.gsub(/^HTTP_/, '').split('_').map{|s| s.titleize}.join("-") headers[header] = value end diff --git a/lib/rspec_api_documentation/open_api/node.rb b/lib/rspec_api_documentation/open_api/node.rb index 2f102c88..71b2179a 100644 --- a/lib/rspec_api_documentation/open_api/node.rb +++ b/lib/rspec_api_documentation/open_api/node.rb @@ -102,9 +102,10 @@ def as_json end end + def settings; @settings ||= {} end + private - def settings; @settings ||= {} end def instance_settings; @instance_settings ||= [] end def self.class_settings; @class_settings ||= [] end end diff --git a/rspec_api_documentation.gemspec b/rspec_api_documentation.gemspec index 978b3bf0..b96fcdf7 100644 --- a/rspec_api_documentation.gemspec +++ b/rspec_api_documentation.gemspec @@ -15,28 +15,52 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.6" s.add_runtime_dependency "rspec", "~> 3.0" + s.add_development_dependency "rspec", "~> 3.0" s.add_runtime_dependency "activesupport", ">= 3.0.0" s.add_runtime_dependency "mustache", "~> 1.0", ">= 0.99.4" - s.add_development_dependency "bundler", ">= 1.16" - s.add_development_dependency "fakefs", "~> 0.6.0" - s.add_development_dependency "sinatra", "~> 2.0.8" - s.add_development_dependency "aruba", "~> 0.14.14" - s.add_development_dependency "capybara", "~> 3.39.2" - s.add_development_dependency "rake", "~> 13.2.1" - s.add_development_dependency "rack-test", "~> 0.6.3" - s.add_development_dependency "rack-oauth2", "~> 1.12.0" - s.add_development_dependency "webmock", "~> 3.23.0" - s.add_development_dependency "rspec-its", "~> 1.3.0" - s.add_development_dependency "faraday", "~> 1.0.0" - s.add_development_dependency "nokogiri", "~> 1.8.4" - s.add_development_dependency "yard", "~> 0.9.15" - s.add_development_dependency "inch", "~> 0.8.0" - s.add_development_dependency "minitest", "~> 5.8.4" - s.add_development_dependency "contracts", "~> 0.17" - s.add_development_dependency "gherkin", "~> 9.0.0" - s.add_development_dependency "multi_json", "~> 1.15.0" - s.add_development_dependency "rspec", "~> 3.0" + if RUBY_VERSION < '2.7' + s.add_development_dependency "bundler", ">= 1.16" + s.add_development_dependency "fakefs", "~> 0.6.0" + s.add_development_dependency "sinatra", "~> 1.4.7" + s.add_development_dependency "aruba", "~> 0.13.0" + s.add_development_dependency "capybara", "~> 2.6.2" + s.add_development_dependency "rake", "~> 10.5.0" + s.add_development_dependency "rack-test", "~> 0.6.3" + s.add_development_dependency "rack-oauth2", "~> 1.2.2" + s.add_development_dependency "webmock", "~> 3.8.3" + s.add_development_dependency "rspec-its", "~> 1.2.0" + s.add_development_dependency "faraday", "~> 1.0.0" + s.add_development_dependency "nokogiri", "~> 1.8.4" + s.add_development_dependency "yard", "~> 0.9.15" + s.add_development_dependency "inch", "~> 0.8.0" + s.add_development_dependency "minitest", "~> 5.8.4" + s.add_development_dependency "contracts", "~> 0.13.0" + s.add_development_dependency "gherkin", "~> 3.2.0" + s.add_development_dependency "multi_json", "~> 1.11.2" + else + s.add_development_dependency "bundler", ">= 1.16" + s.add_development_dependency "fakefs" + s.add_development_dependency "sinatra", "~> 2.0" + s.add_development_dependency "aruba" + s.add_development_dependency "capybara" + s.add_development_dependency "rake" + s.add_development_dependency "rack", "~> 2.2" + s.add_development_dependency "rack-test" + s.add_development_dependency "rack-oauth2" + s.add_development_dependency "webmock" + s.add_development_dependency "rspec-its" + s.add_development_dependency "faraday" + s.add_development_dependency "nokogiri" + s.add_development_dependency "yard" + s.add_development_dependency "inch" + s.add_development_dependency "minitest" + s.add_development_dependency "contracts" + s.add_development_dependency "gherkin" + s.add_development_dependency "multi_json" + s.add_development_dependency "webrick" + s.add_development_dependency "rackup" + end s.files = Dir.glob("lib/**/*") + Dir.glob("templates/**/*") s.require_path = "lib" diff --git a/spec/api_formatter_spec.rb b/spec/api_formatter_spec.rb index 266cad8d..69d0ef18 100644 --- a/spec/api_formatter_spec.rb +++ b/spec/api_formatter_spec.rb @@ -2,7 +2,18 @@ describe RspecApiDocumentation::ApiFormatter do let(:metadata) { {} } - let(:group) { RSpec::Core::ExampleGroup.describe("Orders", metadata) } + let(:group) { + # Create an anonymous class that inherits from ExampleGroup but doesn't auto-register + Class.new(RSpec::Core::ExampleGroup) do + def self.description + "Orders" + end + + def self.metadata + {} + end + end + } let(:output) { StringIO.new } let(:formatter) { RspecApiDocumentation::ApiFormatter.new(output) } diff --git a/spec/example_spec.rb b/spec/example_spec.rb index 1aa94610..3d78b885 100644 --- a/spec/example_spec.rb +++ b/spec/example_spec.rb @@ -64,7 +64,7 @@ end context "when the example is pending" do - let(:rspec_example) { rspec_example_group.pending(description, metadata) {} } + let(:rspec_example) { rspec_example_group.pending(description, metadata) { raise "Pending example" } } it { should be_falsey } end diff --git a/spec/http_test_client_spec.rb b/spec/http_test_client_spec.rb index fd77dc0f..a420cfbf 100644 --- a/spec/http_test_client_spec.rb +++ b/spec/http_test_client_spec.rb @@ -2,14 +2,14 @@ require 'rack/test' require 'capybara' require 'capybara/server' -require 'sinatra/base' require 'webmock/rspec' require 'support/stub_app' describe RspecApiDocumentation::HttpTestClient do before(:all) do WebMock.allow_net_connect! - server = Capybara::Server.new(StubApp.new, 8888) + Capybara.server = :webrick + server = Capybara::Server.new(StubApp.new, port: 8888) server.boot end diff --git a/spec/rack_test_client_spec.rb b/spec/rack_test_client_spec.rb index e3a9b53c..f1b57b84 100644 --- a/spec/rack_test_client_spec.rb +++ b/spec/rack_test_client_spec.rb @@ -1,10 +1,9 @@ require 'spec_helper' require 'rack/test' -require 'sinatra/base' require 'support/stub_app' describe RspecApiDocumentation::RackTestClient do - let(:context) { |example| double(:app => StubApp, :example => example) } + let(:context) { |example| double(:app => StubApp.new, :example => example) } let(:test_client) { RspecApiDocumentation::RackTestClient.new(context, {}) } subject { test_client } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 918dd620..95fd852d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,4 +4,16 @@ require 'pry' RSpec.configure do |config| + config.before(:all) do + if self.class.metadata[:api_doc_dsl] || self.respond_to?(:app) + begin + require 'support/stub_app' + RspecApiDocumentation.configure do |config| + config.app = StubApp.new unless config.app + end + rescue LoadError + # StubApp not available, skip + end + end + end end diff --git a/spec/support/stub_app.rb b/spec/support/stub_app.rb index 35226be2..f00787c0 100644 --- a/spec/support/stub_app.rb +++ b/spec/support/stub_app.rb @@ -1,31 +1,29 @@ -class StubApp < Sinatra::Base - get "/" do - content_type :json - - { :hello => "world" }.to_json - end - - post "/greet" do - content_type :json - - request.body.rewind - begin - data = JSON.parse request.body.read - rescue JSON::ParserError - request.body.rewind - data = request.body.read +class StubApp + def call(env) + req = Rack::Request.new(env) + + case "#{req.request_method} #{req.path_info}" + when "GET /" + [200, {'Content-Type' => 'application/json'}, [{ :hello => "world" }.to_json]] + when "POST /greet" + body = req.body.read + req.body.rewind if req.body.respond_to?(:rewind) + + begin + data = JSON.parse(body) if body && !body.empty? + rescue JSON::ParserError + data = nil + end + + target = data.is_a?(Hash) ? data["target"] : "nurse" + [200, {'Content-Type' => 'application/json', 'Content-Length' => '17'}, [{ :hello => target }.to_json]] + when "GET /xml" + [200, {'Content-Type' => 'application/xml'}, ["World"]] + when "GET /binary" + [200, {'Content-Type' => 'application/octet-stream'}, ["\x01\x02\x03".force_encoding(Encoding::ASCII_8BIT)]] + else + [404, {'Content-Type' => 'text/plain'}, ["Not Found"]] end - { :hello => data["target"] }.to_json - end - - get "/xml" do - content_type :xml - - "World" - end - - get '/binary' do - content_type 'application/octet-stream' - "\x01\x02\x03".force_encoding(Encoding::ASCII_8BIT) end end +