@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,166 @@
|
|
|
1
|
+
# Pattern 5: Database Verification
|
|
2
|
+
|
|
3
|
+
## Strategy
|
|
4
|
+
|
|
5
|
+
Setup test database, execute operations, verify state changes, teardown.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Template (Node/Jest)
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const { setupTestDb, teardownTestDb } = require('./test-utils');
|
|
13
|
+
const { saveRecord, findRecord, deleteRecord } = require('{component_path}');
|
|
14
|
+
|
|
15
|
+
describe('{component_name} Database Operations', () => {
|
|
16
|
+
let db;
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
db = await setupTestDb();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await teardownTestDb(db);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
await db.clear(); // Clean state between tests
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('save creates record in database', async () => {
|
|
31
|
+
const data = { id: 1, value: 'test' };
|
|
32
|
+
await saveRecord(db, data);
|
|
33
|
+
|
|
34
|
+
const found = await db.findOne({ id: 1 });
|
|
35
|
+
expect(found).toBeDefined();
|
|
36
|
+
expect(found.value).toBe('test');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('find retrieves existing record', async () => {
|
|
40
|
+
await db.insert({ id: 2, value: 'existing' });
|
|
41
|
+
|
|
42
|
+
const result = await findRecord(db, 2);
|
|
43
|
+
expect(result.value).toBe('existing');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('delete removes record from database', async () => {
|
|
47
|
+
await db.insert({ id: 3, value: 'to-delete' });
|
|
48
|
+
|
|
49
|
+
await deleteRecord(db, 3);
|
|
50
|
+
|
|
51
|
+
const found = await db.findOne({ id: 3 });
|
|
52
|
+
expect(found).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Template (Python/pytest)
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import pytest
|
|
63
|
+
from {module} import save_record, find_record, delete_record
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def test_db():
|
|
67
|
+
db = setup_test_database()
|
|
68
|
+
yield db
|
|
69
|
+
teardown_test_database(db)
|
|
70
|
+
|
|
71
|
+
@pytest.fixture(autouse=True)
|
|
72
|
+
def clean_db(test_db):
|
|
73
|
+
yield
|
|
74
|
+
test_db.clear()
|
|
75
|
+
|
|
76
|
+
def test_save_creates_record(test_db):
|
|
77
|
+
save_record(test_db, {'id': 1, 'value': 'test'})
|
|
78
|
+
|
|
79
|
+
found = test_db.find_one({'id': 1})
|
|
80
|
+
assert found is not None
|
|
81
|
+
assert found['value'] == 'test'
|
|
82
|
+
|
|
83
|
+
def test_find_retrieves_record(test_db):
|
|
84
|
+
test_db.insert({'id': 2, 'value': 'existing'})
|
|
85
|
+
|
|
86
|
+
result = find_record(test_db, 2)
|
|
87
|
+
assert result['value'] == 'existing'
|
|
88
|
+
|
|
89
|
+
def test_delete_removes_record(test_db):
|
|
90
|
+
test_db.insert({'id': 3, 'value': 'to-delete'})
|
|
91
|
+
|
|
92
|
+
delete_record(test_db, 3)
|
|
93
|
+
|
|
94
|
+
found = test_db.find_one({'id': 3})
|
|
95
|
+
assert found is None
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Template (Bash - SQLite)
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
#!/bin/bash
|
|
104
|
+
# Database Verification: {component_name}
|
|
105
|
+
set -e
|
|
106
|
+
|
|
107
|
+
echo "=== Database Verification: {component_name} ==="
|
|
108
|
+
|
|
109
|
+
TEST_DB=$(mktemp --suffix=.db)
|
|
110
|
+
|
|
111
|
+
cleanup() {
|
|
112
|
+
rm -f "$TEST_DB"
|
|
113
|
+
}
|
|
114
|
+
trap cleanup EXIT
|
|
115
|
+
|
|
116
|
+
# Initialize test database
|
|
117
|
+
sqlite3 "$TEST_DB" "CREATE TABLE {table} (id INTEGER PRIMARY KEY, value TEXT);"
|
|
118
|
+
|
|
119
|
+
# Test 1: Insert
|
|
120
|
+
echo -n "Test 1: Insert record... "
|
|
121
|
+
{insert_command} "$TEST_DB"
|
|
122
|
+
COUNT=$(sqlite3 "$TEST_DB" "SELECT COUNT(*) FROM {table};")
|
|
123
|
+
if [ "$COUNT" -gt 0 ]; then
|
|
124
|
+
echo "PASS ($COUNT records)"
|
|
125
|
+
else
|
|
126
|
+
echo "FAIL (no records inserted)"
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Test 2: Query
|
|
131
|
+
echo -n "Test 2: Query record... "
|
|
132
|
+
VALUE=$(sqlite3 "$TEST_DB" "SELECT value FROM {table} WHERE id=1;")
|
|
133
|
+
if [ "$VALUE" = "{expected_value}" ]; then
|
|
134
|
+
echo "PASS"
|
|
135
|
+
else
|
|
136
|
+
echo "FAIL (expected: {expected_value}, got: $VALUE)"
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Test 3: Delete
|
|
141
|
+
echo -n "Test 3: Delete record... "
|
|
142
|
+
{delete_command} "$TEST_DB"
|
|
143
|
+
COUNT=$(sqlite3 "$TEST_DB" "SELECT COUNT(*) FROM {table};")
|
|
144
|
+
if [ "$COUNT" -eq 0 ]; then
|
|
145
|
+
echo "PASS (deleted)"
|
|
146
|
+
else
|
|
147
|
+
echo "FAIL ($COUNT records remain)"
|
|
148
|
+
exit 1
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
echo "=== All tests passed ==="
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Placeholders
|
|
157
|
+
|
|
158
|
+
| Placeholder | Description |
|
|
159
|
+
|-------------|-------------|
|
|
160
|
+
| `{component_name}` | Name of the database component |
|
|
161
|
+
| `{component_path}` | Import path to the database module |
|
|
162
|
+
| `{module}` | Python module name |
|
|
163
|
+
| `{table}` | Database table name |
|
|
164
|
+
| `{insert_command}` | Command to insert records |
|
|
165
|
+
| `{delete_command}` | Command to delete records |
|
|
166
|
+
| `{expected_value}` | Expected value after operations |
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Pattern 6: External API Verification
|
|
2
|
+
|
|
3
|
+
## Strategy
|
|
4
|
+
|
|
5
|
+
Use MSW (Mock Service Worker) to intercept at network level - not module level.
|
|
6
|
+
This allows real HTTP calls while controlling external responses.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Template (Node/Jest with MSW)
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
import { setupServer } from 'msw/node';
|
|
14
|
+
import { rest } from 'msw';
|
|
15
|
+
import { fetchUserData, postOrder } from '{component_path}';
|
|
16
|
+
|
|
17
|
+
// Setup MSW server with handlers
|
|
18
|
+
const server = setupServer(
|
|
19
|
+
rest.get('https://api.example.com/users/:id', (req, res, ctx) => {
|
|
20
|
+
return res(ctx.json({
|
|
21
|
+
id: req.params.id,
|
|
22
|
+
name: 'Test User',
|
|
23
|
+
email: 'test@example.com'
|
|
24
|
+
}));
|
|
25
|
+
}),
|
|
26
|
+
rest.post('https://api.example.com/orders', async (req, res, ctx) => {
|
|
27
|
+
const body = await req.json();
|
|
28
|
+
return res(ctx.json({
|
|
29
|
+
orderId: 'ORD-123',
|
|
30
|
+
items: body.items,
|
|
31
|
+
status: 'created'
|
|
32
|
+
}));
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
beforeAll(() => server.listen());
|
|
37
|
+
afterEach(() => server.resetHandlers());
|
|
38
|
+
afterAll(() => server.close());
|
|
39
|
+
|
|
40
|
+
describe('{component_name} External API', () => {
|
|
41
|
+
test('fetches user data from API', async () => {
|
|
42
|
+
// Real fetch call - intercepted by MSW at network level
|
|
43
|
+
const user = await fetchUserData(1);
|
|
44
|
+
|
|
45
|
+
expect(user.name).toBe('Test User');
|
|
46
|
+
expect(user.email).toBe('test@example.com');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('posts order to API', async () => {
|
|
50
|
+
const order = await postOrder({ items: [{ sku: 'ABC', qty: 2 }] });
|
|
51
|
+
|
|
52
|
+
expect(order.orderId).toBe('ORD-123');
|
|
53
|
+
expect(order.status).toBe('created');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('handles API errors gracefully', async () => {
|
|
57
|
+
// Override handler for this test
|
|
58
|
+
server.use(
|
|
59
|
+
rest.get('https://api.example.com/users/:id', (req, res, ctx) => {
|
|
60
|
+
return res(ctx.status(500), ctx.json({ error: 'Server error' }));
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
await expect(fetchUserData(1)).rejects.toThrow('API error');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Template (Python/pytest with responses)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import pytest
|
|
75
|
+
import responses
|
|
76
|
+
from {module} import fetch_user_data, post_order
|
|
77
|
+
|
|
78
|
+
@responses.activate
|
|
79
|
+
def test_fetches_user_data():
|
|
80
|
+
responses.add(
|
|
81
|
+
responses.GET,
|
|
82
|
+
'https://api.example.com/users/1',
|
|
83
|
+
json={'id': 1, 'name': 'Test User', 'email': 'test@example.com'},
|
|
84
|
+
status=200
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
user = fetch_user_data(1)
|
|
88
|
+
|
|
89
|
+
assert user['name'] == 'Test User'
|
|
90
|
+
assert user['email'] == 'test@example.com'
|
|
91
|
+
|
|
92
|
+
@responses.activate
|
|
93
|
+
def test_posts_order():
|
|
94
|
+
responses.add(
|
|
95
|
+
responses.POST,
|
|
96
|
+
'https://api.example.com/orders',
|
|
97
|
+
json={'orderId': 'ORD-123', 'status': 'created'},
|
|
98
|
+
status=201
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
order = post_order({'items': [{'sku': 'ABC', 'qty': 2}]})
|
|
102
|
+
|
|
103
|
+
assert order['orderId'] == 'ORD-123'
|
|
104
|
+
assert order['status'] == 'created'
|
|
105
|
+
|
|
106
|
+
@responses.activate
|
|
107
|
+
def test_handles_api_errors():
|
|
108
|
+
responses.add(
|
|
109
|
+
responses.GET,
|
|
110
|
+
'https://api.example.com/users/1',
|
|
111
|
+
json={'error': 'Server error'},
|
|
112
|
+
status=500
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
with pytest.raises(Exception) as exc:
|
|
116
|
+
fetch_user_data(1)
|
|
117
|
+
assert 'API error' in str(exc.value)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Key Difference from jest.mock
|
|
123
|
+
|
|
124
|
+
| Approach | What Happens | Real HTTP? |
|
|
125
|
+
|----------|--------------|------------|
|
|
126
|
+
| `jest.mock('node-fetch')` | Replaces module entirely | No |
|
|
127
|
+
| MSW / responses | Intercepts at network layer | Yes (intercepted before leaving machine) |
|
|
128
|
+
|
|
129
|
+
MSW allows real `fetch()` calls to execute, testing the actual HTTP code path while still controlling external responses.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Placeholders
|
|
134
|
+
|
|
135
|
+
| Placeholder | Description |
|
|
136
|
+
|-------------|-------------|
|
|
137
|
+
| `{component_name}` | Name of the API client component |
|
|
138
|
+
| `{component_path}` | Import path to the API module |
|
|
139
|
+
| `{module}` | Python module name |
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Pattern 3: File Parser Verification
|
|
2
|
+
|
|
3
|
+
## Strategy
|
|
4
|
+
|
|
5
|
+
Create test input file, run parser, verify parsed output structure and values.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Template (Bash)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
#!/bin/bash
|
|
13
|
+
# File Parser Verification: {component_name}
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
echo "=== File Parser Verification: {component_name} ==="
|
|
17
|
+
|
|
18
|
+
# Create test input
|
|
19
|
+
TEST_FILE=$(mktemp --suffix=.{ext})
|
|
20
|
+
cat > "$TEST_FILE" << 'EOF'
|
|
21
|
+
{test_input_content}
|
|
22
|
+
EOF
|
|
23
|
+
|
|
24
|
+
# Cleanup trap
|
|
25
|
+
cleanup() {
|
|
26
|
+
rm -f "$TEST_FILE"
|
|
27
|
+
}
|
|
28
|
+
trap cleanup EXIT
|
|
29
|
+
|
|
30
|
+
# Test 1: Parse succeeds
|
|
31
|
+
echo -n "Test 1: Parse succeeds... "
|
|
32
|
+
OUTPUT=$({parser_command} "$TEST_FILE" 2>&1)
|
|
33
|
+
if [ $? -eq 0 ]; then
|
|
34
|
+
echo "PASS"
|
|
35
|
+
else
|
|
36
|
+
echo "FAIL"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Test 2: Output structure valid
|
|
41
|
+
echo -n "Test 2: Output structure... "
|
|
42
|
+
if echo "$OUTPUT" | jq -e '{json_structure_check}' > /dev/null 2>&1; then
|
|
43
|
+
echo "PASS"
|
|
44
|
+
else
|
|
45
|
+
echo "FAIL"
|
|
46
|
+
echo "Output was: $OUTPUT"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Test 3: Values correct
|
|
51
|
+
echo -n "Test 3: Values correct... "
|
|
52
|
+
VALUE=$(echo "$OUTPUT" | jq -r '{value_path}')
|
|
53
|
+
if [ "$VALUE" = "{expected_value}" ]; then
|
|
54
|
+
echo "PASS"
|
|
55
|
+
else
|
|
56
|
+
echo "FAIL (expected: {expected_value}, got: $VALUE)"
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Test 4: Invalid input handling
|
|
61
|
+
echo -n "Test 4: Invalid input handling... "
|
|
62
|
+
if ! {parser_command} "/nonexistent/file.{ext}" 2>&1; then
|
|
63
|
+
echo "PASS (error returned)"
|
|
64
|
+
else
|
|
65
|
+
echo "FAIL (should have errored)"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo "=== All tests passed ==="
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Template (Node/Jest)
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const fs = require('fs');
|
|
78
|
+
const path = require('path');
|
|
79
|
+
const os = require('os');
|
|
80
|
+
const { parse } = require('{component_path}');
|
|
81
|
+
|
|
82
|
+
describe('{component_name} Parser', () => {
|
|
83
|
+
let testFile;
|
|
84
|
+
|
|
85
|
+
beforeAll(() => {
|
|
86
|
+
testFile = path.join(os.tmpdir(), 'test-input.{ext}');
|
|
87
|
+
fs.writeFileSync(testFile, `{test_input_content}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
afterAll(() => {
|
|
91
|
+
fs.unlinkSync(testFile);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('parses valid input', () => {
|
|
95
|
+
const result = parse(testFile);
|
|
96
|
+
expect(result).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('output has expected structure', () => {
|
|
100
|
+
const result = parse(testFile);
|
|
101
|
+
expect(result).toHaveProperty('{expected_field}');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('values are correct', () => {
|
|
105
|
+
const result = parse(testFile);
|
|
106
|
+
expect(result.{field}).toBe('{expected_value}');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('handles invalid input', () => {
|
|
110
|
+
expect(() => parse('/nonexistent/file.{ext}')).toThrow();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Template (Python/pytest)
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import pytest
|
|
121
|
+
import tempfile
|
|
122
|
+
import os
|
|
123
|
+
from {module} import {parser_function}
|
|
124
|
+
|
|
125
|
+
@pytest.fixture
|
|
126
|
+
def test_file():
|
|
127
|
+
fd, path = tempfile.mkstemp(suffix='.{ext}')
|
|
128
|
+
with os.fdopen(fd, 'w') as f:
|
|
129
|
+
f.write('''{test_input_content}''')
|
|
130
|
+
yield path
|
|
131
|
+
os.unlink(path)
|
|
132
|
+
|
|
133
|
+
def test_parse_succeeds(test_file):
|
|
134
|
+
result = {parser_function}(test_file)
|
|
135
|
+
assert result is not None
|
|
136
|
+
|
|
137
|
+
def test_output_structure(test_file):
|
|
138
|
+
result = {parser_function}(test_file)
|
|
139
|
+
assert '{expected_field}' in result
|
|
140
|
+
|
|
141
|
+
def test_values_correct(test_file):
|
|
142
|
+
result = {parser_function}(test_file)
|
|
143
|
+
assert result['{field}'] == '{expected_value}'
|
|
144
|
+
|
|
145
|
+
def test_handles_invalid_input():
|
|
146
|
+
with pytest.raises({ExpectedException}):
|
|
147
|
+
{parser_function}('/nonexistent/file.{ext}')
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Placeholders
|
|
153
|
+
|
|
154
|
+
| Placeholder | Description |
|
|
155
|
+
|-------------|-------------|
|
|
156
|
+
| `{component_name}` | Name of the parser component |
|
|
157
|
+
| `{component_path}` | Import path to the parser module |
|
|
158
|
+
| `{module}` | Python module name |
|
|
159
|
+
| `{parser_function}` | Name of the parse function |
|
|
160
|
+
| `{parser_command}` | CLI command to run parser |
|
|
161
|
+
| `{ext}` | File extension (e.g., json, yaml, csv) |
|
|
162
|
+
| `{test_input_content}` | Sample input content |
|
|
163
|
+
| `{expected_field}` | Field expected in parsed result |
|
|
164
|
+
| `{field}` | Specific field to check value |
|
|
165
|
+
| `{expected_value}` | Expected value of field |
|
|
166
|
+
| `{json_structure_check}` | jq expression for structure check |
|
|
167
|
+
| `{value_path}` | jq path to specific value |
|
|
168
|
+
| `{ExpectedException}` | Python exception class for errors |
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Pattern 2: HTTP Server Verification
|
|
2
|
+
|
|
3
|
+
## Strategy
|
|
4
|
+
|
|
5
|
+
Start server, wait for ready, make HTTP requests, verify responses, cleanup.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Template (Bash)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
#!/bin/bash
|
|
13
|
+
# HTTP Server Verification: {component_name}
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
echo "=== HTTP Server Verification: {component_name} ==="
|
|
17
|
+
|
|
18
|
+
# Start server in background
|
|
19
|
+
{start_command} &
|
|
20
|
+
SERVER_PID=$!
|
|
21
|
+
echo "Started server (PID: $SERVER_PID)"
|
|
22
|
+
|
|
23
|
+
# Cleanup trap
|
|
24
|
+
cleanup() {
|
|
25
|
+
echo "Cleaning up..."
|
|
26
|
+
kill $SERVER_PID 2>/dev/null || true
|
|
27
|
+
wait $SERVER_PID 2>/dev/null || true
|
|
28
|
+
}
|
|
29
|
+
trap cleanup EXIT
|
|
30
|
+
|
|
31
|
+
# Wait for server to be ready
|
|
32
|
+
echo -n "Waiting for server... "
|
|
33
|
+
for i in {1..30}; do
|
|
34
|
+
if curl -s http://localhost:{port}/health > /dev/null 2>&1; then
|
|
35
|
+
echo "ready"
|
|
36
|
+
break
|
|
37
|
+
fi
|
|
38
|
+
sleep 0.5
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
# Test 1: Health endpoint
|
|
42
|
+
echo -n "Test 1: Health endpoint... "
|
|
43
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:{port}/health)
|
|
44
|
+
if [ "$HTTP_CODE" = "200" ]; then
|
|
45
|
+
echo "PASS (HTTP 200)"
|
|
46
|
+
else
|
|
47
|
+
echo "FAIL (HTTP $HTTP_CODE)"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Test 2: API endpoint response
|
|
52
|
+
echo -n "Test 2: API response... "
|
|
53
|
+
RESPONSE=$(curl -s http://localhost:{port}{endpoint})
|
|
54
|
+
if echo "$RESPONSE" | jq -e '{json_validation}' > /dev/null 2>&1; then
|
|
55
|
+
echo "PASS (valid response)"
|
|
56
|
+
else
|
|
57
|
+
echo "FAIL (invalid response)"
|
|
58
|
+
echo "Response was: $RESPONSE"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Test 3: 404 handling
|
|
63
|
+
echo -n "Test 3: 404 handling... "
|
|
64
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:{port}/nonexistent)
|
|
65
|
+
if [ "$HTTP_CODE" = "404" ]; then
|
|
66
|
+
echo "PASS (HTTP 404)"
|
|
67
|
+
else
|
|
68
|
+
echo "FAIL (HTTP $HTTP_CODE, expected 404)"
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
echo "=== All tests passed ==="
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Template (Node/Jest with Supertest)
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
const request = require('supertest');
|
|
81
|
+
const { createServer } = require('{component_path}');
|
|
82
|
+
|
|
83
|
+
describe('{component_name} HTTP Server', () => {
|
|
84
|
+
let server;
|
|
85
|
+
|
|
86
|
+
beforeAll(async () => {
|
|
87
|
+
server = await createServer();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
afterAll(async () => {
|
|
91
|
+
await server.close();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('health endpoint returns 200', async () => {
|
|
95
|
+
const response = await request(server).get('/health');
|
|
96
|
+
expect(response.status).toBe(200);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('API endpoint returns valid data', async () => {
|
|
100
|
+
const response = await request(server).get('{endpoint}');
|
|
101
|
+
expect(response.status).toBe(200);
|
|
102
|
+
expect(response.body).toHaveProperty('{expected_field}');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('handles 404 gracefully', async () => {
|
|
106
|
+
const response = await request(server).get('/nonexistent');
|
|
107
|
+
expect(response.status).toBe(404);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Template (Python/pytest)
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import pytest
|
|
118
|
+
import requests
|
|
119
|
+
import subprocess
|
|
120
|
+
import time
|
|
121
|
+
|
|
122
|
+
@pytest.fixture(scope='module')
|
|
123
|
+
def server():
|
|
124
|
+
proc = subprocess.Popen(['{start_command}'])
|
|
125
|
+
# Wait for server to start
|
|
126
|
+
for _ in range(30):
|
|
127
|
+
try:
|
|
128
|
+
requests.get('http://localhost:{port}/health')
|
|
129
|
+
break
|
|
130
|
+
except requests.ConnectionError:
|
|
131
|
+
time.sleep(0.5)
|
|
132
|
+
yield 'http://localhost:{port}'
|
|
133
|
+
proc.terminate()
|
|
134
|
+
proc.wait()
|
|
135
|
+
|
|
136
|
+
def test_health_endpoint(server):
|
|
137
|
+
response = requests.get(f'{server}/health')
|
|
138
|
+
assert response.status_code == 200
|
|
139
|
+
|
|
140
|
+
def test_api_endpoint(server):
|
|
141
|
+
response = requests.get(f'{server}{endpoint}')
|
|
142
|
+
assert response.status_code == 200
|
|
143
|
+
assert '{expected_field}' in response.json()
|
|
144
|
+
|
|
145
|
+
def test_404_handling(server):
|
|
146
|
+
response = requests.get(f'{server}/nonexistent')
|
|
147
|
+
assert response.status_code == 404
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Placeholders
|
|
153
|
+
|
|
154
|
+
| Placeholder | Description |
|
|
155
|
+
|-------------|-------------|
|
|
156
|
+
| `{component_name}` | Name of the HTTP server component |
|
|
157
|
+
| `{component_path}` | Import path to the server module |
|
|
158
|
+
| `{start_command}` | Command to start the server |
|
|
159
|
+
| `{port}` | Port the server listens on |
|
|
160
|
+
| `{endpoint}` | API endpoint to test |
|
|
161
|
+
| `{expected_field}` | Field expected in JSON response |
|
|
162
|
+
| `{json_validation}` | jq expression for JSON validation |
|