File size: 3,779 Bytes
7a1ad33 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | /**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
import fs from 'node:fs';
import { SimulationHarness } from './simulationHarness.js';
import { createMockLlmClient } from '../testing/contextTestUtils.js';
import type { ContextProfile } from '../config/profiles.js';
import { powerUserProfile } from '../config/profiles.js';
expect.addSnapshotSerializer({
test: (val) =>
typeof val === 'string' &&
(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i.test(
val,
) ||
/\b[0-9a-f]{32}\b/i.test(val) ||
/\bsynth_[a-zA-Z0-9_]+_[0-9a-f]{32}\b/.test(val) ||
/[\\/]tmp[\\/]sim/.test(val)),
print: (val) => {
if (typeof val !== 'string') return `"${val}"`;
let scrubbed = val
.replace(
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,
'<UUID>',
)
.replace(/\b[0-9a-f]{32}\b/gi, '<UUID>')
.replace(/\bsynth_[a-zA-Z0-9_]+_[0-9a-f]{32}\b/g, 'synth_<NAME>_<HASH>')
.replace(/[\\/]tmp[\\/]sim[^\s"'\]]*/g, '<MOCKED_DIR>');
// Also scrub timestamps in filenames like blob_1234567890_...
scrubbed = scrubbed.replace(/blob_\d+_/g, 'blob_<TIMESTAMP>_');
return `"${scrubbed}"`;
},
});
describe('Power User Lifecycle Tests', () => {
afterAll(async () => {
fs.rmSync('/tmp/sim', { recursive: true, force: true });
fs.rmSync('mock', { recursive: true, force: true });
});
beforeAll(() => {
vi.spyOn(Math, 'random').mockReturnValue(0.5);
});
afterAll(() => {
vi.restoreAllMocks();
});
const mockLlmClient = createMockLlmClient();
it('should correctly execute the three-tier budget pipeline', async () => {
// 1. Setup a Power User Stress Profile
const powerStressProfile: ContextProfile = {
...powerUserProfile,
config: {
...powerUserProfile.config,
budget: {
retainedTokens: 1000,
normalizedTokens: 2000,
maxTokens: 5000,
coalescingThresholdTokens: 200,
},
gcStrategy: 'incremental',
},
};
const harness = await SimulationHarness.create(
powerStressProfile,
mockLlmClient,
);
// Turn 0: System Prompt
await harness.simulateTurn([
{ role: 'user', parts: [{ text: 'System Instructions' }] },
{ role: 'model', parts: [{ text: 'Ack.' }] },
]);
// Turn 1: Normal conversation
await harness.simulateTurn([
{ role: 'user', parts: [{ text: 'Hello!' }] },
{ role: 'model', parts: [{ text: 'Hi!' }] },
]);
// Turn 2: Large message to cross retainedTokens (1000) but stay under normalizedTokens (2000)
// Should trigger 'retained_exceeded' (Normalization pipeline)
await harness.simulateTurn([
{ role: 'user', parts: [{ text: 'A'.repeat(800) }] },
{ role: 'model', parts: [{ text: 'B'.repeat(400) }] },
]);
// Turn 3: Large message to cross normalizedTokens (2000) but stay under maxTokens (5000)
// Should trigger 'normalized_exceeded' (Archiving pipeline)
await harness.simulateTurn([
{ role: 'user', parts: [{ text: 'C'.repeat(1200) }] },
{ role: 'model', parts: [{ text: 'D'.repeat(600) }] },
]);
// Turn 4: Large message to cross maxTokens (5000)
// Should trigger 'gc_backstop' (Emergency pipeline) AND 'nodes_aged_out' (Async BG)
await harness.simulateTurn([
{ role: 'user', parts: [{ text: 'E'.repeat(2500) }] },
{ role: 'model', parts: [{ text: 'F'.repeat(1000) }] },
]);
const goldenState = await harness.getGoldenState();
// Verify snapshots for token trajectory and final projection
expect(goldenState).toMatchSnapshot();
});
});
|