Crawler-Factory

Engineering-Specs/Validation/SOP-Testing-Report.md

Crawler Factory Engineering Specs — TestingSOPs Compliance Report

Scope: 13 spec files (00-Index is a manifest, not a TDD spec — excluded from criterion grading). SOP read fresh from Standards/TestingSOPs.md 2026-04-06. Translation: Python pytest → TypeScript vitest. pytest fixturesvi.mock / beforeEach. Three-layer (unit / integration / contract) reinterpreted for a TS+API+frontend stack: unit (vitest+mocks), integration (Spec 13 cross-cluster smoke + per-spec end-to-end inside test files), contract (zod schemas validate every public boundary).


Per-spec verdict matrix

Criteria (TestingSOPs §): - TDD = TDD Golden Rule. Pattern observed: write failing test → run/expect FAIL → implement → run/expect PASS → commit. - Design = tests assert real behavior, not mock internals. Mocks at boundaries only (fetch, algoliasearch, LLM, playwright). - Coverage = every public function/component has at least one test. Happy path + edge + failure modes covered. - Naming = test names describe behavior (it('does X when Y')), not implementation. - Layers = unit explicit; contract via zod schemas; integration via cluster-validation tasks + Spec 13 smoke. The SOP's "all three before done" gate is satisfied at the cluster level (Spec 13) for the full system; per-spec unit + contract are mandatory.

Status legend: PASS = SOP met without reservation. WARN = met with caveat. FAIL = SOP violated.

# Spec TDD Design Coverage Naming Layers Notes
00 00-Index n/a n/a n/a n/a n/a Manifest only — not a TDD spec. Excluded.
01 01-foundations PASS PASS PASS PASS PASS Canonical exemplar. Test-first → FAIL → impl → PASS → commit at every step. zod contract validation at every boundary. Mocks only at algoliasearch.
02 02-sitemap-discovery PASS PASS PASS PASS PASS Tests cover: robots parse, fallback chain, recursion, gzip, hreflang, depth-3 nesting, 50K-URL urlset, language detection, WAF transport flag. Mocks fetch only; XML fixtures inline.
03 03-waf-sampling PASS PASS PASS PASS PASS All §19b ladder levels tested (L0/L2/L3). Playwright entirely mocked via vi.mock('playwright'); sampler composes mocked sub-modules. SPA detection edge cases comprehensive (5 cases).
04 04-cms-classification PASS PASS PASS PASS PASS 11 CMS branches each tested. Cascade aggregator §19c rules tested (max-conf, agreement boost, LLM threshold). Site-type rubric first-match-wins tested. Cluster integration test (Task 5) ties cms-detector + classifier + site-type-rubric together.
05 05-extraction-sandbox PASS PASS PASS PASS PASS LLM dependency-injected as LlmFn and mocked via vi.fn() in every test. Cross-domain smoke (Task 6) covers all 9 content domains via it.each(FIXTURES). new Function sandbox failure modes tested (syntax error, throw at runtime).
06 06-crawler-client-index-manager PASS PASS PASS PASS PASS TS port of Python reference cited line-for-line. Auth header sanitization tested. Idempotent ensure (no-op / drift / cold start) all distinct test cases. Error propagation explicitly pinned in Task 10.
07 07-brand-discovery PASS PASS PASS PASS PASS Pure module — sampler + classifier injected as parameters precisely so tests inject vi.fn(). 23 tests across 4 logical sections explicitly enumerated in acceptance criteria. Negative paths covered (missing brand index, sampler throws, non-multi-brand site).
08 08-backend-api PASS PASS PASS PASS PASS Every lib/factory/* mocked via vi.mock. SSE event emission order asserted via captured res.write spy. Hand-rolled makeReq / makeRes pattern reused from prepare-dossier.test.ts baseline. ~69 tests across 9 endpoints + shared helpers.
09 09-frontend-foundations PASS PASS PASS PASS WARN Hooks + page shell tests via @testing-library/react. Cleanup-on-unmount explicitly tested for SSE hooks. WARN: contract layer here is shape-checked via inline schemas inside the SSE hooks (logs/urls/pathGroup/classified/done discriminated unions) rather than imported from lib/factory/types.ts; SOP-compliant but slight duplication risk versus Spec 01's authoritative LogEntrySchema. Recommendation: route SSE event schemas through types.ts in Spec 09's hook implementations to keep the contract single-sourced.
10 10-frontend-discovery PASS PASS PASS PASS PASS All 4 components ship with unit tests. useDiscoveryStream mocked. Edge cases include empty-state, level color, traffic-light bands, form validation rejection, grouping, selection cardinality. Accessibility assertions present (aria-live).
11 11-frontend-configurator PASS PASS PASS PASS PASS global.fetch mocked per-test; render-only sub-components rendered with controlled props. Commit-button gating proven across all four states (initial, sandbox-only, real-only, both). Explicit-click requirement tested ("does not auto-commit").
12 12-frontend-orchestrator PASS PASS PASS PASS PASS FSM transitions tested per session.status value (null / discovering / categorized / configuring / crawling / done / error). URL ?session=<id> resume tested. Hooks (useFactorySession, useDiscoveryStream, useCrawlProgress) mocked at the import level via vi.mock.
13 13-bundle-smoke-docs n/a PASS PASS n/a PASS Verification cluster — not a new-code spec. Bundles + provisions + runs LIVE smoke against https://www.algolia.com. Every observation gate has expected output. Smoke is the SOP's "Layer 2 integration" satisfied at cluster level. WARN-NOT-FAIL: TDD criterion is "n/a" because this is not feature code; the discipline rule that applies (verification-before-completion) IS followed — every step has an expected-output gate.

Detail per criterion

1. TDD cadence (RED → GREEN → REFACTOR)

SOP rule: "If you are writing code before a test, you are doing it wrong."

Observed: every code-bearing task in Specs 01–12 follows the literal sequence:

Step N.1: Write failing test for X
Step N.2: Run, expect FAIL
Step N.3: Implement X
Step N.4: Run, expect PASS
Step N.5: Commit

This is not paraphrased — the spec text itself uses expect FAIL / expect PASS and a separate commit step. Spec 01 sets the canonical pattern at Task 1 (DSS registry); every other spec mirrors it.

Verdict: PASS for Specs 01–12. n/a for 00 and 13 (manifest and verification).

2. Test design (assert behavior, mock at boundaries)

SOP rule: "Test behavior, not how it is implemented." Mock external services only.

Observed: every spec mocks only at the system boundary: - algoliasearch — Specs 01, 06 - fetch (global) — Specs 02, 03, 06, 07, 08, 09, 11 - playwright — Spec 03 only (chromium fully stubbed) - LLM provider — Specs 04, 05 (dependency-injected LlmFn) - lib/factory/* — Spec 08 (handler-layer tests; correct boundary for an API handler) - Spec 09 hooks — Specs 10, 11, 12 (mocked at @/hooks/factory/* import)

No spec mocks internal helpers or asserts on private call counts beyond what the contract requires (e.g., "probeWaf called once per host" is a real behavioral guarantee, not implementation-coupling).

Verdict: PASS across the board.

3. Coverage (every public function tested; happy + edge + failure)

SOP rule: "Coverage: 100% on all public methods of BaseModule subclasses." Test happy / empty / boundary / failure / contract / caching.

Observed: every spec's "Acceptance criteria" section enumerates the public surface and the test count required to exercise it. Spot checks: - Spec 02: discoverSitemaps, walkSitemap, groupByPathPrefix, estimateScope all covered with 7 + 5 + 5 + 5 = ~22 tests including edge (cycle, empty, 50K-URL, 403/WAF). - Spec 04: 11 CMS branches × at least 1 test each + cascade boost + LLM threshold + traffic-light bands. - Spec 05: 9 content domains × cross-domain smoke fixture. - Spec 06: every CrawlerClient method + every IndexManager state (cold start / no-op / drift / error propagation).

Failure-mode coverage is consistent: 4xx/5xx HTTP, 403/429 WAF, malformed XML, syntax error in extractor, missing-required-field record, AbortError on timeout.

Verdict: PASS.

4. Naming (test_{what}_{condition}_{expected_result})

SOP rule (Python form): def test_run_with_empty_topic_returns_failed_output().

TS translation: vitest uses it('<behavior phrase>'). Equivalent semantic: the description string IS the SOP-mandated name.

Observed: every spec uses behavior-first descriptions. Examples: - it('returns transport=fetch when robots.txt returns 200') - it('throws on 4xx with body fragment in error message') - it('renders empty-state hint when entries are empty') - it('flags isSpa=true for <50KB body with <noscript> and empty <div id="root">')

No it('test1'), no it('works'), no implementation-coupled names like it('calls _internalHelper').

Verdict: PASS.

5. Three-layer test strategy

SOP rule: unit + integration + contract — all required before "done".

Observed mapping: - Unit: every spec has dedicated unit tests with externals mocked (Spec 01-12). - Contract: every public boundary uses zod (FactorySessionSchema, BlueprintSchema, PathGroupSchema, CmsDetectionSchema, VerdictSchema, BrandRefSchema, ScopeEstimateSchema, SampleSchema, StructureAnalysisSchema, etc.). zod parse-on-input AND parse-on-output. This satisfies the SOP's "Contract: validates Pydantic envelope at every module boundary" criterion (Pydantic→zod translation). - Integration: two strategies layered: - Per-cluster integration test inside the unit suite (Spec 04 §Task 5, Spec 05 §Task 6 cross-domain smoke, Spec 03 §Task 4 regression). - Cluster-level live integration in Spec 13 (real Algolia + real network smoke against algolia.com).

Caveat for Spec 09: the SSE event-shape contracts are defined inline in the hook files rather than promoted to lib/factory/types.ts. Functionally equivalent but architecturally redundant — the discriminated unions for log|urls|pathGroup|classified|done events would be cleaner as a single source of truth in types.ts. WARN, not FAIL — the SOP requires zod at boundaries, not zod-import-from-a-specific-file.

Verdict: PASS (Specs 01–08, 10–13). WARN (Spec 09).


Cross-cutting observations

  1. Mock fixtures are inline. Spec 02 explicitly states "Fixture XML lives inline in the test file as template strings — not separate files." This is correct per SOP §Mock Strategy ("Application code never changes — only what gets injected"). Inline fixtures keep test intent visible at the assertion site.

  2. Test count discipline. Each spec's acceptance criteria includes a concrete test count target (~23 for Spec 01, ~22 for Spec 02, ~31 for Spec 03, ~70+ for Spec 04, ~30+ for Spec 05, ~16+ for Spec 06, ~23 for Spec 07, ~69 for Spec 08, ~27 for Spec 09, ~34 for Spec 10, ~37 for Spec 11, ~17 for Spec 12). Total target across the cluster: ~400+ new tests stacked on the 407 baseline.

  3. Cluster validation step is universal. Every spec ends with a "Task N: Cluster validation — full regression" task that runs npx vitest run and npx tsc --noEmit. This enforces the SOP's "All tests pass in CI before merge — no exceptions."

  4. Conventional commits aligned with TDD. Every spec uses feat(factory): / test(factory): / chore(factory): per CodingSOPs. One commit per logical TDD cycle. This satisfies the SOP's "Atomic commits" rule for tests.

  5. No skipped tests anywhere. No it.skip / xit / TODO-test placeholders observed across all 13 specs.


Summary

  • Total FAILs: 0
  • Total WARNs: 1 (Spec 09 contract layer — SSE event schemas duplicated inline in hook files instead of imported from lib/factory/types.ts)
  • Specs requiring fixes: none. The single WARN is a refactor opportunity, not a blocker.

The Crawler-Factory engineering specs constitute one of the cleanest TDD-discipline corpora I have audited against TestingSOPs. The pattern (test-first → run/FAIL → implement → run/PASS → commit) is repeated literally and explicitly at every code-bearing step across 12 active specs and 1 verification cluster. zod contracts cover every public boundary. Mocks live at the system edge only. Test names describe behavior. Coverage is enumerated in acceptance criteria. Cluster-level integration is reserved for Spec 13's live smoke against algolia.com — the SOP's Layer 2 satisfied at the system boundary.

The single WARN (Spec 09 SSE contract duplication) is cosmetic. Recommendation: when implementing Spec 09's hooks, define the SSE event discriminated unions in lib/factory/types.ts (alongside LogEntrySchema) and import them into the hooks. This collapses the contract back to a single source of truth without changing observable test behavior.

Verdict: full corpus is SOP-compliant. Cleared to execute.