Files
claude-mem/tests/happy-paths/session-init.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

183 lines
5.7 KiB
TypeScript

/**
* Happy Path Test: Session Initialization
*
* Tests that when a user's first tool use occurs, the session is
* created in the database and observations can be queued.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { bashCommandScenario, sessionScenario } from '../helpers/scenarios.js';
import { getWorkerPort } from '../../src/shared/worker-utils.js';
describe('Session Initialization (UserPromptSubmit)', () => {
const WORKER_PORT = getWorkerPort();
beforeEach(() => {
vi.clearAllMocks();
});
it('creates session when first observation is sent', async () => {
// This tests the happy path:
// User types first prompt → Tool runs → Hook sends observation →
// Worker creates session → Observation queued for SDK processing
// Setup: Mock successful response from worker
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ status: 'queued', sessionId: 1 })
});
// Execute: Send first observation (what save-hook does)
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/observations`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionScenario.claudeSessionId,
tool_name: bashCommandScenario.tool_name,
tool_input: bashCommandScenario.tool_input,
tool_response: bashCommandScenario.tool_response,
cwd: '/project/claude-mem'
})
}
);
// Verify: Session created and observation queued
expect(response.ok).toBe(true);
const result = await response.json();
expect(result.status).toBe('queued');
expect(result.sessionId).toBeDefined();
// Verify: fetch was called with correct endpoint and data
expect(global.fetch).toHaveBeenCalledWith(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/observations`,
expect.objectContaining({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: expect.stringContaining(sessionScenario.claudeSessionId)
})
);
});
it('handles missing claudeSessionId gracefully', async () => {
// Setup: Mock error response for missing session ID
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 400,
json: async () => ({ error: 'Missing claudeSessionId' })
});
// Execute: Send observation without session ID
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/observations`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tool_name: 'Bash',
tool_input: { command: 'ls' },
tool_response: { stdout: 'file.txt' }
})
}
);
// Verify: Returns 400 error
expect(response.ok).toBe(false);
expect(response.status).toBe(400);
const error = await response.json();
expect(error.error).toContain('Missing claudeSessionId');
});
it('queues multiple observations for the same session', async () => {
// Setup: Mock successful responses
let callCount = 0;
global.fetch = vi.fn().mockImplementation(async () => {
const currentId = ++callCount;
return {
ok: true,
status: 200,
json: async () => ({ status: 'queued', observationId: currentId })
};
});
const sessionId = sessionScenario.claudeSessionId;
// Execute: Send multiple observations for the same session
const obs1 = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/observations`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionId,
tool_name: 'Read',
tool_input: { file_path: '/test.ts' },
tool_response: { content: 'code...' }
})
}
);
const obs2 = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/observations`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionId,
tool_name: 'Edit',
tool_input: { file_path: '/test.ts', old_string: 'old', new_string: 'new' },
tool_response: { success: true }
})
}
);
// Verify: Both observations were queued successfully
expect(obs1.ok).toBe(true);
expect(obs2.ok).toBe(true);
const result1 = await obs1.json();
const result2 = await obs2.json();
expect(result1.status).toBe('queued');
expect(result2.status).toBe('queued');
expect(result1.observationId).toBe(1);
expect(result2.observationId).toBe(2);
});
it('includes project context from cwd', async () => {
// Setup: Mock successful response
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ status: 'queued' })
});
const projectPath = '/Users/alice/projects/my-app';
// Execute: Send observation with cwd
await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/sessions/observations`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
claudeSessionId: sessionScenario.claudeSessionId,
tool_name: 'Bash',
tool_input: { command: 'npm test' },
tool_response: { stdout: 'PASS', exit_code: 0 },
cwd: projectPath
})
}
);
// Verify: Request includes cwd
expect(global.fetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
body: expect.stringContaining(projectPath)
})
);
});
});