feat(tests): add comprehensive happy path tests for session lifecycle
- Implemented session cleanup tests to ensure proper handling of session completions and cleanup operations. - Added session initialization tests to verify session creation and observation queuing on first tool use. - Created session summary tests to validate summary generation from conversation context upon session pause or stop. - Developed integration tests to cover the full observation lifecycle, including context injection, observation queuing, and error recovery. - Introduced reusable mock factories and scenarios for consistent testing across different test files.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
describe('Session Initialization (UserPromptSubmit)', () => {
|
||||
const WORKER_PORT = 37777;
|
||||
|
||||
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)
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user