ded9671a82
- 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.
285 lines
8.9 KiB
TypeScript
285 lines
8.9 KiB
TypeScript
/**
|
|
* Happy Path Test: Observation Capture (PostToolUse)
|
|
*
|
|
* Tests that tool usage is captured and queued for SDK processing.
|
|
* This is the core functionality of claude-mem - turning tool usage
|
|
* into compressed observations.
|
|
*/
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import {
|
|
bashCommandScenario,
|
|
readFileScenario,
|
|
writeFileScenario,
|
|
editFileScenario,
|
|
grepScenario,
|
|
sessionScenario
|
|
} from '../helpers/scenarios.js';
|
|
import { getWorkerPort } from '../../src/shared/worker-utils.js';
|
|
|
|
describe('Observation Capture (PostToolUse)', () => {
|
|
const WORKER_PORT = getWorkerPort();
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('captures Bash command observation', async () => {
|
|
// Setup: Mock worker response
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ status: 'queued' })
|
|
});
|
|
|
|
// Execute: Send Bash tool observation
|
|
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: Observation 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.tool_name).toBe('Bash');
|
|
expect(requestBody.tool_input.command).toBe('git status');
|
|
});
|
|
|
|
it('captures Read file observation', async () => {
|
|
// Setup: Mock worker response
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ status: 'queued' })
|
|
});
|
|
|
|
// Execute: Send Read tool observation
|
|
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: readFileScenario.tool_name,
|
|
tool_input: readFileScenario.tool_input,
|
|
tool_response: readFileScenario.tool_response,
|
|
cwd: '/project'
|
|
})
|
|
}
|
|
);
|
|
|
|
// Verify: Observation queued successfully
|
|
expect(response.ok).toBe(true);
|
|
const result = await response.json();
|
|
expect(result.status).toBe('queued');
|
|
|
|
// Verify: File path captured correctly
|
|
const fetchCall = (global.fetch as any).mock.calls[0];
|
|
const requestBody = JSON.parse(fetchCall[1].body);
|
|
expect(requestBody.tool_name).toBe('Read');
|
|
expect(requestBody.tool_input.file_path).toContain('index.ts');
|
|
});
|
|
|
|
it('captures Write file observation', async () => {
|
|
// Setup: Mock worker response
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ status: 'queued' })
|
|
});
|
|
|
|
// Execute: Send Write tool observation
|
|
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: writeFileScenario.tool_name,
|
|
tool_input: writeFileScenario.tool_input,
|
|
tool_response: writeFileScenario.tool_response,
|
|
cwd: '/project'
|
|
})
|
|
}
|
|
);
|
|
|
|
// Verify: Observation queued successfully
|
|
expect(response.ok).toBe(true);
|
|
const result = await response.json();
|
|
expect(result.status).toBe('queued');
|
|
});
|
|
|
|
it('captures Edit file observation', async () => {
|
|
// Setup: Mock worker response
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ status: 'queued' })
|
|
});
|
|
|
|
// Execute: Send Edit tool observation
|
|
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: editFileScenario.tool_name,
|
|
tool_input: editFileScenario.tool_input,
|
|
tool_response: editFileScenario.tool_response,
|
|
cwd: '/project'
|
|
})
|
|
}
|
|
);
|
|
|
|
// Verify: Observation queued successfully
|
|
expect(response.ok).toBe(true);
|
|
const result = await response.json();
|
|
expect(result.status).toBe('queued');
|
|
|
|
// Verify: Edit details captured
|
|
const fetchCall = (global.fetch as any).mock.calls[0];
|
|
const requestBody = JSON.parse(fetchCall[1].body);
|
|
expect(requestBody.tool_name).toBe('Edit');
|
|
expect(requestBody.tool_input.old_string).toBe('const PORT = 3000;');
|
|
expect(requestBody.tool_input.new_string).toBe('const PORT = 8080;');
|
|
});
|
|
|
|
it('captures Grep search observation', async () => {
|
|
// Setup: Mock worker response
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ status: 'queued' })
|
|
});
|
|
|
|
// Execute: Send Grep tool observation
|
|
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: grepScenario.tool_name,
|
|
tool_input: grepScenario.tool_input,
|
|
tool_response: grepScenario.tool_response,
|
|
cwd: '/project'
|
|
})
|
|
}
|
|
);
|
|
|
|
// Verify: Observation queued successfully
|
|
expect(response.ok).toBe(true);
|
|
const result = await response.json();
|
|
expect(result.status).toBe('queued');
|
|
});
|
|
|
|
it('handles rapid succession of observations (burst mode)', async () => {
|
|
// Setup: Mock worker to accept all observations
|
|
let observationCount = 0;
|
|
global.fetch = vi.fn().mockImplementation(async () => {
|
|
const currentId = ++observationCount;
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ status: 'queued', observationId: currentId })
|
|
};
|
|
});
|
|
|
|
// Execute: Send 5 observations rapidly (simulates active coding session)
|
|
const observations = [
|
|
bashCommandScenario,
|
|
readFileScenario,
|
|
writeFileScenario,
|
|
editFileScenario,
|
|
grepScenario
|
|
];
|
|
|
|
const promises = observations.map(obs =>
|
|
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: obs.tool_name,
|
|
tool_input: obs.tool_input,
|
|
tool_response: obs.tool_response,
|
|
cwd: '/project'
|
|
})
|
|
})
|
|
);
|
|
|
|
const responses = await Promise.all(promises);
|
|
|
|
// Verify: All observations queued successfully
|
|
expect(responses.every(r => r.ok)).toBe(true);
|
|
expect(observationCount).toBe(5);
|
|
|
|
// Verify: Each got unique ID
|
|
const results = await Promise.all(responses.map(r => r.json()));
|
|
const ids = results.map(r => r.observationId);
|
|
expect(new Set(ids).size).toBe(5); // All IDs unique
|
|
});
|
|
|
|
it('preserves tool metadata in observation', async () => {
|
|
// Setup: Mock worker response
|
|
global.fetch = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ status: 'queued' })
|
|
});
|
|
|
|
const complexTool = {
|
|
tool_name: 'Task',
|
|
tool_input: {
|
|
subagent_type: 'Explore',
|
|
prompt: 'Find authentication code',
|
|
description: 'Search for auth'
|
|
},
|
|
tool_response: {
|
|
result: 'Found auth in /src/auth.ts',
|
|
files_analyzed: ['/src/auth.ts', '/src/login.ts']
|
|
}
|
|
};
|
|
|
|
// Execute: Send complex tool observation
|
|
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,
|
|
...complexTool,
|
|
cwd: '/project'
|
|
})
|
|
}
|
|
);
|
|
|
|
// Verify: All metadata preserved in request
|
|
const fetchCall = (global.fetch as any).mock.calls[0];
|
|
const requestBody = JSON.parse(fetchCall[1].body);
|
|
expect(requestBody.tool_name).toBe('Task');
|
|
expect(requestBody.tool_input.subagent_type).toBe('Explore');
|
|
expect(requestBody.tool_response.files_analyzed).toHaveLength(2);
|
|
});
|
|
});
|