Files
claude-mem/tests/happy-paths/session-summary.test.ts
T
Alex Newman ded9671a82 Refactor worker port handling and improve logging
- Replaced hardcoded migration port with dynamic port retrieval using `getWorkerPort()` in worker-cli.ts.
- Updated context generator to clarify error handling comments.
- Introduced timeout constants in ProcessManager for better maintainability.
- Configured SQLite settings using constants for mmap size and cache size in DatabaseManager.
- Added timeout constants for Git and NPM commands in BranchManager.
- Enhanced error logging in FormattingService and SearchManager to provide more context on failures.
- Removed deprecated silentDebug function and replaced its usage with logger.debug.
- Updated tests to use dynamic worker port retrieval instead of hardcoded values.
2025-12-11 14:49:47 -05:00

249 lines
8.2 KiB
TypeScript

/**
* Happy Path Test: Session Summary (Stop)
*
* Tests that when a user pauses or stops a session, the SDK
* generates a summary from the conversation context.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { sessionSummaryScenario, sessionScenario } from '../helpers/scenarios.js';
import { getWorkerPort } from '../../src/shared/worker-utils.js';
describe('Session Summary (Stop)', () => {
const WORKER_PORT = getWorkerPort();
beforeEach(() => {
vi.clearAllMocks();
});
it('generates summary from last messages', async () => {
// This tests the happy path:
// User stops/pauses → Hook sends last messages → Worker queues for SDK →
// SDK generates summary → Summary saved to database
// Setup: Mock successful response from worker
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ status: 'queued' })
});
// Execute: Send summarize request (what summary-hook does)
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/summarize`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionSummaryScenario.claudeSessionId,
last_user_message: sessionSummaryScenario.last_user_message,
last_assistant_message: sessionSummaryScenario.last_assistant_message,
cwd: '/project/claude-mem'
})
}
);
// Verify: Summary queued successfully
expect(response.ok).toBe(true);
const result = await response.json();
expect(result.status).toBe('queued');
// Verify: Correct data sent to worker
const fetchCall = (global.fetch as any).mock.calls[0];
const requestBody = JSON.parse(fetchCall[1].body);
expect(requestBody.last_user_message).toBe('Thanks, that fixed it!');
expect(requestBody.last_assistant_message).toContain('parser');
});
it('handles missing session ID gracefully', async () => {
// Setup: Mock error response
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 400,
json: async () => ({ error: 'Missing claudeSessionId' })
});
// Execute: Send summarize without session ID
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/summarize`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
last_user_message: 'Some message',
last_assistant_message: 'Some response'
})
}
);
// Verify: Returns error
expect(response.ok).toBe(false);
expect(response.status).toBe(400);
const error = await response.json();
expect(error.error).toContain('Missing claudeSessionId');
});
it('generates summary for different conversation types', async () => {
// Setup: Mock worker responses
const summaries: any[] = [];
global.fetch = vi.fn().mockImplementation(async (url, options) => {
const body = JSON.parse(options.body);
summaries.push(body);
return {
ok: true,
status: 200,
json: async () => ({ status: 'queued', summaryId: summaries.length })
};
});
// Test different conversation scenarios
const scenarios = [
{
type: 'bug_fix',
user: 'Thanks for fixing the parser bug!',
assistant: 'I fixed the XML parser to handle self-closing tags in src/parser.ts:42.'
},
{
type: 'feature_addition',
user: 'Perfect! The search feature works great.',
assistant: 'I added FTS5 full-text search in src/services/search.ts.'
},
{
type: 'exploration',
user: 'That helps me understand the codebase better.',
assistant: 'The authentication flow uses JWT tokens stored in localStorage.'
},
{
type: 'refactoring',
user: 'Much cleaner now!',
assistant: 'I refactored the duplicate code into a shared utility function in src/utils/helpers.ts.'
}
];
// Execute: Send summary for each scenario
for (const scenario of scenarios) {
await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/summarize`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: `session-${scenario.type}`,
last_user_message: scenario.user,
last_assistant_message: scenario.assistant,
cwd: '/project'
})
}
);
}
// Verify: All summaries queued
expect(summaries.length).toBe(4);
expect(summaries[0].last_user_message).toContain('parser bug');
expect(summaries[1].last_user_message).toContain('search');
expect(summaries[2].last_user_message).toContain('understand');
expect(summaries[3].last_user_message).toContain('cleaner');
});
it('preserves long conversation context', async () => {
// Setup: Mock worker response
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ status: 'queued' })
});
// Execute: Send summary with long messages (realistic scenario)
const longAssistantMessage = `I've fixed the bug in the parser. Here's what I did:
1. Added null check for empty tags in src/parser.ts:42
2. Updated the regex pattern to handle self-closing tags
3. Added unit tests to verify the fix works
4. Ran the test suite and confirmed all tests pass
The issue was that the parser wasn't handling XML tags like <tag/> correctly.
It was only expecting <tag></tag> format. Now it handles both formats.`;
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/summarize`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionScenario.claudeSessionId,
last_user_message: 'Thanks for the detailed explanation!',
last_assistant_message: longAssistantMessage,
cwd: '/project'
})
}
);
// Verify: Long message preserved
expect(response.ok).toBe(true);
const fetchCall = (global.fetch as any).mock.calls[0];
const requestBody = JSON.parse(fetchCall[1].body);
expect(requestBody.last_assistant_message.length).toBeGreaterThan(200);
expect(requestBody.last_assistant_message).toContain('parser.ts:42');
expect(requestBody.last_assistant_message).toContain('self-closing tags');
});
it('handles empty or minimal messages gracefully', async () => {
// Setup: Mock worker response
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ status: 'queued' })
});
// Execute: Send summary with minimal messages
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/summarize`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionScenario.claudeSessionId,
last_user_message: 'Thanks!',
last_assistant_message: 'Done.',
cwd: '/project'
})
}
);
// Verify: Still processes minimal messages
expect(response.ok).toBe(true);
const result = await response.json();
expect(result.status).toBe('queued');
});
it('includes project context from cwd', async () => {
// Setup: Mock worker response
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ status: 'queued' })
});
const projectPath = '/Users/alice/projects/my-app';
// Execute: Send summary with project context
await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/summarize`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionScenario.claudeSessionId,
last_user_message: 'Great!',
last_assistant_message: 'Fixed the bug.',
cwd: projectPath
})
}
);
// Verify: Project context included
const fetchCall = (global.fetch as any).mock.calls[0];
const requestBody = JSON.parse(fetchCall[1].body);
expect(requestBody.cwd).toBe(projectPath);
});
});