openapi_minitest 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e0b37db854964620741255ac8d97c19570c3425977a9e6cb507de3a4212bfca
4
- data.tar.gz: 7e03765531c01ebefb133856ab647e1626527f828481ed4d682cd8aa7896e49f
3
+ metadata.gz: e720190cb7ad695482a4e3200826cd437acc19b6cb6d00423b628d5e5efb7416
4
+ data.tar.gz: ee6598e687c92d030492cef298cb78fbbec2c0b76ec529f64d2f47172847f631
5
5
  SHA512:
6
- metadata.gz: f666ddf8f5cb8e3986346d84b5920d7c07e7eb12a13c2f87324ebb7878d848a0bdd2f61dff0ca4611c713fad80cbd9e312e1a32019cc385058eaceaa2aa43d79
7
- data.tar.gz: ac39441a39f4f8a8aa2828c7106f2c2ec34b101af98aca92d688a831ddbfd3d6404f75eff1234d3fb7832d66a8e8cda069c823c7d07d940621f1c020887ffd36
6
+ metadata.gz: 251b5d8354681ffa184b5ccedf8df342034083c9b0e1a3f1b28216f6d9b593214e60f040f12be12737fc9fe5897f34eee5cd030de985cb341d81045fe7c2c1da
7
+ data.tar.gz: 3fbcf78209751bab3a1f95f2fb86c5ce0bc7500b22b0f87938fb59ac9ffeedbd7112380fe7d0829bf75744093dd6c24e1f7e31689f78c41898f2a5b6e4a39de2
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.1.0"
2
+ ".": "0.1.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.1](https://github.com/k0va1/openapi_minitest/compare/v0.1.0...v0.1.1) (2026-02-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * add configurable path sorting with sort_paths option ([432d5ea](https://github.com/k0va1/openapi_minitest/commit/432d5ea33586adc2a09dd0d6c9ff086767b7fbc5))
9
+ * add Rails generator for API docs controller with Scalar UI ([77b5627](https://github.com/k0va1/openapi_minitest/commit/77b562717bddf97ecc1c67b27855bf1e4673d1c5))
10
+ * add thread safety to ResultCollector using MonitorMixin ([18dd97d](https://github.com/k0va1/openapi_minitest/commit/18dd97dcd2b1925b2caee98eb6d45c3194e5da74))
11
+
3
12
  ## 0.1.0 (2026-02-06)
4
13
 
5
14
 
@@ -17,5 +26,3 @@
17
26
  * reset ResultCollector between tests to prevent test pollution ([ecc5bc2](https://github.com/k0va1/openapi_minitest/commit/ecc5bc268a6e8833eebdc6566441c0032b93f2ec))
18
27
  * set manifest version to 0.0.0 for initial release as 0.1.0 ([7e340df](https://github.com/k0va1/openapi_minitest/commit/7e340dfdecc3bafdd1b3bf6147fc9e92e2e10f7e))
19
28
  * use security schemes instead of Authorization header parameter ([eb092ef](https://github.com/k0va1/openapi_minitest/commit/eb092efaa34ae92745c890d88dea18b8b55ca7f8))
20
-
21
- ## Changelog
data/CLAUDE.md CHANGED
@@ -1,3 +1,37 @@
1
- # Project Instructions
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ openapi_minitest is a Ruby gem that generates OpenAPI 3.1.0 documentation from Minitest integration tests. Users call a single `document_response` helper method in their tests to capture API endpoints into an OpenAPI YAML file.
8
+
9
+ ## Commands
10
+
11
+ - **Run all tests + lint:** `bundle exec rake` (default task runs both `test` and `standard`)
12
+ - **Run tests only:** `bundle exec rake test`
13
+ - **Run a single test:** `bundle exec ruby -Ilib:test test/test_openapi_minitest.rb -n test_method_name`
14
+ - **Lint:** `bundle exec rake standard`
15
+ - **Lint autofix:** `bundle exec standardrb --fix`
16
+
17
+ ## Architecture
18
+
19
+ The generation pipeline flows through four components in order:
20
+
21
+ 1. **DSL (`lib/openapi_minitest/dsl.rb`)** — Provides the `document_response` method mixed into test classes. Handles schema validation (optional) and normalization of Symbol schema references to `$ref` format.
22
+
23
+ 2. **ResultCollector (`lib/openapi_minitest/result_collector.rb`)** — Singleton that accumulates all recorded API calls during a test run. Stores two parallel hashes keyed by `"method /path"`: `@operations` (endpoint metadata) and `@responses` (grouped by HTTP status). Handles path normalization (e.g., `/api/users/123` → `/api/users/{user_id}`).
24
+
25
+ 3. **Generator (`lib/openapi_minitest/openapi/generator.rb`)** — Reads from ResultCollector and builds the complete OpenAPI 3.1.0 document hash. Handles path sorting, request body extraction, example merging, and security scheme attachment. Outputs YAML.
26
+
27
+ 4. **Railtie (`lib/openapi_minitest/railtie.rb`)** — Rails integration. Auto-includes DSL in `ActionDispatch::IntegrationTest`, registers `rails openapi:generate` rake task, and hooks into `Minitest.after_run` to trigger generation when `OPENAPI_GENERATE=true`.
28
+
29
+ Configuration and schema registry live in `lib/openapi_minitest/configuration.rb`.
30
+
31
+ ## Test Setup
32
+
33
+ Tests use `OpenapiMinitest::TestHelpers` (defined in `test/test_helper.rb`) which resets both configuration and the ResultCollector singleton in `setup`, ensuring test isolation.
34
+
35
+ ## Conventions
2
36
 
3
37
  - Use Conventional Commits format for all commit messages (e.g., `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`)
data/Makefile CHANGED
@@ -6,12 +6,8 @@ install:
6
6
  console:
7
7
  bin/console
8
8
 
9
-
10
9
  test:
11
10
  bundle exec rake test
12
11
 
13
-
14
-
15
12
  lint-fix:
16
13
  bundle exec standardrb --fix
17
-
data/README.md CHANGED
@@ -155,6 +155,22 @@ OPENAPI_GENERATE=true rails test test/integration/
155
155
  rails openapi:generate
156
156
  ```
157
157
 
158
+ ### 5. Browse Documentation (optional)
159
+
160
+ Run the install generator to add an API docs page powered by [Scalar](https://github.com/scalar/scalar):
161
+
162
+ ```bash
163
+ rails generate openapi_minitest:install
164
+ ```
165
+
166
+ This creates:
167
+
168
+ - `app/controllers/api_docs_controller.rb` — serves the docs UI and the OpenAPI spec file
169
+ - `app/views/api_docs/index.html.erb` — Scalar API reference page (titled with your Rails app name)
170
+ - Routes: `GET /api-docs` and `GET /openapi.yml`
171
+
172
+ Visit `/api-docs` in your browser to explore your API documentation interactively.
173
+
158
174
  ## API Reference
159
175
 
160
176
  ### Configuration Options
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiMinitest
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ desc "Generates an ApiDocsController and view for serving OpenAPI documentation with Scalar UI"
9
+
10
+ def copy_controller
11
+ template "api_docs_controller.rb.tt", "app/controllers/api_docs_controller.rb"
12
+ end
13
+
14
+ def copy_view
15
+ template "index.html.erb.tt", "app/views/api_docs/index.html.erb"
16
+ end
17
+
18
+ def add_routes
19
+ route <<~RUBY
20
+ get "api-docs" => "api_docs#index"
21
+ get "openapi.yml" => "api_docs#spec"
22
+ RUBY
23
+ end
24
+
25
+ private
26
+
27
+ def app_name
28
+ Rails.application.class.respond_to?(:module_parent_name) ? Rails.application.class.module_parent_name : Rails.application.class.parent_name
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ class ApiDocsController < ApplicationController
2
+ def index
3
+ render layout: false
4
+ end
5
+
6
+ def spec
7
+ send_file Rails.root.join("doc/openapi.yml"), type: "text/yaml", disposition: "inline"
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= app_name %> API Documentation</title>
5
+ <meta charset="utf-8"/>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ </head>
8
+ <body>
9
+ <script id="api-reference" data-url="/openapi.yml"></script>
10
+ <script>
11
+ var configuration = {
12
+ theme: 'elysiajs',
13
+ expandAllResponses: true,
14
+ layout: 'modern',
15
+ defaultOpenAllTags: true,
16
+ hideClientButton: true,
17
+ showSidebar: true,
18
+ showDeveloperTools: 'localhost',
19
+ showToolbar: 'localhost',
20
+ operationTitleSource: 'summary',
21
+ persistAuth: false,
22
+ telemetry: true,
23
+ isEditable: false,
24
+ isLoading: false,
25
+ hideModels: false,
26
+ documentDownloadType: 'both',
27
+ hideTestRequestButton: false,
28
+ hideSearch: false,
29
+ showOperationId: false,
30
+ hideDarkModeToggle: false,
31
+ withDefaultFonts: true,
32
+ expandAllModelSections: false,
33
+ orderSchemaPropertiesBy: 'alpha',
34
+ orderRequiredPropertiesFirst: true,
35
+ _integration: 'html',
36
+ darkMode: true,
37
+ hiddenClients: ['unirest'],
38
+ defaultHttpClient: {
39
+ targetKey: 'ruby',
40
+ clientKey: 'faraday'
41
+ }
42
+ }
43
+ document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration)
44
+ </script>
45
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
46
+ </body>
47
+ </html>
@@ -10,7 +10,8 @@ module OpenapiMinitest
10
10
  :security_schemes,
11
11
  :tags,
12
12
  :validate_schema,
13
- :strict_validation
13
+ :strict_validation,
14
+ :sort_paths
14
15
 
15
16
  def initialize
16
17
  @title = "API Documentation"
@@ -22,6 +23,7 @@ module OpenapiMinitest
22
23
  @tags = []
23
24
  @validate_schema = true
24
25
  @strict_validation = false
26
+ @sort_paths = :alphabetical
25
27
  end
26
28
  end
27
29
 
@@ -63,7 +63,7 @@ module OpenapiMinitest
63
63
  paths[path][method] = build_operation(key, operation)
64
64
  end
65
65
 
66
- paths
66
+ (@config.sort_paths == :alphabetical) ? paths.sort.to_h : paths
67
67
  end
68
68
 
69
69
  def build_operation(key, operation)
@@ -1,61 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "singleton"
4
+ require "monitor"
4
5
  require "json"
5
6
 
6
7
  module OpenapiMinitest
7
8
  class ResultCollector
8
9
  include Singleton
10
+ include MonitorMixin
9
11
 
10
12
  def initialize
13
+ super # MonitorMixin requires super
11
14
  reset!
12
15
  end
13
16
 
14
17
  def reset!
15
- @operations = {} # "METHOD /path" => operation data
16
- @responses = {} # "METHOD /path" => { status => [response_data] }
18
+ synchronize do
19
+ @operations = {} # "METHOD /path" => operation data
20
+ @responses = {} # "METHOD /path" => { status => [response_data] }
21
+ end
17
22
  end
18
23
 
19
24
  def record(request:, response:, schema:, summary:, description:, tags:, operation_id:, deprecated:, test_name:)
20
- method = request.request_method.downcase
21
- path = normalize_path(request.path)
22
- key = "#{method} #{path}"
23
-
24
- # Store operation metadata (first one wins for summary, tags merge)
25
- @operations[key] ||= {
26
- method: method,
27
- path: path,
28
- summary: summary,
29
- tags: [],
30
- operation_id: operation_id,
31
- deprecated: deprecated,
32
- parameters: extract_parameters(request, path),
33
- requires_auth: has_authorization_header?(request)
34
- }
35
-
36
- # Merge tags from all tests
37
- @operations[key][:tags] = (@operations[key][:tags] + tags).uniq
38
-
39
- # Store response data grouped by status
40
- status = response.status.to_s
41
- @responses[key] ||= {}
42
- @responses[key][status] ||= []
43
-
44
- @responses[key][status] << {
45
- description: description || default_description(response.status),
46
- schema: schema,
47
- example: parse_body(response.body),
48
- test_name: test_name,
49
- request_example: extract_request_example(request)
50
- }
25
+ synchronize do
26
+ method = request.request_method.downcase
27
+ path = normalize_path(request.path)
28
+ key = "#{method} #{path}"
29
+
30
+ # Store operation metadata (first one wins for summary, tags merge)
31
+ @operations[key] ||= {
32
+ method: method,
33
+ path: path,
34
+ summary: summary,
35
+ tags: [],
36
+ operation_id: operation_id,
37
+ deprecated: deprecated,
38
+ parameters: extract_parameters(request, path),
39
+ requires_auth: has_authorization_header?(request)
40
+ }
41
+
42
+ # Merge tags from all tests
43
+ @operations[key][:tags] = (@operations[key][:tags] + tags).uniq
44
+
45
+ # Store response data grouped by status
46
+ status = response.status.to_s
47
+ @responses[key] ||= {}
48
+ @responses[key][status] ||= []
49
+
50
+ @responses[key][status] << {
51
+ description: description || default_description(response.status),
52
+ schema: schema,
53
+ example: parse_body(response.body),
54
+ test_name: test_name,
55
+ request_example: extract_request_example(request)
56
+ }
57
+ end
51
58
  end
52
59
 
53
- attr_reader :operations
60
+ def operations
61
+ synchronize { @operations }
62
+ end
54
63
 
55
- attr_reader :responses
64
+ def responses
65
+ synchronize { @responses }
66
+ end
56
67
 
57
68
  def empty?
58
- @operations.empty?
69
+ synchronize { @operations.empty? }
59
70
  end
60
71
 
61
72
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiMinitest
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -9,8 +9,7 @@
9
9
  "bump-patch-for-minor-pre-major": true,
10
10
  "draft": false,
11
11
  "prerelease": false,
12
- "version-file": "lib/openapi_minitest/version.rb",
13
- "release-as": "0.1.0"
12
+ "version-file": "lib/openapi_minitest/version.rb"
14
13
  }
15
14
  },
16
15
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_minitest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Koval
@@ -67,6 +67,9 @@ files:
67
67
  - Makefile
68
68
  - README.md
69
69
  - Rakefile
70
+ - lib/generators/openapi_minitest/install/install_generator.rb
71
+ - lib/generators/openapi_minitest/install/templates/api_docs_controller.rb.tt
72
+ - lib/generators/openapi_minitest/install/templates/index.html.erb.tt
70
73
  - lib/openapi_minitest.rb
71
74
  - lib/openapi_minitest/configuration.rb
72
75
  - lib/openapi_minitest/dsl.rb