Skip to content

Commit aa3bf67

Browse files
committed
test: add comprehensive EPISODIC coverage across all memory flows
- create.test.ts: verify EPISODIC with namespaces and reflectionNamespaces in longAndShortTerm preset - add-remove-resources integ: verify `add memory --strategies EPISODIC` persists reflectionNamespaces in agentcore.json - TUI test: drive Add Memory wizard through strategy multi-select, verify EPISODIC is persisted with correct config in agentcore.json
1 parent 38eec3c commit aa3bf67

3 files changed

Lines changed: 236 additions & 0 deletions

File tree

integ-tests/add-remove-resources.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,32 @@ describe('integration: add and remove resources', () => {
3636
expect(found, `Memory "${memoryName}" should be in config`).toBe(true);
3737
});
3838

39+
it('adds a memory with EPISODIC strategy and verifies reflectionNamespaces', async () => {
40+
const episodicMemName = `EpiMem${Date.now().toString().slice(-6)}`;
41+
const result = await runCLI(
42+
['add', 'memory', '--name', episodicMemName, '--strategies', 'EPISODIC', '--json'],
43+
project.projectPath
44+
);
45+
46+
expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0);
47+
const json = JSON.parse(result.stdout);
48+
expect(json.success).toBe(true);
49+
50+
// Verify EPISODIC in config with reflectionNamespaces
51+
const config = await readProjectConfig(project.projectPath);
52+
const memories = config.memories as { name: string; strategies: { type: string; reflectionNamespaces?: string[] }[] }[];
53+
const mem = memories.find(m => m.name === episodicMemName);
54+
expect(mem, 'Memory should exist').toBeTruthy();
55+
56+
const episodic = mem!.strategies.find(s => s.type === 'EPISODIC');
57+
expect(episodic, 'EPISODIC strategy should exist').toBeTruthy();
58+
expect(episodic!.reflectionNamespaces, 'Should have reflectionNamespaces').toBeDefined();
59+
expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0);
60+
61+
// Clean up
62+
await runCLI(['remove', 'memory', '--name', episodicMemName, '--json'], project.projectPath);
63+
});
64+
3965
it('removes the memory resource', async () => {
4066
const result = await runCLI(['remove', 'memory', '--name', memoryName, '--json'], project.projectPath);
4167

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* TUI Integration Test: Add Memory with EPISODIC Strategy
3+
*
4+
* Drives the "Add Memory" wizard through the TUI to verify that when a user
5+
* selects the EPISODIC strategy, it is correctly persisted in agentcore.json
6+
* with both namespaces and reflectionNamespaces.
7+
*
8+
* Exercises:
9+
* - Navigation from HelpScreen -> Add Resource -> Memory
10+
* - Memory name input
11+
* - Expiry selection (default 30 days)
12+
* - Strategy multi-select including EPISODIC
13+
* - Confirm review screen
14+
* - Verification that agentcore.json contains EPISODIC with reflectionNamespaces
15+
*/
16+
import { TuiSession, WaitForTimeoutError } from '../../src/tui-harness/index.js';
17+
import { createMinimalProjectDir } from './helpers.js';
18+
import type { MinimalProjectDirResult } from './helpers.js';
19+
import { readFile as readFileAsync } from 'node:fs/promises';
20+
import { dirname, join } from 'node:path';
21+
import { fileURLToPath } from 'node:url';
22+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
23+
24+
// ---------------------------------------------------------------------------
25+
// Paths & Constants
26+
// ---------------------------------------------------------------------------
27+
28+
const __filename = fileURLToPath(import.meta.url);
29+
const __dirname = dirname(__filename);
30+
const CLI_DIST = join(__dirname, '..', '..', 'dist', 'cli', 'index.mjs');
31+
32+
// ---------------------------------------------------------------------------
33+
// Helpers
34+
// ---------------------------------------------------------------------------
35+
36+
function getScreenText(session: TuiSession): string {
37+
return session.readScreen().lines.join('\n');
38+
}
39+
40+
async function safeWaitFor(session: TuiSession, pattern: string | RegExp, timeoutMs = 10_000): Promise<boolean> {
41+
try {
42+
await session.waitFor(pattern, timeoutMs);
43+
return true;
44+
} catch (err) {
45+
if (err instanceof WaitForTimeoutError) {
46+
return false;
47+
}
48+
throw err;
49+
}
50+
}
51+
52+
const settle = (ms = 400) => new Promise<void>(r => setTimeout(r, ms));
53+
54+
// ---------------------------------------------------------------------------
55+
// Test Suite
56+
// ---------------------------------------------------------------------------
57+
58+
describe('Add Memory with EPISODIC Strategy', () => {
59+
let session: TuiSession;
60+
let projectDir: MinimalProjectDirResult;
61+
62+
beforeAll(async () => {
63+
projectDir = await createMinimalProjectDir({ projectName: 'episodic-test' });
64+
65+
session = await TuiSession.launch({
66+
command: process.execPath,
67+
args: [CLI_DIST],
68+
cwd: projectDir.dir,
69+
cols: 120,
70+
rows: 35,
71+
});
72+
});
73+
74+
afterAll(async () => {
75+
if (session?.alive) {
76+
await session.close();
77+
}
78+
if (projectDir) {
79+
await projectDir.cleanup();
80+
}
81+
});
82+
83+
it('Step 1: reaches HelpScreen', async () => {
84+
const found = await safeWaitFor(session, 'Commands', 15_000);
85+
expect(found).toBe(true);
86+
});
87+
88+
it('Step 2: navigates to Add Resource screen', async () => {
89+
await session.sendKeys('add');
90+
await settle();
91+
await session.sendSpecialKey('enter');
92+
const found = await safeWaitFor(session, 'Add Resource', 5_000);
93+
expect(found).toBe(true);
94+
});
95+
96+
it('Step 3: selects Memory from the resource list', async () => {
97+
// Add Resource list: 0: Agent, 1: Memory
98+
await session.sendSpecialKey('down');
99+
await settle();
100+
101+
const text = getScreenText(session);
102+
expect(text).toContain('Memory');
103+
104+
await session.sendSpecialKey('enter');
105+
const found = await safeWaitFor(session, 'Name', 5_000);
106+
expect(found).toBe(true);
107+
});
108+
109+
it('Step 4: enters memory name', async () => {
110+
await session.sendKeys('EpisodicTestMemory');
111+
await settle();
112+
await session.sendSpecialKey('enter');
113+
114+
const found = await safeWaitFor(session, /[Ee]xpiry|days/, 5_000);
115+
expect(found).toBe(true);
116+
});
117+
118+
it('Step 5: selects default expiry (30 days)', async () => {
119+
// Default is 30 days, just press enter
120+
await session.sendSpecialKey('enter');
121+
await settle();
122+
123+
// Should reach strategies multi-select
124+
const found = await safeWaitFor(session, /[Ss]trateg/, 5_000);
125+
expect(found).toBe(true);
126+
});
127+
128+
it('Step 6: selects all strategies including EPISODIC', async () => {
129+
// Strategy list order matches enum: SEMANTIC, SUMMARIZATION, USER_PREFERENCE, EPISODIC
130+
// Toggle SEMANTIC (cursor starts at 0)
131+
await session.sendSpecialKey('space');
132+
await settle(200);
133+
134+
// Toggle SUMMARIZATION
135+
await session.sendSpecialKey('down');
136+
await session.sendSpecialKey('space');
137+
await settle(200);
138+
139+
// Toggle USER_PREFERENCE
140+
await session.sendSpecialKey('down');
141+
await session.sendSpecialKey('space');
142+
await settle(200);
143+
144+
// Toggle EPISODIC
145+
await session.sendSpecialKey('down');
146+
await session.sendSpecialKey('space');
147+
await settle(200);
148+
149+
// Verify EPISODIC is visible on screen
150+
const text = getScreenText(session);
151+
expect(text).toContain('Episodic');
152+
153+
// Confirm selection
154+
await session.sendSpecialKey('enter');
155+
156+
// Should reach confirm/review screen
157+
const found = await safeWaitFor(session, /[Rr]eview|[Cc]onfirm/, 5_000);
158+
expect(found).toBe(true);
159+
});
160+
161+
it('Step 7: confirm screen shows EPISODIC strategy', () => {
162+
const text = getScreenText(session);
163+
164+
// Verify all strategies appear in the review
165+
expect(text).toContain('SEMANTIC');
166+
expect(text).toContain('EPISODIC');
167+
expect(text).toContain('EpisodicTestMemory');
168+
});
169+
170+
it('Step 8: confirms and creates memory', async () => {
171+
await session.sendSpecialKey('enter');
172+
await settle(1000);
173+
174+
// Should return to HelpScreen or show success
175+
const found = await safeWaitFor(session, /Commands|[Ss]uccess|added/, 10_000);
176+
expect(found).toBe(true);
177+
});
178+
179+
it('Step 9: agentcore.json contains EPISODIC with reflectionNamespaces', async () => {
180+
const configPath = join(projectDir.dir, 'agentcore', 'agentcore.json');
181+
const raw = await readFileAsync(configPath, 'utf-8');
182+
const config = JSON.parse(raw);
183+
184+
const memories = config.memories as { name: string; strategies: { type: string; namespaces?: string[]; reflectionNamespaces?: string[] }[] }[];
185+
expect(memories.length).toBeGreaterThan(0);
186+
187+
const memory = memories.find(m => m.name === 'EpisodicTestMemory');
188+
expect(memory, 'EpisodicTestMemory should exist in agentcore.json').toBeTruthy();
189+
190+
// Verify all 4 strategies present
191+
const types = memory!.strategies.map(s => s.type);
192+
expect(types).toContain('SEMANTIC');
193+
expect(types).toContain('SUMMARIZATION');
194+
expect(types).toContain('USER_PREFERENCE');
195+
expect(types).toContain('EPISODIC');
196+
197+
// Verify EPISODIC has namespaces AND reflectionNamespaces
198+
const episodic = memory!.strategies.find(s => s.type === 'EPISODIC');
199+
expect(episodic, 'EPISODIC strategy should exist').toBeTruthy();
200+
expect(episodic!.namespaces, 'EPISODIC should have namespaces').toBeDefined();
201+
expect(episodic!.namespaces!.length).toBeGreaterThan(0);
202+
expect(episodic!.reflectionNamespaces, 'EPISODIC should have reflectionNamespaces').toBeDefined();
203+
expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0);
204+
});
205+
});

src/cli/commands/create/__tests__/create.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ describe('create command', () => {
138138

139139
const summarization = memory?.strategies?.find((s: { type: string }) => s.type === 'SUMMARIZATION');
140140
expect(summarization?.namespaces).toEqual(['/summaries/{actorId}/{sessionId}']);
141+
142+
const episodic = memory?.strategies?.find((s: { type: string }) => s.type === 'EPISODIC');
143+
expect(episodic, 'EPISODIC strategy should exist in longAndShortTerm').toBeTruthy();
144+
expect(episodic?.namespaces).toEqual(['/strategy/{memoryStrategyId}/actor/{actorId}/']);
145+
expect(episodic?.reflectionNamespaces).toEqual(['/strategy/{memoryStrategyId}/actor/{actorId}/']);
141146
});
142147
});
143148

0 commit comments

Comments
 (0)