/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { TodoWriteTool } from './todoWrite.js';
import * as fs from 'fs/promises';
import * as fsSync from 'fs';
// Mock fs modules
vi.mock('fs/promises');
vi.mock('fs');
const mockFs = vi.mocked(fs);
const mockFsSync = vi.mocked(fsSync);
describe('TodoWriteTool', () => {
    let tool;
    let mockAbortSignal;
    let mockConfig;
    beforeEach(() => {
        mockConfig = {
            getSessionId: () => 'test-session-123',
        };
        tool = new TodoWriteTool(mockConfig);
        mockAbortSignal = new AbortController().signal;
        vi.clearAllMocks();
    });
    afterEach(() => {
        vi.restoreAllMocks();
    });
    describe('validateToolParams', () => {
        it('should validate correct parameters', () => {
            const params = {
                todos: [
                    { id: '1', content: 'Task 1', status: 'pending' },
                    { id: '2', content: 'Task 2', status: 'in_progress' },
                ],
            };
            const result = tool.validateToolParams(params);
            expect(result).toBeNull();
        });
        it('should accept empty todos array', () => {
            const params = {
                todos: [],
            };
            const result = tool.validateToolParams(params);
            expect(result).toBeNull();
        });
        it('should accept single todo', () => {
            const params = {
                todos: [{ id: '1', content: 'Task 1', status: 'pending' }],
            };
            const result = tool.validateToolParams(params);
            expect(result).toBeNull();
        });
        it('should reject todos with empty content', () => {
            const params = {
                todos: [
                    { id: '1', content: '', status: 'pending' },
                    { id: '2', content: 'Task 2', status: 'pending' },
                ],
            };
            const result = tool.validateToolParams(params);
            expect(result).toContain('Each todo must have a non-empty "content" string');
        });
        it('should reject todos with empty id', () => {
            const params = {
                todos: [
                    { id: '', content: 'Task 1', status: 'pending' },
                    { id: '2', content: 'Task 2', status: 'pending' },
                ],
            };
            const result = tool.validateToolParams(params);
            expect(result).toContain('non-empty "id" string');
        });
        it('should reject todos with invalid status', () => {
            const params = {
                todos: [
                    {
                        id: '1',
                        content: 'Task 1',
                        status: 'invalid',
                    },
                    { id: '2', content: 'Task 2', status: 'pending' },
                ],
            };
            const result = tool.validateToolParams(params);
            expect(result).toContain('Each todo must have a valid "status" (pending, in_progress, completed)');
        });
        it('should reject todos with duplicate IDs', () => {
            const params = {
                todos: [
                    { id: '1', content: 'Task 1', status: 'pending' },
                    { id: '1', content: 'Task 2', status: 'pending' },
                ],
            };
            const result = tool.validateToolParams(params);
            expect(result).toContain('unique');
        });
    });
    describe('execute', () => {
        it('should create new todos file when none exists', async () => {
            const params = {
                todos: [
                    { id: '1', content: 'Task 1', status: 'pending' },
                    { id: '2', content: 'Task 2', status: 'in_progress' },
                ],
            };
            // Mock file not existing
            mockFs.readFile.mockRejectedValue({ code: 'ENOENT' });
            mockFs.mkdir.mockResolvedValue(undefined);
            mockFs.writeFile.mockResolvedValue(undefined);
            const invocation = tool.build(params);
            const result = await invocation.execute(mockAbortSignal);
            expect(result.llmContent).toContain('success');
            expect(result.returnDisplay).toEqual({
                type: 'todo_list',
                todos: [
                    { id: '1', content: 'Task 1', status: 'pending' },
                    { id: '2', content: 'Task 2', status: 'in_progress' },
                ],
            });
            expect(mockFs.writeFile).toHaveBeenCalledWith(expect.stringContaining('test-session-123.json'), expect.stringContaining('"todos"'), 'utf-8');
        });
        it('should replace todos with new ones', async () => {
            const existingTodos = [
                { id: '1', content: 'Existing Task', status: 'completed' },
            ];
            const params = {
                todos: [
                    { id: '1', content: 'Updated Task', status: 'completed' },
                    { id: '2', content: 'New Task', status: 'pending' },
                ],
            };
            // Mock existing file
            mockFs.readFile.mockResolvedValue(JSON.stringify({ todos: existingTodos }));
            mockFs.mkdir.mockResolvedValue(undefined);
            mockFs.writeFile.mockResolvedValue(undefined);
            const invocation = tool.build(params);
            const result = await invocation.execute(mockAbortSignal);
            expect(result.llmContent).toContain('success');
            expect(result.returnDisplay).toEqual({
                type: 'todo_list',
                todos: [
                    { id: '1', content: 'Updated Task', status: 'completed' },
                    { id: '2', content: 'New Task', status: 'pending' },
                ],
            });
            expect(mockFs.writeFile).toHaveBeenCalledWith(expect.stringContaining('test-session-123.json'), expect.stringMatching(/"Updated Task"/), 'utf-8');
        });
        it('should handle file write errors', async () => {
            const params = {
                todos: [
                    { id: '1', content: 'Task 1', status: 'pending' },
                    { id: '2', content: 'Task 2', status: 'pending' },
                ],
            };
            mockFs.readFile.mockRejectedValue({ code: 'ENOENT' });
            mockFs.mkdir.mockResolvedValue(undefined);
            mockFs.writeFile.mockRejectedValue(new Error('Write failed'));
            const invocation = tool.build(params);
            const result = await invocation.execute(mockAbortSignal);
            expect(result.llmContent).toContain('"success":false');
            expect(result.returnDisplay).toContain('Error writing todos');
        });
        it('should handle empty todos array', async () => {
            const params = {
                todos: [],
            };
            mockFs.mkdir.mockResolvedValue(undefined);
            mockFs.writeFile.mockResolvedValue(undefined);
            const invocation = tool.build(params);
            const result = await invocation.execute(mockAbortSignal);
            expect(result.llmContent).toContain('success');
            expect(result.returnDisplay).toEqual({
                type: 'todo_list',
                todos: [],
            });
            expect(mockFs.writeFile).toHaveBeenCalledWith(expect.stringContaining('test-session-123.json'), expect.stringContaining('"todos"'), 'utf-8');
        });
    });
    describe('tool properties', () => {
        it('should have correct tool name', () => {
            expect(TodoWriteTool.Name).toBe('todo_write');
            expect(tool.name).toBe('todo_write');
        });
        it('should have correct display name', () => {
            expect(tool.displayName).toBe('Todo Write');
        });
        it('should have correct kind', () => {
            expect(tool.kind).toBe('think');
        });
        it('should have schema with required properties', () => {
            const schema = tool.schema;
            expect(schema.name).toBe('todo_write');
            expect(schema.parametersJsonSchema).toHaveProperty('properties.todos');
            expect(schema.parametersJsonSchema).not.toHaveProperty('properties.merge');
        });
    });
    describe('getDescription', () => {
        it('should return "Create todos" when no todos file exists', () => {
            // Mock existsSync to return false (file doesn't exist)
            mockFsSync.existsSync.mockReturnValue(false);
            const params = {
                todos: [{ id: '1', content: 'Test todo', status: 'pending' }],
            };
            const invocation = tool.build(params);
            expect(invocation.getDescription()).toBe('Create todos');
        });
        it('should return "Update todos" when todos file exists', () => {
            // Mock existsSync to return true (file exists)
            mockFsSync.existsSync.mockReturnValue(true);
            const params = {
                todos: [
                    { id: '1', content: 'Updated todo', status: 'completed' },
                ],
            };
            const invocation = tool.build(params);
            expect(invocation.getDescription()).toBe('Update todos');
        });
    });
});
//# sourceMappingURL=todoWrite.test.js.map