Skip to content

Commit 67d52ac

Browse files
committed
test: Add dummy memory mock logic to offline summarization tests (#9658)
1 parent 3ecf14e commit 67d52ac

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import {setup} from '../../../../setup.mjs';
2+
3+
const appName = 'MemoryCoreTest';
4+
5+
process.env.MODEL_PROVIDER = 'ollama';
6+
process.env.OLLAMA_MODEL = 'gemma4';
7+
8+
setup({
9+
neoConfig: {
10+
allowVdomUpdatesInTests: false,
11+
unitTestMode : true,
12+
useDomApiRenderer : false
13+
},
14+
appConfig: {
15+
name : appName,
16+
isMounted : () => true,
17+
vnodeInitialising: false
18+
}
19+
});
20+
21+
import { test, expect } from '@playwright/test';
22+
import path from 'path';
23+
import {fileURLToPath} from 'url';
24+
import dotenv from 'dotenv';
25+
26+
const __filename = fileURLToPath(import.meta.url);
27+
const __dirname = path.dirname(__filename);
28+
dotenv.config({path: path.resolve(__dirname, '../../../../../../.env'), quiet: true});
29+
30+
test.describe('Memory Core Offline Summarization', () => {
31+
let SDK, TextEmbeddingService, dummySessionId;
32+
let localModelActive = false;
33+
34+
// We must use dynamic imports in Playwright tests inside beforeAll or the test body
35+
// because Neo globals are established during setup()
36+
test.beforeAll(async () => {
37+
SDK = await import('../../../../../../ai/services.mjs');
38+
TextEmbeddingService = (await import('../../../../../../ai/mcp/server/memory-core/services/TextEmbeddingService.mjs')).default;
39+
40+
// Force 'ollama' routing for this test
41+
SDK.Memory_Config.data.modelProvider = 'ollama';
42+
SDK.Memory_Config.data.ollama.model = 'gemma4';
43+
SDK.Memory_Config.data.ollama.embeddingModel = 'gemma4';
44+
SDK.Memory_Config.data.autoSummarize = false;
45+
46+
// Offline tests cannot hit Gemini's API. Mock TextEmbeddingService.
47+
TextEmbeddingService.embedText = async () => new Array(3072).fill(Math.random());
48+
49+
// Check if Ollama daemon and gemma4 are available
50+
try {
51+
const host = SDK.Memory_Config.data.ollama.host;
52+
const res = await fetch(`${host}/api/tags`);
53+
if (res.ok) {
54+
const data = await res.json();
55+
const hasGemma4 = data.models?.some(m => m.name.startsWith('gemma4'));
56+
if (hasGemma4) {
57+
localModelActive = true;
58+
}
59+
}
60+
} catch (e) {
61+
console.log('[Playwright] Ollama daemon not reachable, skipping active test logic.');
62+
}
63+
});
64+
65+
test.afterAll(async () => {
66+
// Clean up dummy turns so we don't pollute the real memory core
67+
if (dummySessionId && localModelActive) {
68+
try {
69+
const memCol = await SDK.Memory_ChromaManager.getMemoryCollection();
70+
await memCol.delete({ where: { sessionId: dummySessionId } });
71+
72+
const sumCol = await SDK.Memory_ChromaManager.getSummaryCollection();
73+
await sumCol.delete({ where: { sessionId: dummySessionId } });
74+
console.log(`[Cleanup] Deleted dummy session ${dummySessionId} from DB.`);
75+
} catch (e) {
76+
console.warn(`[Cleanup] Failed to delete session ${dummySessionId}:`, e);
77+
}
78+
}
79+
});
80+
81+
test('SessionService routes to Ollama (gemma4) via SDK and correctly summarizes memories', async () => {
82+
test.setTimeout(300000); // 5 minutes to allow Gemma 4 to fully summarize on slow hardware
83+
84+
if (!localModelActive) {
85+
test.skip(true, 'Skipping: Ollama or gemma4 not found locally');
86+
return;
87+
}
88+
89+
console.log('INIT DB Lifecycled...');
90+
await SDK.Memory_LifecycleService.ready();
91+
92+
console.log('Waiting SessionService.initAsync() implicitly via SDK');
93+
await SDK.Memory_SessionService.initAsync();
94+
95+
dummySessionId = crypto.randomUUID();
96+
console.log(`[Playwright] Generating Dummy Turns for session ${dummySessionId}...`);
97+
98+
const turns = [
99+
{ prompt: "How do I create a Neo.mjs button?", thought: "Query the UI library for buttons.", response: "Use Neo.button.Base configs." },
100+
{ prompt: "Now make it red.", thought: "Use inline styles or cls.", response: "Add style: {color: 'red'} to the element." },
101+
{ prompt: "Does it support icons?", thought: "Check iconCls.", response: "Yes, use the iconCls property." },
102+
{ prompt: "How to handle clicks?", thought: "DOM events dispatcher.", response: "Bind a click listener via domListeners property." },
103+
{ prompt: "Can a button float?", thought: "Neo components support floating.", response: "Set floating: true." }
104+
];
105+
106+
// Ensure database is ready before adding memories
107+
await SDK.Memory_ChromaManager.ready();
108+
109+
for (const turn of turns) {
110+
const addResult = await SDK.Memory_Service.addMemory({
111+
prompt: turn.prompt,
112+
thought: turn.thought,
113+
response: turn.response,
114+
sessionId: dummySessionId
115+
});
116+
if (addResult.error) console.error('ADD_MEMORY ERROR:', addResult);
117+
}
118+
119+
console.log(`[Playwright] Injected 5 dummy turns via SDK. Triggering Memory_SessionService.summarizeSession...`);
120+
const startTime = Date.now();
121+
122+
// This invokes local Gemma 4
123+
const result = await SDK.Memory_SessionService.summarizeSession(dummySessionId);
124+
125+
console.log(`[Playwright] Summarization complete! Took ${Math.round((Date.now()-startTime)/1000)}s`);
126+
console.log('Summarization Result:', result);
127+
128+
expect(result).not.toBeNull();
129+
expect(result.sessionId).toBe(dummySessionId);
130+
expect(result.memoryCount).toBe(5);
131+
expect(result.summaryId).toBe(`summary_${dummySessionId}`);
132+
expect(result.title).toBeTruthy();
133+
134+
// Verify summary actually got written
135+
const summaryCollection = await SDK.Memory_ChromaManager.getSummaryCollection();
136+
const savedSummary = await summaryCollection.get({
137+
ids: [result.summaryId],
138+
include: ['metadatas', 'documents']
139+
});
140+
141+
expect(savedSummary.ids.length).toBe(1);
142+
const metadata = savedSummary.metadatas[0];
143+
144+
console.log('[Playwright] Gemma 4 Summary generated:', savedSummary.documents[0]);
145+
console.log('[Playwright] Metadata Extracted:', metadata);
146+
expect(metadata.title).toBeDefined();
147+
expect(typeof metadata.quality).toBe('number');
148+
expect(typeof metadata.productivity).toBe('number');
149+
});
150+
});

0 commit comments

Comments
 (0)