feat: Migrate Task Master to generateObject for structured AI responses (#1262)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Ben Vargas <ben@example.com>
This commit is contained in:
@@ -43,7 +43,23 @@ jest.unstable_mockModule(
|
||||
() => ({
|
||||
generateTextService: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ mainResult: { content: '{}' }, telemetryData: {} })
|
||||
.mockResolvedValue({ mainResult: { content: '{}' }, telemetryData: {} }),
|
||||
generateObjectService: jest.fn().mockResolvedValue({
|
||||
mainResult: {
|
||||
task: {
|
||||
id: 1,
|
||||
title: 'Updated Task',
|
||||
description: 'Updated description',
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
priority: 'medium',
|
||||
details: null,
|
||||
testStrategy: null,
|
||||
subtasks: []
|
||||
}
|
||||
},
|
||||
telemetryData: {}
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
@@ -120,3 +136,206 @@ describe('updateTaskById validation', () => {
|
||||
expect(log).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTaskById success path with generateObjectService', () => {
|
||||
let fs;
|
||||
let generateObjectService;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error('process.exit called');
|
||||
});
|
||||
fs = await import('fs');
|
||||
const aiServices = await import(
|
||||
'../../../../../scripts/modules/ai-services-unified.js'
|
||||
);
|
||||
generateObjectService = aiServices.generateObjectService;
|
||||
});
|
||||
|
||||
test('successfully updates task with all fields from generateObjectService', async () => {
|
||||
fs.existsSync.mockReturnValue(true);
|
||||
readJSON.mockReturnValue({
|
||||
tag: 'master',
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Original Task',
|
||||
description: 'Original description',
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
priority: 'low',
|
||||
details: null,
|
||||
testStrategy: null,
|
||||
subtasks: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const updatedTaskData = {
|
||||
id: 1,
|
||||
title: 'Updated Task',
|
||||
description: 'Updated description',
|
||||
status: 'pending',
|
||||
dependencies: [2],
|
||||
priority: 'high',
|
||||
details: 'New implementation details',
|
||||
testStrategy: 'Unit tests required',
|
||||
subtasks: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Subtask 1',
|
||||
description: 'First subtask',
|
||||
status: 'pending',
|
||||
dependencies: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
generateObjectService.mockResolvedValue({
|
||||
mainResult: {
|
||||
task: updatedTaskData
|
||||
},
|
||||
telemetryData: {
|
||||
model: 'claude-3-5-sonnet-20241022',
|
||||
inputTokens: 100,
|
||||
outputTokens: 200
|
||||
}
|
||||
});
|
||||
|
||||
const result = await updateTaskById(
|
||||
'tasks/tasks.json',
|
||||
1,
|
||||
'Update task with new requirements',
|
||||
false,
|
||||
{ tag: 'master' },
|
||||
'json'
|
||||
);
|
||||
|
||||
// Verify generateObjectService was called (not generateTextService)
|
||||
expect(generateObjectService).toHaveBeenCalled();
|
||||
const callArgs = generateObjectService.mock.calls[0][0];
|
||||
|
||||
// Verify correct arguments were passed
|
||||
expect(callArgs).toMatchObject({
|
||||
role: 'main',
|
||||
commandName: 'update-task',
|
||||
objectName: 'task'
|
||||
});
|
||||
expect(callArgs.schema).toBeDefined();
|
||||
expect(callArgs.systemPrompt).toContain(
|
||||
'update a software development task'
|
||||
);
|
||||
expect(callArgs.prompt).toContain('Update task with new requirements');
|
||||
|
||||
// Verify the returned task contains all expected fields
|
||||
expect(result).toEqual({
|
||||
updatedTask: expect.objectContaining({
|
||||
id: 1,
|
||||
title: 'Updated Task',
|
||||
description: 'Updated description',
|
||||
status: 'pending',
|
||||
dependencies: [2],
|
||||
priority: 'high',
|
||||
details: 'New implementation details',
|
||||
testStrategy: 'Unit tests required',
|
||||
subtasks: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 1,
|
||||
title: 'Subtask 1',
|
||||
description: 'First subtask',
|
||||
status: 'pending'
|
||||
})
|
||||
])
|
||||
}),
|
||||
telemetryData: expect.objectContaining({
|
||||
model: 'claude-3-5-sonnet-20241022',
|
||||
inputTokens: 100,
|
||||
outputTokens: 200
|
||||
}),
|
||||
tagInfo: undefined
|
||||
});
|
||||
});
|
||||
|
||||
test('handles generateObjectService with malformed mainResult', async () => {
|
||||
fs.existsSync.mockReturnValue(true);
|
||||
readJSON.mockReturnValue({
|
||||
tag: 'master',
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Task',
|
||||
description: 'Description',
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
priority: 'medium',
|
||||
details: null,
|
||||
testStrategy: null,
|
||||
subtasks: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
generateObjectService.mockResolvedValue({
|
||||
mainResult: {
|
||||
task: null // Malformed: task is null
|
||||
},
|
||||
telemetryData: {}
|
||||
});
|
||||
|
||||
await expect(
|
||||
updateTaskById(
|
||||
'tasks/tasks.json',
|
||||
1,
|
||||
'Update task',
|
||||
false,
|
||||
{ tag: 'master' },
|
||||
'json'
|
||||
)
|
||||
).rejects.toThrow('Received invalid task object from AI');
|
||||
});
|
||||
|
||||
test('handles generateObjectService with missing required fields', async () => {
|
||||
fs.existsSync.mockReturnValue(true);
|
||||
readJSON.mockReturnValue({
|
||||
tag: 'master',
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Task',
|
||||
description: 'Description',
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
priority: 'medium',
|
||||
details: null,
|
||||
testStrategy: null,
|
||||
subtasks: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
generateObjectService.mockResolvedValue({
|
||||
mainResult: {
|
||||
task: {
|
||||
id: 1,
|
||||
// Missing title and description
|
||||
status: 'pending',
|
||||
dependencies: [],
|
||||
priority: 'medium'
|
||||
}
|
||||
},
|
||||
telemetryData: {}
|
||||
});
|
||||
|
||||
await expect(
|
||||
updateTaskById(
|
||||
'tasks/tasks.json',
|
||||
1,
|
||||
'Update task',
|
||||
false,
|
||||
{ tag: 'master' },
|
||||
'json'
|
||||
)
|
||||
).rejects.toThrow('Updated task missing required fields');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user