/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Task } from './task.js';
import { MessageBusType, CoreToolCallStatus, } from '@google/gemini-cli-core';
import { createMockConfig } from '../utils/testing_utils.js';
describe('Task Race Condition', () => {
    let mockConfig;
    let messageBus;
    beforeEach(() => {
        messageBus = {
            subscribe: vi.fn(),
            unsubscribe: vi.fn(),
            publish: vi.fn(),
        };
        mockConfig = createMockConfig({
            messageBus,
        });
    });
    it('should not hang when multiple tool confirmations are processed while waiting', async () => {
        // @ts-expect-error - private constructor
        const task = new Task('task-id', 'context-id', mockConfig);
        // 1. Register two tools as scheduled
        task['_registerToolCall']('tool-1', 'scheduled');
        task['_registerToolCall']('tool-2', 'scheduled');
        // 2. Both transition to awaiting_approval
        const updateHandler = messageBus.subscribe.mock.calls.find((c) => c[0] === MessageBusType.TOOL_CALLS_UPDATE)?.[1];
        updateHandler({
            type: MessageBusType.TOOL_CALLS_UPDATE,
            schedulerId: 'task-id',
            toolCalls: [
                {
                    request: { callId: 'tool-1', name: 't1' },
                    status: CoreToolCallStatus.AwaitingApproval,
                    correlationId: 'corr-1',
                    confirmationDetails: { type: 'info' },
                },
                {
                    request: { callId: 'tool-2', name: 't2' },
                    status: CoreToolCallStatus.AwaitingApproval,
                    correlationId: 'corr-2',
                    confirmationDetails: { type: 'info' },
                },
            ],
        });
        // 3. Confirm Tool 1. This makes isAwaitingApprovalOnly() return false.
        for await (const _ of task.acceptUserMessage({
            userMessage: {
                parts: [
                    {
                        kind: 'data',
                        data: { callId: 'tool-1', outcome: 'proceed_once' },
                    },
                ],
            },
        }, new AbortController().signal)) {
            // consume generator
        }
        // 4. Start waiting. This should now block because Tool 1 is confirmed (so we are waiting for its execution).
        const waitPromise = task.waitForPendingTools();
        // 5. Confirm Tool 2 while waiting.
        for await (const _ of task.acceptUserMessage({
            userMessage: {
                parts: [
                    {
                        kind: 'data',
                        data: { callId: 'tool-2', outcome: 'proceed_once' },
                    },
                ],
            },
        }, new AbortController().signal)) {
            // consume generator
        }
        // 6. Both tools complete successfully
        updateHandler({
            type: MessageBusType.TOOL_CALLS_UPDATE,
            schedulerId: 'task-id',
            toolCalls: [
                {
                    request: { callId: 'tool-1', name: 't1' },
                    status: CoreToolCallStatus.Success,
                    response: { responseParts: [] },
                },
                {
                    request: { callId: 'tool-2', name: 't2' },
                    status: CoreToolCallStatus.Success,
                    response: { responseParts: [] },
                },
            ],
        });
        // 7. Verify that the original waitPromise resolves.
        await expect(waitPromise).resolves.toBeUndefined();
    });
    it('should reject waitForPendingTools when tools are cancelled', async () => {
        // @ts-expect-error - private constructor
        const task = new Task('task-id', 'context-id', mockConfig);
        // 1. Register a tool
        task['_registerToolCall']('tool-1', 'scheduled');
        // 2. Start waiting
        const waitPromise = task.waitForPendingTools();
        // 3. Cancel pending tools
        task.cancelPendingTools('User requested cancellation');
        // 4. Verify waitPromise rejects with the reason
        await expect(waitPromise).rejects.toThrow('User requested cancellation');
    });
    it('should handle concurrent tool scheduling correctly', async () => {
        // @ts-expect-error - private constructor
        const task = new Task('task-id', 'context-id', mockConfig);
        // 1. Register a tool and start waiting
        task['_registerToolCall']('tool-1', 'scheduled');
        const waitPromise = task.waitForPendingTools();
        // 2. Schedule another tool concurrently (e.g. from a secondary user message)
        // This should NOT resolve the current waitPromise until both are done
        await task.scheduleToolCalls([{ callId: 'tool-2', name: 't2', args: {} }], new AbortController().signal);
        expect(task['pendingToolCalls'].size).toBe(2);
        // 3. Resolve tool 1
        task['_resolveToolCall']('tool-1');
        // 4. Verify waitPromise is still pending
        let resolved = false;
        waitPromise.then(() => (resolved = true));
        await new Promise((resolve) => setTimeout(resolve, 10));
        expect(resolved).toBe(false);
        // 5. Resolve tool 2
        task['_resolveToolCall']('tool-2');
        // 6. Now it should resolve
        await expect(waitPromise).resolves.toBeUndefined();
    });
});
//# sourceMappingURL=race-condition.test.js.map