ruvnet-kb-first 5.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/LICENSE +21 -0
- package/README.md +674 -0
- package/SKILL.md +740 -0
- package/bin/kb-first.js +123 -0
- package/install/init-project.sh +435 -0
- package/install/install-global.sh +257 -0
- package/install/kb-first-autodetect.sh +108 -0
- package/install/kb-first-command.md +80 -0
- package/install/kb-first-skill.md +262 -0
- package/package.json +87 -0
- package/phases/00-assessment.md +529 -0
- package/phases/01-storage.md +194 -0
- package/phases/01.5-hooks-setup.md +521 -0
- package/phases/02-kb-creation.md +413 -0
- package/phases/03-persistence.md +125 -0
- package/phases/04-visualization.md +170 -0
- package/phases/05-integration.md +114 -0
- package/phases/06-scaffold.md +130 -0
- package/phases/07-build.md +493 -0
- package/phases/08-verification.md +597 -0
- package/phases/09-security.md +512 -0
- package/phases/10-documentation.md +613 -0
- package/phases/11-deployment.md +670 -0
- package/phases/testing.md +713 -0
- package/scripts/1.5-hooks-verify.sh +252 -0
- package/scripts/8.1-code-scan.sh +58 -0
- package/scripts/8.2-import-check.sh +42 -0
- package/scripts/8.3-source-returns.sh +52 -0
- package/scripts/8.4-startup-verify.sh +65 -0
- package/scripts/8.5-fallback-check.sh +63 -0
- package/scripts/8.6-attribution.sh +56 -0
- package/scripts/8.7-confidence.sh +56 -0
- package/scripts/8.8-gap-logging.sh +70 -0
- package/scripts/9-security-audit.sh +202 -0
- package/scripts/init-project.sh +395 -0
- package/scripts/verify-enforcement.sh +167 -0
- package/src/commands/hooks.js +361 -0
- package/src/commands/init.js +315 -0
- package/src/commands/phase.js +372 -0
- package/src/commands/score.js +380 -0
- package/src/commands/status.js +193 -0
- package/src/commands/verify.js +286 -0
- package/src/index.js +56 -0
- package/src/mcp-server.js +412 -0
- package/templates/attention-router.ts +534 -0
- package/templates/code-analysis.ts +683 -0
- package/templates/federated-kb-learner.ts +649 -0
- package/templates/gnn-engine.ts +1091 -0
- package/templates/intentions.md +277 -0
- package/templates/kb-client.ts +905 -0
- package/templates/schema.sql +303 -0
- package/templates/sona-config.ts +312 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
Updated: 2026-01-01 20:10:00 EST | Version 1.0.0
|
|
2
|
+
Created: 2026-01-01 20:10:00 EST
|
|
3
|
+
|
|
4
|
+
# Testing Strategy for KB-First Applications
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
|
|
8
|
+
Comprehensive testing ensures your KB-First application:
|
|
9
|
+
- Returns accurate, source-backed responses
|
|
10
|
+
- Handles edge cases gracefully
|
|
11
|
+
- Maintains KB integrity over time
|
|
12
|
+
- Meets performance requirements
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Testing Pyramid for KB-First
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─────────────────┐
|
|
20
|
+
│ E2E Tests │ ← Few, slow, high confidence
|
|
21
|
+
│ (10-20) │
|
|
22
|
+
├─────────────────┤
|
|
23
|
+
│ Integration │ ← Medium count
|
|
24
|
+
│ Tests (50-100) │
|
|
25
|
+
├─────────────────┤
|
|
26
|
+
│ KB Quality │ ← KB-specific layer
|
|
27
|
+
│ Tests (100+) │
|
|
28
|
+
├─────────────────┤
|
|
29
|
+
│ Unit Tests │ ← Many, fast, isolated
|
|
30
|
+
│ (200-500) │
|
|
31
|
+
└─────────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Test Categories
|
|
37
|
+
|
|
38
|
+
| Category | Purpose | When to Run | Target |
|
|
39
|
+
|----------|---------|-------------|--------|
|
|
40
|
+
| Unit | Individual functions | Every commit | 200+ tests |
|
|
41
|
+
| KB Quality | Search accuracy, coverage | Daily | 100+ queries |
|
|
42
|
+
| Integration | API + KB + Domain | Every PR | 50+ scenarios |
|
|
43
|
+
| E2E | Full user flows | Before deploy | 10-20 journeys |
|
|
44
|
+
| Performance | Response times | Weekly | <500ms p95 |
|
|
45
|
+
| Regression | Prevent regressions | Every release | All critical paths |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 1. Unit Testing
|
|
50
|
+
|
|
51
|
+
### What to Test
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// src/domain/__tests__/retirement.test.ts
|
|
55
|
+
|
|
56
|
+
import { calculateWithdrawal } from '../retirement';
|
|
57
|
+
import { kb } from '../../kb';
|
|
58
|
+
|
|
59
|
+
// Mock KB for unit tests
|
|
60
|
+
jest.mock('../../kb');
|
|
61
|
+
|
|
62
|
+
describe('calculateWithdrawal', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
(kb.search as jest.Mock).mockResolvedValue({
|
|
65
|
+
value: 0.04,
|
|
66
|
+
sources: [{
|
|
67
|
+
nodeId: 'wr-001',
|
|
68
|
+
expert: 'William Bengen',
|
|
69
|
+
confidence: 0.95,
|
|
70
|
+
url: 'https://example.com/bengen-1994'
|
|
71
|
+
}]
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('returns withdrawal amount with sources', async () => {
|
|
76
|
+
const result = await calculateWithdrawal(1000000);
|
|
77
|
+
|
|
78
|
+
expect(result.data.amount).toBe(40000);
|
|
79
|
+
expect(result.kbSources).toHaveLength(1);
|
|
80
|
+
expect(result.kbSources[0].expert).toBe('William Bengen');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('throws when KB unavailable', async () => {
|
|
84
|
+
(kb.search as jest.Mock).mockRejectedValue(new Error('KB unavailable'));
|
|
85
|
+
|
|
86
|
+
await expect(calculateWithdrawal(1000000)).rejects.toThrow('KB unavailable');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('never returns hardcoded values', async () => {
|
|
90
|
+
// Even if KB returns unexpected format, don't fall back to hardcoded
|
|
91
|
+
(kb.search as jest.Mock).mockResolvedValue({ value: null, sources: [] });
|
|
92
|
+
|
|
93
|
+
await expect(calculateWithdrawal(1000000)).rejects.toThrow();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Unit Test Requirements
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
[ ] Every domain function has tests
|
|
102
|
+
[ ] KB is mocked in unit tests
|
|
103
|
+
[ ] Tests verify kbSources are returned
|
|
104
|
+
[ ] Tests verify no hardcoded fallbacks
|
|
105
|
+
[ ] Tests cover error paths
|
|
106
|
+
[ ] Coverage target: ≥90%
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 2. KB Quality Testing
|
|
112
|
+
|
|
113
|
+
### 2.1 Search Accuracy Tests
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// tests/kb-quality/search-accuracy.test.ts
|
|
117
|
+
|
|
118
|
+
describe('KB Search Accuracy', () => {
|
|
119
|
+
const testQueries = [
|
|
120
|
+
{
|
|
121
|
+
query: 'safe withdrawal rate retirement',
|
|
122
|
+
expectedTopics: ['4% rule', 'Bengen', 'withdrawal'],
|
|
123
|
+
minConfidence: 0.7
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
query: 'social security claiming age',
|
|
127
|
+
expectedTopics: ['62', '67', 'FRA', 'delayed credits'],
|
|
128
|
+
minConfidence: 0.7
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
query: 'Roth conversion strategy',
|
|
132
|
+
expectedTopics: ['tax bracket', 'IRMAA', 'conversion ladder'],
|
|
133
|
+
minConfidence: 0.6
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
testQueries.forEach(({ query, expectedTopics, minConfidence }) => {
|
|
138
|
+
test(`"${query}" returns relevant results`, async () => {
|
|
139
|
+
const results = await kb.search(query);
|
|
140
|
+
|
|
141
|
+
// Must return results
|
|
142
|
+
expect(results.length).toBeGreaterThan(0);
|
|
143
|
+
|
|
144
|
+
// Top result must have minimum confidence
|
|
145
|
+
expect(results[0].confidence).toBeGreaterThanOrEqual(minConfidence);
|
|
146
|
+
|
|
147
|
+
// Results must contain expected topics
|
|
148
|
+
const content = results.map(r => r.content.toLowerCase()).join(' ');
|
|
149
|
+
const foundTopics = expectedTopics.filter(t =>
|
|
150
|
+
content.includes(t.toLowerCase())
|
|
151
|
+
);
|
|
152
|
+
expect(foundTopics.length).toBeGreaterThanOrEqual(1);
|
|
153
|
+
|
|
154
|
+
// All results must have sources
|
|
155
|
+
results.forEach(r => {
|
|
156
|
+
expect(r.sourceExpert).toBeDefined();
|
|
157
|
+
expect(r.sourceExpert).not.toBe('');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2.2 Coverage Tests
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// tests/kb-quality/coverage.test.ts
|
|
168
|
+
|
|
169
|
+
describe('KB Coverage', () => {
|
|
170
|
+
const requiredTopics = [
|
|
171
|
+
'withdrawal strategies',
|
|
172
|
+
'social security optimization',
|
|
173
|
+
'Medicare planning',
|
|
174
|
+
'tax-efficient withdrawal',
|
|
175
|
+
'Roth conversions',
|
|
176
|
+
'required minimum distributions',
|
|
177
|
+
'estate planning basics',
|
|
178
|
+
'inflation protection',
|
|
179
|
+
'sequence of returns risk',
|
|
180
|
+
'bucket strategy'
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
requiredTopics.forEach(topic => {
|
|
184
|
+
test(`KB covers "${topic}"`, async () => {
|
|
185
|
+
const results = await kb.search(topic);
|
|
186
|
+
|
|
187
|
+
expect(results.length).toBeGreaterThan(0);
|
|
188
|
+
expect(results[0].confidence).toBeGreaterThan(0.5);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('KB has minimum node count', async () => {
|
|
193
|
+
const stats = await kb.getStats();
|
|
194
|
+
expect(stats.totalNodes).toBeGreaterThanOrEqual(500);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('KB has minimum expert count', async () => {
|
|
198
|
+
const stats = await kb.getStats();
|
|
199
|
+
expect(stats.uniqueExperts).toBeGreaterThanOrEqual(50);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('average confidence is acceptable', async () => {
|
|
203
|
+
const stats = await kb.getStats();
|
|
204
|
+
expect(stats.avgConfidence).toBeGreaterThanOrEqual(0.7);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 2.3 Gap Detection Tests
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// tests/kb-quality/gaps.test.ts
|
|
213
|
+
|
|
214
|
+
describe('KB Gap Detection', () => {
|
|
215
|
+
const edgeCaseQueries = [
|
|
216
|
+
'retirement planning for freelancers',
|
|
217
|
+
'pension vs lump sum decision',
|
|
218
|
+
'international retirement tax treaties',
|
|
219
|
+
'cryptocurrency in retirement portfolio'
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
edgeCaseQueries.forEach(query => {
|
|
223
|
+
test(`handles "${query}" gracefully`, async () => {
|
|
224
|
+
const results = await kb.search(query);
|
|
225
|
+
|
|
226
|
+
if (results.length === 0 || results[0].confidence < 0.5) {
|
|
227
|
+
// Should log gap
|
|
228
|
+
const gaps = await kb.getRecentGaps();
|
|
229
|
+
const gapLogged = gaps.some(g => g.query.includes(query));
|
|
230
|
+
expect(gapLogged).toBe(true);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 3. Integration Testing
|
|
240
|
+
|
|
241
|
+
### 3.1 API + KB Integration
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// tests/integration/api-kb.test.ts
|
|
245
|
+
|
|
246
|
+
describe('API-KB Integration', () => {
|
|
247
|
+
let app: Express;
|
|
248
|
+
|
|
249
|
+
beforeAll(async () => {
|
|
250
|
+
// Start app with real KB connection
|
|
251
|
+
app = await startApp();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
afterAll(async () => {
|
|
255
|
+
await stopApp();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('POST /api/calculate-withdrawal returns sources', async () => {
|
|
259
|
+
const response = await request(app)
|
|
260
|
+
.post('/api/calculate-withdrawal')
|
|
261
|
+
.send({ portfolio: 1000000, age: 65 });
|
|
262
|
+
|
|
263
|
+
expect(response.status).toBe(200);
|
|
264
|
+
expect(response.body.withdrawal).toBeDefined();
|
|
265
|
+
expect(response.body.sources).toBeInstanceOf(Array);
|
|
266
|
+
expect(response.body.sources.length).toBeGreaterThan(0);
|
|
267
|
+
expect(response.body.sources[0].expert).toBeDefined();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('API fails gracefully when KB unavailable', async () => {
|
|
271
|
+
// Temporarily disconnect KB
|
|
272
|
+
await kb.disconnect();
|
|
273
|
+
|
|
274
|
+
const response = await request(app)
|
|
275
|
+
.post('/api/calculate-withdrawal')
|
|
276
|
+
.send({ portfolio: 1000000 });
|
|
277
|
+
|
|
278
|
+
expect(response.status).toBe(503);
|
|
279
|
+
expect(response.body.error).toContain('KB');
|
|
280
|
+
|
|
281
|
+
// Reconnect
|
|
282
|
+
await kb.connect();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('all endpoints return sources array', async () => {
|
|
286
|
+
const endpoints = [
|
|
287
|
+
{ method: 'post', path: '/api/calculate-withdrawal', body: { portfolio: 1000000 } },
|
|
288
|
+
{ method: 'post', path: '/api/recommend-strategy', body: { age: 65, risk: 'moderate' } },
|
|
289
|
+
{ method: 'get', path: '/api/ss-claiming-age?birthYear=1960' }
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
for (const endpoint of endpoints) {
|
|
293
|
+
const response = await request(app)[endpoint.method](endpoint.path)
|
|
294
|
+
.send(endpoint.body);
|
|
295
|
+
|
|
296
|
+
expect(response.body.sources).toBeDefined();
|
|
297
|
+
expect(Array.isArray(response.body.sources)).toBe(true);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### 3.2 Intelligence Layer Integration
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// tests/integration/gnn-integration.test.ts
|
|
307
|
+
|
|
308
|
+
describe('GNN Integration', () => {
|
|
309
|
+
test('decision simulation returns cascade effects', async () => {
|
|
310
|
+
const result = await api.post('/api/simulate-decision', {
|
|
311
|
+
variable: 'ss_claiming_age',
|
|
312
|
+
newValue: 70
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(result.body.effects).toBeInstanceOf(Array);
|
|
316
|
+
expect(result.body.effects.length).toBeGreaterThan(0);
|
|
317
|
+
|
|
318
|
+
// Should affect related variables
|
|
319
|
+
const affectedVars = result.body.effects.map(e => e.variable);
|
|
320
|
+
expect(affectedVars).toContain('tax_bracket');
|
|
321
|
+
expect(affectedVars).toContain('roth_conversion_space');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 4. End-to-End Testing
|
|
329
|
+
|
|
330
|
+
### 4.1 User Journey Tests
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// tests/e2e/user-journeys.test.ts
|
|
334
|
+
|
|
335
|
+
describe('User Journeys', () => {
|
|
336
|
+
test('complete retirement planning flow', async () => {
|
|
337
|
+
const browser = await chromium.launch();
|
|
338
|
+
const page = await browser.newPage();
|
|
339
|
+
|
|
340
|
+
// 1. Load app
|
|
341
|
+
await page.goto('http://localhost:3000');
|
|
342
|
+
await expect(page.locator('h1')).toContainText('Retirement Advisor');
|
|
343
|
+
|
|
344
|
+
// 2. Enter profile
|
|
345
|
+
await page.fill('[name="age"]', '60');
|
|
346
|
+
await page.fill('[name="portfolio"]', '1000000');
|
|
347
|
+
await page.selectOption('[name="risk"]', 'moderate');
|
|
348
|
+
await page.click('button[type="submit"]');
|
|
349
|
+
|
|
350
|
+
// 3. Verify recommendations appear with sources
|
|
351
|
+
await expect(page.locator('.recommendation')).toBeVisible();
|
|
352
|
+
await expect(page.locator('.sources')).toBeVisible();
|
|
353
|
+
await expect(page.locator('.source-expert')).toHaveCount({ min: 1 });
|
|
354
|
+
|
|
355
|
+
// 4. Run simulation
|
|
356
|
+
await page.click('[data-test="simulate-ss-70"]');
|
|
357
|
+
await expect(page.locator('.cascade-effects')).toBeVisible();
|
|
358
|
+
|
|
359
|
+
// 5. Verify all displayed values have sources
|
|
360
|
+
const values = await page.locator('.domain-value').all();
|
|
361
|
+
for (const value of values) {
|
|
362
|
+
const sources = await value.locator('..').locator('.sources');
|
|
363
|
+
await expect(sources).toBeVisible();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
await browser.close();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('handles errors gracefully', async () => {
|
|
370
|
+
const browser = await chromium.launch();
|
|
371
|
+
const page = await browser.newPage();
|
|
372
|
+
|
|
373
|
+
// Simulate KB failure
|
|
374
|
+
await page.route('**/api/**', route => {
|
|
375
|
+
route.fulfill({ status: 503, body: JSON.stringify({ error: 'KB unavailable' }) });
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
await page.goto('http://localhost:3000');
|
|
379
|
+
await page.fill('[name="age"]', '60');
|
|
380
|
+
await page.click('button[type="submit"]');
|
|
381
|
+
|
|
382
|
+
// Should show error, not crash
|
|
383
|
+
await expect(page.locator('.error-message')).toContainText('unavailable');
|
|
384
|
+
|
|
385
|
+
await browser.close();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 5. Performance Testing
|
|
393
|
+
|
|
394
|
+
### 5.1 Response Time Tests
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// tests/performance/response-times.test.ts
|
|
398
|
+
|
|
399
|
+
describe('Performance', () => {
|
|
400
|
+
const TIMEOUT_MS = 500; // p95 target
|
|
401
|
+
|
|
402
|
+
test('KB search completes within SLA', async () => {
|
|
403
|
+
const times: number[] = [];
|
|
404
|
+
|
|
405
|
+
for (let i = 0; i < 100; i++) {
|
|
406
|
+
const start = Date.now();
|
|
407
|
+
await kb.search('withdrawal rate');
|
|
408
|
+
times.push(Date.now() - start);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
times.sort((a, b) => a - b);
|
|
412
|
+
const p95 = times[Math.floor(times.length * 0.95)];
|
|
413
|
+
|
|
414
|
+
expect(p95).toBeLessThan(TIMEOUT_MS);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('API endpoints meet SLA', async () => {
|
|
418
|
+
const endpoints = [
|
|
419
|
+
'/api/calculate-withdrawal',
|
|
420
|
+
'/api/recommend-strategy',
|
|
421
|
+
'/api/simulate-decision'
|
|
422
|
+
];
|
|
423
|
+
|
|
424
|
+
for (const endpoint of endpoints) {
|
|
425
|
+
const times: number[] = [];
|
|
426
|
+
|
|
427
|
+
for (let i = 0; i < 50; i++) {
|
|
428
|
+
const start = Date.now();
|
|
429
|
+
await request(app).post(endpoint).send({ portfolio: 1000000 });
|
|
430
|
+
times.push(Date.now() - start);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
times.sort((a, b) => a - b);
|
|
434
|
+
const p95 = times[Math.floor(times.length * 0.95)];
|
|
435
|
+
|
|
436
|
+
expect(p95).toBeLessThan(1000); // 1s for API
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### 5.2 Load Testing
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
#!/bin/bash
|
|
446
|
+
# scripts/load-test.sh
|
|
447
|
+
|
|
448
|
+
echo "=== KB-First Load Test ==="
|
|
449
|
+
|
|
450
|
+
# Use k6 or artillery
|
|
451
|
+
k6 run - <<EOF
|
|
452
|
+
import http from 'k6/http';
|
|
453
|
+
import { check, sleep } from 'k6';
|
|
454
|
+
|
|
455
|
+
export const options = {
|
|
456
|
+
vus: 50, // 50 virtual users
|
|
457
|
+
duration: '5m', // 5 minutes
|
|
458
|
+
thresholds: {
|
|
459
|
+
http_req_duration: ['p(95)<1000'], // 95% under 1s
|
|
460
|
+
http_req_failed: ['rate<0.01'], // <1% errors
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
export default function () {
|
|
465
|
+
const res = http.post('http://localhost:3000/api/calculate-withdrawal',
|
|
466
|
+
JSON.stringify({ portfolio: 1000000 }),
|
|
467
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
check(res, {
|
|
471
|
+
'status is 200': (r) => r.status === 200,
|
|
472
|
+
'has sources': (r) => JSON.parse(r.body).sources?.length > 0,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
sleep(1);
|
|
476
|
+
}
|
|
477
|
+
EOF
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## 6. Regression Testing
|
|
483
|
+
|
|
484
|
+
### 6.1 Golden File Tests
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// tests/regression/golden-files.test.ts
|
|
488
|
+
|
|
489
|
+
describe('Regression - Golden Files', () => {
|
|
490
|
+
const goldenCases = [
|
|
491
|
+
{
|
|
492
|
+
name: 'standard-withdrawal',
|
|
493
|
+
input: { portfolio: 1000000, age: 65, risk: 'moderate' },
|
|
494
|
+
goldenFile: 'tests/golden/standard-withdrawal.json'
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: 'early-retirement',
|
|
498
|
+
input: { portfolio: 2000000, age: 55, risk: 'aggressive' },
|
|
499
|
+
goldenFile: 'tests/golden/early-retirement.json'
|
|
500
|
+
}
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
goldenCases.forEach(({ name, input, goldenFile }) => {
|
|
504
|
+
test(`regression: ${name}`, async () => {
|
|
505
|
+
const result = await api.post('/api/calculate-withdrawal', input);
|
|
506
|
+
const golden = JSON.parse(fs.readFileSync(goldenFile, 'utf-8'));
|
|
507
|
+
|
|
508
|
+
// Values should be within 5% of golden
|
|
509
|
+
expect(result.body.withdrawal).toBeCloseTo(golden.withdrawal, -2);
|
|
510
|
+
|
|
511
|
+
// Sources should include same experts
|
|
512
|
+
const resultExperts = result.body.sources.map(s => s.expert);
|
|
513
|
+
const goldenExperts = golden.sources.map(s => s.expert);
|
|
514
|
+
expect(resultExperts).toEqual(expect.arrayContaining(goldenExperts));
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### 6.2 Update Golden Files Script
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
#!/bin/bash
|
|
524
|
+
# scripts/update-golden-files.sh
|
|
525
|
+
|
|
526
|
+
echo "Updating golden files..."
|
|
527
|
+
echo "⚠️ Only run this after verifying new behavior is correct!"
|
|
528
|
+
|
|
529
|
+
read -p "Are you sure? (type 'yes'): " confirm
|
|
530
|
+
if [ "$confirm" != "yes" ]; then
|
|
531
|
+
echo "Aborted"
|
|
532
|
+
exit 1
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
# Generate new golden files
|
|
536
|
+
node tests/generate-golden.js
|
|
537
|
+
echo "✅ Golden files updated"
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## 7. Test Configuration
|
|
543
|
+
|
|
544
|
+
### Jest Configuration
|
|
545
|
+
|
|
546
|
+
```javascript
|
|
547
|
+
// jest.config.js
|
|
548
|
+
module.exports = {
|
|
549
|
+
projects: [
|
|
550
|
+
{
|
|
551
|
+
displayName: 'unit',
|
|
552
|
+
testMatch: ['<rootDir>/src/**/__tests__/**/*.test.ts'],
|
|
553
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup/unit.ts'],
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
displayName: 'kb-quality',
|
|
557
|
+
testMatch: ['<rootDir>/tests/kb-quality/**/*.test.ts'],
|
|
558
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup/kb.ts'],
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
displayName: 'integration',
|
|
562
|
+
testMatch: ['<rootDir>/tests/integration/**/*.test.ts'],
|
|
563
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup/integration.ts'],
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
displayName: 'e2e',
|
|
567
|
+
testMatch: ['<rootDir>/tests/e2e/**/*.test.ts'],
|
|
568
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup/e2e.ts'],
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
coverageThreshold: {
|
|
572
|
+
global: {
|
|
573
|
+
branches: 80,
|
|
574
|
+
functions: 90,
|
|
575
|
+
lines: 90,
|
|
576
|
+
statements: 90,
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Run Commands
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
# All tests
|
|
586
|
+
npm test
|
|
587
|
+
|
|
588
|
+
# Specific suites
|
|
589
|
+
npm run test:unit
|
|
590
|
+
npm run test:kb-quality
|
|
591
|
+
npm run test:integration
|
|
592
|
+
npm run test:e2e
|
|
593
|
+
|
|
594
|
+
# With coverage
|
|
595
|
+
npm run test:coverage
|
|
596
|
+
|
|
597
|
+
# Watch mode
|
|
598
|
+
npm run test:watch
|
|
599
|
+
|
|
600
|
+
# CI mode (no watch, fail fast)
|
|
601
|
+
npm run test:ci
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## 8. Test Requirements Checklist
|
|
607
|
+
|
|
608
|
+
Before marking Phase 7.7 complete:
|
|
609
|
+
|
|
610
|
+
```
|
|
611
|
+
UNIT TESTS:
|
|
612
|
+
[ ] All domain functions tested
|
|
613
|
+
[ ] KB mocked in unit tests
|
|
614
|
+
[ ] Coverage ≥90%
|
|
615
|
+
[ ] No hardcoded value tests pass
|
|
616
|
+
|
|
617
|
+
KB QUALITY TESTS:
|
|
618
|
+
[ ] Search accuracy tests (100+ queries)
|
|
619
|
+
[ ] Coverage tests (all required topics)
|
|
620
|
+
[ ] Gap detection verified
|
|
621
|
+
|
|
622
|
+
INTEGRATION TESTS:
|
|
623
|
+
[ ] All API endpoints tested
|
|
624
|
+
[ ] KB integration verified
|
|
625
|
+
[ ] Intelligence layer integration tested
|
|
626
|
+
[ ] Error handling verified
|
|
627
|
+
|
|
628
|
+
E2E TESTS:
|
|
629
|
+
[ ] Core user journeys pass
|
|
630
|
+
[ ] Sources displayed in UI
|
|
631
|
+
[ ] Error states handled gracefully
|
|
632
|
+
|
|
633
|
+
PERFORMANCE TESTS:
|
|
634
|
+
[ ] p95 response time <500ms (KB search)
|
|
635
|
+
[ ] p95 response time <1000ms (API)
|
|
636
|
+
[ ] Load test passes (50 VUs, 5 min)
|
|
637
|
+
|
|
638
|
+
REGRESSION TESTS:
|
|
639
|
+
[ ] Golden file tests pass
|
|
640
|
+
[ ] No regressions from previous version
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## 9. Continuous Testing
|
|
646
|
+
|
|
647
|
+
### Pre-commit Hook
|
|
648
|
+
|
|
649
|
+
```bash
|
|
650
|
+
#!/bin/bash
|
|
651
|
+
# .husky/pre-commit
|
|
652
|
+
|
|
653
|
+
npm run test:unit -- --bail
|
|
654
|
+
npm run lint
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### PR Checks
|
|
658
|
+
|
|
659
|
+
```yaml
|
|
660
|
+
# .github/workflows/test.yml
|
|
661
|
+
name: Tests
|
|
662
|
+
on: [pull_request]
|
|
663
|
+
|
|
664
|
+
jobs:
|
|
665
|
+
test:
|
|
666
|
+
runs-on: ubuntu-latest
|
|
667
|
+
steps:
|
|
668
|
+
- uses: actions/checkout@v4
|
|
669
|
+
- uses: actions/setup-node@v4
|
|
670
|
+
- run: npm ci
|
|
671
|
+
- run: npm run test:unit
|
|
672
|
+
- run: npm run test:kb-quality
|
|
673
|
+
- run: npm run test:integration
|
|
674
|
+
- run: npm run test:coverage
|
|
675
|
+
- uses: codecov/codecov-action@v3
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Nightly Full Suite
|
|
679
|
+
|
|
680
|
+
```yaml
|
|
681
|
+
# .github/workflows/nightly.yml
|
|
682
|
+
name: Nightly Tests
|
|
683
|
+
on:
|
|
684
|
+
schedule:
|
|
685
|
+
- cron: '0 2 * * *' # 2 AM daily
|
|
686
|
+
|
|
687
|
+
jobs:
|
|
688
|
+
full-suite:
|
|
689
|
+
runs-on: ubuntu-latest
|
|
690
|
+
steps:
|
|
691
|
+
- uses: actions/checkout@v4
|
|
692
|
+
- uses: actions/setup-node@v4
|
|
693
|
+
- run: npm ci
|
|
694
|
+
- run: npm run test:all
|
|
695
|
+
- run: npm run test:e2e
|
|
696
|
+
- run: npm run test:performance
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Exit Criteria
|
|
702
|
+
|
|
703
|
+
Phase 7.7 (Testing) is complete when:
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
[ ] Unit test coverage ≥90%
|
|
707
|
+
[ ] All KB quality tests pass
|
|
708
|
+
[ ] All integration tests pass
|
|
709
|
+
[ ] E2E tests cover core journeys
|
|
710
|
+
[ ] Performance tests meet SLA
|
|
711
|
+
[ ] CI/CD pipeline configured
|
|
712
|
+
[ ] Pre-commit hooks installed
|
|
713
|
+
```
|