Files
suaveplan/openspec/archive/2026-05-02-testing-integration-step-ca/tasks.md
2026-05-27 23:29:53 +01:00

6.0 KiB
Raw Blame History

Tasks — testing-integration-step-ca

1. Package scaffolding

  • 1.1 Create packages/dev/testing-integration-step-ca/ with the standard layout (package.json, tsconfig.json, vite.config.ts, README.md, src/index.ts, src/fixture/).
  • 1.2 package.json: name @suaveplan/testing-integration-step-ca, version 0.1.0, peerDependencies for @suaveplan/testing-integration and testcontainers (NOT dependencies), dependencies for @suaveplan/error and @suaveplan/types only.
  • 1.3 Subpath exports: ., ./fixture, ./constants.
  • 1.4 Add the package to CLAUDE.md Packages section, then run bun scripts/folder-reorg/generate-manifest.ts.

2. Image + constants

  • 2.1 Pin smallstep/step-ca to a specific tag + digest in src/constants/index.ts as STEPCA_IMAGE.
  • 2.2 Define STEPCA_HEALTH_TIMEOUT_MS = 60_000, STEPCA_HEALTH_POLL_INTERVAL_MS = 1000.
  • 2.3 Define STEPCA_ENV_VARS = ['STEPCA_URL','STEPCA_ROOT_CERT_PEM','STEPCA_ROOT_CERT_PATH','STEPCA_FINGERPRINT','STEPCA_PROVISIONER_PASSWORD'] as const.
  • 2.4 Define STEPCA_DEFAULT_PROVISIONER = 'api', STEPCA_DEFAULT_DNS = 'localhost,step-ca', STEPCA_INTERNAL_PORT = 8443.

3. Error codes

  • 3.1 Register codes via @suaveplan/error's defineCodes with prefix INTEGRATION.STEP_CA.*: INIT_FAILED, JWK_PROVISIONER_FAILED, ROOT_READ_FAILED, FINGERPRINT_FAILED, ISSUE_CERT_FAILED, PARSE_PEM_FAILED, HEALTH_TIMEOUT.

4. Fixture core (src/fixture/step-ca-fixture.ts)

  • 4.1 Implement StepCaFixture implements ServiceFixture<StepCaConfig> with name === 'step-ca'.
  • 4.2 Constructor options: { image?; caName?; dnsNames?; provisionerName?; healthTimeoutMs?; healthPollIntervalMs?; containerFactory?; execFn?; fetchFn? }.
  • 4.3 start() env-bridge: when ALL FIVE STEPCA_ENV_VARS are set+non-empty → return passthrough config; partial (4 of 5) → fall through to container mode.
  • 4.4 Container mode: start container via factory (default: dynamic GenericContainer import), expose internal 8443 → host port, set environment variables required by step-ca.
  • 4.5 Init sequence (steps 1–6 from proposal §What Changes/Container mode); each step wrapped in a try/catch that throws the matching INTEGRATION.STEP_CA.* error with the underlying stderr attached.
  • 4.6 Materialize rootCertPem to a host-side tmpfile (fs.mkdtempSync + tmp/<name>.crt), set rootCertPath accordingly.
  • 4.7 If waitForHealth() rejects, stop the container exactly once and clear the reference before re-throwing.

5. Health probe

  • 5.1 healthCheck() returns false when no container started.
  • 5.2 Container mode: GET https://localhost:<caPort>/health via injected fetchFn (default globalThis.fetch) with TLS verification disabled (use a per-call https.Agent or Bun-equivalent that the injected fetch can apply). Return res.ok. Network errors → false.
  • 5.3 waitForHealth() delegates to new HealthPoller({ timeoutMs, intervalMs }).poll(this) with STEPCA_HEALTH_TIMEOUT_MS / STEPCA_HEALTH_POLL_INTERVAL_MS defaults.
  • 5.4 Timeout rejection MUST be a TestEnvironmentError whose message contains Health check timed out for fixture "step-ca".

6. issueCert() helper

  • 6.1 Signature issueCert({ commonName, sans?, ttl? }) → Promise<{ certPem; keyPem; chainPem; serial }>.
  • 6.2 Shells step ca certificate <commonName> <crt> <key> --provisioner <provName> --provisioner-password-file <pwFile> --not-after=<ttl> --san <san1> --san <san2> ... inside the container via injected execFn.
  • 6.3 Reads cert + key from container, splits leaf + chain via PEM block parser (own implementation; no external dep), extracts serial via step certificate inspect --format json.
  • 6.4 Throws INTEGRATION.STEP_CA.ISSUE_CERT_FAILED on non-zero exit; INTEGRATION.STEP_CA.PARSE_PEM_FAILED on malformed PEM.
  • 6.5 Verifies commonName is non-empty before shelling — empty → INTEGRATION.STEP_CA.ISSUE_CERT_FAILED (no shell invocation).

7. Tests (100% Istanbul, per AGENTS.md §7)

  • 7.1 src/fixture/step-ca-fixture.test.ts — passthrough mode (full + partial), container mode happy path, init sequence command order, image override, containerFactory injection, execFn injection, fetchFn injection, health-check truth table, waitForHealth timeout + cleanup, stop() idempotency.
  • 7.2 src/fixture/step-ca-fixture-issue-cert.test.ts — issueCert happy path (with mocked execFn), SAN passthrough, TTL passthrough, malformed PEM rejection, empty commonName rejection, non-zero exit rejection.
  • 7.3 src/fixture/step-ca-fixture-parse.test.ts — PEM block splitter (leaf + chain), serial extraction, edge cases (empty input, missing END line, multiple chain certs).
  • 7.4 Integration-level smoke test src/fixture/step-ca-fixture.integration.test.ts (gated by INTEGRATION_DOCKER=1) that actually boots the container, issues a cert, and verifies the leaf chains to the root using Node's tls.createSecureContext({ ca: rootCertPem }).

8. Pre-publish gate

  • 8.1 bunx turbo lint typecheck test --filter=@suaveplan/testing-integration-step-ca green.
  • 8.2 bunx turbo test:coverage --filter=@suaveplan/testing-integration-step-ca reports 100% on touched modules.
  • 8.3 Spec-conformance test file (src/fixture/step-ca-fixture.spec-conformance.test.ts) asserts every "Verified by" anchor in specs/testing-integration-step-ca/spec.md resolves to a real test.

9. Documentation

  • 9.1 Root README.md for the package: purpose, basic example (new StepCaFixture() + issueCert), advanced example (using the root cert with NODE_EXTRA_CA_CERTS and Playwright --ignore-certificate-errors-spki-list), API reference, implementation notes, ≥200 words.
  • 9.2 Co-located .md for each implementation file per CLAUDE.md Documentation rules.

10. Archive

  • 10.1 On ship: fold the spec delta into openspec/specs/testing-integration-step-ca/spec.md and move this folder to openspec/archive/<yyyy-mm-dd>-testing-integration-step-ca/.