specproof 0.2.1 โ†’ 0.4.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.
@@ -1,69 +0,0 @@
1
- name: ๐Ÿค– CI
2
-
3
- on:
4
- pull_request:
5
- branches: main
6
- merge_group:
7
- push:
8
- branches: main
9
- workflow_dispatch:
10
-
11
- jobs:
12
- build:
13
- name: Build
14
- runs-on: ubuntu-latest
15
- steps:
16
- - name: ๐Ÿ“ฅ Checkout code
17
- uses: actions/checkout@v5
18
-
19
- - name: ๐Ÿž Setup Bun
20
- uses: oven-sh/setup-bun@v2
21
- with:
22
- bun-version: latest
23
-
24
- - name: ๐Ÿงฐ Install dependencies
25
- run: bun install
26
-
27
- - name: ๐Ÿ—๏ธ Build
28
- run: bun run build
29
-
30
- lint:
31
- name: Lint
32
- runs-on: ubuntu-latest
33
- steps:
34
- - name: ๐Ÿ“ฅ Checkout code
35
- uses: actions/checkout@v5
36
-
37
- - name: ๐Ÿž Setup Bun
38
- uses: oven-sh/setup-bun@v2
39
- with:
40
- bun-version: latest
41
-
42
- - name: ๐Ÿงฐ Install dependencies
43
- run: bun install
44
-
45
- - name: ๐Ÿงน ESLint
46
- run: bun run lint:es
47
-
48
- - name: ๐Ÿ”Ž Typecheck
49
- run: bun run lint:ts
50
-
51
- test:
52
- name: Test
53
- runs-on: ubuntu-latest
54
- env:
55
- NODE_ENV: test
56
- steps:
57
- - name: ๐Ÿ“ฅ Checkout code
58
- uses: actions/checkout@v5
59
-
60
- - name: ๐Ÿž Setup Bun
61
- uses: oven-sh/setup-bun@v2
62
- with:
63
- bun-version: latest
64
-
65
- - name: ๐Ÿงฐ Install dependencies
66
- run: bun install
67
-
68
- - name: ๐Ÿงพ Run unit & contract tests
69
- run: bun run test:unit
@@ -1,49 +0,0 @@
1
- name: ๐Ÿšข Release
2
-
3
- on:
4
- push:
5
- branches: main
6
-
7
- permissions:
8
- contents: read
9
- id-token: write # npm provenance
10
-
11
- jobs:
12
- publish:
13
- name: ๐Ÿš€ Publish to npm
14
- runs-on: ubuntu-latest
15
-
16
- env:
17
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
18
-
19
- steps:
20
- - name: ๐Ÿ“ฅ Checkout code
21
- uses: actions/checkout@v5
22
-
23
- - name: ๐ŸŸข Setup Node
24
- uses: actions/setup-node@v4
25
- with:
26
- node-version: 22
27
- registry-url: https://registry.npmjs.org
28
-
29
- - name: ๐Ÿ”Ž Skip if this version is already on npm
30
- id: check
31
- run: |
32
- NAME=$(node -p "require('./package.json').name")
33
- VERSION=$(node -p "require('./package.json').version")
34
- if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then
35
- echo "::notice::$NAME@$VERSION is already published โ€” nothing to do"
36
- echo "published=true" >> "$GITHUB_OUTPUT"
37
- else
38
- echo "published=false" >> "$GITHUB_OUTPUT"
39
- fi
40
-
41
- - name: โš ๏ธ Warn when NPM_TOKEN is missing
42
- if: steps.check.outputs.published == 'false' && env.NPM_TOKEN == ''
43
- run: echo "::warning::NPM_TOKEN secret is not set โ€” skipping npm publish. Add it under Settings โ†’ Secrets and variables โ†’ Actions."
44
-
45
- - name: ๐Ÿš€ Publish to npm
46
- if: steps.check.outputs.published == 'false' && env.NPM_TOKEN != ''
47
- run: npm publish --provenance --access public
48
- env:
49
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md DELETED
@@ -1,52 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [0.2.1] - 2026-07-14
9
-
10
- ### Changed
11
- - Renamed the CI workflow file from `test-unit.yml` to `ci.yml` and refreshed the emoji in both workflow titles.
12
-
13
- ## [0.2.0] - 2026-07-14
14
-
15
- ### Added
16
- - Bundled `example/` demo target: the fictional TaskFlow API (7 operations) plus fixture test files, audited automatically when no `SPECPROOF_REPO` is set โ€” the app now renders a meaningful proof out of the box, demonstrating every verdict state.
17
- - Unit test suite for the coverage analyzer (`lib/api-test-coverage.test.ts`, 30 tests): path-param normalization, snippet extraction, test-file parsing, spec discovery, evidence merging, and report assembly with independently computed expectations.
18
- - CI workflow with separate `Build`, `Lint`, and `Test` jobs, running on pull requests and in the merge queue.
19
- - Merge queue ruleset on `main`: PRs required, all CI checks must pass, no direct pushes.
20
- - Marketing showcase page (`marketing/`) and brand assets (`public/`), with a banner in the README.
21
-
22
- ### Changed
23
- - Releases now publish only after a PR merges to `main` through the merge queue; the manual release trigger was removed.
24
- - `buildCoverageReport()` accepts an optional target repo root, and the analyzer's parsing internals are exported for testing.
25
- - Larger SpecProof masthead in the coverage view.
26
-
27
- ## [0.1.0] - 2026-07-13
28
-
29
- ### Added
30
- - Initial public release under new name SpecProof (npm package `specproof`).
31
- - GitHub Actions release workflow: automatically publishes to npm when package version is updated.
32
- - Comprehensive project documentation in CLAUDE.md.
33
- - Generalized spec/test discovery: auto-discovers OpenAPI specs (`openapi*.json`, `swagger*.json`) and test files in any target repo; no longer OmniLens-specific.
34
- - Support for explicit spec path via `SPECPROOF_SPEC` environment variable.
35
-
36
- ### Changed
37
- - **Breaking**: Renamed environment variable `OMNILENS_REPO` โ†’ `SPECPROOF_REPO` for specifying the target repo to audit.
38
- - **Breaking**: Renamed environment variable `OMNILENS_SPEC` โ†’ `SPECPROOF_SPEC` for specifying the OpenAPI spec path.
39
- - Renamed npm package from `test-ledger` to `specproof`.
40
- - Renamed generator script `scripts/generate-ledger.ts` โ†’ `scripts/generate-proof.ts` and npm command `generate:ledger` โ†’ `generate:proof`.
41
- - Renamed generated artifact `app/ledger.generated.json` โ†’ `app/proof.generated.json`.
42
- - Renamed contract test file `app/ledger-contract.test.ts` โ†’ `app/proof-contract.test.ts`.
43
- - Renamed stylesheet `app/ledger.css` โ†’ `app/proof.css`.
44
- - Renamed React component `CoverageLedger` โ†’ `CoverageProof`.
45
- - Updated CSS class prefix `lg-` โ†’ `sp-` throughout the codebase.
46
- - Generalized code comments and documentation from OmniLens-specific to target-agnostic language.
47
- - GitHub Actions workflow `test-unit.yml` renamed to "Proof Contract Tests" with updated environment variable references.
48
- - Removed `"private": true` from package.json to allow npm publishing.
49
- - Updated package.json metadata: added `repository`, `homepage`, `description`, and `keywords`.
50
-
51
- ### Removed
52
- - OmniLens-specific hardcoded paths and configuration.
package/CLAUDE.md DELETED
@@ -1,82 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## What this is
6
-
7
- SpecProof: a standalone Next.js app that renders an audit view of any repo's API test coverage: every OpenAPI operation and response status, cross-examined against the target repo's test assertions. Clicking a stamped verdict shows the actual test snippet that proves (or fails to prove) coverage.
8
-
9
- This repo does not contain a real API or its tests โ€” it audits a target repo from the outside, the way OpenAPI tooling works when installed into a repo. Nothing repo-specific is baked in. It does carry a hand-written demo target in `example/` (see "Example fixture" below), which is what gets audited when no `SPECPROOF_REPO` is set.
10
-
11
- ## Architecture: generate โ†’ checked-in artifact โ†’ contract-test
12
-
13
- 1. **Sources of truth**, read from the target repo (`SPECPROOF_REPO` env var; defaults to the current working directory):
14
- - The OpenAPI spec โ€” auto-discovered as the shallowest `openapi*.json` / `swagger*.json` in the tree, or set explicitly with `SPECPROOF_SPEC` (relative to the repo root).
15
- - The repo's `*.test.ts` / `*.test.tsx` / `*.test.js` files โ€” parsed via regex for `describe("METHOD /path")` blocks and `.status).toBe(NNN)` / `.status).toEqual(NNN)` assertions.
16
- 2. **Analyzer** (`lib/api-test-coverage.ts`): joins the two sources into a `CoverageReport` (tags โ†’ operations โ†’ statuses, each status with assertion counts and extracted `it()` snippets). Operations are joined on the describe title's method + path; `{param}`, `[param]`, and `:param` segments are treated as equivalent. When several test files describe the same operation, the one with the most `it()` blocks wins.
17
- 3. **Generator** (`scripts/generate-proof.ts`): calls the analyzer, writes the result to `app/proof.generated.json`. This is checked in, deterministic, and regenerated automatically before `dev`/`build`. When no target spec is found it leaves the existing artifact untouched (exit 0), so the app always builds.
18
- 4. **Consumer** (`app/page.tsx` โ†’ `components/CoverageProof.tsx`): renders the checked-in artifact only. No target checkout is needed to build or deploy the app itself. An empty proof renders an empty-state hint.
19
- 5. **Drift guard** (`app/proof-contract.test.ts`): fails when the artifact is stale relative to the spec/tests, when the proof's audited operations don't exactly match the spec's operations, or when a quoted snippet no longer points at a real `it()`/`test()` line. This suite self-skips (via `describe.skipIf`) when no target spec is resolvable โ€” in this repo one always is (the example fixture), so `bun run test:unit` always exercises the full analyzer here.
20
-
21
- ## Example fixture (`example/`)
22
-
23
- `example/` is a hand-written demo target: `example/api/openapi.json` (the fictional "TaskFlow" API, 7 operations) plus `example/tests/*.test.ts` written in exactly the `describe("METHOD /path")` / `.status).toBe(NNN)` shape the parser expects. Because spec auto-discovery finds the shallowest `openapi*.json` under the target root (cwd by default), running `dev` / `generate:proof` / `test:unit` from this repo audits the example automatically โ€” no `SPECPROOF_REPO` needed. When the package is installed into another repo, the example sits under `node_modules/`, which the scanner excludes, so it never pollutes a real audit.
24
-
25
- - The example files are **fixture data parsed as text**, never executed or compiled: they are excluded from vitest (`vitest.config.ts`), tsc (`tsconfig.json`), and eslint (`eslint.config.mjs`). They only need to look like a prettier-formatted test suite.
26
- - The spec and tests are **deliberately out of step** so the demo shows every verdict state: fully proven operations (`POST /auth/login`, `GET /tasks`, `GET /tasks/{taskId}`), partial coverage (`POST /tasks`, `PATCH /tasks/{taskId}`), untested operations (`DELETE /tasks/{taskId}`, `GET /projects`), and one status the tests assert but the spec omits (422 on `POST /tasks`). Preserve that mix when editing.
27
- - Editing anything in `example/` makes the checked-in proof stale and fails the contract tests: run `bun run generate:proof` and commit the regenerated `app/proof.generated.json`.
28
-
29
- Because of this split, most day-to-day work happens in exactly one of two places:
30
- - Changing what's audited/how coverage is computed โ†’ `lib/api-test-coverage.ts` (+ regenerate the proof).
31
- - Changing how the proof is displayed โ†’ `components/CoverageProof.tsx` / `app/proof.css`, reading the existing `app/proof.generated.json` as fixture data โ€” no target checkout needed.
32
-
33
- ## Commands
34
-
35
- Requires [Bun](https://bun.sh).
36
-
37
- ```bash
38
- bun install
39
- bun run dev # generate:proof + next dev (port 3001)
40
- bun run build # generate:proof + next build
41
- bun run start # serve the production build (port 3001)
42
- bun run generate:proof # regenerate app/proof.generated.json only
43
- bun run test:unit # contract tests (vitest run; audits example/ unless SPECPROOF_REPO is set)
44
- bun run test:unit:watch # vitest watch mode
45
- bun run lint # eslint + tsc --noEmit
46
- bun run lint:es # eslint only
47
- bun run lint:ts # tsc --noEmit only
48
- ```
49
-
50
- To run a single test file: `bunx vitest run app/proof-contract.test.ts` (contract tests) or `bunx vitest run lib/api-test-coverage.test.ts` (analyzer unit tests). The unit tests' inline fixtures deliberately use operations that don't exist in the example spec (`/widgets`, `/gadgets`) โ€” the analyzer parses this repo's own `*.test.ts` files as raw text when auditing `example/`, and colliding paths would pollute the generated proof.
51
-
52
- To audit a real repo instead of the bundled example:
53
-
54
- ```bash
55
- SPECPROOF_REPO=/path/to/target-repo bun run generate:proof
56
- # optionally, if the spec has a nonstandard name/location:
57
- SPECPROOF_REPO=/path/to/target-repo SPECPROOF_SPEC=docs/api-spec.json bun run generate:proof
58
- ```
59
-
60
- ## Updating the proof
61
-
62
- When a target repo's test or OpenAPI spec changes, the checked-in `app/proof.generated.json` goes stale and the contract tests fail. Fix:
63
-
64
- ```bash
65
- SPECPROOF_REPO=/path/to/target-repo bun run generate:proof
66
- git add app/proof.generated.json && git commit
67
- ```
68
-
69
- ## CI & Releasing
70
-
71
- `main` is protected by a repo ruleset (PR required, merge queue, no direct pushes). The flow: open a PR โ†’ the CI workflow (`.github/workflows/test-unit.yml`, jobs `Build` / `Lint` / `Test`) runs on the PR and again in the merge queue โ†’ all three checks must pass โ†’ clicking merge queues the PR and GitHub merges it (squash).
72
-
73
- Every merge to `main` then triggers `.github/workflows/release.yml`, which publishes to npm when `package.json`'s version isn't already on the registry (needs the `NPM_TOKEN` repo secret). To release: bump `version` in `package.json`, open a PR, and merge. There is no manual release trigger.
74
-
75
- ## Notes on the parsing approach
76
-
77
- `lib/api-test-coverage.ts` parses test files with regexes rather than a real TS/AST parser, relying on prettier-consistent formatting conventions in the audited repo:
78
- - `describe("METHOD /path")` titles are matched for the HTTP method prefix (`GET|POST|PUT|DELETE|PATCH`) followed by the operation path.
79
- - `it()`/`test()` blocks are extracted by matching the opening call, then scanning for the next `});` at the same indentation level โ€” not a brace-matching parser, so it depends on consistent formatting in the audited repo.
80
- - Snippet source is dedented before being stored/rendered.
81
-
82
- If this parsing logic needs updating, check it against real test files in a target repo โ€” the contract test's "every quoted snippet points at a real it()/test() block" check is what catches regressions here.
@@ -1,95 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { describe, expect, it } from "vitest";
4
-
5
- import {
6
- resolveSpecPath,
7
- TARGET_REPO_ROOT,
8
- type CoverageReport,
9
- } from "@/lib/api-test-coverage";
10
- import { buildProof, GENERATED_PROOF_PATH } from "@/scripts/generate-proof";
11
-
12
- /**
13
- * Contract tests that keep the rendered proof in lockstep with the target
14
- * repo it audits. Three drift modes are covered:
15
- *
16
- * 1. The OpenAPI spec gains/loses an operation without the proof following
17
- * (spec <-> proof, both directions).
18
- * 2. A quoted test snippet no longer points at a real it()/test() block in
19
- * its test file โ€” the evidence the UI shows must be re-readable from the
20
- * source it cites.
21
- * 3. The spec or a test file changed without regenerating
22
- * proof.generated.json.
23
- */
24
-
25
- function loadCheckedInProof(): CoverageReport {
26
- return JSON.parse(fs.readFileSync(GENERATED_PROOF_PATH, "utf8"));
27
- }
28
-
29
- // The drift guards compare the artifact against the audited target repo
30
- // (SPECPROOF_REPO, or the current directory). Without an OpenAPI spec to audit
31
- // there is nothing to compare against, so the suite self-skips.
32
- const specPath = resolveSpecPath();
33
- if (!specPath) {
34
- console.warn(
35
- `proof-contract: skipping โ€” no OpenAPI spec found under ${TARGET_REPO_ROOT} (set SPECPROOF_REPO to the repo to audit, or SPECPROOF_SPEC to the spec file)`,
36
- );
37
- }
38
-
39
- describe.skipIf(!specPath)("SpecProof contract", () => {
40
- it("audits every operation in the OpenAPI spec, and nothing else", () => {
41
- const spec = JSON.parse(fs.readFileSync(specPath!, "utf8")) as {
42
- paths: Record<string, Record<string, unknown>>;
43
- };
44
-
45
- const documented = Object.entries(spec.paths)
46
- .flatMap(([opPath, methods]) =>
47
- Object.keys(methods).map((method) => `${method} ${opPath}`),
48
- )
49
- .sort();
50
-
51
- const audited = loadCheckedInProof()
52
- .tags.flatMap((tag) => tag.operations)
53
- .map((op) => `${op.method} ${op.specPath}`)
54
- .sort();
55
-
56
- expect(documented.length).toBeGreaterThan(0);
57
- expect(audited).toEqual(documented);
58
- });
59
-
60
- it("every quoted snippet points at a real it()/test() block in its test file", () => {
61
- const sources = new Map<string, string[]>();
62
- const readLines = (testFile: string) => {
63
- const cached = sources.get(testFile);
64
- if (cached) return cached;
65
- const lines = fs
66
- .readFileSync(path.join(TARGET_REPO_ROOT, testFile), "utf8")
67
- .split("\n");
68
- sources.set(testFile, lines);
69
- return lines;
70
- };
71
-
72
- for (const tag of loadCheckedInProof().tags) {
73
- for (const op of tag.operations) {
74
- if (!op.testFile) continue;
75
- for (const status of op.statuses) {
76
- for (const snippet of status.snippets) {
77
- const line = readLines(op.testFile)[snippet.startLine - 1] ?? "";
78
- expect(
79
- /(?:it|test)\(/.test(line),
80
- `${op.method} ${op.specPath} ${status.code}: snippet "${snippet.title}" cites ${op.testFile}:${snippet.startLine}, but that line is not an it()/test() block`,
81
- ).toBe(true);
82
- }
83
- }
84
- }
85
- }
86
- });
87
-
88
- it("proof.generated.json is up to date with the spec and tests", () => {
89
- const checkedIn = loadCheckedInProof();
90
- // If this fails, the OpenAPI spec or a test file changed without
91
- // regenerating the proof: run `bun run generate:proof` and commit the
92
- // result.
93
- expect(checkedIn).toEqual(buildProof());
94
- });
95
- });
@@ -1,288 +0,0 @@
1
- {
2
- "tags": [
3
- {
4
- "tag": "Auth",
5
- "description": "Session and token management",
6
- "operations": [
7
- {
8
- "method": "post",
9
- "specPath": "/auth/login",
10
- "summary": "Exchange credentials for a session token",
11
- "testFile": "example/tests/auth.test.ts",
12
- "testCount": 2,
13
- "statuses": [
14
- {
15
- "code": "200",
16
- "description": "Session token issued",
17
- "documented": true,
18
- "assertions": 1,
19
- "snippets": [
20
- {
21
- "title": "returns a session token for valid credentials",
22
- "source": "it(\"returns a session token for valid credentials\", async () => {\n const res = await api.post(\"/auth/login\", {\n email: \"ada@example.com\",\n password: \"correct horse battery staple\",\n });\n\n expect(res.status).toBe(200);\n expect(res.body.token).toMatch(/^tk_/);\n});",
23
- "startLine": 6
24
- }
25
- ]
26
- },
27
- {
28
- "code": "401",
29
- "description": "Invalid credentials",
30
- "documented": true,
31
- "assertions": 1,
32
- "snippets": [
33
- {
34
- "title": "rejects invalid credentials",
35
- "source": "it(\"rejects invalid credentials\", async () => {\n const res = await api.post(\"/auth/login\", {\n email: \"ada@example.com\",\n password: \"not-the-password\",\n });\n\n expect(res.status).toBe(401);\n expect(res.body.token).toBeUndefined();\n});",
36
- "startLine": 16
37
- }
38
- ]
39
- }
40
- ],
41
- "coveredCount": 2,
42
- "gapCount": 0
43
- }
44
- ],
45
- "coveredCount": 2,
46
- "totalCount": 2
47
- },
48
- {
49
- "tag": "Tasks",
50
- "description": "Create and manage tasks",
51
- "operations": [
52
- {
53
- "method": "get",
54
- "specPath": "/tasks",
55
- "summary": "List tasks for the authenticated user",
56
- "testFile": "example/tests/tasks.test.ts",
57
- "testCount": 2,
58
- "statuses": [
59
- {
60
- "code": "200",
61
- "description": "Task list",
62
- "documented": true,
63
- "assertions": 1,
64
- "snippets": [
65
- {
66
- "title": "lists tasks for the authenticated user",
67
- "source": "it(\"lists tasks for the authenticated user\", async () => {\n const res = await api.get(\"/tasks\");\n\n expect(res.status).toBe(200);\n expect(Array.isArray(res.body.tasks)).toBe(true);\n});",
68
- "startLine": 14
69
- }
70
- ]
71
- },
72
- {
73
- "code": "401",
74
- "description": "Missing or invalid bearer token",
75
- "documented": true,
76
- "assertions": 1,
77
- "snippets": [
78
- {
79
- "title": "rejects requests without a bearer token",
80
- "source": "it(\"rejects requests without a bearer token\", async () => {\n const res = await api.get(\"/tasks\", { auth: false });\n\n expect(res.status).toBe(401);\n});",
81
- "startLine": 21
82
- }
83
- ]
84
- }
85
- ],
86
- "coveredCount": 2,
87
- "gapCount": 0
88
- },
89
- {
90
- "method": "post",
91
- "specPath": "/tasks",
92
- "summary": "Create a task",
93
- "testFile": "example/tests/tasks.test.ts",
94
- "testCount": 3,
95
- "statuses": [
96
- {
97
- "code": "201",
98
- "description": "Task created",
99
- "documented": true,
100
- "assertions": 1,
101
- "snippets": [
102
- {
103
- "title": "creates a task and returns it",
104
- "source": "it(\"creates a task and returns it\", async () => {\n const res = await api.post(\"/tasks\", {\n title: \"Ship the Q3 roadmap\",\n projectId: \"proj_1\",\n });\n\n expect(res.status).toBe(201);\n expect(res.body.id).toBeDefined();\n expect(res.body.title).toBe(\"Ship the Q3 roadmap\");\n});",
105
- "startLine": 29
106
- }
107
- ]
108
- },
109
- {
110
- "code": "400",
111
- "description": "Malformed request body",
112
- "documented": true,
113
- "assertions": 1,
114
- "snippets": [
115
- {
116
- "title": "rejects a task without a title",
117
- "source": "it(\"rejects a task without a title\", async () => {\n const res = await api.post(\"/tasks\", { projectId: \"proj_1\" });\n\n expect(res.status).toBe(400);\n expect(res.body.error).toBe(\"title is required\");\n});",
118
- "startLine": 40
119
- }
120
- ]
121
- },
122
- {
123
- "code": "401",
124
- "description": "Missing or invalid bearer token",
125
- "documented": true,
126
- "assertions": 0,
127
- "snippets": []
128
- },
129
- {
130
- "code": "422",
131
- "description": "",
132
- "documented": false,
133
- "assertions": 1,
134
- "snippets": [
135
- {
136
- "title": "rejects a due date in the past",
137
- "source": "it(\"rejects a due date in the past\", async () => {\n const res = await api.post(\"/tasks\", {\n title: \"Time travel\",\n dueDate: \"1999-12-31\",\n });\n\n expect(res.status).toBe(422);\n});",
138
- "startLine": 47
139
- }
140
- ]
141
- }
142
- ],
143
- "coveredCount": 2,
144
- "gapCount": 1
145
- },
146
- {
147
- "method": "get",
148
- "specPath": "/tasks/{taskId}",
149
- "summary": "Fetch a single task",
150
- "testFile": "example/tests/tasks.test.ts",
151
- "testCount": 2,
152
- "statuses": [
153
- {
154
- "code": "200",
155
- "description": "The task",
156
- "documented": true,
157
- "assertions": 1,
158
- "snippets": [
159
- {
160
- "title": "returns a task by id",
161
- "source": "it(\"returns a task by id\", async () => {\n const created = await api.post(\"/tasks\", { title: \"Write release notes\" });\n const res = await api.get(`/tasks/${created.body.id}`);\n\n expect(res.status).toBe(200);\n expect(res.body.id).toBe(created.body.id);\n});",
162
- "startLine": 58
163
- }
164
- ]
165
- },
166
- {
167
- "code": "404",
168
- "description": "No task with this id",
169
- "documented": true,
170
- "assertions": 1,
171
- "snippets": [
172
- {
173
- "title": "returns 404 for an unknown task id",
174
- "source": "it(\"returns 404 for an unknown task id\", async () => {\n const res = await api.get(\"/tasks/task_does_not_exist\");\n\n expect(res.status).toBe(404);\n});",
175
- "startLine": 66
176
- }
177
- ]
178
- }
179
- ],
180
- "coveredCount": 2,
181
- "gapCount": 0
182
- },
183
- {
184
- "method": "patch",
185
- "specPath": "/tasks/{taskId}",
186
- "summary": "Update a task's fields",
187
- "testFile": "example/tests/tasks.test.ts",
188
- "testCount": 1,
189
- "statuses": [
190
- {
191
- "code": "200",
192
- "description": "Updated task",
193
- "documented": true,
194
- "assertions": 1,
195
- "snippets": [
196
- {
197
- "title": "updates the fields of a task",
198
- "source": "it(\"updates the fields of a task\", async () => {\n const created = await api.post(\"/tasks\", { title: \"Refine the backlog\" });\n const res = await api.patch(`/tasks/${created.body.id}`, {\n title: \"Refine and prioritize the backlog\",\n });\n\n expect(res.status).toBe(200);\n expect(res.body.title).toBe(\"Refine and prioritize the backlog\");\n});",
199
- "startLine": 74
200
- }
201
- ]
202
- },
203
- {
204
- "code": "404",
205
- "description": "No task with this id",
206
- "documented": true,
207
- "assertions": 0,
208
- "snippets": []
209
- },
210
- {
211
- "code": "409",
212
- "description": "Task was modified concurrently",
213
- "documented": true,
214
- "assertions": 0,
215
- "snippets": []
216
- }
217
- ],
218
- "coveredCount": 1,
219
- "gapCount": 2
220
- },
221
- {
222
- "method": "delete",
223
- "specPath": "/tasks/{taskId}",
224
- "summary": "Delete a task",
225
- "testFile": null,
226
- "testCount": 0,
227
- "statuses": [
228
- {
229
- "code": "204",
230
- "description": "Task deleted",
231
- "documented": true,
232
- "assertions": 0,
233
- "snippets": []
234
- },
235
- {
236
- "code": "404",
237
- "description": "No task with this id",
238
- "documented": true,
239
- "assertions": 0,
240
- "snippets": []
241
- }
242
- ],
243
- "coveredCount": 0,
244
- "gapCount": 2
245
- }
246
- ],
247
- "coveredCount": 7,
248
- "totalCount": 12
249
- },
250
- {
251
- "tag": "Projects",
252
- "description": "Group tasks into projects",
253
- "operations": [
254
- {
255
- "method": "get",
256
- "specPath": "/projects",
257
- "summary": "List projects",
258
- "testFile": null,
259
- "testCount": 0,
260
- "statuses": [
261
- {
262
- "code": "200",
263
- "description": "Project list",
264
- "documented": true,
265
- "assertions": 0,
266
- "snippets": []
267
- },
268
- {
269
- "code": "401",
270
- "description": "Missing or invalid bearer token",
271
- "documented": true,
272
- "assertions": 0,
273
- "snippets": []
274
- }
275
- ],
276
- "coveredCount": 0,
277
- "gapCount": 2
278
- }
279
- ],
280
- "coveredCount": 0,
281
- "totalCount": 2
282
- }
283
- ],
284
- "operationCount": 7,
285
- "coveredCount": 9,
286
- "totalCount": 16,
287
- "untestedOperations": 2
288
- }