diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml new file mode 100644 index 0000000..064d1c2 --- /dev/null +++ b/.github/workflows/link-check.yml @@ -0,0 +1,59 @@ +name: Link Check + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '0 0 * * *' # Daily at midnight + +jobs: + link-checker: + name: Link Checker + runs-on: ubuntu-latest + permissions: + issues: write # Required for creating issues from file + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 22 + + - name: Enable pnpm + run: corepack enable + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build docs + run: pnpm run docs:build + + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 + with: + # Check all files in the built site + args: | + docs/.vitepress/dist + --root-dir docs/.vitepress/dist + --include-mail=false + --exclude "^file://" + --accept '200..=299,403' + # Don't fail action on broken links (we'll create an issue instead) + fail: false + # Output format + format: markdown + # Output file path + output: lychee/out.md + + - name: Create Issue From File + if: steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@e8ef132d6df98ed982188e460ebb3b5d4ef3a9cd # v5 + with: + title: Link Checker Report + content-filepath: ./lychee/out.md + labels: report, automated issue diff --git a/docs/superpowers/specs/2026-03-28-bdd-spec-tests-design.md b/docs/superpowers/specs/2026-03-28-bdd-spec-tests-design.md deleted file mode 100644 index e08308c..0000000 --- a/docs/superpowers/specs/2026-03-28-bdd-spec-tests-design.md +++ /dev/null @@ -1,186 +0,0 @@ -# BDD Spec Compliance Tests Design - -## Status - -Draft - -## Overview - -Replace integration tests with BDD-style Gherkin scenarios for spec compliance testing. The feature files serve as executable documentation and are portable across JavaScript, Go, and Python implementations. - -## Goals - -- Executable spec compliance documentation -- Cross-language portability (JS → Go → Python) -- Fine-grained scenario coverage of IETF spec requirements -- Self-evident step definitions for porting - -## Approach - -Use the Cucumber/Gherkin ecosystem: - -- JavaScript: `@cucumber/cucumber` -- Go: `github.com/cucumber/godog` -- Python: `behave` - -Each language implements identical step definitions using native HTTP clients and SQLite drivers. - -## Directory Structure - -``` -tests/spec/ -├── idempotency.feature # All scenarios (~50) -├── steps/ -│ ├── world.js # Shared state & hooks -│ ├── http-steps.js # HTTP request steps -│ ├── response-steps.js # Response assertion steps -│ └── storage-steps.js # DB verification steps -``` - -## Scenario Design - -### Header Validation (~8 scenarios) - -- Missing `Idempotency-Key` on POST → 400 -- Key exceeding 256 characters → 400 -- Invalid format (not a string) → 400 -- Empty string key → 400 - -### First Request Handling (~10 scenarios) - -- Creates idempotency record in DB -- Returns 200 status -- Stores response body in DB -- Stores response status in DB -- Creates expected resource (e.g., order) - -### Duplicate Request Handling (~8 scenarios) - -- Returns cached response -- Sets `Idempotent-Replayed: true` header -- Does not create duplicate DB records -- Does not duplicate resource creation - -### Fingerprint Conflict (~6 scenarios) - -- Same body, different key → 409 Conflict -- Response body contains conflict message -- Only one resource created - -### Key Reuse Conflict (~4 scenarios) - -- Same key, different body → 422 Unprocessable Content -- Response body contains error message - -### Concurrent Requests (~6 scenarios) - -- Duplicate request during processing → 409 Conflict -- Response body indicates "request outstanding" - -### Error Response Format (~8 scenarios) - -- 400 error body follows RFC 9457 -- 409 error body follows RFC 9457 -- 422 error body follows RFC 9457 -- `Content-Type: application/problem+json` - -### Edge Cases (~6 scenarios) - -- GET requests (no idempotency required): should return 200, no idempotency record created -- PUT requests: should return 200, creates idempotency record -- DELETE requests: should return 200, creates idempotency record -- Empty request body: should return 200, creates idempotency record - -## Step Definitions - -### HTTP Steps - -```gherkin -Given a POST endpoint at "/api" -Given a PUT endpoint at "/api" -Given a DELETE endpoint at "/api" -Given a GET endpoint at "/api" -Given an idempotency key "xxx" -Given an idempotency key of 257 characters -When I send a POST request to "/api" with body {...} -When I send a POST request to "/api" without an idempotency key -``` - -### Response Steps - -```gherkin -Then the response status should be 400 -Then the response body should contain {...} -Then the response should have header "Idempotent-Replayed" with value "true" -Then the response content-type should be "application/problem+json" -``` - -### Storage Steps - -```gherkin -And an idempotency record should exist with key "xxx" -And the idempotency record status should be "complete" -And the idempotency record response body should be {...} -And N orders should exist in the database -``` - -## Cross-Language Portability - -### Feature File Sharing - -The canonical `idempotency.feature` file lives in this repository at `tests/spec/idempotency.feature`. When porting to Go or Python, copy this file verbatim into the corresponding project (e.g., `tests/spec/idempotency.feature` in idempot-go and idempot-py repositories). - -### What copies verbatim - -- `idempotency.feature` file - -### What gets re-implemented - -- Step definitions (using native HTTP clients and SQLite drivers) - -### Language-specific dependencies - -**JavaScript** - -- `@cucumber/cucumber` -- `better-sqlite3` -- `express` or `fastify` (test server) - -**Go** - -- `github.com/cucumber/godog` -- `github.com/mattn/go-sqlite3` - -**Python** - -- `behave` -- `sqlite3` (stdlib) -- `flask` or `http.server` (test server) - -## Implementation Notes - -### Database Setup - -- SQLite for all three languages -- Schema created before each scenario -- Schema dropped after each scenario -- Idempotency records table + orders table - -### Test Server - -- JavaScript: Express/Fastify with middleware -- Go: `net/http` with middleware -- Python: Flask with middleware - -### State Management - -- Each scenario gets fresh DB and server -- Step definitions use shared world object for state -- No dependencies between scenarios - -## Validation Criteria - -- All ~50 scenarios pass in JS -- Feature file is identical across all three languages -- Step definitions are self-evident (no spec knowledge required) -- Each spec requirement has corresponding scenarios diff --git a/package.json b/package.json index 8728f39..9bb5241 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "spec:sync": "./scripts/sync-spec.sh", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", - "docs:preview": "vitepress preview docs" + "docs:preview": "vitepress preview docs", + "lint:links": "./scripts/link-check.sh" }, "dependencies": { "jsonpath-plus": "^10.3.0", diff --git a/scripts/link-check.sh b/scripts/link-check.sh new file mode 100755 index 0000000..7000732 --- /dev/null +++ b/scripts/link-check.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +echo "Running lychee link check on dist folder..." +lychee docs/.vitepress/dist \ + --root-dir docs/.vitepress/dist \ + --include-mail=false \ + --exclude "^file://" \ + --accept '200..=299,403'