vanara 0.1.0 → 0.2.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/README.md +14 -0
- package/bin/vanara.js +66 -2
- package/catalog/agents/api-designer/AGENT.md +140 -0
- package/catalog/agents/api-designer/examples/openapi-snippet.yaml +130 -0
- package/catalog/agents/api-designer/examples/review-notes.md +61 -0
- package/catalog/agents/api-designer/references/contract-and-openapi.md +82 -0
- package/catalog/agents/api-designer/references/design-checklist.md +68 -0
- package/catalog/agents/api-designer/references/versioning-and-evolution.md +63 -0
- package/catalog/agents/api-designer/scripts/lint-openapi.mjs +180 -0
- package/catalog/agents/code-reviewer/AGENT.md +168 -0
- package/catalog/agents/code-reviewer/examples/pr-comment-template.md +48 -0
- package/catalog/agents/code-reviewer/examples/sample-review-output.md +38 -0
- package/catalog/agents/code-reviewer/references/review-checklist.md +46 -0
- package/catalog/agents/code-reviewer/references/security-review.md +62 -0
- package/catalog/agents/code-reviewer/references/severity-rubric.md +63 -0
- package/catalog/agents/code-reviewer/scripts/review-guard.mjs +102 -0
- package/catalog/agents/debugger/AGENT.md +112 -0
- package/catalog/agents/debugger/examples/postmortem-template.md +59 -0
- package/catalog/agents/debugger/examples/repro-template.md +50 -0
- package/catalog/agents/debugger/references/bisection.md +63 -0
- package/catalog/agents/debugger/references/debugging-method.md +57 -0
- package/catalog/agents/debugger/references/observability.md +61 -0
- package/catalog/agents/debugger/scripts/parse-stacktrace.mjs +109 -0
- package/catalog/agents/pr-summarizer/AGENT.md +156 -0
- package/catalog/agents/pr-summarizer/examples/sample-pr-summary.md +46 -0
- package/catalog/agents/pr-summarizer/examples/weak-vs-strong-summary.md +68 -0
- package/catalog/agents/pr-summarizer/references/identifying-risk.md +68 -0
- package/catalog/agents/pr-summarizer/references/reading-the-diff.md +68 -0
- package/catalog/agents/pr-summarizer/references/test-plan-and-missing-tests.md +64 -0
- package/catalog/agents/pr-summarizer/references/writing-the-summary.md +63 -0
- package/catalog/agents/pr-summarizer/scripts/diff-risk.mjs +111 -0
- package/catalog/agents/refactoring-specialist/AGENT.md +185 -0
- package/catalog/agents/refactoring-specialist/examples/before-after.md +91 -0
- package/catalog/agents/refactoring-specialist/examples/refactor-plan.md +97 -0
- package/catalog/agents/refactoring-specialist/references/large-scale-refactor.md +50 -0
- package/catalog/agents/refactoring-specialist/references/safe-workflow.md +75 -0
- package/catalog/agents/refactoring-specialist/references/smell-catalog.md +76 -0
- package/catalog/agents/refactoring-specialist/scripts/detect-smells.mjs +150 -0
- package/catalog/agents/security-auditor/AGENT.md +168 -0
- package/catalog/agents/security-auditor/examples/audit-report.md +68 -0
- package/catalog/agents/security-auditor/examples/finding-template.md +31 -0
- package/catalog/agents/security-auditor/references/audit-workflow.md +116 -0
- package/catalog/agents/security-auditor/references/severity-and-reporting.md +56 -0
- package/catalog/agents/security-auditor/references/vuln-classes.md +129 -0
- package/catalog/agents/security-auditor/scripts/scan-secrets.mjs +86 -0
- package/catalog/agents/technical-writer/AGENT.md +163 -0
- package/catalog/agents/technical-writer/examples/how-to-example.md +76 -0
- package/catalog/agents/technical-writer/examples/reference-example.md +75 -0
- package/catalog/agents/technical-writer/references/clarity-and-style.md +52 -0
- package/catalog/agents/technical-writer/references/diataxis-and-doc-types.md +61 -0
- package/catalog/agents/technical-writer/references/structure-and-maintenance.md +59 -0
- package/catalog/agents/technical-writer/scripts/readability.mjs +103 -0
- package/catalog/agents/test-author/AGENT.md +177 -0
- package/catalog/agents/test-author/examples/test-plan.md +54 -0
- package/catalog/agents/test-author/examples/unit-test-example.ts +55 -0
- package/catalog/agents/test-author/references/aaa-and-naming.md +75 -0
- package/catalog/agents/test-author/references/mocking-and-fixtures.md +94 -0
- package/catalog/agents/test-author/references/test-types-and-pyramid.md +60 -0
- package/catalog/agents/test-author/scripts/check-coverage.mjs +145 -0
- package/catalog/agents/threat-modeler/AGENT.md +163 -0
- package/catalog/agents/threat-modeler/examples/dfd-example.md +55 -0
- package/catalog/agents/threat-modeler/examples/threat-model.md +81 -0
- package/catalog/agents/threat-modeler/references/dfd-and-trust-boundaries.md +76 -0
- package/catalog/agents/threat-modeler/references/mitigation-catalog.md +77 -0
- package/catalog/agents/threat-modeler/references/stride-method.md +68 -0
- package/catalog/agents/threat-modeler/scripts/stride-checklist.mjs +183 -0
- package/catalog/agents/vuln-scanner/AGENT.md +146 -0
- package/catalog/agents/vuln-scanner/examples/finding-template.md +57 -0
- package/catalog/agents/vuln-scanner/examples/scan-report.md +49 -0
- package/catalog/agents/vuln-scanner/references/remediation-and-severity.md +68 -0
- package/catalog/agents/vuln-scanner/references/scan-types-and-tools.md +83 -0
- package/catalog/agents/vuln-scanner/references/triage-and-false-positives.md +73 -0
- package/catalog/agents/vuln-scanner/scripts/parse-scan-results.mjs +133 -0
- package/catalog/packs/code-review-pack/PACK.md +100 -0
- package/catalog/packs/code-review-pack/examples/pr-flow-example.md +50 -0
- package/catalog/packs/code-review-pack/references/rollout-guide.md +40 -0
- package/catalog/packs/security-pack/PACK.md +128 -0
- package/catalog/packs/security-pack/examples/owasp-vuln-triage-and-fix.md +66 -0
- package/catalog/packs/security-pack/examples/threat-model-then-audit.md +77 -0
- package/catalog/packs/security-pack/references/choosing-the-right-tool.md +37 -0
- package/catalog/packs/security-pack/references/threat-model-to-remediation.md +69 -0
- package/catalog/packs/security-pack/references/workflow.md +56 -0
- package/catalog/skills/api-pagination/SKILL.md +83 -0
- package/catalog/skills/api-pagination/examples/keyset-queries.sql +38 -0
- package/catalog/skills/api-pagination/examples/response-shapes.md +51 -0
- package/catalog/skills/api-pagination/references/consuming-pages.md +39 -0
- package/catalog/skills/api-pagination/references/counting-and-totals.md +41 -0
- package/catalog/skills/api-pagination/references/cursor-encoding.md +34 -0
- package/catalog/skills/api-pagination/scripts/check-cursor.mjs +51 -0
- package/catalog/skills/caching-strategies/SKILL.md +180 -0
- package/catalog/skills/caching-strategies/examples/http-cache-headers.md +82 -0
- package/catalog/skills/caching-strategies/examples/redis-cache-aside.js +110 -0
- package/catalog/skills/caching-strategies/references/cache-patterns.md +93 -0
- package/catalog/skills/caching-strategies/references/eviction-and-ttl.md +67 -0
- package/catalog/skills/caching-strategies/references/invalidation-and-stampede.md +99 -0
- package/catalog/skills/caching-strategies/scripts/ttl-jitter.mjs +99 -0
- package/catalog/skills/conventional-commits/SKILL.md +135 -0
- package/catalog/skills/conventional-commits/examples/commit-examples.md +60 -0
- package/catalog/skills/conventional-commits/examples/commitlint.config.js +31 -0
- package/catalog/skills/conventional-commits/references/breaking-changes-semver.md +50 -0
- package/catalog/skills/conventional-commits/references/scoping-commits.md +42 -0
- package/catalog/skills/conventional-commits/references/spec.md +78 -0
- package/catalog/skills/conventional-commits/scripts/lint-commit.mjs +97 -0
- package/catalog/skills/database-migrations/SKILL.md +137 -0
- package/catalog/skills/database-migrations/examples/add-column-safe.sql +34 -0
- package/catalog/skills/database-migrations/examples/migration-config.example.json +27 -0
- package/catalog/skills/database-migrations/references/expand-contract.md +74 -0
- package/catalog/skills/database-migrations/references/rollback-and-safety.md +71 -0
- package/catalog/skills/database-migrations/references/zero-downtime-changes.md +84 -0
- package/catalog/skills/database-migrations/scripts/check-migration-reversible.mjs +139 -0
- package/catalog/skills/error-handling-patterns/SKILL.md +136 -0
- package/catalog/skills/error-handling-patterns/examples/express-error-middleware.js +93 -0
- package/catalog/skills/error-handling-patterns/examples/result-pattern.ts +81 -0
- package/catalog/skills/error-handling-patterns/references/error-taxonomy.md +91 -0
- package/catalog/skills/error-handling-patterns/references/language-patterns.md +102 -0
- package/catalog/skills/error-handling-patterns/references/retry-and-backoff.md +76 -0
- package/catalog/skills/error-handling-patterns/scripts/lint-empty-catch.mjs +113 -0
- package/catalog/skills/git-collaboration-workflows/SKILL.md +104 -0
- package/catalog/skills/git-collaboration-workflows/examples/branch-protection.md +48 -0
- package/catalog/skills/git-collaboration-workflows/examples/pr-review-playbook.md +48 -0
- package/catalog/skills/git-collaboration-workflows/references/branching-models.md +52 -0
- package/catalog/skills/git-collaboration-workflows/references/history-hygiene.md +44 -0
- package/catalog/skills/git-collaboration-workflows/scripts/check-branch-hygiene.mjs +62 -0
- package/catalog/skills/owasp-top10/SKILL.md +112 -0
- package/catalog/skills/owasp-top10/examples/sql-injection-fix.md +33 -0
- package/catalog/skills/owasp-top10/examples/xss-fix.md +27 -0
- package/catalog/skills/owasp-top10/references/access-control.md +36 -0
- package/catalog/skills/owasp-top10/references/crypto-failures.md +29 -0
- package/catalog/skills/owasp-top10/references/injection.md +39 -0
- package/catalog/skills/owasp-top10/references/ssrf-and-design.md +35 -0
- package/catalog/skills/owasp-top10/scripts/scan-injection.mjs +73 -0
- package/catalog/skills/prompt-engineering/SKILL.md +100 -0
- package/catalog/skills/prompt-engineering/examples/classification-prompt.md +29 -0
- package/catalog/skills/prompt-engineering/examples/extraction-prompt.md +25 -0
- package/catalog/skills/prompt-engineering/references/prompt-injection.md +34 -0
- package/catalog/skills/prompt-engineering/references/structured-output.md +34 -0
- package/catalog/skills/prompt-engineering/references/techniques.md +33 -0
- package/catalog/skills/prompt-engineering/scripts/validate-output.mjs +71 -0
- package/catalog/skills/readme-writing/SKILL.md +176 -0
- package/catalog/skills/readme-writing/examples/README.template.md +73 -0
- package/catalog/skills/readme-writing/examples/good-readme-example.md +95 -0
- package/catalog/skills/readme-writing/references/anatomy.md +107 -0
- package/catalog/skills/readme-writing/references/maintenance.md +68 -0
- package/catalog/skills/readme-writing/references/writing-style.md +79 -0
- package/catalog/skills/readme-writing/scripts/lint-readme.mjs +117 -0
- package/catalog/skills/refactoring-patterns/SKILL.md +178 -0
- package/catalog/skills/refactoring-patterns/examples/extract-function.before-after.js +57 -0
- package/catalog/skills/refactoring-patterns/examples/replace-conditional-with-polymorphism.before-after.ts +63 -0
- package/catalog/skills/refactoring-patterns/references/code-smells.md +92 -0
- package/catalog/skills/refactoring-patterns/references/refactoring-catalog.md +78 -0
- package/catalog/skills/refactoring-patterns/references/safe-workflow.md +74 -0
- package/catalog/skills/refactoring-patterns/scripts/detect-smells.mjs +137 -0
- package/catalog/skills/rest-api-design/SKILL.md +147 -0
- package/catalog/skills/rest-api-design/examples/error-envelope.json +26 -0
- package/catalog/skills/rest-api-design/examples/orders-api.openapi.yaml +106 -0
- package/catalog/skills/rest-api-design/references/error-handling.md +43 -0
- package/catalog/skills/rest-api-design/references/pagination.md +41 -0
- package/catalog/skills/rest-api-design/references/status-codes.md +32 -0
- package/catalog/skills/rest-api-design/references/versioning.md +31 -0
- package/catalog/skills/rest-api-design/scripts/check-envelope.mjs +62 -0
- package/catalog/skills/secrets-management/SKILL.md +134 -0
- package/catalog/skills/secrets-management/examples/env-example.md +41 -0
- package/catalog/skills/secrets-management/examples/rotation-runbook.md +50 -0
- package/catalog/skills/secrets-management/references/leak-response.md +47 -0
- package/catalog/skills/secrets-management/references/rotation.md +46 -0
- package/catalog/skills/secrets-management/references/secret-managers.md +52 -0
- package/catalog/skills/secrets-management/scripts/detect-hardcoded.mjs +96 -0
- package/catalog/skills/secure-auth/SKILL.md +135 -0
- package/catalog/skills/secure-auth/examples/auth-flow.md +60 -0
- package/catalog/skills/secure-auth/examples/jwt-verify.ts +89 -0
- package/catalog/skills/secure-auth/references/auth-attacks.md +76 -0
- package/catalog/skills/secure-auth/references/oauth2-oidc.md +65 -0
- package/catalog/skills/secure-auth/references/password-hashing.md +70 -0
- package/catalog/skills/secure-auth/references/sessions-vs-jwt.md +69 -0
- package/catalog/skills/secure-auth/scripts/check-password-policy.mjs +114 -0
- package/catalog/skills/sql-index-tuning/SKILL.md +171 -0
- package/catalog/skills/sql-index-tuning/examples/explain-walkthrough.sql +91 -0
- package/catalog/skills/sql-index-tuning/examples/index-ddl.sql +67 -0
- package/catalog/skills/sql-index-tuning/references/btree-internals.md +58 -0
- package/catalog/skills/sql-index-tuning/references/composite-and-covering.md +60 -0
- package/catalog/skills/sql-index-tuning/references/reading-explain.md +54 -0
- package/catalog/skills/sql-index-tuning/references/selectivity-and-cardinality.md +62 -0
- package/catalog/skills/sql-index-tuning/scripts/suggest-index.mjs +213 -0
- package/catalog/skills/test-plan-design/SKILL.md +154 -0
- package/catalog/skills/test-plan-design/examples/test-case-template.md +45 -0
- package/catalog/skills/test-plan-design/examples/test-plan.md +57 -0
- package/catalog/skills/test-plan-design/references/case-design.md +79 -0
- package/catalog/skills/test-plan-design/references/risk-prioritization.md +58 -0
- package/catalog/skills/test-plan-design/references/test-pyramid.md +58 -0
- package/catalog/skills/test-plan-design/scripts/coverage-gaps.mjs +109 -0
- package/free-tier.json +15 -0
- package/package.json +15 -4
- package/premium-index.json +1 -0
- package/src/config.js +31 -1
- package/src/license.js +143 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// suggest-index.mjs
|
|
3
|
+
// Propose a composite B-tree index from a query's WHERE / ORDER BY columns,
|
|
4
|
+
// honoring two rules from this skill:
|
|
5
|
+
// 1. equality-before-range -- equality columns first, then a single range column
|
|
6
|
+
// 2. leftmost-prefix -- the proposed order is what the index can seek on
|
|
7
|
+
// Equality columns are ordered most-selective-first when a "cardinality" hint is
|
|
8
|
+
// given (higher distinct-count => more selective => leads). ORDER BY columns not
|
|
9
|
+
// already in the key are appended so the sort can be served from the index.
|
|
10
|
+
//
|
|
11
|
+
// Zero dependencies. Node built-ins only.
|
|
12
|
+
//
|
|
13
|
+
// Usage:
|
|
14
|
+
// node suggest-index.mjs '<json-spec>' # propose from a literal JSON spec
|
|
15
|
+
// echo '<json-spec>' | node suggest-index.mjs # propose from stdin
|
|
16
|
+
// node suggest-index.mjs --selftest # run built-in assertions (exit 0/1)
|
|
17
|
+
//
|
|
18
|
+
// Spec shape:
|
|
19
|
+
// {
|
|
20
|
+
// "table": "orders",
|
|
21
|
+
// "predicates": [
|
|
22
|
+
// { "column": "tenant_id", "op": "eq", "cardinality": 5000 },
|
|
23
|
+
// { "column": "status", "op": "eq", "cardinality": 5 },
|
|
24
|
+
// { "column": "created_at", "op": "range" }
|
|
25
|
+
// ],
|
|
26
|
+
// "orderBy": [ { "column": "created_at", "direction": "desc" } ]
|
|
27
|
+
// }
|
|
28
|
+
// op is one of: "eq" (equality) | "range" (>, <, BETWEEN, LIKE 'x%').
|
|
29
|
+
|
|
30
|
+
const RANGE_OPS = new Set(['range', 'gt', 'lt', 'gte', 'lte', 'between', 'like_prefix']);
|
|
31
|
+
const EQ_OPS = new Set(['eq', 'equals', '=']);
|
|
32
|
+
|
|
33
|
+
export function suggestIndex(spec) {
|
|
34
|
+
const warnings = [];
|
|
35
|
+
if (!spec || typeof spec !== 'object') throw new Error('spec must be an object');
|
|
36
|
+
|
|
37
|
+
const table = typeof spec.table === 'string' && spec.table ? spec.table : 'my_table';
|
|
38
|
+
const predicates = Array.isArray(spec.predicates) ? spec.predicates : [];
|
|
39
|
+
const orderBy = Array.isArray(spec.orderBy) ? spec.orderBy : [];
|
|
40
|
+
|
|
41
|
+
const equality = predicates.filter((p) => EQ_OPS.has(p.op));
|
|
42
|
+
const ranges = predicates.filter((p) => RANGE_OPS.has(p.op));
|
|
43
|
+
const unknown = predicates.filter((p) => !EQ_OPS.has(p.op) && !RANGE_OPS.has(p.op));
|
|
44
|
+
for (const p of unknown) warnings.push(`predicate on "${p.column}" has unknown op "${p.op}"; ignored`);
|
|
45
|
+
|
|
46
|
+
// 1. Equality columns first, most-selective (highest cardinality) first.
|
|
47
|
+
// Stable: ties and missing cardinality preserve input order.
|
|
48
|
+
const eqSorted = equality
|
|
49
|
+
.map((p, i) => ({ p, i }))
|
|
50
|
+
.sort((a, b) => {
|
|
51
|
+
const ca = num(a.p.cardinality);
|
|
52
|
+
const cb = num(b.p.cardinality);
|
|
53
|
+
if (ca !== cb) return cb - ca; // higher cardinality leads
|
|
54
|
+
return a.i - b.i;
|
|
55
|
+
})
|
|
56
|
+
.map((x) => x.p.column);
|
|
57
|
+
|
|
58
|
+
// 2. At most ONE range column is useful in the seek; the rest become filters.
|
|
59
|
+
let rangeCol = null;
|
|
60
|
+
if (ranges.length > 0) {
|
|
61
|
+
rangeCol = ranges[0].column;
|
|
62
|
+
for (let i = 1; i < ranges.length; i++) {
|
|
63
|
+
warnings.push(
|
|
64
|
+
`multiple range predicates; only "${rangeCol}" can seek -- "${ranges[i].column}" becomes a filter`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3. ORDER BY columns not already present, appended so the sort is index-served.
|
|
70
|
+
const columns = [...eqSorted];
|
|
71
|
+
if (rangeCol) columns.push(rangeCol);
|
|
72
|
+
for (const o of orderBy) {
|
|
73
|
+
const col = typeof o === 'string' ? o : o && o.column;
|
|
74
|
+
if (col && !columns.includes(col)) columns.push(col);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (columns.length === 0) {
|
|
78
|
+
warnings.push('no usable equality/range/order-by columns found; nothing to index');
|
|
79
|
+
return { table, columns, ddl: null, warnings };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Carry ORDER BY direction onto matching trailing columns (DESC needs it stated).
|
|
83
|
+
const dir = new Map();
|
|
84
|
+
for (const o of orderBy) {
|
|
85
|
+
if (o && typeof o === 'object' && o.column && o.direction) {
|
|
86
|
+
dir.set(o.column, String(o.direction).toLowerCase() === 'desc' ? 'DESC' : 'ASC');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const rendered = columns.map((c) => (dir.get(c) === 'DESC' ? `${c} DESC` : c));
|
|
90
|
+
|
|
91
|
+
const indexName = `ix_${table}_${columns.join('_')}`.slice(0, 63);
|
|
92
|
+
const ddl = `CREATE INDEX CONCURRENTLY ${indexName}\n ON ${table} (${rendered.join(', ')});`;
|
|
93
|
+
|
|
94
|
+
if (rangeCol && eqSorted.length === 0) {
|
|
95
|
+
warnings.push('no equality columns -- a leading range column limits selectivity; verify it pays off');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { table, columns, ddl, warnings };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function num(v) {
|
|
102
|
+
const n = Number(v);
|
|
103
|
+
return Number.isFinite(n) ? n : -1; // missing cardinality sorts last (-1)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function run(json) {
|
|
107
|
+
let spec;
|
|
108
|
+
try {
|
|
109
|
+
spec = typeof json === 'string' ? JSON.parse(json) : json;
|
|
110
|
+
} catch {
|
|
111
|
+
console.error('✗ invalid JSON spec');
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
let result;
|
|
115
|
+
try {
|
|
116
|
+
result = suggestIndex(spec);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error(`✗ ${e.message}`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
console.log(`Proposed index columns: (${result.columns.join(', ')})`);
|
|
122
|
+
if (result.ddl) console.log(result.ddl);
|
|
123
|
+
for (const w of result.warnings) console.log(`! ${w}`);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function selftest() {
|
|
128
|
+
const cases = [
|
|
129
|
+
{
|
|
130
|
+
name: 'equality-before-range + selectivity ordering',
|
|
131
|
+
spec: {
|
|
132
|
+
table: 'orders',
|
|
133
|
+
predicates: [
|
|
134
|
+
{ column: 'status', op: 'eq', cardinality: 5 },
|
|
135
|
+
{ column: 'tenant_id', op: 'eq', cardinality: 5000 },
|
|
136
|
+
{ column: 'created_at', op: 'range' },
|
|
137
|
+
],
|
|
138
|
+
orderBy: [{ column: 'created_at', direction: 'desc' }],
|
|
139
|
+
},
|
|
140
|
+
// tenant_id (higher cardinality) leads, then status, then the range/sort col
|
|
141
|
+
expectColumns: ['tenant_id', 'status', 'created_at'],
|
|
142
|
+
expectDdlIncludes: 'created_at DESC',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'order-by column appended when not already in key',
|
|
146
|
+
spec: {
|
|
147
|
+
table: 'events',
|
|
148
|
+
predicates: [{ column: 'user_id', op: 'eq' }],
|
|
149
|
+
orderBy: [{ column: 'occurred_at', direction: 'asc' }],
|
|
150
|
+
},
|
|
151
|
+
expectColumns: ['user_id', 'occurred_at'],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'preserve input order when no cardinality hints',
|
|
155
|
+
spec: {
|
|
156
|
+
table: 'memberships',
|
|
157
|
+
predicates: [
|
|
158
|
+
{ column: 'tenant_id', op: 'eq' },
|
|
159
|
+
{ column: 'role', op: 'eq' },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
expectColumns: ['tenant_id', 'role'],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'second range predicate warns and becomes a filter',
|
|
166
|
+
spec: {
|
|
167
|
+
table: 'orders',
|
|
168
|
+
predicates: [
|
|
169
|
+
{ column: 'tenant_id', op: 'eq' },
|
|
170
|
+
{ column: 'created_at', op: 'range' },
|
|
171
|
+
{ column: 'total_cents', op: 'range' },
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
expectColumns: ['tenant_id', 'created_at'],
|
|
175
|
+
expectWarningIncludes: 'total_cents',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'empty predicates yields no index',
|
|
179
|
+
spec: { table: 'orders', predicates: [], orderBy: [] },
|
|
180
|
+
expectColumns: [],
|
|
181
|
+
expectNullDdl: true,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
let allOk = true;
|
|
186
|
+
for (const c of cases) {
|
|
187
|
+
const r = suggestIndex(c.spec);
|
|
188
|
+
let ok = arrEq(r.columns, c.expectColumns);
|
|
189
|
+
if (ok && c.expectDdlIncludes) ok = !!r.ddl && r.ddl.includes(c.expectDdlIncludes);
|
|
190
|
+
if (ok && c.expectWarningIncludes) ok = r.warnings.some((w) => w.includes(c.expectWarningIncludes));
|
|
191
|
+
if (ok && c.expectNullDdl) ok = r.ddl === null;
|
|
192
|
+
allOk = allOk && ok;
|
|
193
|
+
console.log(
|
|
194
|
+
`${ok ? '✓' : '✗'} ${c.name}: got (${r.columns.join(', ')})` +
|
|
195
|
+
(ok ? '' : ` -- expected (${c.expectColumns.join(', ')})`),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
console.log(allOk ? 'All selftests passed.' : 'Selftest FAILURES present.');
|
|
199
|
+
process.exit(allOk ? 0 : 1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function arrEq(a, b) {
|
|
203
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => v === b[i]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const arg = process.argv[2];
|
|
207
|
+
if (arg === '--selftest') selftest();
|
|
208
|
+
else if (arg) process.exit(run(arg) ? 0 : 1);
|
|
209
|
+
else {
|
|
210
|
+
let buf = '';
|
|
211
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
212
|
+
process.stdin.on('end', () => process.exit(run(buf) ? 0 : 1));
|
|
213
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-plan-design
|
|
3
|
+
description: How to design a test plan — scope and risk-based prioritization, the test pyramid, case-design techniques (equivalence partitioning, boundary values, decision tables), entry/exit criteria, coverage and requirement traceability, test data and environments. Deep reference with a runnable coverage-gap check.
|
|
4
|
+
type: skill
|
|
5
|
+
version: 2.0.0
|
|
6
|
+
updated: 2026-06-29
|
|
7
|
+
---
|
|
8
|
+
# Test Plan Design
|
|
9
|
+
|
|
10
|
+
A test plan is a **budget**: finite effort spent where it prevents the most expensive failures. The goal
|
|
11
|
+
is never "test everything" — that is impossible and economically illiterate. The goal is to make a
|
|
12
|
+
defensible decision about *what* to test, *at which layer*, *with which cases*, and *when you are done*.
|
|
13
|
+
This skill is the deep reference for that decision. Heavy detail lives in `references/`; copy-paste
|
|
14
|
+
material in `examples/`; a runnable traceability check in `scripts/`.
|
|
15
|
+
|
|
16
|
+
## Mental model
|
|
17
|
+
|
|
18
|
+
You are allocating scarce attention across a feature. Four questions drive every plan:
|
|
19
|
+
|
|
20
|
+
| Question | Answer comes from |
|
|
21
|
+
|---|---|
|
|
22
|
+
| What matters most? | risk = impact × likelihood (scope & prioritization) |
|
|
23
|
+
| At which layer do I test it? | the test pyramid (unit / integration / E2E) |
|
|
24
|
+
| Which concrete cases? | case-design techniques (partitions, boundaries, decision tables) |
|
|
25
|
+
| When am I done? | exit criteria + coverage traced to requirements |
|
|
26
|
+
|
|
27
|
+
Skip any one of these and the plan degrades: no risk ranking and you over-test trivia; no case design and
|
|
28
|
+
you write five tests that all exercise the same path; no traceability and you ship a requirement nobody
|
|
29
|
+
verified.
|
|
30
|
+
|
|
31
|
+
## 1. Scope and risk-based prioritization
|
|
32
|
+
|
|
33
|
+
Start by listing what the feature must do, then rank each item by **risk = impact × likelihood**.
|
|
34
|
+
|
|
35
|
+
- **Impact**: what does failure cost? Money movement, data loss, security, and legal/compliance are
|
|
36
|
+
top-tier; cosmetic glitches are bottom-tier.
|
|
37
|
+
- **Likelihood**: how probable is a defect? New code, complex logic, many integrations, and unclear
|
|
38
|
+
requirements raise it; stable, simple, well-understood code lowers it.
|
|
39
|
+
|
|
40
|
+
Spend the most rigor at the top of the ranked list and explicitly decide to *under*-test the bottom.
|
|
41
|
+
Writing down "we are not testing X because it is low-risk" is a feature of a good plan, not a gap. The
|
|
42
|
+
full scoring rubric and a worked heat-map live in [references/risk-prioritization.md](references/risk-prioritization.md).
|
|
43
|
+
|
|
44
|
+
## 2. The test pyramid — choosing a layer
|
|
45
|
+
|
|
46
|
+
Each behavior should be tested at the **lowest layer that can meaningfully verify it**. Lower layers are
|
|
47
|
+
faster, cheaper, and more precise about *where* a defect is.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
/\ E2E few — full user journeys, real-ish stack, slow & flaky
|
|
51
|
+
/ \ Integration some — module + its collaborators (DB, gateway, queue)
|
|
52
|
+
/____\ Unit many — one function/class, no I/O, milliseconds
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- **Unit**: pure logic — pricing math, validation, state machines. No network, no DB. Run in ms.
|
|
56
|
+
- **Integration**: a module talking to a real (or faithfully faked) collaborator — a repository hitting a
|
|
57
|
+
test DB, a client calling a mocked gateway with success/decline/timeout responses.
|
|
58
|
+
- **E2E**: a thin layer covering only critical journeys end to end (e.g. "add to cart → pay → confirm").
|
|
59
|
+
|
|
60
|
+
The classic anti-pattern is the **inverted pyramid** (an "ice-cream cone"): many slow E2E tests and few
|
|
61
|
+
units. It is slow, flaky, and tells you *that* something broke but not *where*. Layer-selection heuristics
|
|
62
|
+
and the integration/contract-test distinction are in [references/test-pyramid.md](references/test-pyramid.md).
|
|
63
|
+
|
|
64
|
+
## 3. Case-design techniques
|
|
65
|
+
|
|
66
|
+
For each unit under test, do not invent cases ad hoc — derive them. Three techniques cover the vast
|
|
67
|
+
majority of input-space reasoning:
|
|
68
|
+
|
|
69
|
+
- **Equivalence partitioning**: split the input domain into classes that should behave identically, then
|
|
70
|
+
test *one* representative per class. If ages 18–65 are all "standard", you need one value from that
|
|
71
|
+
band, not forty.
|
|
72
|
+
- **Boundary value analysis**: bugs cluster at edges. For a valid range `[18, 65]`, test `17, 18, 19` and
|
|
73
|
+
`64, 65, 66` — just-below, on, and just-above each boundary. Off-by-one errors live here.
|
|
74
|
+
- **Decision tables**: when output depends on a combination of conditions, enumerate the combinations so
|
|
75
|
+
no rule is missed.
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
Discount rules (decision table)
|
|
79
|
+
Member? | Cart ≥ $100 | Coupon valid | → Discount
|
|
80
|
+
no | no | no | 0%
|
|
81
|
+
no | yes | no | 5%
|
|
82
|
+
yes | no | no | 10%
|
|
83
|
+
yes | yes | yes | 25% ← interaction case, easy to miss
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A worked example combining all three on one function is in [references/case-design.md](references/case-design.md),
|
|
87
|
+
and a ready-to-fill case template is in `examples/test-case-template.md`.
|
|
88
|
+
|
|
89
|
+
## 4. Entry and exit criteria
|
|
90
|
+
|
|
91
|
+
Make "ready to test" and "ready to ship" explicit so the plan ends on data, not vibes.
|
|
92
|
+
|
|
93
|
+
- **Entry criteria** (can we start?): code merged to the test branch, build green, test environment and
|
|
94
|
+
seed data available, dependencies/stubs deployed.
|
|
95
|
+
- **Exit criteria** (can we stop?): all planned cases executed; all CRITICAL/HIGH-risk requirements have a
|
|
96
|
+
passing test; coverage target met (e.g. 80% line on changed code); zero open Sev-1/Sev-2 defects; known
|
|
97
|
+
issues triaged and accepted by the owner.
|
|
98
|
+
|
|
99
|
+
Exit criteria are the contract that prevents both premature shipping and endless gold-plating.
|
|
100
|
+
|
|
101
|
+
## 5. Coverage and traceability to requirements
|
|
102
|
+
|
|
103
|
+
Coverage has two distinct meanings — track both:
|
|
104
|
+
|
|
105
|
+
1. **Code coverage** (line/branch): a *necessary* signal, not a *sufficient* one. 100% line coverage with
|
|
106
|
+
zero assertions proves nothing.
|
|
107
|
+
2. **Requirement coverage**: every requirement maps to at least one test that verifies it. This is the one
|
|
108
|
+
that catches "we built it but nobody tested it."
|
|
109
|
+
|
|
110
|
+
Maintain a traceability matrix (requirement ↔ test case). The runnable check
|
|
111
|
+
`scripts/coverage-gaps.mjs` takes your requirements and test cases as JSON and lists any requirement with
|
|
112
|
+
**no** covering test — run it in CI so an untested requirement fails the build. See
|
|
113
|
+
`examples/test-plan.md` for a filled matrix.
|
|
114
|
+
|
|
115
|
+
## 6. Test data and environments
|
|
116
|
+
|
|
117
|
+
- **Data**: prefer small, purpose-built fixtures over a copy of production. Cover the equivalence classes
|
|
118
|
+
and boundaries from §3. Never use real PII in test data — synthesize it. Make data setup deterministic
|
|
119
|
+
and self-cleaning so tests are independent and order-free.
|
|
120
|
+
- **Environments**: keep a dedicated, reproducible test environment (containers/IaC) so "works on my
|
|
121
|
+
machine" is not a defense. Pin external dependencies via fakes/contract tests; do not let a flaky
|
|
122
|
+
third-party sandbox gate your pipeline.
|
|
123
|
+
|
|
124
|
+
## Common pitfalls (failure modes)
|
|
125
|
+
|
|
126
|
+
- **Testing everything equally** — no risk ranking, so trivial getters get the same attention as the
|
|
127
|
+
payment path. Rank first.
|
|
128
|
+
- **Inverted pyramid** — leaning on slow, flaky E2E for logic that a unit test would pin precisely.
|
|
129
|
+
- **Coverage theater** — chasing a line-coverage number with assertion-free tests; green bar, no safety.
|
|
130
|
+
- **Redundant cases** — five tests in the same equivalence class, zero at the boundary where the bug is.
|
|
131
|
+
- **No traceability** — a requirement ships with no test because nothing connected the two.
|
|
132
|
+
- **Non-deterministic data/time** — tests depend on "today" or shared mutable rows and fail intermittently.
|
|
133
|
+
- **Vague exit criteria** — "looks good" instead of measurable gates, so testing ends by exhaustion.
|
|
134
|
+
|
|
135
|
+
## When NOT to use / trade-offs
|
|
136
|
+
|
|
137
|
+
A heavyweight, document-driven test plan is overkill for a one-line bug fix or a throwaway spike — there,
|
|
138
|
+
a couple of targeted tests and a green CI run are the proportionate response. Formal test plans earn their
|
|
139
|
+
cost on high-risk, multi-team, regulated, or long-lived features. Likewise, exhaustive case enumeration is
|
|
140
|
+
wasteful on low-risk surfaces; reserve decision tables and full boundary analysis for logic where a defect
|
|
141
|
+
is expensive. The plan should be *proportional to risk* — a plan that costs more than the failures it
|
|
142
|
+
prevents is itself a failure. For ongoing change to an existing suite (what to re-run after a change, how
|
|
143
|
+
to fight flakiness over time), that is a different discipline — see the pairing below.
|
|
144
|
+
|
|
145
|
+
## Files in this package
|
|
146
|
+
|
|
147
|
+
- `references/risk-prioritization.md` — risk scoring rubric, impact/likelihood matrix, worked heat-map
|
|
148
|
+
- `references/test-pyramid.md` — layer-selection heuristics, integration vs contract tests, anti-patterns
|
|
149
|
+
- `references/case-design.md` — equivalence partitioning, boundary analysis, decision tables, worked example
|
|
150
|
+
- `examples/test-plan.md` — a complete, filled-in test plan with a traceability matrix
|
|
151
|
+
- `examples/test-case-template.md` — a reusable single-test-case template
|
|
152
|
+
- `scripts/coverage-gaps.mjs` — runnable check: lists requirements with no covering test (`--selftest`)
|
|
153
|
+
|
|
154
|
+
Pairs with the `qa-strategist` agent, the `test-author` agent, and the `regression-strategy` skill.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Test Case Template
|
|
2
|
+
|
|
3
|
+
A reusable shape for a single test case. Keep one per case; the `id` is what the traceability matrix and
|
|
4
|
+
`scripts/coverage-gaps.mjs` reference.
|
|
5
|
+
|
|
6
|
+
```yaml
|
|
7
|
+
id: coupon_min_5000
|
|
8
|
+
requirement: R3 # links back to a requirement in the test plan
|
|
9
|
+
title: Coupon applies exactly at the $50 minimum
|
|
10
|
+
layer: unit # unit | integration | e2e
|
|
11
|
+
technique: boundary # partition | boundary | decision-table | exploratory
|
|
12
|
+
priority: high # from risk ranking
|
|
13
|
+
|
|
14
|
+
preconditions:
|
|
15
|
+
- a valid, non-expired coupon "SAVE10" exists
|
|
16
|
+
|
|
17
|
+
# Arrange / Act / Assert
|
|
18
|
+
arrange:
|
|
19
|
+
cartTotal: 50.00 # the boundary value (min is inclusive)
|
|
20
|
+
coupon: "SAVE10"
|
|
21
|
+
act:
|
|
22
|
+
call: validateCoupon(coupon, cartTotal)
|
|
23
|
+
assert:
|
|
24
|
+
result: applied
|
|
25
|
+
discountPercent: 10
|
|
26
|
+
|
|
27
|
+
notes: paired with coupon_min_4999 (just below) and coupon_min_5001 (just above)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Filled boundary trio
|
|
31
|
+
|
|
32
|
+
The same requirement R3 needs three cases to pin the `$50` edge — copy the block above per row:
|
|
33
|
+
|
|
34
|
+
| id | cartTotal | expected |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `coupon_min_4999` | `49.99` | `min_not_met` |
|
|
37
|
+
| `coupon_min_5000` | `50.00` | `applied` |
|
|
38
|
+
| `coupon_min_5001` | `50.01` | `applied` |
|
|
39
|
+
|
|
40
|
+
## Why this shape
|
|
41
|
+
|
|
42
|
+
- `id` + `requirement` make traceability machine-checkable.
|
|
43
|
+
- `technique` documents *why* this case exists, so reviewers can spot a partition or boundary that is
|
|
44
|
+
missing.
|
|
45
|
+
- Arrange/Act/Assert keeps each case focused on one behavior with one reason to fail.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Test Plan — Checkout v2
|
|
2
|
+
|
|
3
|
+
A filled-in example you can copy and adapt. Keep it short; a plan nobody reads is wasted effort.
|
|
4
|
+
|
|
5
|
+
## 1. Scope
|
|
6
|
+
|
|
7
|
+
In scope: cart pricing, tax, coupon validation, payment capture/refund, order confirmation.
|
|
8
|
+
Out of scope: marketing emails (covered by the comms team), the static footer.
|
|
9
|
+
|
|
10
|
+
## 2. Risk ranking
|
|
11
|
+
|
|
12
|
+
| Area | Impact | Likelihood | Risk | Primary layer |
|
|
13
|
+
|---|---|---|---|---|
|
|
14
|
+
| Payment capture/refund | 5 | 4 | 20 | integration + E2E |
|
|
15
|
+
| Price & tax math | 5 | 3 | 15 | unit |
|
|
16
|
+
| Coupon validation | 3 | 4 | 12 | unit (decision table) |
|
|
17
|
+
| Inventory decrement | 4 | 3 | 12 | integration |
|
|
18
|
+
| Confirmation email | 2 | 2 | 4 | integration (fake mailer) |
|
|
19
|
+
| Footer year | 1 | 1 | 1 | not tested (accepted by PO) |
|
|
20
|
+
|
|
21
|
+
## 3. Entry criteria
|
|
22
|
+
|
|
23
|
+
- Feature branch merged to `test`, CI build green.
|
|
24
|
+
- Test environment provisioned; seed catalog + test cards loaded.
|
|
25
|
+
- Payment gateway sandbox reachable OR contract fakes deployed.
|
|
26
|
+
|
|
27
|
+
## 4. Exit criteria
|
|
28
|
+
|
|
29
|
+
- All planned cases executed.
|
|
30
|
+
- Every CRITICAL/HIGH requirement has ≥1 passing test (see matrix).
|
|
31
|
+
- Line coverage ≥ 80% on changed files.
|
|
32
|
+
- Zero open Sev-1/Sev-2 defects; remaining issues triaged and accepted.
|
|
33
|
+
|
|
34
|
+
## 5. Requirement → test traceability matrix
|
|
35
|
+
|
|
36
|
+
This is the data `scripts/coverage-gaps.mjs` consumes (as JSON) to fail the build on any uncovered
|
|
37
|
+
requirement.
|
|
38
|
+
|
|
39
|
+
| Req ID | Requirement | Covering test(s) | Layer |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| R1 | Totals include tax for the buyer's region | `tax_us`, `tax_eu_boundary` | unit |
|
|
42
|
+
| R2 | Expired coupons are rejected | `coupon_expired` | unit |
|
|
43
|
+
| R3 | Coupon needs cart ≥ $50 | `coupon_min_4999`, `coupon_min_5000` | unit |
|
|
44
|
+
| R4 | Declined card shows a retry message | `pay_declined` | integration |
|
|
45
|
+
| R5 | Gateway timeout does not double-charge | `pay_timeout_idempotent` | integration |
|
|
46
|
+
| R6 | Successful pay → confirmation screen | `e2e_happy_checkout` | E2E |
|
|
47
|
+
|
|
48
|
+
## 6. Test data & environment
|
|
49
|
+
|
|
50
|
+
- Cards: `4242…` (success), `4000…0002` (decline), a sandbox timeout trigger.
|
|
51
|
+
- Cart fixtures hit each coupon partition (below-min, at-min, above-min) and tax boundary.
|
|
52
|
+
- Data is seeded per-run and torn down after; no shared mutable rows.
|
|
53
|
+
|
|
54
|
+
## 7. Notes
|
|
55
|
+
|
|
56
|
+
- Confirmation email asserted via a fake mailer, not the real provider (avoids flaky external dep).
|
|
57
|
+
- Footer year explicitly out of scope — recorded so it reads as a decision, not a miss.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Case-Design Techniques
|
|
2
|
+
|
|
3
|
+
Do not invent test cases ad hoc — derive them. These three techniques cover most input-space reasoning
|
|
4
|
+
and stop you from writing many tests that all hit the same path while missing the one that breaks.
|
|
5
|
+
|
|
6
|
+
## 1. Equivalence partitioning
|
|
7
|
+
|
|
8
|
+
Split the input domain into classes whose members *should* behave identically, then test **one
|
|
9
|
+
representative per class**. Testing forty values from the same class adds cost, not confidence.
|
|
10
|
+
|
|
11
|
+
Example — a shipping-cost function by weight (kg):
|
|
12
|
+
|
|
13
|
+
| Partition | Range | Representative |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| invalid (negative) | `w < 0` | `-1` |
|
|
16
|
+
| light | `0 ≤ w ≤ 5` | `2` |
|
|
17
|
+
| medium | `5 < w ≤ 20` | `12` |
|
|
18
|
+
| heavy | `20 < w ≤ 50` | `35` |
|
|
19
|
+
| over-limit (rejected) | `w > 50` | `60` |
|
|
20
|
+
|
|
21
|
+
Five tests cover the whole domain's behavior classes.
|
|
22
|
+
|
|
23
|
+
## 2. Boundary value analysis
|
|
24
|
+
|
|
25
|
+
Defects cluster at edges (off-by-one, `<` vs `<=`). For each boundary test **just-below, on, and
|
|
26
|
+
just-above**. For the light/medium boundary at `5`:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
w = 4.99 → light
|
|
30
|
+
w = 5.00 → light (boundary is inclusive)
|
|
31
|
+
w = 5.01 → medium
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Repeat at every partition edge (`0`, `5`, `20`, `50`). Boundaries are where equivalence partitioning and
|
|
35
|
+
real bugs intersect, so this is the highest-yield technique per test.
|
|
36
|
+
|
|
37
|
+
## 3. Decision tables
|
|
38
|
+
|
|
39
|
+
When output depends on a **combination** of conditions, enumerate the combinations so no rule — and no
|
|
40
|
+
interaction between rules — is missed.
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
Loan approval
|
|
44
|
+
Credit ≥ 700 | Income ≥ 50k | Existing debt | → Decision
|
|
45
|
+
no | no | - | reject
|
|
46
|
+
no | yes | yes | reject
|
|
47
|
+
yes | no | no | review
|
|
48
|
+
yes | yes | no | approve
|
|
49
|
+
yes | yes | yes | review ← interaction, easy to miss
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For N independent booleans there are 2^N rows; collapse with "don't care" (`-`) entries where a condition
|
|
53
|
+
cannot change the outcome, and keep the interaction rows that ad-hoc testing skips.
|
|
54
|
+
|
|
55
|
+
## Worked example — combining all three
|
|
56
|
+
|
|
57
|
+
Function: `validateCoupon(code, cartTotal)` returns `applied | min_not_met | expired | invalid`.
|
|
58
|
+
|
|
59
|
+
1. **Partition** `cartTotal`: invalid (`< 0`), below-min (`0..49.99`), at-or-above-min (`>= 50`).
|
|
60
|
+
2. **Boundaries** on the `50` minimum: `49.99`, `50.00`, `50.01`.
|
|
61
|
+
3. **Decision table** over `code` state × threshold:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
code state | cartTotal ≥ 50 | → result
|
|
65
|
+
valid | yes | applied
|
|
66
|
+
valid | no | min_not_met
|
|
67
|
+
expired | yes | expired (expiry checked before threshold)
|
|
68
|
+
unknown | - | invalid
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Result: ~8 deliberate cases that cover the domain, the edges, and the rule interactions — versus a dozen
|
|
72
|
+
random inputs that would likely all land in the same partition.
|
|
73
|
+
|
|
74
|
+
## Pitfalls
|
|
75
|
+
|
|
76
|
+
- **All cases in one partition** — many "valid" inputs, none invalid, none at a boundary.
|
|
77
|
+
- **Skipping interaction rows** — testing each condition alone but never the combination that flips the
|
|
78
|
+
result.
|
|
79
|
+
- **Boundary blindness** — testing `2` and `35` but never `5` or `50`, exactly where off-by-one hides.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Risk-Based Prioritization
|
|
2
|
+
|
|
3
|
+
You cannot test everything, so test what matters most first. Rank each feature area by
|
|
4
|
+
**risk = impact × likelihood** and let that ranking drive where rigor goes.
|
|
5
|
+
|
|
6
|
+
## Scoring rubric
|
|
7
|
+
|
|
8
|
+
Score each axis 1–5, multiply, and sort descending.
|
|
9
|
+
|
|
10
|
+
### Impact (cost of failure)
|
|
11
|
+
|
|
12
|
+
| Score | Meaning | Examples |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| 5 | Catastrophic | money moved wrongly, data loss, security breach, legal/compliance breach |
|
|
15
|
+
| 4 | Severe | core flow blocked for all users, corrupted records |
|
|
16
|
+
| 3 | Moderate | feature broken for a segment, recoverable with effort |
|
|
17
|
+
| 2 | Minor | inconvenient, easy workaround |
|
|
18
|
+
| 1 | Cosmetic | visual glitch, no functional effect |
|
|
19
|
+
|
|
20
|
+
### Likelihood (probability of a defect)
|
|
21
|
+
|
|
22
|
+
| Score | Meaning | Signals that raise it |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| 5 | Very likely | brand-new code, complex logic, vague requirements, many integrations |
|
|
25
|
+
| 4 | Likely | recently changed, moderate complexity |
|
|
26
|
+
| 3 | Possible | some history, average complexity |
|
|
27
|
+
| 2 | Unlikely | stable code, simple logic, well understood |
|
|
28
|
+
| 1 | Rare | trivial, unchanged for a long time, heavily exercised in production |
|
|
29
|
+
|
|
30
|
+
## Worked heat-map (checkout feature)
|
|
31
|
+
|
|
32
|
+
| Area | Impact | Likelihood | Risk | Rigor |
|
|
33
|
+
|---|---|---|---|---|
|
|
34
|
+
| Payment capture / refund | 5 | 4 | 20 | exhaustive: unit + integration (success/decline/timeout) + E2E |
|
|
35
|
+
| Price & tax calculation | 5 | 3 | 15 | unit-heavy: partitions + full boundary analysis + decision table |
|
|
36
|
+
| Inventory decrement | 4 | 3 | 12 | integration with concurrency case |
|
|
37
|
+
| Coupon validation | 3 | 4 | 12 | decision table over rule combinations |
|
|
38
|
+
| Order confirmation email | 2 | 2 | 4 | one integration test with a fake mailer |
|
|
39
|
+
| Footer copyright year | 1 | 1 | 1 | not tested — documented as accepted |
|
|
40
|
+
|
|
41
|
+
The bottom rows matter as much as the top: writing "footer year — not tested, low risk" is an explicit,
|
|
42
|
+
defensible decision, not an oversight.
|
|
43
|
+
|
|
44
|
+
## Turning risk into a budget
|
|
45
|
+
|
|
46
|
+
1. Sort areas by risk score.
|
|
47
|
+
2. Allocate effort top-down until the budget (time, people) runs out.
|
|
48
|
+
3. For anything below the cut line, record the decision to under-test and who accepted it.
|
|
49
|
+
4. Re-rank when the system changes — risk is not static; a quiet module becomes high-risk the moment it
|
|
50
|
+
is rewritten.
|
|
51
|
+
|
|
52
|
+
## Anti-patterns
|
|
53
|
+
|
|
54
|
+
- **Flat prioritization** — every area gets equal attention, so the payment path and a tooltip compete
|
|
55
|
+
for the same hour.
|
|
56
|
+
- **Gut-feel only** — skipping the score and "just knowing" what is risky; the matrix forces the
|
|
57
|
+
conversation and creates an auditable record.
|
|
58
|
+
- **Set-and-forget** — never re-scoring after a refactor moves the risk.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# The Test Pyramid — Choosing a Layer
|
|
2
|
+
|
|
3
|
+
Test each behavior at the **lowest layer that can meaningfully verify it**. Lower is faster, cheaper, and
|
|
4
|
+
more precise about *where* a defect lives.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
/\ E2E few — full journeys, real-ish stack, slow, flakiest
|
|
8
|
+
/ \ Integration some — module + collaborators (DB, gateway, queue)
|
|
9
|
+
/____\ Unit many — one function/class, no I/O, milliseconds
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## The three layers
|
|
13
|
+
|
|
14
|
+
### Unit
|
|
15
|
+
- Scope: a single function/class with no I/O.
|
|
16
|
+
- Verifies: pure logic — pricing, validation, parsing, state machines.
|
|
17
|
+
- Speed: milliseconds; run thousands on every commit.
|
|
18
|
+
- Rule of thumb: if it needs a network, a DB, or the clock, it is not a unit test (inject those).
|
|
19
|
+
|
|
20
|
+
### Integration
|
|
21
|
+
- Scope: a module talking to a real or faithfully faked collaborator.
|
|
22
|
+
- Verifies: wiring — does the repository persist correctly against a test DB? Does the payment client
|
|
23
|
+
handle success, decline, and timeout from a mocked gateway?
|
|
24
|
+
- Speed: tens to hundreds of ms; run on every push.
|
|
25
|
+
|
|
26
|
+
### End-to-end (E2E)
|
|
27
|
+
- Scope: the whole system through its real entry point (HTTP, UI).
|
|
28
|
+
- Verifies: critical user journeys only — login, checkout, the one flow whose failure is unacceptable.
|
|
29
|
+
- Speed: seconds; the slowest and flakiest. Keep this layer thin.
|
|
30
|
+
|
|
31
|
+
## Layer-selection heuristics
|
|
32
|
+
|
|
33
|
+
| If you are verifying... | Prefer |
|
|
34
|
+
|---|---|
|
|
35
|
+
| a calculation or branch in logic | unit |
|
|
36
|
+
| that two components talk correctly | integration |
|
|
37
|
+
| that a third-party contract holds | contract test (a focused integration test) |
|
|
38
|
+
| that a user can complete a journey | E2E (one happy path + key failure) |
|
|
39
|
+
|
|
40
|
+
Push tests down whenever possible. A bug findable by a unit test should not be left for E2E to catch
|
|
41
|
+
slowly and vaguely.
|
|
42
|
+
|
|
43
|
+
## Integration vs contract tests
|
|
44
|
+
|
|
45
|
+
A **contract test** pins the shape of the boundary with an external service (request/response schema) so
|
|
46
|
+
you catch drift without depending on the live service in CI. Use it instead of hitting a flaky vendor
|
|
47
|
+
sandbox on every build; run the real integration on a slower cadence.
|
|
48
|
+
|
|
49
|
+
## Anti-patterns
|
|
50
|
+
|
|
51
|
+
- **Ice-cream cone (inverted pyramid)** — many E2E, few units. Slow, flaky, and tells you *that*
|
|
52
|
+
something broke but not *where*.
|
|
53
|
+
- **Testing the framework** — asserting that the ORM saves a row or the HTTP library sends a header; test
|
|
54
|
+
*your* logic, not the dependency's.
|
|
55
|
+
- **Integration tests that are secretly E2E** — spinning up the entire stack to check one function;
|
|
56
|
+
collapse it to a unit.
|
|
57
|
+
- **No E2E at all** — every layer green, yet the assembled product is broken because nothing exercised
|
|
58
|
+
the seams.
|