@qball-inc/the-bulwark 1.0.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.
- package/.claude-plugin/plugin.json +43 -0
- package/agents/bulwark-fix-validator.md +633 -0
- package/agents/bulwark-implementer.md +391 -0
- package/agents/bulwark-issue-analyzer.md +308 -0
- package/agents/bulwark-standards-reviewer.md +221 -0
- package/agents/plan-creation-architect.md +323 -0
- package/agents/plan-creation-eng-lead.md +352 -0
- package/agents/plan-creation-po.md +300 -0
- package/agents/plan-creation-qa-critic.md +334 -0
- package/agents/product-ideation-competitive-analyzer.md +298 -0
- package/agents/product-ideation-idea-validator.md +268 -0
- package/agents/product-ideation-market-researcher.md +292 -0
- package/agents/product-ideation-pattern-documenter.md +308 -0
- package/agents/product-ideation-segment-analyzer.md +303 -0
- package/agents/product-ideation-strategist.md +259 -0
- package/agents/statusline-setup.md +97 -0
- package/hooks/hooks.json +59 -0
- package/package.json +45 -0
- package/scripts/hooks/cleanup-stale.sh +13 -0
- package/scripts/hooks/enforce-quality.sh +166 -0
- package/scripts/hooks/implementer-quality.sh +256 -0
- package/scripts/hooks/inject-protocol.sh +52 -0
- package/scripts/hooks/suggest-pipeline.sh +175 -0
- package/scripts/hooks/track-pipeline-start.sh +37 -0
- package/scripts/hooks/track-pipeline-stop.sh +52 -0
- package/scripts/init-rules.sh +35 -0
- package/scripts/init.sh +151 -0
- package/skills/anthropic-validator/SKILL.md +607 -0
- package/skills/anthropic-validator/references/agents-checklist.md +131 -0
- package/skills/anthropic-validator/references/commands-checklist.md +102 -0
- package/skills/anthropic-validator/references/hooks-checklist.md +151 -0
- package/skills/anthropic-validator/references/mcp-checklist.md +136 -0
- package/skills/anthropic-validator/references/plugins-checklist.md +148 -0
- package/skills/anthropic-validator/references/skills-checklist.md +85 -0
- package/skills/assertion-patterns/SKILL.md +296 -0
- package/skills/bug-magnet-data/SKILL.md +284 -0
- package/skills/bug-magnet-data/context/cli-args.md +91 -0
- package/skills/bug-magnet-data/context/db-query.md +104 -0
- package/skills/bug-magnet-data/context/file-contents.md +103 -0
- package/skills/bug-magnet-data/context/http-body.md +91 -0
- package/skills/bug-magnet-data/context/process-spawn.md +123 -0
- package/skills/bug-magnet-data/data/booleans/boundaries.yaml +143 -0
- package/skills/bug-magnet-data/data/collections/arrays.yaml +114 -0
- package/skills/bug-magnet-data/data/collections/objects.yaml +123 -0
- package/skills/bug-magnet-data/data/concurrency/race-conditions.yaml +118 -0
- package/skills/bug-magnet-data/data/concurrency/state-machines.yaml +115 -0
- package/skills/bug-magnet-data/data/dates/boundaries.yaml +137 -0
- package/skills/bug-magnet-data/data/dates/invalid.yaml +132 -0
- package/skills/bug-magnet-data/data/dates/timezone.yaml +118 -0
- package/skills/bug-magnet-data/data/encoding/charset.yaml +79 -0
- package/skills/bug-magnet-data/data/encoding/normalization.yaml +105 -0
- package/skills/bug-magnet-data/data/formats/email.yaml +154 -0
- package/skills/bug-magnet-data/data/formats/json.yaml +187 -0
- package/skills/bug-magnet-data/data/formats/url.yaml +165 -0
- package/skills/bug-magnet-data/data/language-specific/javascript.yaml +182 -0
- package/skills/bug-magnet-data/data/language-specific/python.yaml +174 -0
- package/skills/bug-magnet-data/data/language-specific/rust.yaml +148 -0
- package/skills/bug-magnet-data/data/numbers/boundaries.yaml +161 -0
- package/skills/bug-magnet-data/data/numbers/precision.yaml +89 -0
- package/skills/bug-magnet-data/data/numbers/special.yaml +69 -0
- package/skills/bug-magnet-data/data/strings/boundaries.yaml +109 -0
- package/skills/bug-magnet-data/data/strings/injection.yaml +208 -0
- package/skills/bug-magnet-data/data/strings/special-chars.yaml +190 -0
- package/skills/bug-magnet-data/data/strings/unicode.yaml +139 -0
- package/skills/bug-magnet-data/references/external-lists.md +115 -0
- package/skills/bulwark-brainstorm/SKILL.md +563 -0
- package/skills/bulwark-brainstorm/references/at-teammate-prompts.md +60 -0
- package/skills/bulwark-brainstorm/references/role-critical-analyst.md +78 -0
- package/skills/bulwark-brainstorm/references/role-development-lead.md +66 -0
- package/skills/bulwark-brainstorm/references/role-product-delivery-lead.md +79 -0
- package/skills/bulwark-brainstorm/references/role-product-manager.md +62 -0
- package/skills/bulwark-brainstorm/references/role-project-sme.md +59 -0
- package/skills/bulwark-brainstorm/references/role-technical-architect.md +66 -0
- package/skills/bulwark-research/SKILL.md +298 -0
- package/skills/bulwark-research/references/viewpoint-contrarian.md +63 -0
- package/skills/bulwark-research/references/viewpoint-direct-investigation.md +62 -0
- package/skills/bulwark-research/references/viewpoint-first-principles.md +65 -0
- package/skills/bulwark-research/references/viewpoint-practitioner.md +62 -0
- package/skills/bulwark-research/references/viewpoint-prior-art.md +66 -0
- package/skills/bulwark-scaffold/SKILL.md +330 -0
- package/skills/bulwark-statusline/SKILL.md +161 -0
- package/skills/bulwark-statusline/scripts/statusline.sh +144 -0
- package/skills/bulwark-verify/SKILL.md +519 -0
- package/skills/code-review/SKILL.md +428 -0
- package/skills/code-review/examples/anti-patterns/linting.ts +181 -0
- package/skills/code-review/examples/anti-patterns/security.ts +91 -0
- package/skills/code-review/examples/anti-patterns/standards.ts +195 -0
- package/skills/code-review/examples/anti-patterns/type-safety.ts +108 -0
- package/skills/code-review/examples/recommended/linting.ts +195 -0
- package/skills/code-review/examples/recommended/security.ts +154 -0
- package/skills/code-review/examples/recommended/standards.ts +231 -0
- package/skills/code-review/examples/recommended/type-safety.ts +181 -0
- package/skills/code-review/frameworks/angular.md +218 -0
- package/skills/code-review/frameworks/django.md +235 -0
- package/skills/code-review/frameworks/express.md +207 -0
- package/skills/code-review/frameworks/flask.md +298 -0
- package/skills/code-review/frameworks/generic.md +146 -0
- package/skills/code-review/frameworks/react.md +152 -0
- package/skills/code-review/frameworks/vue.md +244 -0
- package/skills/code-review/references/linting-patterns.md +221 -0
- package/skills/code-review/references/security-patterns.md +125 -0
- package/skills/code-review/references/standards-patterns.md +246 -0
- package/skills/code-review/references/type-safety-patterns.md +130 -0
- package/skills/component-patterns/SKILL.md +131 -0
- package/skills/component-patterns/references/pattern-cli-command.md +118 -0
- package/skills/component-patterns/references/pattern-database.md +166 -0
- package/skills/component-patterns/references/pattern-external-api.md +139 -0
- package/skills/component-patterns/references/pattern-file-parser.md +168 -0
- package/skills/component-patterns/references/pattern-http-server.md +162 -0
- package/skills/component-patterns/references/pattern-process-spawner.md +133 -0
- package/skills/continuous-feedback/SKILL.md +327 -0
- package/skills/continuous-feedback/references/collect-instructions.md +81 -0
- package/skills/continuous-feedback/references/specialize-code-review.md +82 -0
- package/skills/continuous-feedback/references/specialize-general.md +98 -0
- package/skills/continuous-feedback/references/specialize-test-audit.md +81 -0
- package/skills/create-skill/SKILL.md +359 -0
- package/skills/create-skill/references/agent-conventions.md +194 -0
- package/skills/create-skill/references/agent-template.md +195 -0
- package/skills/create-skill/references/content-guidance.md +291 -0
- package/skills/create-skill/references/decision-framework.md +124 -0
- package/skills/create-skill/references/template-pipeline.md +217 -0
- package/skills/create-skill/references/template-reference-heavy.md +111 -0
- package/skills/create-skill/references/template-research.md +210 -0
- package/skills/create-skill/references/template-script-driven.md +172 -0
- package/skills/create-skill/references/template-simple.md +80 -0
- package/skills/create-subagent/SKILL.md +353 -0
- package/skills/create-subagent/references/agent-conventions.md +268 -0
- package/skills/create-subagent/references/content-guidance.md +232 -0
- package/skills/create-subagent/references/decision-framework.md +134 -0
- package/skills/create-subagent/references/template-single-agent.md +192 -0
- package/skills/fix-bug/SKILL.md +241 -0
- package/skills/governance-protocol/SKILL.md +116 -0
- package/skills/init/SKILL.md +341 -0
- package/skills/issue-debugging/SKILL.md +385 -0
- package/skills/issue-debugging/references/anti-patterns.md +245 -0
- package/skills/issue-debugging/references/debug-report-schema.md +227 -0
- package/skills/mock-detection/SKILL.md +511 -0
- package/skills/mock-detection/references/false-positive-prevention.md +402 -0
- package/skills/mock-detection/references/stub-patterns.md +236 -0
- package/skills/pipeline-templates/SKILL.md +215 -0
- package/skills/pipeline-templates/references/code-change-workflow.md +277 -0
- package/skills/pipeline-templates/references/code-review.md +336 -0
- package/skills/pipeline-templates/references/fix-validation.md +421 -0
- package/skills/pipeline-templates/references/new-feature.md +335 -0
- package/skills/pipeline-templates/references/research-brainstorm.md +161 -0
- package/skills/pipeline-templates/references/research-planning.md +257 -0
- package/skills/pipeline-templates/references/test-audit.md +389 -0
- package/skills/pipeline-templates/references/test-execution-fix.md +238 -0
- package/skills/plan-creation/SKILL.md +497 -0
- package/skills/product-ideation/SKILL.md +372 -0
- package/skills/product-ideation/references/analysis-frameworks.md +161 -0
- package/skills/session-handoff/SKILL.md +139 -0
- package/skills/session-handoff/references/examples.md +223 -0
- package/skills/setup-lsp/SKILL.md +312 -0
- package/skills/setup-lsp/references/server-registry.md +85 -0
- package/skills/setup-lsp/references/troubleshooting.md +135 -0
- package/skills/subagent-output-templating/SKILL.md +415 -0
- package/skills/subagent-output-templating/references/examples.md +440 -0
- package/skills/subagent-prompting/SKILL.md +364 -0
- package/skills/subagent-prompting/references/examples.md +342 -0
- package/skills/test-audit/SKILL.md +531 -0
- package/skills/test-audit/references/known-limitations.md +41 -0
- package/skills/test-audit/references/priority-classification.md +30 -0
- package/skills/test-audit/references/prompts/deep-mode-detection.md +83 -0
- package/skills/test-audit/references/prompts/synthesis.md +57 -0
- package/skills/test-audit/references/rewrite-instructions.md +46 -0
- package/skills/test-audit/references/schemas/audit-output.yaml +100 -0
- package/skills/test-audit/references/schemas/diagnostic-output.yaml +49 -0
- package/skills/test-audit/scripts/data-flow-analyzer.ts +509 -0
- package/skills/test-audit/scripts/integration-mock-detector.ts +462 -0
- package/skills/test-audit/scripts/package.json +20 -0
- package/skills/test-audit/scripts/skip-detector.ts +211 -0
- package/skills/test-audit/scripts/verification-counter.ts +295 -0
- package/skills/test-classification/SKILL.md +310 -0
- package/skills/test-fixture-creation/SKILL.md +295 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# False Positive Prevention Reference
|
|
2
|
+
|
|
3
|
+
Two-tier allowlist and decision tree for mock-detection Stage 2 analysis. Prevents flagging legitimate test patterns as T1-T4 violations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tier 1: Universal Safe (Never Flag)
|
|
8
|
+
|
|
9
|
+
These patterns are safe regardless of test type. Do not flag as violations.
|
|
10
|
+
|
|
11
|
+
### Test Framework Primitives
|
|
12
|
+
|
|
13
|
+
| Pattern | Why Safe |
|
|
14
|
+
|---------|----------|
|
|
15
|
+
| `describe()`, `it()`, `test()` | Test structure, not test doubles |
|
|
16
|
+
| `beforeEach()`, `afterEach()` | Lifecycle hooks |
|
|
17
|
+
| `beforeAll()`, `afterAll()` | Suite-level setup/teardown |
|
|
18
|
+
| `expect()`, `assert()` | Assertion primitives |
|
|
19
|
+
| `jest.setTimeout()`, `vi.setConfig()` | Framework configuration |
|
|
20
|
+
| `jest.useFakeTimers()` | Timer control (not mocking SUT) |
|
|
21
|
+
| `jest.clearAllMocks()` | Cleanup utility |
|
|
22
|
+
|
|
23
|
+
### Test Data Construction
|
|
24
|
+
|
|
25
|
+
| Pattern | Why Safe |
|
|
26
|
+
|---------|----------|
|
|
27
|
+
| `*Builder` classes | Construct input data, not replacement behavior |
|
|
28
|
+
| `*Factory` functions | `UserFactory.create()`, `OrderFactory.build()` |
|
|
29
|
+
| `create*()` data factories | `createTestUser()`, `createSampleOrder()` |
|
|
30
|
+
| `build*()` data factories | `buildConfig()`, `buildPayload()` |
|
|
31
|
+
| `make*()` data factories | `makeUser()`, `makeRequest()` |
|
|
32
|
+
| `generate*()` data factories | `generateToken()`, `generateId()` |
|
|
33
|
+
| Faker/Chance libraries | `faker.person.fullName()`, `chance.email()` |
|
|
34
|
+
| Literal test data | `const input = { name: 'Alice', age: 30 }` in unit tests |
|
|
35
|
+
|
|
36
|
+
### Assertion Utilities
|
|
37
|
+
|
|
38
|
+
| Pattern | Why Safe |
|
|
39
|
+
|---------|----------|
|
|
40
|
+
| Custom matchers | `expect.extend({ toBeValidEmail })` |
|
|
41
|
+
| Snapshot utilities | `expect(result).toMatchSnapshot()` |
|
|
42
|
+
| `supertest` / `pactum` | HTTP assertion libraries (test real endpoints) |
|
|
43
|
+
| `testing-library` queries | `screen.getByText()`, `render()` |
|
|
44
|
+
|
|
45
|
+
### Logging and Instrumentation
|
|
46
|
+
|
|
47
|
+
| Pattern | Why Safe |
|
|
48
|
+
|---------|----------|
|
|
49
|
+
| Test loggers | `const logger = createTestLogger()` — observability, not behavior replacement |
|
|
50
|
+
| Console suppression | `jest.spyOn(console, 'error').mockImplementation(() => {})` — noise reduction |
|
|
51
|
+
| Performance timers | `performance.mark()`, `performance.measure()` |
|
|
52
|
+
|
|
53
|
+
**Exception for console suppression**: If a test asserts on console output (e.g., `expect(console.error).toHaveBeenCalledWith('specific message')`), this becomes a T2 pattern. Flag only if there is no accompanying result assertion.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Tier 2: Context-Dependent (Check Test Type)
|
|
58
|
+
|
|
59
|
+
These patterns require evaluating the test type before deciding.
|
|
60
|
+
|
|
61
|
+
### HTTP Interception (nock / MSW)
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// MSW setup
|
|
65
|
+
const server = setupServer(
|
|
66
|
+
rest.get('https://api.external.com/users', (req, res, ctx) => {
|
|
67
|
+
return res(ctx.json([{ id: 1, name: 'Alice' }]));
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Test Type | Verdict | Rationale |
|
|
73
|
+
|-----------|---------|-----------|
|
|
74
|
+
| Unit test | Safe | Isolating external HTTP is expected |
|
|
75
|
+
| Integration test (external API) | Safe | Mocking third-party APIs you don't control is acceptable |
|
|
76
|
+
| Integration test (own services) | **Flag T3** | Mocking your own service boundaries defeats integration testing |
|
|
77
|
+
| E2E test | **Flag** | E2E should hit real endpoints |
|
|
78
|
+
|
|
79
|
+
**Decision rule**: Check the intercepted URL. If it points to an external third-party service, safe. If it points to the application's own API or internal services, flag.
|
|
80
|
+
|
|
81
|
+
### InMemory* Fakes
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
class InMemoryUserRepository implements UserRepository {
|
|
85
|
+
private users = new Map<string, User>();
|
|
86
|
+
async findById(id: string) { return this.users.get(id); }
|
|
87
|
+
async save(user: User) { this.users.set(user.id, user); }
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Test Type | Verdict | Rationale |
|
|
92
|
+
|-----------|---------|-----------|
|
|
93
|
+
| Unit test | Safe | Isolating persistence for unit logic is expected |
|
|
94
|
+
| Integration test (repo layer) | **Flag T3** | If testing repository integration, use real database |
|
|
95
|
+
| Integration test (service layer) | Context-dependent | OK if repo isn't the boundary under test |
|
|
96
|
+
|
|
97
|
+
**Decision rule**: Is the InMemory replacement sitting at the integration boundary the test claims to verify? If yes, flag. If it's a supporting dependency not under test, safe.
|
|
98
|
+
|
|
99
|
+
### Dependency Injection Test Doubles
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Test provides a mock via DI
|
|
103
|
+
const service = new OrderService(mockPaymentGateway, mockInventory);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Test Type | Verdict | Rationale |
|
|
107
|
+
|-----------|---------|-----------|
|
|
108
|
+
| Unit test | Safe | DI-based isolation is textbook unit testing |
|
|
109
|
+
| Integration test | **Evaluate** | Which dependencies are mocked? Are they the integration boundaries? |
|
|
110
|
+
| E2E test | **Flag** | DI should use real implementations |
|
|
111
|
+
|
|
112
|
+
**Decision rule**: In integration tests, flag DI mocks only for the specific boundaries the test claims to integrate. Supporting dependencies that aren't part of the integration scope are acceptable.
|
|
113
|
+
|
|
114
|
+
### Test Containers
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const pgContainer = await new PostgreSqlContainer().start();
|
|
118
|
+
const redisContainer = await new GenericContainer('redis:7').start();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Test Type | Verdict | Rationale |
|
|
122
|
+
|-----------|---------|-----------|
|
|
123
|
+
| All types | **Always safe** | Real infrastructure, not a test double |
|
|
124
|
+
|
|
125
|
+
### Environment/Config Overrides
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
process.env.DATABASE_URL = 'postgres://localhost:5432/test';
|
|
129
|
+
process.env.API_KEY = 'test-key-not-real';
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Test Type | Verdict | Rationale |
|
|
133
|
+
|-----------|---------|-----------|
|
|
134
|
+
| All types | Safe | Configuration, not behavior replacement |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Decision Tree
|
|
139
|
+
|
|
140
|
+
Use this tree to evaluate whether a detected pattern is a violation.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Is it a test framework primitive or assertion utility?
|
|
144
|
+
YES -> SAFE (Tier 1)
|
|
145
|
+
NO -> continue
|
|
146
|
+
|
|
147
|
+
Is it test data construction (factory, builder, literal)?
|
|
148
|
+
YES -> SAFE (Tier 1)
|
|
149
|
+
NO -> continue
|
|
150
|
+
|
|
151
|
+
Is it a test container or real infrastructure?
|
|
152
|
+
YES -> SAFE (Tier 1)
|
|
153
|
+
NO -> continue
|
|
154
|
+
|
|
155
|
+
Is it console/logging suppression without assertion on output?
|
|
156
|
+
YES -> SAFE (Tier 1)
|
|
157
|
+
NO -> continue
|
|
158
|
+
|
|
159
|
+
--- Beyond this point: context-dependent (Tier 2) ---
|
|
160
|
+
|
|
161
|
+
Identify the TEST TYPE (unit / integration / E2E):
|
|
162
|
+
- File name contains `.unit.` or is in `__tests__/unit/` -> UNIT
|
|
163
|
+
- File name contains `.integration.` or `.int.` -> INTEGRATION
|
|
164
|
+
- File name contains `.e2e.` or `.spec.` with E2E markers -> E2E
|
|
165
|
+
- Otherwise -> infer from test content (imports, setup patterns)
|
|
166
|
+
|
|
167
|
+
Is it HTTP interception (nock, MSW, fetch mock)?
|
|
168
|
+
UNIT test -> SAFE
|
|
169
|
+
INTEGRATION test -> Is the intercepted URL your own service?
|
|
170
|
+
YES -> FLAG T3 (mocking own integration boundary)
|
|
171
|
+
NO -> SAFE (external API mock is acceptable)
|
|
172
|
+
E2E test -> FLAG
|
|
173
|
+
|
|
174
|
+
Is it an InMemory*/Fake* class?
|
|
175
|
+
UNIT test -> SAFE
|
|
176
|
+
INTEGRATION test -> Does it replace the boundary under test?
|
|
177
|
+
YES -> FLAG T3
|
|
178
|
+
NO -> SAFE (supporting dependency)
|
|
179
|
+
E2E test -> FLAG
|
|
180
|
+
|
|
181
|
+
Is it a DI-injected mock?
|
|
182
|
+
UNIT test -> SAFE
|
|
183
|
+
INTEGRATION test -> Is the mocked dependency an integration boundary?
|
|
184
|
+
YES -> FLAG T3
|
|
185
|
+
NO -> SAFE
|
|
186
|
+
E2E test -> FLAG
|
|
187
|
+
|
|
188
|
+
Is it jest.mock()/vi.mock() on a module?
|
|
189
|
+
Does it mock the module under test?
|
|
190
|
+
YES -> FLAG T1 (mocking SUT)
|
|
191
|
+
NO -> Is this an integration test mocking an integration boundary?
|
|
192
|
+
YES -> FLAG T3
|
|
193
|
+
NO -> SAFE (isolating irrelevant dependency)
|
|
194
|
+
|
|
195
|
+
None of the above matched?
|
|
196
|
+
-> FLAG as confidence: medium, recommend manual review
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Decision Tree (YAML Encoding)
|
|
202
|
+
|
|
203
|
+
For structured consumption by the detection agent:
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
decision_tree:
|
|
207
|
+
- check: "test_framework_primitive"
|
|
208
|
+
description: "describe/it/test/expect/beforeEach/afterEach/beforeAll/afterAll"
|
|
209
|
+
verdict: safe
|
|
210
|
+
tier: 1
|
|
211
|
+
|
|
212
|
+
- check: "test_data_construction"
|
|
213
|
+
description: "*Builder, *Factory, create*/build*/make*/generate*, faker, literal input data"
|
|
214
|
+
verdict: safe
|
|
215
|
+
tier: 1
|
|
216
|
+
|
|
217
|
+
- check: "test_container_or_real_infra"
|
|
218
|
+
description: "testcontainers, real database connections, real servers"
|
|
219
|
+
verdict: safe
|
|
220
|
+
tier: 1
|
|
221
|
+
|
|
222
|
+
- check: "console_logging_suppression"
|
|
223
|
+
description: "jest.spyOn(console, ...) without assertion on output"
|
|
224
|
+
verdict: safe
|
|
225
|
+
tier: 1
|
|
226
|
+
|
|
227
|
+
- check: "http_interception"
|
|
228
|
+
description: "nock, MSW, jest.mock('node-fetch')"
|
|
229
|
+
conditions:
|
|
230
|
+
- test_type: unit
|
|
231
|
+
verdict: safe
|
|
232
|
+
- test_type: integration
|
|
233
|
+
sub_check: "intercepted URL is own service?"
|
|
234
|
+
if_yes: "flag_T3"
|
|
235
|
+
if_no: "safe"
|
|
236
|
+
- test_type: e2e
|
|
237
|
+
verdict: flag
|
|
238
|
+
|
|
239
|
+
- check: "inmemory_fake_class"
|
|
240
|
+
description: "InMemory*, Fake* implements Interface"
|
|
241
|
+
conditions:
|
|
242
|
+
- test_type: unit
|
|
243
|
+
verdict: safe
|
|
244
|
+
- test_type: integration
|
|
245
|
+
sub_check: "replaces boundary under test?"
|
|
246
|
+
if_yes: "flag_T3"
|
|
247
|
+
if_no: "safe"
|
|
248
|
+
- test_type: e2e
|
|
249
|
+
verdict: flag
|
|
250
|
+
|
|
251
|
+
- check: "di_injected_mock"
|
|
252
|
+
description: "new Service(mockDep, fakeDep)"
|
|
253
|
+
conditions:
|
|
254
|
+
- test_type: unit
|
|
255
|
+
verdict: safe
|
|
256
|
+
- test_type: integration
|
|
257
|
+
sub_check: "mocked dep is integration boundary?"
|
|
258
|
+
if_yes: "flag_T3"
|
|
259
|
+
if_no: "safe"
|
|
260
|
+
- test_type: e2e
|
|
261
|
+
verdict: flag
|
|
262
|
+
|
|
263
|
+
- check: "module_mock"
|
|
264
|
+
description: "jest.mock('./module'), vi.mock('./module')"
|
|
265
|
+
conditions:
|
|
266
|
+
- sub_check: "mocks module under test?"
|
|
267
|
+
if_yes: "flag_T1"
|
|
268
|
+
if_no:
|
|
269
|
+
sub_check: "integration test mocking boundary?"
|
|
270
|
+
if_yes: "flag_T3"
|
|
271
|
+
if_no: "safe"
|
|
272
|
+
|
|
273
|
+
- check: "unmatched_pattern"
|
|
274
|
+
description: "Pattern not covered by above rules"
|
|
275
|
+
verdict: "flag_medium_confidence"
|
|
276
|
+
note: "Recommend manual review"
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Common False Positive Scenarios
|
|
282
|
+
|
|
283
|
+
These patterns have been observed to trigger false positives. The detection agent should recognize and skip them.
|
|
284
|
+
|
|
285
|
+
### 1. Supertest / Pactum Request Chains
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// NOT a violation - supertest creates real HTTP requests to real server
|
|
289
|
+
const response = await request(app)
|
|
290
|
+
.get('/api/users')
|
|
291
|
+
.expect(200)
|
|
292
|
+
.expect('Content-Type', /json/);
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Why flagged incorrectly**: `request(app)` looks like it might be wrapping the app in a test double. It's actually creating a real HTTP connection.
|
|
296
|
+
|
|
297
|
+
### 2. React Testing Library Render
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// NOT a violation - renders real component in real DOM
|
|
301
|
+
const { getByText, getByRole } = render(<LoginForm onSubmit={mockSubmit} />);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Why flagged incorrectly**: `render()` might look like it creates a fake DOM. It uses jsdom, which is real DOM behavior. The `mockSubmit` callback is an acceptable mock for a unit test.
|
|
305
|
+
|
|
306
|
+
### 3. Timer Mocking
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
jest.useFakeTimers();
|
|
310
|
+
// ... test code ...
|
|
311
|
+
jest.advanceTimersByTime(1000);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Why flagged incorrectly**: "Fake" in the method name. Timer mocking is framework-supported test control, not SUT mocking.
|
|
315
|
+
|
|
316
|
+
### 4. Module Re-exports in Test Utilities
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// test-utils.ts
|
|
320
|
+
export { render, screen } from '@testing-library/react';
|
|
321
|
+
export { userEvent } from '@testing-library/user-event';
|
|
322
|
+
export const testDb = createTestDatabase();
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Why flagged incorrectly**: Test utility file might look like a mock factory. It's re-exporting real libraries.
|
|
326
|
+
|
|
327
|
+
### 5. Snapshot Testing
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
expect(component).toMatchInlineSnapshot(`
|
|
331
|
+
<div class="user-card">
|
|
332
|
+
<span>Alice</span>
|
|
333
|
+
</div>
|
|
334
|
+
`);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Why flagged incorrectly**: Inline snapshots contain hardcoded HTML that might look like manually constructed test data. Snapshots verify real output.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Worked Example: Mixed-Type File
|
|
342
|
+
|
|
343
|
+
A single test file may contain both unit and integration sections. The **same mock pattern** can be safe or a violation depending on which section it appears in.
|
|
344
|
+
|
|
345
|
+
### Scenario
|
|
346
|
+
|
|
347
|
+
`error-handler.test.ts` contains:
|
|
348
|
+
- **Lines 1-100**: Unit tests for `categorizeError`, `calculateBackoff`, `withRetry`, `ErrorHandlerService`
|
|
349
|
+
- **Lines 105-150**: Integration tests under `describe('Error Handling Integration', ...)`
|
|
350
|
+
|
|
351
|
+
### The Pattern: `jest.fn().mockResolvedValue()`
|
|
352
|
+
|
|
353
|
+
**In the unit section (SAFE — no violation):**
|
|
354
|
+
```typescript
|
|
355
|
+
describe('Retry Mechanism', () => {
|
|
356
|
+
it('should succeed on first attempt', async () => {
|
|
357
|
+
const operation = jest.fn().mockResolvedValue('success');
|
|
358
|
+
const result = await withRetry(operation, { maxAttempts: 3 });
|
|
359
|
+
expect(result).toBe('success');
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
- `operation` is an **injected dependency** (callback parameter to `withRetry`)
|
|
364
|
+
- The SUT is `withRetry`, not `operation`
|
|
365
|
+
- Mocking injected deps in unit tests is appropriate
|
|
366
|
+
- **Verdict: SAFE** (Tier 1 Universal Safe — injected dependency in unit test)
|
|
367
|
+
|
|
368
|
+
**In the integration section (T3 VIOLATION):**
|
|
369
|
+
```typescript
|
|
370
|
+
describe('Error Handling Integration', () => {
|
|
371
|
+
it('should handle transient errors with retry and recovery', async () => {
|
|
372
|
+
const operation = jest.fn()
|
|
373
|
+
.mockRejectedValueOnce(new Error('ECONNRESET'))
|
|
374
|
+
.mockResolvedValue({ data: 'recovered' });
|
|
375
|
+
const result = await service.executeWithRetry(operation, { maxAttempts: 3 });
|
|
376
|
+
expect(result).toEqual({ data: 'recovered' });
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
- `operation` is the **integration boundary** — the external system call
|
|
381
|
+
- Integration tests exist to verify real system interactions
|
|
382
|
+
- Mocking the boundary defeats the purpose of the integration test
|
|
383
|
+
- **Verdict: T3 VIOLATION** (Mock at integration boundary)
|
|
384
|
+
|
|
385
|
+
### Key Takeaway
|
|
386
|
+
|
|
387
|
+
Never classify an entire file as one test type. Evaluate each describe block independently against the rubric for **its** test type.
|
|
388
|
+
|
|
389
|
+
If AST integration-mock metadata is available (from `just integration-mocks`), it provides ground truth for section boundaries and mock locations within integration/e2e blocks.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Summary for LLM Agent
|
|
394
|
+
|
|
395
|
+
When evaluating a potential violation:
|
|
396
|
+
|
|
397
|
+
1. **Check Tier 1 first** — if the pattern matches Universal Safe, skip immediately
|
|
398
|
+
2. **Identify test type** — unit, integration, or E2E (file name, directory, or content)
|
|
399
|
+
3. **Walk the decision tree** for Tier 2 patterns — context determines the verdict
|
|
400
|
+
4. **When uncertain**, flag as `confidence: medium` with a note explaining the ambiguity
|
|
401
|
+
5. **Never flag** test data factories, test containers, or framework primitives
|
|
402
|
+
6. **Always flag** mocking the system under test (T1) regardless of test type
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Stub Patterns Reference
|
|
2
|
+
|
|
3
|
+
Extended test double detection patterns for mock-detection Stage 2 analysis. Covers the Meszaros test double taxonomy beyond basic `jest.mock()` / `vi.mock()` patterns.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Meszaros Test Double Taxonomy
|
|
8
|
+
|
|
9
|
+
| Type | Purpose | Detection Priority |
|
|
10
|
+
|------|---------|-------------------|
|
|
11
|
+
| **Dummy** | Fills parameter lists, never used | Low (rarely harmful) |
|
|
12
|
+
| **Stub** | Returns canned answers | Medium (may hide real behavior) |
|
|
13
|
+
| **Spy** | Records calls for later verification | Medium (T2 risk if no result check) |
|
|
14
|
+
| **Mock** | Pre-programmed expectations | High (T1/T3 risk) |
|
|
15
|
+
| **Fake** | Working implementation with shortcuts | Context-dependent |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Naming Convention Patterns
|
|
20
|
+
|
|
21
|
+
### Direct Name Matches
|
|
22
|
+
|
|
23
|
+
These naming patterns indicate test doubles. When found in integration tests, evaluate whether they break the integration chain.
|
|
24
|
+
|
|
25
|
+
| Pattern | Regex | Examples |
|
|
26
|
+
|---------|-------|----------|
|
|
27
|
+
| `Mock*` | `/^[Mm]ock[A-Z]/` | `MockDatabase`, `mockUserService`, `MockHttpClient` |
|
|
28
|
+
| `Stub*` | `/^[Ss]tub[A-Z]/` | `StubRepository`, `stubAuthProvider`, `StubCache` |
|
|
29
|
+
| `Fake*` | `/^[Ff]ake[A-Z]/` | `FakeFileSystem`, `fakeEmailSender`, `FakeQueue` |
|
|
30
|
+
| `InMemory*` | `/^[Ii]n[Mm]emory[A-Z]/` | `InMemoryDatabase`, `InMemoryCache`, `InMemoryEventBus` |
|
|
31
|
+
| `Dummy*` | `/^[Dd]ummy[A-Z]/` | `DummyLogger`, `dummyConfig`, `DummyTransport` |
|
|
32
|
+
| `Spy*` | `/^[Ss]py[A-Z]/` | `SpyLogger`, `spyNotifier` |
|
|
33
|
+
| `*Builder` | `/[A-Z]\w+Builder$/` | `UserBuilder`, `OrderBuilder`, `ConfigBuilder` |
|
|
34
|
+
| `Test*` | `/^[Tt]est[A-Z]/` | `TestServer`, `TestDatabase`, `TestHelper` |
|
|
35
|
+
|
|
36
|
+
### Counter-Examples (NOT test doubles)
|
|
37
|
+
|
|
38
|
+
These match naming patterns but are NOT test doubles:
|
|
39
|
+
|
|
40
|
+
| Name | Why It's Not a Double |
|
|
41
|
+
|------|----------------------|
|
|
42
|
+
| `mockImplementation()` | Jest API method, not a variable |
|
|
43
|
+
| `InMemoryCache` in production code | Real implementation choice, not a test shortcut |
|
|
44
|
+
| `TestUtils.formatDate()` | Test utility, not a replacement for production code |
|
|
45
|
+
| `buildUser()` in a factory module | Production factory, not test-only builder |
|
|
46
|
+
| `stubborn`, `mockingbird` | English words, not test double prefixes |
|
|
47
|
+
|
|
48
|
+
**Rule**: Match on PascalCase/camelCase boundaries only. `mockUser` matches; `mockingbird` does not.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Class Hierarchy Detection
|
|
53
|
+
|
|
54
|
+
### Implements/Extends Patterns
|
|
55
|
+
|
|
56
|
+
Classes that implement interfaces or extend base classes as test replacements:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// DETECT: Class implements interface with test-double name
|
|
60
|
+
class FakeDatabase implements DatabasePort {
|
|
61
|
+
private data = new Map<string, unknown>();
|
|
62
|
+
async get(key: string) { return this.data.get(key); }
|
|
63
|
+
async set(key: string, value: unknown) { this.data.set(key, value); }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// DETECT: Class extends base with override
|
|
67
|
+
class StubEmailService extends EmailService {
|
|
68
|
+
override async send() { return { sent: true }; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// DETECT: Abstract test base
|
|
72
|
+
class TestRepository<T> implements Repository<T> {
|
|
73
|
+
protected items: T[] = [];
|
|
74
|
+
async findAll() { return this.items; }
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Detection signals** (AST-level):
|
|
79
|
+
1. Class name matches a test double naming pattern (Fake*, Stub*, Mock*, Test*, InMemory*)
|
|
80
|
+
2. Class implements an interface OR extends a base class
|
|
81
|
+
3. Class defined in a test file or `__tests__/` directory
|
|
82
|
+
|
|
83
|
+
**Violation evaluation**:
|
|
84
|
+
- In unit tests: Generally acceptable (isolating external dependencies)
|
|
85
|
+
- In integration tests: Flag if the class replaces an integration boundary the test claims to verify
|
|
86
|
+
|
|
87
|
+
### Manual Stub Without Naming Convention
|
|
88
|
+
|
|
89
|
+
Some developers create test doubles without naming conventions:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// No Fake/Stub/Mock prefix, but still a test double
|
|
93
|
+
const database = {
|
|
94
|
+
query: async () => [{ id: 1, name: 'test' }],
|
|
95
|
+
insert: async () => ({ id: 1 }),
|
|
96
|
+
delete: async () => true,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Replacement object matching an interface shape
|
|
100
|
+
const logger = {
|
|
101
|
+
info: () => {},
|
|
102
|
+
warn: () => {},
|
|
103
|
+
error: () => {},
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Detection signals**:
|
|
108
|
+
1. Object literal assigned to variable with interface-matching shape
|
|
109
|
+
2. All methods are no-ops (`() => {}`) or return hardcoded values
|
|
110
|
+
3. Variable used where a dependency injection parameter is expected
|
|
111
|
+
|
|
112
|
+
**Note**: These are harder to detect deterministically. Flag as `confidence: medium` for LLM review.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Factory Function Patterns
|
|
117
|
+
|
|
118
|
+
### Test Data Factories (Usually Safe)
|
|
119
|
+
|
|
120
|
+
Factory functions that create test input data are generally NOT violations:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// SAFE: Creates input data for the system under test
|
|
124
|
+
function createTestUser(overrides = {}) {
|
|
125
|
+
return { id: 'user-1', name: 'Test User', email: 'test@example.com', ...overrides };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// SAFE: Builder pattern for test data
|
|
129
|
+
const user = new UserBuilder().withName('Alice').withRole('admin').build();
|
|
130
|
+
|
|
131
|
+
// SAFE: Faker/factory-based generation
|
|
132
|
+
const order = OrderFactory.create({ status: 'pending' });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Why safe**: These create *input data*, not *replacement behavior*. The system under test still processes this data through real code paths.
|
|
136
|
+
|
|
137
|
+
### Test Infrastructure Factories (Context-Dependent)
|
|
138
|
+
|
|
139
|
+
Factory functions that create test infrastructure need evaluation:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// CONTEXT-DEPENDENT: Creates a test server
|
|
143
|
+
function createTestServer(routes) {
|
|
144
|
+
const app = express();
|
|
145
|
+
routes.forEach(r => app.get(r.path, r.handler));
|
|
146
|
+
return app.listen(0); // Random port
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// CONTEXT-DEPENDENT: Creates an in-memory database
|
|
150
|
+
function createTestDatabase(seed = []) {
|
|
151
|
+
const db = new Map();
|
|
152
|
+
seed.forEach(item => db.set(item.id, item));
|
|
153
|
+
return { get: (id) => db.get(id), set: (id, v) => db.set(id, v) };
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Evaluation rule**: If the factory replaces a real dependency that the test claims to verify integration with, it's a violation. If it provides supporting infrastructure (e.g., a test HTTP server to receive real requests), it's acceptable.
|
|
158
|
+
|
|
159
|
+
### Factory Naming Conventions
|
|
160
|
+
|
|
161
|
+
| Pattern | Regex | Typically Safe? |
|
|
162
|
+
|---------|-------|-----------------|
|
|
163
|
+
| `create*` | `/^create[A-Z]/` | Yes (data factory) |
|
|
164
|
+
| `build*` | `/^build[A-Z]/` | Yes (builder pattern) |
|
|
165
|
+
| `make*` | `/^make[A-Z]/` | Yes (data factory) |
|
|
166
|
+
| `generate*` | `/^generate[A-Z]/` | Yes (data factory) |
|
|
167
|
+
| `setup*` | `/^setup[A-Z]/` | Evaluate (infrastructure) |
|
|
168
|
+
| `createTest*` | `/^createTest[A-Z]/` | Evaluate (test infrastructure) |
|
|
169
|
+
| `buildMock*` | `/^buildMock[A-Z]/` | Flag (explicit mock factory) |
|
|
170
|
+
| `createFake*` | `/^createFake[A-Z]/` | Flag (explicit fake factory) |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Framework-Specific Patterns
|
|
175
|
+
|
|
176
|
+
### Jest / Vitest
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Already detected by mock-detection core patterns:
|
|
180
|
+
jest.mock('./module');
|
|
181
|
+
jest.spyOn(object, 'method');
|
|
182
|
+
vi.mock('./module');
|
|
183
|
+
vi.spyOn(object, 'method');
|
|
184
|
+
|
|
185
|
+
// Extended patterns (detect via this reference):
|
|
186
|
+
jest.fn().mockReturnValue(value); // Inline mock function
|
|
187
|
+
jest.fn().mockResolvedValue(value); // Async mock
|
|
188
|
+
jest.fn().mockImplementation(fn); // Custom implementation mock
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Sinon
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
sinon.stub(object, 'method').returns(value);
|
|
195
|
+
sinon.mock(object).expects('method');
|
|
196
|
+
sinon.fake.returns(value);
|
|
197
|
+
sinon.spy(object, 'method');
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Test Containers / Real Infrastructure
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// NOT a test double - real infrastructure for integration tests
|
|
204
|
+
const container = await new GenericContainer('postgres:15').start();
|
|
205
|
+
const redis = await new GenericContainer('redis:7').start();
|
|
206
|
+
|
|
207
|
+
// NOT a test double - real HTTP interception for external APIs
|
|
208
|
+
const server = setupServer(
|
|
209
|
+
rest.get('/api/users', (req, res, ctx) => res(ctx.json(users)))
|
|
210
|
+
);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Rule**: Test containers and MSW/nock interceptors for *external* APIs are legitimate test infrastructure, not violations. See `false-positive-prevention.md` for the full allowlist.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Detection Priority by Test Type
|
|
218
|
+
|
|
219
|
+
| Test Type | What to Flag | What to Allow |
|
|
220
|
+
|-----------|-------------|---------------|
|
|
221
|
+
| **Unit** | T1 (mock SUT), T2 (call-only assert) | External dep mocks, data factories |
|
|
222
|
+
| **Integration** | T3 (mock boundary), T3+ (broken chain), manual stubs replacing integration points | Data factories, test containers, external API mocks |
|
|
223
|
+
| **E2E** | Any mock/stub/fake that shortcuts the flow | Seed data, test user factories |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Summary for LLM Agent
|
|
228
|
+
|
|
229
|
+
When analyzing a test file:
|
|
230
|
+
|
|
231
|
+
1. **Scan for naming patterns** from the tables above
|
|
232
|
+
2. **Check class hierarchies** for implements/extends with test-double names
|
|
233
|
+
3. **Identify factory functions** and classify as data factory (safe) vs infrastructure factory (evaluate)
|
|
234
|
+
4. **Cross-reference with test type** (unit vs integration vs E2E)
|
|
235
|
+
5. **Apply the mock appropriateness rubric** from mock-detection SKILL.md
|
|
236
|
+
6. **Consult false-positive-prevention.md** before flagging borderline patterns
|