@nklisch/pi-agile-workflow 0.15.3
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.
- package/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +28 -0
- package/CHANGELOG.md +591 -0
- package/README.md +107 -0
- package/docs/ARCHITECTURE.md +716 -0
- package/docs/MIGRATION.md +419 -0
- package/docs/PRINCIPLES.md +464 -0
- package/docs/ROADMAP.md +95 -0
- package/docs/SPEC.md +855 -0
- package/docs/VISION.md +106 -0
- package/extensions/agile-workflow.test.ts +419 -0
- package/extensions/agile-workflow.ts +467 -0
- package/hooks/hooks.json +52 -0
- package/hooks/scripts/prompt-context.py +628 -0
- package/hooks/scripts/substrate-maintainer.py +359 -0
- package/hooks/scripts/test_prompt_context.py +757 -0
- package/package.json +30 -0
- package/scripts/install-work-view.sh +165 -0
- package/scripts/tests/agent-metadata.test.sh +75 -0
- package/scripts/tests/bump-version.test.sh +552 -0
- package/scripts/tests/channel-parity.test.sh +117 -0
- package/scripts/tests/convert-content-integrity.test.sh +246 -0
- package/scripts/tests/convert-install-routing.test.sh +159 -0
- package/scripts/tests/install-work-view.test.sh +411 -0
- package/scripts/tests/pi-package-metadata.test.sh +119 -0
- package/scripts/tests/work-board-shim.test.sh +239 -0
- package/scripts/tests/work-view-dist-version.test.sh +105 -0
- package/scripts/work-board.sh +64 -0
- package/scripts/work-view.sh +423 -0
- package/skills/autopilot/SKILL.md +330 -0
- package/skills/autopilot/agents/openai.yaml +6 -0
- package/skills/board/SKILL.md +62 -0
- package/skills/board/agents/openai.yaml +6 -0
- package/skills/bold-refactor/SKILL.md +244 -0
- package/skills/bold-refactor/agents/openai.yaml +6 -0
- package/skills/bug-scan/SKILL.md +378 -0
- package/skills/bug-scan/agents/openai.yaml +6 -0
- package/skills/bug-scan/references/async-promises.md +119 -0
- package/skills/bug-scan/references/concurrency-races.md +148 -0
- package/skills/bug-scan/references/data-layer.md +167 -0
- package/skills/bug-scan/references/error-handling.md +197 -0
- package/skills/bug-scan/references/gate-item-template.md +94 -0
- package/skills/bug-scan/references/language-footguns.md +219 -0
- package/skills/bug-scan/references/parked-item-template.md +103 -0
- package/skills/bug-scan/references/report-template.md +115 -0
- package/skills/bug-scan/references/resource-leaks.md +199 -0
- package/skills/bug-scan/references/state-closures.md +148 -0
- package/skills/bug-scan/references/time-numbers.md +158 -0
- package/skills/convert/SKILL.md +1463 -0
- package/skills/convert/agents/openai.yaml +6 -0
- package/skills/convert/references/legacy-overlap-migration.md +179 -0
- package/skills/deep-code-scan/SKILL.md +378 -0
- package/skills/deep-code-scan/agents/openai.yaml +6 -0
- package/skills/deep-code-scan/references/artifact-ledger.md +148 -0
- package/skills/deep-code-scan/references/consolidation.md +91 -0
- package/skills/deep-code-scan/references/decomposition.md +127 -0
- package/skills/deep-code-scan/references/item-templates.md +263 -0
- package/skills/deep-code-scan/references/lane-catalog.md +129 -0
- package/skills/deep-code-scan/references/review-gauntlet.md +108 -0
- package/skills/deep-code-scan/references/scanner-brief.md +152 -0
- package/skills/e2e-test-design/SKILL.md +517 -0
- package/skills/e2e-test-design/references/anti-tautology.md +261 -0
- package/skills/e2e-test-design/references/service-mocks.md +234 -0
- package/skills/e2e-test-design/references/test-taxonomy.md +211 -0
- package/skills/epic-design/SKILL.md +476 -0
- package/skills/epicize/SKILL.md +210 -0
- package/skills/epicize/agents/openai.yaml +6 -0
- package/skills/feature-design/SKILL.md +472 -0
- package/skills/fix/SKILL.md +173 -0
- package/skills/gate-cruft/SKILL.md +258 -0
- package/skills/gate-docs/SKILL.md +278 -0
- package/skills/gate-patterns/SKILL.md +389 -0
- package/skills/gate-refactor/SKILL.md +408 -0
- package/skills/gate-security/SKILL.md +275 -0
- package/skills/gate-tests/SKILL.md +367 -0
- package/skills/groom/SKILL.md +162 -0
- package/skills/groom/agents/openai.yaml +6 -0
- package/skills/ideate/SKILL.md +185 -0
- package/skills/ideate/agents/openai.yaml +6 -0
- package/skills/implement/SKILL.md +288 -0
- package/skills/implement-orchestrator/SKILL.md +604 -0
- package/skills/park/SKILL.md +111 -0
- package/skills/perf-design/SKILL.md +427 -0
- package/skills/perf-scout/SKILL.md +404 -0
- package/skills/perf-scout/agents/openai.yaml +6 -0
- package/skills/perf-scout/references/algorithmic-and-data-structures.md +126 -0
- package/skills/perf-scout/references/approximation-and-precomputation.md +136 -0
- package/skills/perf-scout/references/caching-and-memoization.md +123 -0
- package/skills/perf-scout/references/compiler-runtime-and-language.md +136 -0
- package/skills/perf-scout/references/database-and-storage-internals.md +144 -0
- package/skills/perf-scout/references/distributed-systems.md +150 -0
- package/skills/perf-scout/references/game-engine-and-realtime.md +136 -0
- package/skills/perf-scout/references/gpu-and-accelerators.md +130 -0
- package/skills/perf-scout/references/idea-ranking.md +75 -0
- package/skills/perf-scout/references/io-and-batching.md +135 -0
- package/skills/perf-scout/references/memory-and-data-locality.md +137 -0
- package/skills/perf-scout/references/parallelism-and-vectorization.md +126 -0
- package/skills/perf-scout/references/parked-item-template.md +117 -0
- package/skills/perf-scout/references/peer-review-pass.md +150 -0
- package/skills/perf-scout/references/report-template.md +148 -0
- package/skills/principles/SKILL.md +678 -0
- package/skills/principles/references/models.md +191 -0
- package/skills/principles/references/subagents.md +158 -0
- package/skills/prose-author/SKILL.md +185 -0
- package/skills/refactor-conventions-creator/SKILL.md +359 -0
- package/skills/refactor-conventions-creator/agents/openai.yaml +6 -0
- package/skills/refactor-conventions-creator/references/common-structures.md +91 -0
- package/skills/refactor-conventions-creator/references/common-styles.md +90 -0
- package/skills/refactor-design/SKILL.md +384 -0
- package/skills/release-deploy/SKILL.md +667 -0
- package/skills/release-deploy/agents/openai.yaml +6 -0
- package/skills/research/SKILL.md +166 -0
- package/skills/research/agents/openai.yaml +6 -0
- package/skills/review/SKILL.md +267 -0
- package/skills/review/references/deep-review.md +98 -0
- package/skills/review/references/review-lenses.md +68 -0
- package/skills/review/references/substrate-side-effects.md +161 -0
- package/skills/review/references/target-resolution.md +52 -0
- package/skills/scope/SKILL.md +486 -0
- package/work-view/Cargo.lock +486 -0
- package/work-view/Cargo.toml +9 -0
- package/work-view/crates/cli/.work-view-version +1 -0
- package/work-view/crates/cli/Cargo.toml +18 -0
- package/work-view/crates/cli/src/actionable.rs +500 -0
- package/work-view/crates/cli/src/args.rs +792 -0
- package/work-view/crates/cli/src/board/assets/board.css +855 -0
- package/work-view/crates/cli/src/board/assets/board.js +259 -0
- package/work-view/crates/cli/src/board/assets/card.js +139 -0
- package/work-view/crates/cli/src/board/assets/components.css +290 -0
- package/work-view/crates/cli/src/board/assets/dependency.js +1193 -0
- package/work-view/crates/cli/src/board/assets/detail.js +256 -0
- package/work-view/crates/cli/src/board/assets/filters.js +389 -0
- package/work-view/crates/cli/src/board/assets/index.html +77 -0
- package/work-view/crates/cli/src/board/assets/kanban.js +231 -0
- package/work-view/crates/cli/src/board/assets/markdown.js +260 -0
- package/work-view/crates/cli/src/board/assets/motion.css +131 -0
- package/work-view/crates/cli/src/board/assets/state.js +245 -0
- package/work-view/crates/cli/src/board/assets/table.js +304 -0
- package/work-view/crates/cli/src/board/assets/tokens.css +602 -0
- package/work-view/crates/cli/src/board/assets/views.js +41 -0
- package/work-view/crates/cli/src/board/assets.rs +162 -0
- package/work-view/crates/cli/src/board/feed.rs +204 -0
- package/work-view/crates/cli/src/board/mod.rs +246 -0
- package/work-view/crates/cli/src/board/open.rs +144 -0
- package/work-view/crates/cli/src/board/server.rs +407 -0
- package/work-view/crates/cli/src/main.rs +195 -0
- package/work-view/crates/cli/src/render.rs +354 -0
- package/work-view/crates/cli/src/scope.rs +157 -0
- package/work-view/crates/cli/src/stale.rs +581 -0
- package/work-view/crates/cli/tests/board-js/dependency-table.test.mjs +64 -0
- package/work-view/crates/cli/tests/board-js/expanded-browsing.test.mjs +79 -0
- package/work-view/crates/cli/tests/board-js/harness-smoke.test.mjs +11 -0
- package/work-view/crates/cli/tests/board-js/harness.mjs +437 -0
- package/work-view/crates/cli/tests/board-js/kanban-detail.test.mjs +75 -0
- package/work-view/crates/cli/tests/board-js/markdown-filter.test.mjs +60 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/CONVENTIONS.md +3 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/epics/epic-alpha.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-a.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-b.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-alpha-1.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-research-1.md +19 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-done.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-shipped.md +18 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/backlog/idea-backlog.md +7 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/CONVENTIONS.md +6 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-dep-done.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-design-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-impl-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/stories/story-review-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/CONVENTIONS.md +4 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/epics/malformed-no-closing-fence.md +8 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/features/good-item.md +16 -0
- package/work-view/crates/cli/tests/integration.rs +2833 -0
- package/work-view/crates/core/Cargo.toml +13 -0
- package/work-view/crates/core/src/error.rs +61 -0
- package/work-view/crates/core/src/filter.rs +804 -0
- package/work-view/crates/core/src/graph.rs +324 -0
- package/work-view/crates/core/src/index.rs +590 -0
- package/work-view/crates/core/src/lib.rs +54 -0
- package/work-view/crates/core/src/model.rs +202 -0
- package/work-view/crates/core/src/parse.rs +494 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/CONVENTIONS.md +15 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/epics/epic-core.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-parser.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-query.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/malformed-no-id.md +15 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-parse-unit.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-security-gate.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-old.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-parser.md +17 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/backlog/idea-future-board.md +7 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/CONVENTIONS.md +10 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/archive/dup-item.md +18 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/releases/v2.0/dup-item.md +18 -0
- package/work-view/crates/core/tests/integration.rs +541 -0
- package/work-view/dist/.gitattributes +1 -0
- package/work-view/dist/README.md +42 -0
- package/work-view/dist/aarch64-apple-darwin/work-view +0 -0
- package/work-view/dist/aarch64-unknown-linux-musl/work-view +0 -0
- package/work-view/dist/x86_64-apple-darwin/work-view +0 -0
- package/work-view/dist/x86_64-unknown-linux-musl/work-view +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Anti-Tautology
|
|
2
|
+
|
|
3
|
+
Patterns that make a test useless, and grep-able heuristics the `--audit` mode
|
|
4
|
+
uses to find them. Referenced from SKILL.md Phase 5c (design-time
|
|
5
|
+
guardrails) and Phase A2 (audit dispatch brief).
|
|
6
|
+
|
|
7
|
+
## The core failure mode
|
|
8
|
+
|
|
9
|
+
A tautological test verifies that the code does what the code does — it
|
|
10
|
+
restates the implementation as an assertion. When the implementation changes,
|
|
11
|
+
the test changes with it; when the implementation has a bug, the test has
|
|
12
|
+
the same bug. It passes when the system is broken in the same way the test
|
|
13
|
+
is broken. It catches nothing.
|
|
14
|
+
|
|
15
|
+
The fix: tests must assert on **what the system promises a user**, not on
|
|
16
|
+
**how the system computes**. The promise is the invariant. If you can't state
|
|
17
|
+
the invariant in one English sentence without referring to function names or
|
|
18
|
+
internal modules, you're describing implementation, not contract.
|
|
19
|
+
|
|
20
|
+
## The five tautology patterns
|
|
21
|
+
|
|
22
|
+
### 1. Mock-and-assert-the-mock
|
|
23
|
+
|
|
24
|
+
The test sets up a mock, calls the system, then asserts the mock was called
|
|
25
|
+
in a specific way. There is no real I/O, no real outcome, no real user. The
|
|
26
|
+
test verifies that the wiring of mocks matches what the test author wrote
|
|
27
|
+
into the mocks.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
# Tautological
|
|
31
|
+
mock_db.save.return_value = {"id": 1}
|
|
32
|
+
result = service.create_user("alice")
|
|
33
|
+
assert mock_db.save.called_with(name="alice") # asserting on the mock
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# Non-tautological — real DB, real outcome
|
|
38
|
+
result = service.create_user("alice") # writes to a real Postgres container
|
|
39
|
+
row = db.execute("SELECT name FROM users WHERE id = ?", result.id).fetchone()
|
|
40
|
+
assert row["name"] == "alice" # asserting on user-visible state
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Grep signal:** `assert.*called_with`, `expect.*toHaveBeenCalled`,
|
|
44
|
+
`verify(mock`, `mock.assert_called`. Every match is a candidate for audit.
|
|
45
|
+
|
|
46
|
+
### 2. Mirror-the-implementation
|
|
47
|
+
|
|
48
|
+
The test reads as a step-by-step re-implementation of the function being
|
|
49
|
+
tested. The assertions check that each internal step produced an expected
|
|
50
|
+
intermediate value.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
# Tautological — testing the algorithm by re-running the algorithm
|
|
54
|
+
def test_calculate_total():
|
|
55
|
+
items = [{"price": 10, "qty": 2}, {"price": 5, "qty": 3}]
|
|
56
|
+
expected = items[0]["price"] * items[0]["qty"] + items[1]["price"] * items[1]["qty"]
|
|
57
|
+
assert calculate_total(items) == expected
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The `expected` value is computed by re-implementing `calculate_total`. If
|
|
61
|
+
the function is wrong, the test is wrong the same way.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# Non-tautological — concrete expected value
|
|
65
|
+
def test_calculate_total():
|
|
66
|
+
items = [{"price": 10, "qty": 2}, {"price": 5, "qty": 3}]
|
|
67
|
+
assert calculate_total(items) == 35 # the contract: this input → this output
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Grep signal:** Tests where `expected` is a computed expression involving
|
|
71
|
+
the same operations as the function under test. Harder to grep mechanically;
|
|
72
|
+
audit sub-agent reads test bodies looking for this shape.
|
|
73
|
+
|
|
74
|
+
### 3. Assertion-free tests
|
|
75
|
+
|
|
76
|
+
The test runs the system but never asserts anything substantive. It "passes"
|
|
77
|
+
as long as nothing throws. Most common in golden-path tests written under
|
|
78
|
+
deadline pressure.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Tautological — runs the code, asserts nothing
|
|
82
|
+
test('checkout works', async () => {
|
|
83
|
+
await page.goto('/checkout');
|
|
84
|
+
await page.click('#submit');
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The test fails only if `goto` or `click` themselves throw. It passes when
|
|
89
|
+
the checkout silently submits a corrupt order.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// Non-tautological
|
|
93
|
+
test('checkout creates an order and redirects to confirmation', async () => {
|
|
94
|
+
await page.goto('/checkout');
|
|
95
|
+
await page.click('#submit');
|
|
96
|
+
await expect(page).toHaveURL(/\/confirmation\?order=\d+/);
|
|
97
|
+
const orderId = (await page.url()).match(/order=(\d+)/)[1];
|
|
98
|
+
const order = await db.query('SELECT * FROM orders WHERE id = ?', orderId);
|
|
99
|
+
expect(order.status).toBe('placed');
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Grep signal:** test bodies with no `assert` / `expect` / `should` / `must`
|
|
104
|
+
calls. Also tests whose only assertion is a `toBeTruthy()` / `toBeDefined()`
|
|
105
|
+
on the call's return value with no value check.
|
|
106
|
+
|
|
107
|
+
### 4. No-op golden path
|
|
108
|
+
|
|
109
|
+
The test sets up a happy path but the system under test does nothing
|
|
110
|
+
meaningful before the assertion fires. Common when test data accidentally
|
|
111
|
+
matches the empty / default response.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# Tautological — empty list assertion when list was never populated
|
|
115
|
+
def test_list_users_returns_users():
|
|
116
|
+
response = client.get("/users")
|
|
117
|
+
assert isinstance(response.json(), list) # passes for []
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If the database was never seeded, `[]` satisfies this test. The system could
|
|
121
|
+
be returning the empty list because it crashed, because it has a bug, or
|
|
122
|
+
because everything works — the test can't distinguish.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
# Non-tautological — seeded data with specific assertion
|
|
126
|
+
def test_list_users_returns_seeded_users():
|
|
127
|
+
seed_user(name="alice")
|
|
128
|
+
seed_user(name="bob")
|
|
129
|
+
response = client.get("/users")
|
|
130
|
+
names = [u["name"] for u in response.json()]
|
|
131
|
+
assert set(names) == {"alice", "bob"}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Grep signal:** Hard to detect mechanically. Audit sub-agent reads tests
|
|
135
|
+
where the assertion target could be satisfied by an empty/default response.
|
|
136
|
+
|
|
137
|
+
### 5. Snapshot-only
|
|
138
|
+
|
|
139
|
+
The test captures a snapshot and asserts the system matches it. When the
|
|
140
|
+
implementation changes, the snapshot gets regenerated. No semantic invariant
|
|
141
|
+
is ever stated.
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
// Tautological — the snapshot becomes whatever the code produces
|
|
145
|
+
test('user serialization', () => {
|
|
146
|
+
const user = serialize(buildUser());
|
|
147
|
+
expect(user).toMatchSnapshot();
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
When the serialization changes to drop a critical field, the snapshot updates
|
|
152
|
+
and the test still passes.
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// Non-tautological — semantic assertions
|
|
156
|
+
test('user serialization includes id, name, email and never includes password', () => {
|
|
157
|
+
const user = serialize(buildUser({ password: 'secret' }));
|
|
158
|
+
expect(user).toHaveProperty('id');
|
|
159
|
+
expect(user).toHaveProperty('name');
|
|
160
|
+
expect(user).toHaveProperty('email');
|
|
161
|
+
expect(user).not.toHaveProperty('password');
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Grep signal:** `toMatchSnapshot`, `assertSnapshot`, `.snap` files. Audit
|
|
166
|
+
flags every snapshot test for human review.
|
|
167
|
+
|
|
168
|
+
## Mock-boundary violation patterns
|
|
169
|
+
|
|
170
|
+
Distinct from tautology but in the same audit pass. These flag tests that
|
|
171
|
+
mock at the wrong boundary.
|
|
172
|
+
|
|
173
|
+
### In-process mocks where a service-level mock exists
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Violation — mocking the DB driver
|
|
177
|
+
@patch("psycopg2.connect")
|
|
178
|
+
def test_create_user(mock_connect):
|
|
179
|
+
...
|
|
180
|
+
|
|
181
|
+
# Fix — Testcontainers, real Postgres
|
|
182
|
+
def test_create_user(postgres_container):
|
|
183
|
+
...
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Grep signal:** `@patch.*psycopg`, `@patch.*boto3`, `@patch.*requests`,
|
|
187
|
+
`jest.mock("aws-sdk")`, `sinon.stub.*http`. Every match is a candidate for
|
|
188
|
+
promotion to service-level.
|
|
189
|
+
|
|
190
|
+
### In-process mocks of code the system-under-test owns
|
|
191
|
+
|
|
192
|
+
The system mocks its own internal modules, then asserts the integration. The
|
|
193
|
+
"integration" never integrates.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# Violation — mocking your own service
|
|
197
|
+
@patch("app.services.payment.charge")
|
|
198
|
+
def test_checkout(mock_charge):
|
|
199
|
+
mock_charge.return_value = {"status": "ok"}
|
|
200
|
+
...
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Grep signal:** `@patch("app.` / `@patch("src.` / `jest.mock("./` /
|
|
204
|
+
`jest.mock("@/`. Mocking internal modules in e2e tests is almost always
|
|
205
|
+
wrong — the test should let the internal modules run.
|
|
206
|
+
|
|
207
|
+
### Time-based mocks where libfaketime would work
|
|
208
|
+
|
|
209
|
+
In-process time mocking (freezegun, sinon.useFakeTimers) is OK for unit tests
|
|
210
|
+
but disqualifies an e2e test. Use libfaketime via `LD_PRELOAD` to skew the
|
|
211
|
+
clock for the entire container.
|
|
212
|
+
|
|
213
|
+
**Grep signal:** `freeze_time`, `freezegun`, `useFakeTimers`, `sinon.clock`.
|
|
214
|
+
|
|
215
|
+
## Audit-mode heuristics
|
|
216
|
+
|
|
217
|
+
The `--audit` sub-agent runs these ripgrep passes over the test directory
|
|
218
|
+
and reports findings:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# 1. Mock-and-assert-the-mock
|
|
222
|
+
rg -n 'assert.*called_with|expect.*toHaveBeenCalled|verify\(.*mock' tests/
|
|
223
|
+
|
|
224
|
+
# 2. Internal-module mocking
|
|
225
|
+
rg -n '@patch\("(app|src)\.|jest\.mock\("(\.|@/)' tests/
|
|
226
|
+
|
|
227
|
+
# 3. AWS / DB driver mocks (should be service-level)
|
|
228
|
+
rg -n '@patch.*\b(boto3|psycopg|pymongo|redis)|jest\.mock\("(aws-sdk|pg|mongodb|ioredis)"' tests/
|
|
229
|
+
|
|
230
|
+
# 4. Time mocking (should be libfaketime in e2e)
|
|
231
|
+
rg -n 'freeze_time|freezegun|useFakeTimers|sinon\.useFakeTimers' tests/
|
|
232
|
+
|
|
233
|
+
# 5. Snapshot tests
|
|
234
|
+
rg -n 'toMatchSnapshot|assertSnapshot' tests/ ; find tests/ -name '*.snap'
|
|
235
|
+
|
|
236
|
+
# 6. Assertion-free tests (heuristic — fewer assertions than expected)
|
|
237
|
+
# Sub-agent inspects per-test body; no clean grep available
|
|
238
|
+
|
|
239
|
+
# 7. HTTP request mocks (should be WireMock / service-level)
|
|
240
|
+
rg -n 'requests_mock|nock\(|httpretty|jest\.mock\("axios"' tests/
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Each match is a **candidate**, not a confirmed finding. The sub-agent reads
|
|
244
|
+
the matched test to confirm before filing an item.
|
|
245
|
+
|
|
246
|
+
## Writing the invariant
|
|
247
|
+
|
|
248
|
+
Every test in a design must have a one-line invariant stated in user-visible
|
|
249
|
+
terms. Examples:
|
|
250
|
+
|
|
251
|
+
- "After registration → login → POST /projects, the new project ID appears
|
|
252
|
+
in GET /dashboard"
|
|
253
|
+
- "When the DB is unreachable, POST /projects returns 503 with Retry-After
|
|
254
|
+
set"
|
|
255
|
+
- "parse(serialize(req)) returns a request semantically equal to req for all
|
|
256
|
+
valid requests"
|
|
257
|
+
- "Checkout succeeds within 3 retries when the payment gateway has 500ms
|
|
258
|
+
latency; exactly one order row is created"
|
|
259
|
+
|
|
260
|
+
If you can't write the invariant, the test is tautological. Stop and rethink
|
|
261
|
+
what user-visible promise the test is supposed to verify.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Service-Level Mocks Catalog
|
|
2
|
+
|
|
3
|
+
Reference for `e2e-test-design` SKILL.md Phase 5a. Picks come from this
|
|
4
|
+
catalog. If a dep type isn't here, the design must either find a service-level
|
|
5
|
+
substitute, build a **custom mock container** (per the policy at the bottom),
|
|
6
|
+
or carry a strong written justification for an in-process mock.
|
|
7
|
+
|
|
8
|
+
## Mock-ladder principle
|
|
9
|
+
|
|
10
|
+
1. **Off-the-shelf service mock** (preferred) — listed below by dep type
|
|
11
|
+
2. **Custom mock container** (fallback) — language-agnostic patterns at the bottom
|
|
12
|
+
3. **In-process mock** (last resort) — requires strong justification documented in the feature body
|
|
13
|
+
|
|
14
|
+
Audit mode actively promotes in-process mocks to custom containers wherever
|
|
15
|
+
feasible. Don't accept an in-process mock just because it's already there.
|
|
16
|
+
|
|
17
|
+
## Catalog by dependency type
|
|
18
|
+
|
|
19
|
+
### Cloud (AWS)
|
|
20
|
+
|
|
21
|
+
**LocalStack** (`localstack/localstack`) — emulates ~30 AWS services
|
|
22
|
+
(S3, DynamoDB, SQS, SNS, Lambda, IAM, Cognito, Secrets Manager, KMS, STS,
|
|
23
|
+
API Gateway, EventBridge, Step Functions, CloudWatch, Kinesis, SES, SSM, etc.).
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
services:
|
|
27
|
+
localstack:
|
|
28
|
+
image: localstack/localstack:latest
|
|
29
|
+
environment:
|
|
30
|
+
SERVICES: s3,sqs,dynamodb
|
|
31
|
+
AWS_DEFAULT_REGION: us-east-1
|
|
32
|
+
ports: ["4566:4566"]
|
|
33
|
+
healthcheck:
|
|
34
|
+
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use the `awslocal` CLI or point AWS SDK clients at the endpoint URL.
|
|
38
|
+
|
|
39
|
+
### Cloud (GCP)
|
|
40
|
+
|
|
41
|
+
No single-image equivalent. Per-service options:
|
|
42
|
+
- **Cloud Storage** → fake-gcs-server (`fsouza/fake-gcs-server`)
|
|
43
|
+
- **Pub/Sub** → official emulator image (`gcr.io/google.com/cloudsdktool/cloud-sdk`)
|
|
44
|
+
- **Firestore / Datastore** → official emulator
|
|
45
|
+
- **BigQuery** → BigQuery emulator (community)
|
|
46
|
+
|
|
47
|
+
### Cloud (Azure)
|
|
48
|
+
|
|
49
|
+
- **Storage (blob/queue/table)** → Azurite (`mcr.microsoft.com/azure-storage/azurite`)
|
|
50
|
+
- **Cosmos DB** → Cosmos DB Linux emulator (Microsoft official)
|
|
51
|
+
- **Event Hubs / Service Bus** → no first-party emulator; consider a custom mock container
|
|
52
|
+
|
|
53
|
+
### Relational databases
|
|
54
|
+
|
|
55
|
+
**Testcontainers** (`testcontainers.com`) — most languages have bindings;
|
|
56
|
+
spins up real database containers per test session.
|
|
57
|
+
|
|
58
|
+
| Database | Image |
|
|
59
|
+
|---|---|
|
|
60
|
+
| Postgres | `postgres:<version>` |
|
|
61
|
+
| MySQL | `mysql:<version>` |
|
|
62
|
+
| MariaDB | `mariadb:<version>` |
|
|
63
|
+
| MS SQL Server | `mcr.microsoft.com/mssql/server` |
|
|
64
|
+
| Oracle Free | `gvenzl/oracle-free` |
|
|
65
|
+
| CockroachDB | `cockroachdb/cockroach` |
|
|
66
|
+
|
|
67
|
+
Use real schema migrations; seed with deterministic fixtures.
|
|
68
|
+
|
|
69
|
+
### NoSQL / KV / cache
|
|
70
|
+
|
|
71
|
+
| Service | Image |
|
|
72
|
+
|---|---|
|
|
73
|
+
| MongoDB | `mongo:<version>` |
|
|
74
|
+
| Redis | `redis:<version>` |
|
|
75
|
+
| DynamoDB local | `amazon/dynamodb-local` (or via LocalStack) |
|
|
76
|
+
| Cassandra | `cassandra:<version>` |
|
|
77
|
+
| ScyllaDB | `scylladb/scylla` |
|
|
78
|
+
| Elasticsearch | `elasticsearch:<version>` |
|
|
79
|
+
| OpenSearch | `opensearchproject/opensearch` |
|
|
80
|
+
|
|
81
|
+
### HTTP APIs (third-party services)
|
|
82
|
+
|
|
83
|
+
| Tool | When to use |
|
|
84
|
+
|---|---|
|
|
85
|
+
| **WireMock** (`wiremock/wiremock`) | OpenAPI-driven mocks; record/replay; configurable response delays and failure injection |
|
|
86
|
+
| **Mockoon** (`mockoon/cli`) | GUI-built mock spec; export to docker |
|
|
87
|
+
| **Prism** (`stoplight/prism`) | Mocks directly from an OpenAPI/Swagger document |
|
|
88
|
+
| **MockServer** (`mockserver/mockserver`) | Programmatic mock expectations via REST or Java client |
|
|
89
|
+
|
|
90
|
+
WireMock is the default workhorse — it handles record-and-replay, stateful
|
|
91
|
+
scenarios, and is fully configurable as a docker service.
|
|
92
|
+
|
|
93
|
+
### Email (SMTP)
|
|
94
|
+
|
|
95
|
+
| Service | Image | Notes |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| **MailHog** | `mailhog/mailhog` | SMTP sink + HTTP UI for inspecting sent mail |
|
|
98
|
+
| **smtp4dev** | `rnwood/smtp4dev` | Similar, with .NET-friendly features |
|
|
99
|
+
| **MailCatcher** | `schickling/mailcatcher` | Lightweight |
|
|
100
|
+
|
|
101
|
+
### Object storage (S3-compatible)
|
|
102
|
+
|
|
103
|
+
| Service | Image |
|
|
104
|
+
|---|---|
|
|
105
|
+
| **MinIO** | `minio/minio` |
|
|
106
|
+
| LocalStack S3 | `localstack/localstack` (S3 service enabled) |
|
|
107
|
+
|
|
108
|
+
### Message queues / streaming
|
|
109
|
+
|
|
110
|
+
| Service | Image |
|
|
111
|
+
|---|---|
|
|
112
|
+
| Kafka (single-node, fast) | `redpandadata/redpanda` (Kafka-compatible, faster startup) |
|
|
113
|
+
| Kafka (official) | `apache/kafka` |
|
|
114
|
+
| RabbitMQ | `rabbitmq:<version>-management` |
|
|
115
|
+
| NATS | `nats:<version>` |
|
|
116
|
+
| MQTT | `eclipse-mosquitto` |
|
|
117
|
+
|
|
118
|
+
### Auth / identity
|
|
119
|
+
|
|
120
|
+
| Service | Image | When |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| **Keycloak** | `quay.io/keycloak/keycloak` | OAuth2/OIDC/SAML — full IdP |
|
|
123
|
+
| **dex** | `ghcr.io/dexidp/dex` | Lighter OIDC provider |
|
|
124
|
+
| **mock-oauth2-server** | `ghcr.io/navikt/mock-oauth2-server` | Minimal OIDC for tests |
|
|
125
|
+
|
|
126
|
+
### Network failure injection
|
|
127
|
+
|
|
128
|
+
| Tool | Image / Binary | Capabilities |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| **Toxiproxy** | `ghcr.io/shopify/toxiproxy` | latency, bandwidth, jitter, slow-close, down, limit-data, slicer toxics |
|
|
131
|
+
| **Pumba** | `gaiaadm/pumba` | container kill, pause, stop, rm; netem-based netem injection |
|
|
132
|
+
| **tc / netem** | host kernel | direct Linux traffic-control rules |
|
|
133
|
+
|
|
134
|
+
### Time / clock
|
|
135
|
+
|
|
136
|
+
| Tool | Approach |
|
|
137
|
+
|---|---|
|
|
138
|
+
| **libfaketime** | `LD_PRELOAD` shim; overrides syscalls; works inside containers |
|
|
139
|
+
| Frozen-time test helpers | language-level (freezegun in Python, sinon in JS) — these are in-process mocks; prefer libfaketime for true e2e |
|
|
140
|
+
|
|
141
|
+
### Browsers / E2E UI
|
|
142
|
+
|
|
143
|
+
Not "service mocks" exactly, but commonly part of the same stack:
|
|
144
|
+
|
|
145
|
+
| Tool | Notes |
|
|
146
|
+
|---|---|
|
|
147
|
+
| **Playwright** | Multi-browser; can run headless in CI; built-in trace viewer |
|
|
148
|
+
| **Selenium Grid** | Multi-node; mature; heavier |
|
|
149
|
+
| Cypress (note: in-process) | Useful but its same-origin and in-process design means it's NOT an e2e tool by the strict definition here |
|
|
150
|
+
|
|
151
|
+
### Search
|
|
152
|
+
|
|
153
|
+
| Service | Image |
|
|
154
|
+
|---|---|
|
|
155
|
+
| Elasticsearch | `elasticsearch:<version>` |
|
|
156
|
+
| OpenSearch | `opensearchproject/opensearch` |
|
|
157
|
+
| Meilisearch | `getmeili/meilisearch` |
|
|
158
|
+
| Typesense | `typesense/typesense` |
|
|
159
|
+
|
|
160
|
+
### Background job systems
|
|
161
|
+
|
|
162
|
+
Most are layered on a queue/DB and don't need a separate mock — bring up the
|
|
163
|
+
underlying Redis/Postgres/RabbitMQ and the job system runs against it
|
|
164
|
+
naturally.
|
|
165
|
+
|
|
166
|
+
## Building a custom mock container
|
|
167
|
+
|
|
168
|
+
When no off-the-shelf service-level mock exists for a dependency, design a
|
|
169
|
+
purpose-built container. **Do not default to a single framework or language**
|
|
170
|
+
— that's how mock containers become alien to the project they're embedded in.
|
|
171
|
+
|
|
172
|
+
### Picking the framework
|
|
173
|
+
|
|
174
|
+
In order:
|
|
175
|
+
|
|
176
|
+
1. **Match the upstream service's actual tech.** If you're mocking an internal
|
|
177
|
+
Rails API, a Rails skeleton is the most faithful substitute — the same
|
|
178
|
+
serialization quirks, the same routing semantics. If the upstream is a Go
|
|
179
|
+
gRPC service, mock it in Go with the same `.proto` files.
|
|
180
|
+
|
|
181
|
+
2. **Match the project's primary stack.** If the project is a TypeScript
|
|
182
|
+
monorepo, a TypeScript mock is one less language to maintain. If the
|
|
183
|
+
project is Python, the mock is Python.
|
|
184
|
+
|
|
185
|
+
3. **Ask the user.** When neither rule suggests an obvious choice (e.g., a
|
|
186
|
+
greenfield project mocking a third-party SaaS), surface the decision via
|
|
187
|
+
`structured question tool`.
|
|
188
|
+
|
|
189
|
+
### Container shape
|
|
190
|
+
|
|
191
|
+
Regardless of language, a custom mock container should:
|
|
192
|
+
|
|
193
|
+
- Be **stateless** between test runs (state in memory or a wiped volume)
|
|
194
|
+
- Be **deterministic** — same input → same output, no randomness without a
|
|
195
|
+
seed knob
|
|
196
|
+
- Expose a **healthcheck endpoint** for docker-compose `depends_on:
|
|
197
|
+
condition: service_healthy`
|
|
198
|
+
- Expose a **reset endpoint** (`POST /__reset`) the test fixture calls between
|
|
199
|
+
tests to wipe state
|
|
200
|
+
- Expose **inspection endpoints** (`GET /__received`) so tests can assert what
|
|
201
|
+
the system-under-test sent
|
|
202
|
+
- Read **scenario config** from a file or env var so different tests can
|
|
203
|
+
configure different failure modes (slow, error, partial response, etc.)
|
|
204
|
+
|
|
205
|
+
### Patterns
|
|
206
|
+
|
|
207
|
+
**Record-and-replay** — when you have access to the real upstream, capture
|
|
208
|
+
real traffic (WireMock's `record` mode, or a small proxy) and replay it in
|
|
209
|
+
tests. Best for stable, read-heavy upstreams.
|
|
210
|
+
|
|
211
|
+
**Configurable WireMock** — for HTTP-only deps, a WireMock container with
|
|
212
|
+
mounted mappings JSON often replaces a hand-written custom server. Less code
|
|
213
|
+
to maintain.
|
|
214
|
+
|
|
215
|
+
**Mini-server with deterministic logic** — when the upstream has nontrivial
|
|
216
|
+
behavior (state machines, calculations, side effects), a small server in the
|
|
217
|
+
matched language is right. Keep the surface tight: only the endpoints the
|
|
218
|
+
system-under-test actually calls.
|
|
219
|
+
|
|
220
|
+
**Hybrid (real service + injected behavior)** — sometimes the upstream IS
|
|
221
|
+
something you can run in docker (e.g., your own service) but you need to
|
|
222
|
+
inject failure. Toxiproxy in front of the real service is the right tool;
|
|
223
|
+
don't build a custom mock.
|
|
224
|
+
|
|
225
|
+
### Anti-patterns
|
|
226
|
+
|
|
227
|
+
- Building a Python mock for a Java project just because Python is "easier" —
|
|
228
|
+
the project ends up maintaining a Python toolchain solely for tests
|
|
229
|
+
- Reimplementing the upstream's full surface — only mock what the
|
|
230
|
+
system-under-test actually calls
|
|
231
|
+
- Mock containers that drift from the real upstream's behavior without anyone
|
|
232
|
+
noticing — pin to a real-upstream contract test where possible
|
|
233
|
+
- Mock containers that share state between tests — every test should start
|
|
234
|
+
clean via `__reset`
|