rc-minitest-openapi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dc9be68f3e6661181cebec8f0470921dc0a94d4f5692d31d83b6e5ebb956c6fd
4
+ data.tar.gz: aae8e8280d8668d86fcddf48fd505ba225b10468b1b1cd4aaa511c3e7209e216
5
+ SHA512:
6
+ metadata.gz: 70227955c8d7e173818dcc119dcedc28e1eaaec110991a1915ea30672f20c81f617e39d08b8d1f130dfe6ddcc06104ee8d7b9bd1005ec306c5b1ca019a4cd343
7
+ data.tar.gz: 2f06ed50395e49d2039fe8a43a16e2cad2a210fff0dde93bafc7a0e0ea50db2e3284ac0eef35b5563ec7ae741dd31f176885b643026586d1291eef3b32ab0707
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # rc-minitest-openapi
2
+
3
+ Generate an OpenAPI 3.0 document from your Rails minitest API tests.
4
+
5
+ Tests declare each operation and its response schema; responses are validated
6
+ against that schema as the suite runs; the `openapi:generate` rake task writes
7
+ the document. A helper-method DSL and an rswag-style nested block DSL are both
8
+ provided.
9
+
10
+ See the [repository README](https://github.com/RailsComposer/minitest-openapi)
11
+ for full documentation.
12
+
13
+ ## License
14
+
15
+ MIT
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+
6
+ module Minitest
7
+ module OpenAPI
8
+ # Per-suite settings. Configure in test_helper.rb:
9
+ #
10
+ # Minitest::OpenAPI.configure do |c|
11
+ # c.output_path = "openapi/v1/openapi.json"
12
+ # c.base = "openapi/base.json" # or a Hash
13
+ # end
14
+ class Configuration
15
+ # Where openapi:generate writes the document (relative to Rails.root).
16
+ attr_accessor :output_path
17
+
18
+ # Validate each response body against its declared schema as tests run.
19
+ attr_accessor :validate_responses
20
+
21
+ DEFAULT_BASE = {
22
+ "openapi" => "3.0.3",
23
+ "info" => {"title" => "API", "version" => "1.0.0"},
24
+ "components" => {"schemas" => {}}
25
+ }.freeze
26
+
27
+ def initialize
28
+ @output_path = "openapi/openapi.json"
29
+ @validate_responses = true
30
+ @base = deep_dup(DEFAULT_BASE)
31
+ end
32
+
33
+ # The base document — everything not derived from tests (info, servers,
34
+ # security schemes, reusable component schemas). A Hash, or a path to a
35
+ # JSON/YAML file. Test-recorded operations are merged into `paths`.
36
+ attr_reader :base
37
+
38
+ def base=(value)
39
+ @base =
40
+ case value
41
+ when Hash then value
42
+ when String then load_base_file(value)
43
+ else raise ArgumentError, "base must be a Hash or a file path"
44
+ end
45
+ end
46
+
47
+ def base_document
48
+ deep_dup(@base)
49
+ end
50
+
51
+ private
52
+
53
+ def load_base_file(path)
54
+ resolved = Minitest::OpenAPI.resolve_path(path)
55
+ content = File.read(resolved)
56
+ path.end_with?(".json") ? JSON.parse(content) : YAML.safe_load(content)
57
+ end
58
+
59
+ def deep_dup(obj)
60
+ case obj
61
+ when Hash then obj.transform_values { |v| deep_dup(v) }
62
+ when Array then obj.map { |v| deep_dup(v) }
63
+ else obj
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest
4
+ module OpenAPI
5
+ # The in-memory OpenAPI document. Tests record operations into it as they
6
+ # run; #to_h merges those operations into the base document's `paths`.
7
+ class Document
8
+ # Canonical ordering, so the emitted document is identical regardless
9
+ # of the order tests recorded into it (minitest randomizes test order).
10
+ VERB_ORDER = %w[get put post patch delete options head trace].freeze
11
+ OPERATION_KEYS = %w[
12
+ tags summary description operationId parameters requestBody responses
13
+ ].freeze
14
+
15
+ def initialize(base)
16
+ @base = base
17
+ @paths = {}
18
+ end
19
+
20
+ # The base document's reusable schemas, used to resolve $refs while
21
+ # validating responses.
22
+ def components
23
+ @base["components"] || {}
24
+ end
25
+
26
+ # Records one operation/response pair. Called by the recorder.
27
+ def record(verb:, path:, status:, schema: nil, summary: nil, operation_id: nil,
28
+ description: nil, tags: nil, parameters: nil, request_body: nil,
29
+ response_description: nil, content_type: "application/json")
30
+ operation = ((@paths[path] ||= {})[verb.to_s.downcase] ||= {})
31
+ operation["summary"] ||= summary if summary
32
+ operation["operationId"] ||= operation_id if operation_id
33
+ operation["description"] ||= description if description
34
+ operation["tags"] ||= tags if tags && !tags.empty?
35
+ operation["parameters"] ||= parameters if parameters && !parameters.empty?
36
+ operation["requestBody"] ||= request_body if request_body
37
+
38
+ responses = (operation["responses"] ||= {})
39
+ entry = (responses[status.to_s] ||= {})
40
+ entry["description"] ||= response_description || "#{status} response"
41
+ if schema
42
+ entry["content"] ||= {content_type => {"schema" => schema}}
43
+ end
44
+ operation
45
+ end
46
+
47
+ # The assembled document: the base with recorded operations merged into
48
+ # `paths`. Paths, verbs, operation keys, and response statuses are all
49
+ # canonically ordered, so the output is stable across test runs.
50
+ def to_h
51
+ doc = deep_dup(@base)
52
+ base_paths = doc["paths"] || {}
53
+ paths = {}
54
+ (base_paths.keys | @paths.keys).sort.each do |path|
55
+ merged = (base_paths[path] || {}).merge(@paths[path] || {})
56
+ paths[path] = canonical_path_item(merged)
57
+ end
58
+ doc["paths"] = paths
59
+ doc
60
+ end
61
+
62
+ def empty?
63
+ @paths.empty?
64
+ end
65
+
66
+ private
67
+
68
+ # Orders the verbs within a path item, and each operation's keys.
69
+ def canonical_path_item(verbs)
70
+ ordered = {}
71
+ (VERB_ORDER & verbs.keys).each { |verb| ordered[verb] = canonical_operation(verbs[verb]) }
72
+ (verbs.keys - VERB_ORDER).sort.each { |key| ordered[key] = verbs[key] }
73
+ ordered
74
+ end
75
+
76
+ # Orders an operation's keys and sorts its responses by status code.
77
+ def canonical_operation(operation)
78
+ ordered = {}
79
+ OPERATION_KEYS.each { |key| ordered[key] = operation[key] if operation.key?(key) }
80
+ (operation.keys - OPERATION_KEYS).sort.each { |key| ordered[key] = operation[key] }
81
+ if ordered["responses"].is_a?(Hash)
82
+ ordered["responses"] = ordered["responses"].sort_by { |status, _| status.to_i }.to_h
83
+ end
84
+ ordered
85
+ end
86
+
87
+ def deep_dup(obj)
88
+ case obj
89
+ when Hash then obj.transform_values { |v| deep_dup(v) }
90
+ when Array then obj.map { |v| deep_dup(v) }
91
+ else obj
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest
4
+ module OpenAPI
5
+ # Helper-method DSL for classic class-based integration tests:
6
+ #
7
+ # class MediaEntriesApiTest < ActionDispatch::IntegrationTest
8
+ # include Minitest::OpenAPI::DSL
9
+ #
10
+ # test "lists media entries" do
11
+ # openapi_get "/api/v1/media_entries",
12
+ # summary: "List media entries",
13
+ # response: {status: 200, schema: {"$ref" => "#/components/schemas/MediaEntry"}}
14
+ # assert_response :success
15
+ # end
16
+ # end
17
+ #
18
+ # Each openapi_<verb> performs the request, validates the response body
19
+ # against the declared schema, records the operation, and returns the
20
+ # response so further assertions can be made.
21
+ module DSL
22
+ %i[get post patch put delete].each do |verb|
23
+ define_method(:"openapi_#{verb}") do |request_path, response:, doc_path: nil, summary: nil, operation_id: nil, description: nil, tags: nil, parameters: nil, params: nil, headers: nil, body: nil, request_body: nil|
24
+ Recorder.run(
25
+ test: self, verb: verb, request_path: request_path, doc_path: doc_path,
26
+ response: response, summary: summary, operation_id: operation_id,
27
+ description: description, tags: tags, parameters: parameters,
28
+ params: params, headers: headers, body: body, request_body: request_body
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module Minitest
6
+ module OpenAPI
7
+ class Railtie < Rails::Railtie
8
+ rake_tasks do
9
+ load File.expand_path("tasks/openapi.rake", __dir__)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest
4
+ module OpenAPI
5
+ # Shared engine behind both DSLs: performs the HTTP request through the
6
+ # integration test, validates the response body against the declared
7
+ # schema, and records the operation into the document.
8
+ module Recorder
9
+ module_function
10
+
11
+ # test - the ActionDispatch::IntegrationTest instance
12
+ # verb - :get/:post/:patch/:put/:delete
13
+ # request_path - the URL to request
14
+ # doc_path - templated path recorded in the document (default: request_path)
15
+ # response - an Integer status, or { status:, schema:, description: }
16
+ # assert_status - when true, fails the test if the status differs
17
+ def run(test:, verb:, request_path:, response:, doc_path: nil, summary: nil,
18
+ operation_id: nil, description: nil, tags: nil, parameters: nil, params: nil,
19
+ headers: nil, body: nil, request_body: nil, assert_status: false)
20
+ spec = normalize_response(response)
21
+ doc_path ||= request_path
22
+
23
+ options = {}
24
+ payload = body.nil? ? params : body
25
+ options[:params] = payload unless payload.nil?
26
+ options[:headers] = headers if headers
27
+ options[:as] = :json unless body.nil?
28
+ test.public_send(verb, request_path, **options)
29
+
30
+ actual = test.response
31
+
32
+ Minitest::OpenAPI.document.record(
33
+ verb: verb, path: doc_path, status: spec[:status], schema: spec[:schema],
34
+ summary: summary, operation_id: operation_id, description: description,
35
+ tags: tags, parameters: parameters, request_body: request_body,
36
+ response_description: spec[:description]
37
+ )
38
+
39
+ if assert_status
40
+ test.assert_equal spec[:status], actual.status,
41
+ "expected #{verb.to_s.upcase} #{request_path} to respond #{spec[:status]}, " \
42
+ "got #{actual.status}"
43
+ end
44
+
45
+ if Minitest::OpenAPI.configuration.validate_responses &&
46
+ spec[:schema] && actual.status == spec[:status]
47
+ Validator.new(Minitest::OpenAPI.components).validate!(
48
+ spec[:schema], parse_body(actual),
49
+ context: "#{verb.to_s.upcase} #{doc_path} -> #{spec[:status]}"
50
+ )
51
+ end
52
+
53
+ actual
54
+ end
55
+
56
+ def normalize_response(response)
57
+ case response
58
+ when Integer
59
+ {status: response, schema: nil, description: nil}
60
+ when Hash
61
+ {status: Integer(response.fetch(:status)), schema: response[:schema],
62
+ description: response[:description]}
63
+ else
64
+ raise ArgumentError, "response: must be a status Integer or a Hash with :status"
65
+ end
66
+ end
67
+
68
+ def parse_body(actual)
69
+ JSON.parse(actual.body)
70
+ rescue JSON::ParserError
71
+ raise Validator::ResponseMismatch,
72
+ "response body was not valid JSON (HTTP #{actual.status})"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/spec"
4
+
5
+ module Minitest
6
+ module OpenAPI
7
+ # Nested block DSL for minitest/spec users. Extend an integration test
8
+ # class with it; api_path / api_operation / api_response build nested
9
+ # describe blocks and run_api_test! defines the test:
10
+ #
11
+ # class MediaEntriesApiTest < ActionDispatch::IntegrationTest
12
+ # extend Minitest::OpenAPI::Spec
13
+ #
14
+ # api_path "/api/v1/media_entries" do
15
+ # api_operation :get, summary: "List media entries" do
16
+ # api_response 200, schema: {"$ref" => "#/components/schemas/MediaEntry"} do
17
+ # run_api_test!
18
+ # end
19
+ # end
20
+ # end
21
+ # end
22
+ #
23
+ # Metadata accumulates down the nesting; run_api_test! merges the whole
24
+ # chain. Its optional block runs in the test instance and returns request
25
+ # overrides ({ path:, params:, headers:, body: }) — use it to build a
26
+ # concrete URL from records created in the test.
27
+ module Spec
28
+ def self.extended(base)
29
+ base.extend(Minitest::Spec::DSL) unless base.is_a?(Minitest::Spec::DSL)
30
+ end
31
+
32
+ # Metadata declared on this describe merged onto its parent's.
33
+ def openapi_metadata
34
+ inherited = superclass.respond_to?(:openapi_metadata) ? superclass.openapi_metadata : {}
35
+ inherited.merge(@openapi_metadata || {})
36
+ end
37
+
38
+ def api_path(path, &block)
39
+ describe("path #{path}") do
40
+ @openapi_metadata = {path: path}
41
+ class_eval(&block)
42
+ end
43
+ end
44
+
45
+ def api_operation(verb, **meta, &block)
46
+ describe(verb.to_s) do
47
+ @openapi_metadata = {verb: verb}.merge(meta)
48
+ class_eval(&block)
49
+ end
50
+ end
51
+
52
+ def api_response(status, **meta, &block)
53
+ describe("#{status} response") do
54
+ @openapi_metadata = {response_status: status}.merge(meta)
55
+ class_eval(&block)
56
+ end
57
+ end
58
+
59
+ def run_api_test!(description = nil, &request_block)
60
+ meta = openapi_metadata
61
+ verb = meta.fetch(:verb) { raise Error, "run_api_test! must be nested in api_operation" }
62
+ status = meta.fetch(:response_status) { raise Error, "run_api_test! must be nested in api_response" }
63
+ doc_path = meta.fetch(:path) { raise Error, "run_api_test! must be nested in api_path" }
64
+
65
+ it(description || "conforms to the #{status} response") do
66
+ overrides = request_block ? instance_exec(&request_block) : {}
67
+ overrides ||= {}
68
+ Recorder.run(
69
+ test: self,
70
+ verb: verb,
71
+ request_path: overrides[:path] || doc_path,
72
+ doc_path: doc_path,
73
+ response: {status: status, schema: meta[:schema], description: meta[:description]},
74
+ summary: meta[:summary],
75
+ operation_id: meta[:operation_id],
76
+ description: meta[:operation_description],
77
+ tags: meta[:tags],
78
+ parameters: meta[:parameters],
79
+ request_body: meta[:request_body],
80
+ params: overrides[:params],
81
+ headers: overrides[:headers],
82
+ body: overrides[:body],
83
+ assert_status: true
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :openapi do
4
+ desc "Run the API tests and write the OpenAPI document. Pass test paths to " \
5
+ "scope the run, e.g. openapi:generate[test/integration/api]."
6
+ task :generate, [:paths] do |_task, args|
7
+ paths = [args[:paths], *args.extras].compact
8
+ paths = ["test/integration"] if paths.empty?
9
+ command = ["bin/rails", "test", *paths]
10
+ puts "openapi: #{command.join(" ")}"
11
+
12
+ # MINITEST_OPENAPI makes minitest-openapi write the document after the run.
13
+ # PARALLEL_WORKERS=1 keeps every operation in one process / one document.
14
+ env = {"MINITEST_OPENAPI" => "1", "PARALLEL_WORKERS" => "1"}
15
+ abort "openapi: test run failed; document not written" unless system(env, *command)
16
+ end
17
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_schemer"
4
+
5
+ module Minitest
6
+ module OpenAPI
7
+ # Validates a response body against the schema a test declared for it.
8
+ # OpenAPI schemas are JSON Schema with a few divergences; `nullable: true`
9
+ # is normalized to a "null" type so a standard JSON Schema validator can
10
+ # be used. $refs of the form #/components/schemas/X resolve against the
11
+ # base document's components.
12
+ class Validator
13
+ class ResponseMismatch < StandardError; end
14
+
15
+ def initialize(components)
16
+ @components = components || {}
17
+ end
18
+
19
+ # Raises ResponseMismatch if `body` does not satisfy `schema`.
20
+ def validate!(schema, body, context:)
21
+ return if schema.nil?
22
+
23
+ root = {
24
+ "$schema" => "https://json-schema.org/draft/2020-12/schema",
25
+ "allOf" => [normalize(schema)],
26
+ "components" => normalize(@components)
27
+ }
28
+ errors = JSONSchemer.schema(root).validate(body).to_a
29
+ return if errors.empty?
30
+
31
+ raise ResponseMismatch, "#{context} response did not match its declared schema:\n" +
32
+ errors.first(8).map { |e| " #{format_error(e)}" }.join("\n")
33
+ end
34
+
35
+ private
36
+
37
+ def format_error(error)
38
+ pointer = error["data_pointer"].to_s
39
+ location = pointer.empty? ? "(root)" : pointer
40
+ "#{location}: #{error["type"]}"
41
+ end
42
+
43
+ # OpenAPI `nullable: true` has no JSON Schema equivalent. Normalize it:
44
+ # add "null" to `type`, and — since an `enum` is exhaustive — add `nil`
45
+ # to any `enum` so a null value declared nullable is actually allowed.
46
+ def normalize(node)
47
+ case node
48
+ when Hash
49
+ normalized = node.each_with_object({}) { |(k, v), acc| acc[k] = normalize(v) }
50
+ if normalized.delete("nullable")
51
+ if normalized["type"].is_a?(String)
52
+ normalized["type"] = [normalized["type"], "null"]
53
+ end
54
+ if normalized["enum"].is_a?(Array) && !normalized["enum"].include?(nil)
55
+ normalized["enum"] += [nil]
56
+ end
57
+ end
58
+ normalized
59
+ when Array
60
+ node.map { |child| normalize(child) }
61
+ else
62
+ node
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest
4
+ module OpenAPI
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+ require "pathname"
6
+ require "fileutils"
7
+ require "minitest"
8
+
9
+ require_relative "openapi/version"
10
+ require_relative "openapi/configuration"
11
+ require_relative "openapi/document"
12
+ require_relative "openapi/validator"
13
+ require_relative "openapi/recorder"
14
+ require_relative "openapi/dsl"
15
+ require_relative "openapi/spec"
16
+
17
+ require_relative "openapi/railtie" if defined?(Rails::Railtie)
18
+
19
+ module Minitest
20
+ # Generates an OpenAPI 3.0 document from minitest API tests. Tests declare
21
+ # each operation and its response schema; the live response is validated
22
+ # against that schema as the suite runs; `openapi:generate` writes the doc.
23
+ module OpenAPI
24
+ class Error < StandardError; end
25
+
26
+ class << self
27
+ def configuration
28
+ @configuration ||= Configuration.new
29
+ end
30
+
31
+ def configure
32
+ yield configuration
33
+ end
34
+
35
+ # The document being assembled this run.
36
+ def document
37
+ @document ||= Document.new(configuration.base_document)
38
+ end
39
+
40
+ # Component schemas available for $ref resolution during validation.
41
+ def components
42
+ document.components
43
+ end
44
+
45
+ # Drops the accumulated document (used between isolated test runs).
46
+ def reset!
47
+ @document = nil
48
+ end
49
+
50
+ # Writes the assembled document. Returns the absolute path written.
51
+ def generate!(path = nil)
52
+ target = resolve_path(path || configuration.output_path)
53
+ FileUtils.mkdir_p(File.dirname(target))
54
+ File.write(target, "#{JSON.pretty_generate(document.to_h)}\n")
55
+ target
56
+ end
57
+
58
+ # Resolves a path relative to Rails.root when available, else the cwd.
59
+ def resolve_path(path)
60
+ return path.to_s if Pathname.new(path).absolute?
61
+
62
+ root = defined?(Rails) && Rails.respond_to?(:root) && Rails.root
63
+ File.join((root || Dir.pwd).to_s, path)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # When MINITEST_OPENAPI is set (the openapi:generate rake task sets it), write
70
+ # the document once the suite finishes. Registered here, at require time,
71
+ # rather than via a minitest plugin file: plugin auto-discovery is unreliable
72
+ # under Rails' test runner and with git-sourced gems, whereas test_helper.rb
73
+ # requires this file directly. Ordinary test runs leave MINITEST_OPENAPI unset
74
+ # and are unaffected (responses are still validated; the file is just not
75
+ # written).
76
+ if ENV["MINITEST_OPENAPI"]
77
+ Minitest.after_run do
78
+ warn "minitest-openapi: wrote #{Minitest::OpenAPI.generate!}"
79
+ end
80
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rc-minitest-openapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - RailsComposer
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: minitest
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '5.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: json_schemer
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: railties
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '7.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '7.1'
54
+ description: rc-minitest-openapi turns Rails minitest integration tests into the source
55
+ of truth for an OpenAPI 3.0 document. Tests declare each operation and its response
56
+ schema, the live response is validated against that schema as the suite runs, and
57
+ the openapi:generate rake task writes the document. An rswag-style block DSL and
58
+ plain helper methods are both supported.
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - README.md
64
+ - lib/minitest/openapi.rb
65
+ - lib/minitest/openapi/configuration.rb
66
+ - lib/minitest/openapi/document.rb
67
+ - lib/minitest/openapi/dsl.rb
68
+ - lib/minitest/openapi/railtie.rb
69
+ - lib/minitest/openapi/recorder.rb
70
+ - lib/minitest/openapi/spec.rb
71
+ - lib/minitest/openapi/tasks/openapi.rake
72
+ - lib/minitest/openapi/validator.rb
73
+ - lib/minitest/openapi/version.rb
74
+ homepage: https://github.com/RailsComposer/minitest-openapi
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ homepage_uri: https://github.com/RailsComposer/minitest-openapi
79
+ source_code_uri: https://github.com/RailsComposer/minitest-openapi
80
+ changelog_uri: https://github.com/RailsComposer/minitest-openapi/blob/main/CHANGELOG.md
81
+ rubygems_mfa_required: 'true'
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.2'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.6.9
97
+ specification_version: 4
98
+ summary: Generate an OpenAPI document from your minitest API tests.
99
+ test_files: []