feat(#71): implement graph data API
Implemented three new API endpoints for knowledge graph visualization: 1. GET /api/knowledge/graph - Full knowledge graph - Returns all entries and links with optional filtering - Supports filtering by tags, status, and node count limit - Includes orphan detection (entries with no links) 2. GET /api/knowledge/graph/stats - Graph statistics - Total entries and links counts - Orphan entries detection - Average links per entry - Top 10 most connected entries - Tag distribution across entries 3. GET /api/knowledge/graph/:slug - Entry-centered subgraph - Returns graph centered on specific entry - Supports depth parameter (1-5) for traversal distance - Includes all connected nodes up to specified depth New Files: - apps/api/src/knowledge/graph.controller.ts - apps/api/src/knowledge/graph.controller.spec.ts Modified Files: - apps/api/src/knowledge/dto/graph-query.dto.ts (added GraphFilterDto) - apps/api/src/knowledge/entities/graph.entity.ts (extended with new types) - apps/api/src/knowledge/services/graph.service.ts (added new methods) - apps/api/src/knowledge/services/graph.service.spec.ts (added tests) - apps/api/src/knowledge/knowledge.module.ts (registered controller) - apps/api/src/knowledge/dto/index.ts (exported new DTOs) - docs/scratchpads/71-graph-data-api.md (implementation notes) Test Coverage: 21 tests (all passing) - 14 service tests including orphan detection, filtering, statistics - 7 controller tests for all three endpoints Follows TDD principles with tests written before implementation. All code quality gates passed (lint, typecheck, tests). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
615
apps/orchestrator/src/spawner/agent-lifecycle.service.spec.ts
Normal file
615
apps/orchestrator/src/spawner/agent-lifecycle.service.spec.ts
Normal file
@@ -0,0 +1,615 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { AgentLifecycleService } from './agent-lifecycle.service';
|
||||
import { ValkeyService } from '../valkey/valkey.service';
|
||||
import type { AgentState } from '../valkey/types';
|
||||
|
||||
describe('AgentLifecycleService', () => {
|
||||
let service: AgentLifecycleService;
|
||||
let mockValkeyService: {
|
||||
getAgentState: ReturnType<typeof vi.fn>;
|
||||
setAgentState: ReturnType<typeof vi.fn>;
|
||||
updateAgentStatus: ReturnType<typeof vi.fn>;
|
||||
publishEvent: ReturnType<typeof vi.fn>;
|
||||
listAgents: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
const mockAgentId = 'test-agent-123';
|
||||
const mockTaskId = 'test-task-456';
|
||||
|
||||
beforeEach(() => {
|
||||
// Create mocks
|
||||
mockValkeyService = {
|
||||
getAgentState: vi.fn(),
|
||||
setAgentState: vi.fn(),
|
||||
updateAgentStatus: vi.fn(),
|
||||
publishEvent: vi.fn(),
|
||||
listAgents: vi.fn(),
|
||||
};
|
||||
|
||||
// Create service with mock
|
||||
service = new AgentLifecycleService(mockValkeyService as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('transitionToRunning', () => {
|
||||
it('should transition from spawning to running', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'spawning',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'running',
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
});
|
||||
|
||||
const result = await service.transitionToRunning(mockAgentId);
|
||||
|
||||
expect(result.status).toBe('running');
|
||||
expect(result.startedAt).toBeDefined();
|
||||
expect(mockValkeyService.updateAgentStatus).toHaveBeenCalledWith(
|
||||
mockAgentId,
|
||||
'running',
|
||||
undefined,
|
||||
);
|
||||
expect(mockValkeyService.publishEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'agent.running',
|
||||
agentId: mockAgentId,
|
||||
taskId: mockTaskId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if agent not found', async () => {
|
||||
mockValkeyService.getAgentState.mockResolvedValue(null);
|
||||
|
||||
await expect(service.transitionToRunning(mockAgentId)).rejects.toThrow(
|
||||
`Agent ${mockAgentId} not found`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid transition from running', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
|
||||
await expect(service.transitionToRunning(mockAgentId)).rejects.toThrow(
|
||||
'Invalid state transition from running to running',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid transition from completed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'completed',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
|
||||
await expect(service.transitionToRunning(mockAgentId)).rejects.toThrow(
|
||||
'Invalid state transition from completed to running',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitionToCompleted', () => {
|
||||
it('should transition from running to completed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'completed',
|
||||
completedAt: expect.any(String),
|
||||
});
|
||||
|
||||
const result = await service.transitionToCompleted(mockAgentId);
|
||||
|
||||
expect(result.status).toBe('completed');
|
||||
expect(result.completedAt).toBeDefined();
|
||||
expect(mockValkeyService.updateAgentStatus).toHaveBeenCalledWith(
|
||||
mockAgentId,
|
||||
'completed',
|
||||
undefined,
|
||||
);
|
||||
expect(mockValkeyService.publishEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'agent.completed',
|
||||
agentId: mockAgentId,
|
||||
taskId: mockTaskId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if agent not found', async () => {
|
||||
mockValkeyService.getAgentState.mockResolvedValue(null);
|
||||
|
||||
await expect(service.transitionToCompleted(mockAgentId)).rejects.toThrow(
|
||||
`Agent ${mockAgentId} not found`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid transition from spawning', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'spawning',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
|
||||
await expect(service.transitionToCompleted(mockAgentId)).rejects.toThrow(
|
||||
'Invalid state transition from spawning to completed',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitionToFailed', () => {
|
||||
it('should transition from spawning to failed with error', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'spawning',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
const errorMessage = 'Failed to spawn agent';
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'failed',
|
||||
error: errorMessage,
|
||||
completedAt: expect.any(String),
|
||||
});
|
||||
|
||||
const result = await service.transitionToFailed(mockAgentId, errorMessage);
|
||||
|
||||
expect(result.status).toBe('failed');
|
||||
expect(result.error).toBe(errorMessage);
|
||||
expect(result.completedAt).toBeDefined();
|
||||
expect(mockValkeyService.updateAgentStatus).toHaveBeenCalledWith(
|
||||
mockAgentId,
|
||||
'failed',
|
||||
errorMessage,
|
||||
);
|
||||
expect(mockValkeyService.publishEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'agent.failed',
|
||||
agentId: mockAgentId,
|
||||
taskId: mockTaskId,
|
||||
error: errorMessage,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should transition from running to failed with error', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
const errorMessage = 'Runtime error occurred';
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'failed',
|
||||
error: errorMessage,
|
||||
completedAt: expect.any(String),
|
||||
});
|
||||
|
||||
const result = await service.transitionToFailed(mockAgentId, errorMessage);
|
||||
|
||||
expect(result.status).toBe('failed');
|
||||
expect(result.error).toBe(errorMessage);
|
||||
});
|
||||
|
||||
it('should throw error if agent not found', async () => {
|
||||
mockValkeyService.getAgentState.mockResolvedValue(null);
|
||||
|
||||
await expect(service.transitionToFailed(mockAgentId, 'Error')).rejects.toThrow(
|
||||
`Agent ${mockAgentId} not found`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid transition from completed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'completed',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
|
||||
await expect(service.transitionToFailed(mockAgentId, 'Error')).rejects.toThrow(
|
||||
'Invalid state transition from completed to failed',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitionToKilled', () => {
|
||||
it('should transition from spawning to killed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'spawning',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'killed',
|
||||
completedAt: expect.any(String),
|
||||
});
|
||||
|
||||
const result = await service.transitionToKilled(mockAgentId);
|
||||
|
||||
expect(result.status).toBe('killed');
|
||||
expect(result.completedAt).toBeDefined();
|
||||
expect(mockValkeyService.updateAgentStatus).toHaveBeenCalledWith(
|
||||
mockAgentId,
|
||||
'killed',
|
||||
undefined,
|
||||
);
|
||||
expect(mockValkeyService.publishEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'agent.killed',
|
||||
agentId: mockAgentId,
|
||||
taskId: mockTaskId,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should transition from running to killed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'killed',
|
||||
completedAt: expect.any(String),
|
||||
});
|
||||
|
||||
const result = await service.transitionToKilled(mockAgentId);
|
||||
|
||||
expect(result.status).toBe('killed');
|
||||
});
|
||||
|
||||
it('should throw error if agent not found', async () => {
|
||||
mockValkeyService.getAgentState.mockResolvedValue(null);
|
||||
|
||||
await expect(service.transitionToKilled(mockAgentId)).rejects.toThrow(
|
||||
`Agent ${mockAgentId} not found`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid transition from completed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'completed',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
|
||||
await expect(service.transitionToKilled(mockAgentId)).rejects.toThrow(
|
||||
'Invalid state transition from completed to killed',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAgentLifecycleState', () => {
|
||||
it('should return agent state from Valkey', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
|
||||
const result = await service.getAgentLifecycleState(mockAgentId);
|
||||
|
||||
expect(result).toEqual(mockState);
|
||||
expect(mockValkeyService.getAgentState).toHaveBeenCalledWith(mockAgentId);
|
||||
});
|
||||
|
||||
it('should return null if agent not found', async () => {
|
||||
mockValkeyService.getAgentState.mockResolvedValue(null);
|
||||
|
||||
const result = await service.getAgentLifecycleState(mockAgentId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('listAgentLifecycleStates', () => {
|
||||
it('should return all agent states from Valkey', async () => {
|
||||
const mockStates: AgentState[] = [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
status: 'running',
|
||||
taskId: 'task-1',
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
},
|
||||
{
|
||||
agentId: 'agent-2',
|
||||
status: 'completed',
|
||||
taskId: 'task-2',
|
||||
startedAt: '2026-02-02T09:00:00Z',
|
||||
completedAt: '2026-02-02T10:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
mockValkeyService.listAgents.mockResolvedValue(mockStates);
|
||||
|
||||
const result = await service.listAgentLifecycleStates();
|
||||
|
||||
expect(result).toEqual(mockStates);
|
||||
expect(mockValkeyService.listAgents).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return empty array if no agents', async () => {
|
||||
mockValkeyService.listAgents.mockResolvedValue([]);
|
||||
|
||||
const result = await service.listAgentLifecycleStates();
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('state persistence', () => {
|
||||
it('should update completedAt timestamp on terminal states', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
|
||||
let capturedState: AgentState | undefined;
|
||||
mockValkeyService.updateAgentStatus.mockImplementation(async (agentId, status, error) => {
|
||||
capturedState = {
|
||||
...mockState,
|
||||
status,
|
||||
error,
|
||||
completedAt: new Date().toISOString(),
|
||||
};
|
||||
return capturedState;
|
||||
});
|
||||
|
||||
await service.transitionToCompleted(mockAgentId);
|
||||
|
||||
expect(capturedState?.completedAt).toBeDefined();
|
||||
});
|
||||
|
||||
it('should preserve startedAt timestamp through transitions', async () => {
|
||||
const startedAt = '2026-02-02T10:00:00Z';
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'completed',
|
||||
completedAt: '2026-02-02T11:00:00Z',
|
||||
});
|
||||
|
||||
const result = await service.transitionToCompleted(mockAgentId);
|
||||
|
||||
expect(result.startedAt).toBe(startedAt);
|
||||
});
|
||||
|
||||
it('should set startedAt if not already set when transitioning to running', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'spawning',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'running',
|
||||
// No startedAt in response
|
||||
});
|
||||
mockValkeyService.setAgentState.mockResolvedValue(undefined);
|
||||
|
||||
await service.transitionToRunning(mockAgentId);
|
||||
|
||||
expect(mockValkeyService.setAgentState).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
startedAt: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set startedAt if already present in response', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'spawning',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'running',
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
});
|
||||
|
||||
await service.transitionToRunning(mockAgentId);
|
||||
|
||||
// Should not call setAgentState since startedAt is already present
|
||||
expect(mockValkeyService.setAgentState).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set completedAt if not already set when transitioning to completed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'completed',
|
||||
// No completedAt in response
|
||||
});
|
||||
mockValkeyService.setAgentState.mockResolvedValue(undefined);
|
||||
|
||||
await service.transitionToCompleted(mockAgentId);
|
||||
|
||||
expect(mockValkeyService.setAgentState).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentId: mockAgentId,
|
||||
status: 'completed',
|
||||
completedAt: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set completedAt if not already set when transitioning to failed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'failed',
|
||||
error: 'Test error',
|
||||
// No completedAt in response
|
||||
});
|
||||
mockValkeyService.setAgentState.mockResolvedValue(undefined);
|
||||
|
||||
await service.transitionToFailed(mockAgentId, 'Test error');
|
||||
|
||||
expect(mockValkeyService.setAgentState).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentId: mockAgentId,
|
||||
status: 'failed',
|
||||
completedAt: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set completedAt if not already set when transitioning to killed', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'killed',
|
||||
// No completedAt in response
|
||||
});
|
||||
mockValkeyService.setAgentState.mockResolvedValue(undefined);
|
||||
|
||||
await service.transitionToKilled(mockAgentId);
|
||||
|
||||
expect(mockValkeyService.setAgentState).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentId: mockAgentId,
|
||||
status: 'killed',
|
||||
completedAt: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('event emission', () => {
|
||||
it('should emit events with correct structure', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'spawning',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'running',
|
||||
startedAt: '2026-02-02T10:00:00Z',
|
||||
});
|
||||
|
||||
await service.transitionToRunning(mockAgentId);
|
||||
|
||||
expect(mockValkeyService.publishEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'agent.running',
|
||||
agentId: mockAgentId,
|
||||
taskId: mockTaskId,
|
||||
timestamp: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should include error in failed event', async () => {
|
||||
const mockState: AgentState = {
|
||||
agentId: mockAgentId,
|
||||
status: 'running',
|
||||
taskId: mockTaskId,
|
||||
};
|
||||
const errorMessage = 'Test error';
|
||||
|
||||
mockValkeyService.getAgentState.mockResolvedValue(mockState);
|
||||
mockValkeyService.updateAgentStatus.mockResolvedValue({
|
||||
...mockState,
|
||||
status: 'failed',
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
await service.transitionToFailed(mockAgentId, errorMessage);
|
||||
|
||||
expect(mockValkeyService.publishEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'agent.failed',
|
||||
agentId: mockAgentId,
|
||||
taskId: mockTaskId,
|
||||
error: errorMessage,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user