reqcord 0.1.2 → 0.1.4

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: 71c23f11e62a357bbc63bc7479cd77d65da3245062b9c0594e4b3d3dc0421fd2
4
- data.tar.gz: d20cfd0db3a617adc966f2641da79c155be232baad7fb94b47db235820da3a2d
3
+ metadata.gz: 87d47bb7440a886650db640312c804f7889a24420be0e1e2b81806ac4114a63c
4
+ data.tar.gz: d8198f3e86749a170c144bad28eae898f876c8e4a82ca933fa9d1a320204aaad
5
5
  SHA512:
6
- metadata.gz: a5c2d1ebe2463d5c3647e843d4f70b04114fe3d225a8059b5e6e09d9461945305203c5bbfe4189039a8b48edc713cf39a3d0e5cafadefb6648f62f70aaff4595
7
- data.tar.gz: 98406f42d54d21b3d7854a4b36960be53cfe0293be738ca1d1e64194215920b4194c30649a730662e078d4b9558298c1ec9a13d15096207bb4a99257733d4b1f
6
+ metadata.gz: 0f10290846b36da08d91f98998c8ef14c12427bf509bd8fb84d70a8b1f1bbec9dc389765e1b75be13d647f41000a1e295c39ffa89a70847c74ec84bb0e8ce3bf
7
+ data.tar.gz: e3c55c9f404b54b4ad2d284be04fc89f13ca5922c5ed52667c8ffad84fd808de3ef826e8b7ff97ad4e0dff8066a44af8122140cbfbb311257739c37b9430d088
data/CHANGELOG.md CHANGED
@@ -1,6 +1,32 @@
1
1
  # Changelog
2
2
 
3
- ## [0.1.2] - Unreleased
3
+ ## [0.1.4] - 2026-09-16
4
+
5
+ ### Changed
6
+
7
+ * A failing test run no longer discards the output. The docs are generated
8
+ from what the suite captured, the warning names the exit status and the
9
+ report points out that routes reached only by failing tests are listed as
10
+ uncovered. The previous behaviour is `test.strict: true`.
11
+
12
+ ### Added
13
+
14
+ * `test.strict` (`REQCORD_STRICT=1`): abort on a failing suite instead of
15
+ documenting what it captured.
16
+
17
+ ## [0.1.3] - 2026-09-16
18
+
19
+ ### Added
20
+
21
+ * `routes.prefix` accepts a list (`[/v1, /v2, /partner]`) for APIs whose
22
+ versions or audiences do not share a root.
23
+
24
+ ### Changed
25
+
26
+ * The `reqcord:init` template now names test directories (`test.paths`)
27
+ instead of running the whole suite, and documents every key inline.
28
+
29
+ ## [0.1.2] - 2026-09-16
4
30
 
5
31
  ### Added
6
32
 
@@ -52,6 +52,7 @@ before any test runs. YAML aliases are disabled.
52
52
  | --- | --- |
53
53
  | `REQCORD_TEST_FRAMEWORK` | `test.framework` |
54
54
  | `REQCORD_TEST_COMMAND` | `test.command` (and therefore `test.paths`) |
55
+ | `REQCORD_STRICT` | `test.strict` (`1` or `true`) |
55
56
  | `REQCORD_OUTPUT` | `output.directory` |
56
57
  | `REQCORD_BASE_URL` | `variables.base_url` |
57
58
  | `RESOURCE`, `VERSION` | run-time filters, see [Filtering a run](#filtering-a-run) |
@@ -123,9 +124,23 @@ absent.
123
124
 
124
125
  With neither `paths` nor `command`, Reqcord runs `bin/rails test`.
125
126
 
126
- Whatever runs must exit successfully. A failing suite aborts the run with
127
- `Reqcord::GenerationError` and nothing is written — documentation is only
128
- generated from a green suite.
127
+ ### `test.strict`
128
+
129
+ Default `false`. What happens when the suite does not exit successfully:
130
+
131
+ * `false` — the run continues and documents what the tests captured. A
132
+ warning names the exit status, and the report reminds you that routes
133
+ exercised only by failing tests show up as *uncovered*. A test that failed
134
+ on an assertion after its request got a `2xx` still counts: the request
135
+ and response are what the application really did.
136
+ * `true` (or `REQCORD_STRICT=1`) — a failing suite aborts the run with
137
+ `Reqcord::GenerationError` and nothing is written. Use it in CI when the
138
+ generated docs are an artifact that must come from a green build.
139
+
140
+ ```yaml
141
+ test:
142
+ strict: true
143
+ ```
129
144
 
130
145
  ---
131
146
 
@@ -146,6 +161,20 @@ routes:
146
161
  prefix: /api/v2
147
162
  ```
148
163
 
164
+ A list documents routes under any of the prefixes — for APIs whose versions
165
+ or audiences do not share a root:
166
+
167
+ ```yaml
168
+ routes:
169
+ prefix:
170
+ - /v1
171
+ - /v2
172
+ - /partner
173
+ ```
174
+
175
+ One prefix already covers every version beneath it (`/api` includes `/api/v1`
176
+ and `/api/v2`); use `VERSION=v2` to generate for one of them.
177
+
149
178
  Rails' own routes (`rails/…`, Active Storage, Action Mailbox, Turbo) are always
150
179
  left out. `redirect(...)` routes and plain Rack mounts cannot be documented from
151
180
  a test; they are counted as *skipped* in the report rather than dropped.
data/examples/reqcord.yml CHANGED
@@ -19,9 +19,16 @@ test:
19
19
  # Or spell the command out yourself; it wins over `paths`. Globs expand.
20
20
  # command: bin/rails test test/integration test/api
21
21
 
22
+ # A failing suite still documents what it captured; routes reached only by
23
+ # failing tests are listed as uncovered. `true` aborts instead
24
+ # (REQCORD_STRICT=1 per run).
25
+ # strict: false
26
+
22
27
  routes:
23
- # Only routes under this prefix are documented. Filter a single run with
24
- # RESOURCE=customers,cart or VERSION=v2 instead of editing this file.
28
+ # Only routes under this prefix are documented; a list works too:
29
+ # prefix: [/api, /partner]
30
+ # Filter a single run with RESOURCE=customers,cart or VERSION=v2 instead of
31
+ # editing this file.
25
32
  prefix: /api
26
33
 
27
34
  output:
@@ -8,7 +8,11 @@ module Reqcord
8
8
  # No default command: with neither `command` nor `paths` the generator
9
9
  # falls back to `bin/rails test`, and `paths` alone must be able to win.
10
10
  "test" => {
11
- "framework" => "minitest"
11
+ "framework" => "minitest",
12
+
13
+ # A red suite still documents what its green tests captured; strict
14
+ # runs abort instead, for CI that treats the docs as an artifact.
15
+ "strict" => false
12
16
  },
13
17
 
14
18
  "routes" => {
@@ -123,8 +127,21 @@ module Reqcord
123
127
  Array(data.dig("test", "paths")).map(&:to_s)
124
128
  end
125
129
 
130
+ # Abort on a failing suite instead of documenting what was captured.
131
+ def strict_tests?
132
+ value = ENV.fetch("REQCORD_STRICT") { data.dig("test", "strict") }
133
+
134
+ [true, "true", "1"].include?(value)
135
+ end
136
+
137
+ # `prefix: /api` or `prefix: [/v1, /v2]`; a route under any of them is
138
+ # documented. Empty means every route.
139
+ def route_prefixes
140
+ Array(data.dig("routes", "prefix")).map(&:to_s).reject(&:empty?)
141
+ end
142
+
126
143
  def route_prefix
127
- data.dig("routes", "prefix")
144
+ route_prefixes.first
128
145
  end
129
146
 
130
147
  def output_directory
@@ -56,6 +56,14 @@ module Reqcord
56
56
  # run can say why a request did not turn into documentation.
57
57
  attr_reader :unmatched_paths
58
58
 
59
+ # Exit status of the test run, so a caller can tell a fully green run
60
+ # from documentation generated out of a partly failing suite.
61
+ attr_reader :test_status
62
+
63
+ def tests_passed?
64
+ test_status.nil? || test_status.success?
65
+ end
66
+
59
67
  # Routes seen in the table but not documentable, by reason (redirect,
60
68
  # mount). Set by collect_routes; exposed so a run can be reconciled.
61
69
  attr_accessor :skipped_routes
@@ -161,6 +169,10 @@ module Reqcord
161
169
  Reqcord.log("skipped #{skipped_total} route(s) that cannot be documented: #{reasons}")
162
170
  end
163
171
 
172
+ unless tests_passed?
173
+ Reqcord.warn("the test run failed: routes exercised only by failing tests are listed as uncovered")
174
+ end
175
+
164
176
  return if unmatched_paths.empty?
165
177
 
166
178
  shown = unmatched_paths.uniq.first(5)
@@ -187,7 +199,7 @@ module Reqcord
187
199
  collector = RouteCollector.new(
188
200
  resources: resources,
189
201
  version: version,
190
- prefix: configuration.route_prefix
202
+ prefix: configuration.route_prefixes
191
203
  )
192
204
 
193
205
  routes = collector.call
@@ -254,10 +266,19 @@ module Reqcord
254
266
  wait_thread.value
255
267
  end
256
268
 
269
+ @test_status = status
270
+
257
271
  return if status.success?
258
272
 
259
- raise GenerationError,
260
- "Test suite failed while generating Reqcord documentation"
273
+ if configuration.strict_tests?
274
+ raise GenerationError,
275
+ "Test suite failed while generating Reqcord documentation (test.strict is on)"
276
+ end
277
+
278
+ # A red suite still tells the truth about the requests that passed;
279
+ # dropping everything would hide the docs behind an unrelated failure.
280
+ Reqcord.warn("test run exited with status #{status.exitstatus}; documenting what it captured anyway")
281
+ Reqcord.warn(" set test.strict: true (or REQCORD_STRICT=1) to abort on a failing suite")
261
282
  end
262
283
 
263
284
  def read_exchanges(capture_file)
@@ -211,10 +211,12 @@ module Reqcord
211
211
  )
212
212
  end
213
213
 
214
+ # `prefix` is one string or a list; nothing configured means every route.
214
215
  def matches_prefix?(path)
215
- return true if prefix.nil? || prefix.empty?
216
+ prefixes = Array(prefix).map(&:to_s).reject(&:empty?)
217
+ return true if prefixes.empty?
216
218
 
217
- path.start_with?(prefix)
219
+ prefixes.any? { |candidate| path.start_with?(candidate) }
218
220
  end
219
221
 
220
222
  # `RESOURCE=customers`, `RESOURCE=cart` (a singular resource is served by
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Reqcord
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
@@ -11,31 +11,62 @@ namespace :reqcord do
11
11
  end
12
12
 
13
13
  content = <<~YAML
14
+ # Reqcord configuration. Every key is documented in the gem's
15
+ # docs/configuration.md. Precedence: environment > this file > defaults.
14
16
  version: 1
15
17
 
16
18
  test:
19
+ # minitest or rspec
17
20
  framework: minitest
18
- command: bin/rails test
21
+
22
+ # The tests that exercise your API. Reqcord runs `bin/rails test <paths>`
23
+ # (or `rspec <paths>`) with capture enabled; a directory is enough.
24
+ paths:
25
+ - test/integration
26
+
27
+ # Or spell the command out yourself; it wins over `paths`.
28
+ # command: bin/rails test test/integration test/api
29
+
30
+ # A failing suite still documents what it captured. Set true to abort
31
+ # instead (or run with REQCORD_STRICT=1).
32
+ # strict: false
19
33
 
20
34
  routes:
35
+ # Only routes under this prefix are documented; a list works too
36
+ # (`prefix: [/v1, /v2]`). Filter a single run with
37
+ # RESOURCE=customers,cart or VERSION=v2.
21
38
  prefix: /api
22
39
 
23
40
  output:
24
41
  directory: docs/api
42
+
43
+ # Routes no test reached with a 2xx are listed in the index either
44
+ # way; `true` also writes a page for each of them.
25
45
  include_uncovered: false
26
46
 
47
+ # markdown: pages under docs/api, curl: one runnable .sh per endpoint,
48
+ # postman: postman/collection.json (import into Postman or Hoppscotch).
27
49
  exporters:
28
50
  - curl
29
51
  - markdown
30
52
  - postman
31
53
 
32
54
  variables:
55
+ # Host of every generated cURL and the Postman `base_url` variable.
33
56
  base_url: http://localhost:3000
34
57
 
35
58
  sanitize:
59
+ # Header values are replaced verbatim. Authorization, Cookie and
60
+ # X-Api-Key are always redacted, configured here or not.
36
61
  headers:
37
62
  Authorization: "Bearer {{token}}"
38
63
  X-Api-Key: "{{api_key}}"
64
+
65
+ # Body keys, matched at any depth in requests and responses. password,
66
+ # token, access_token, api_key, secret are always redacted; add the
67
+ # fields your API exposes (a signed payment link, for example).
68
+ body:
69
+ password: "{{password}}"
39
70
  YAML
40
71
 
41
72
  File.write(path, content)
@@ -82,7 +113,7 @@ namespace :reqcord do
82
113
  Reqcord::RouteCollector.call(
83
114
  resources: ENV.fetch("RESOURCE", "").split(",").map(&:strip).reject(&:empty?),
84
115
  version: ENV["VERSION"],
85
- prefix: Reqcord.configuration.route_prefix
116
+ prefix: Reqcord.configuration.route_prefixes
86
117
  )
87
118
 
88
119
  if routes.empty?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reqcord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmet Saridogan