/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { updateExtension, updateAllUpdatableExtensions, checkForAllExtensionUpdates, } from './update.js';
import { ExtensionUpdateState, } from '../../ui/state/extensions.js';
import { ExtensionStorage } from './storage.js';
import { copyExtension } from '../extension-manager.js';
import { checkForExtensionUpdate } from './github.js';
import { loadInstallMetadata } from '../extension.js';
import * as fs from 'node:fs';
// Mock dependencies
vi.mock('./storage.js', () => ({
    ExtensionStorage: {
        createTmpDir: vi.fn(),
    },
}));
vi.mock('../extension-manager.js', () => ({
    copyExtension: vi.fn(),
    // We don't need to mock the class implementation if we pass a mock instance
}));
vi.mock('./github.js', () => ({
    checkForExtensionUpdate: vi.fn(),
}));
vi.mock('../extension.js', () => ({
    loadInstallMetadata: vi.fn(),
}));
vi.mock('node:fs', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        promises: {
            ...actual.promises,
            rm: vi.fn(),
        },
    };
});
describe('Extension Update Logic', () => {
    let mockExtensionManager;
    let mockDispatch;
    const mockExtension = {
        name: 'test-extension',
        version: '1.0.0',
        path: '/path/to/extension',
    };
    beforeEach(() => {
        vi.clearAllMocks();
        mockExtensionManager = {
            loadExtensionConfig: vi.fn(),
            installOrUpdateExtension: vi.fn(),
        };
        mockDispatch = vi.fn();
        // Default mock behaviors
        vi.mocked(ExtensionStorage.createTmpDir).mockResolvedValue('/tmp/mock-dir');
        vi.mocked(loadInstallMetadata).mockReturnValue({
            source: 'https://example.com/repo.git',
            type: 'git',
        });
    });
    describe('updateExtension', () => {
        it('should return undefined if state is already UPDATING', async () => {
            const result = await updateExtension(mockExtension, mockExtensionManager, ExtensionUpdateState.UPDATING, mockDispatch);
            expect(result).toBeUndefined();
            expect(mockDispatch).not.toHaveBeenCalled();
        });
        it('should throw error and set state to ERROR if install metadata type is unknown', async () => {
            vi.mocked(loadInstallMetadata).mockReturnValue({
                type: undefined,
            });
            await expect(updateExtension(mockExtension, mockExtensionManager, ExtensionUpdateState.UPDATE_AVAILABLE, mockDispatch)).rejects.toThrow('type is unknown');
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.UPDATING,
                },
            });
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.ERROR,
                },
            });
        });
        it('should throw error and set state to UP_TO_DATE if extension is linked', async () => {
            vi.mocked(loadInstallMetadata).mockReturnValue({
                type: 'link',
                source: '',
            });
            await expect(updateExtension(mockExtension, mockExtensionManager, ExtensionUpdateState.UPDATE_AVAILABLE, mockDispatch)).rejects.toThrow('Extension is linked');
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.UP_TO_DATE,
                },
            });
        });
        it('should successfully update extension and set state to UPDATED_NEEDS_RESTART by default', async () => {
            vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(Promise.resolve({
                name: 'test-extension',
                version: '1.0.0',
            }));
            vi.mocked(mockExtensionManager.installOrUpdateExtension).mockResolvedValue({
                ...mockExtension,
                version: '1.1.0',
            });
            const result = await updateExtension(mockExtension, mockExtensionManager, ExtensionUpdateState.UPDATE_AVAILABLE, mockDispatch);
            expect(mockExtensionManager.installOrUpdateExtension).toHaveBeenCalled();
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.UPDATED_NEEDS_RESTART,
                },
            });
            expect(result).toEqual({
                name: 'test-extension',
                originalVersion: '1.0.0',
                updatedVersion: '1.1.0',
            });
            expect(fs.promises.rm).toHaveBeenCalledWith('/tmp/mock-dir', {
                recursive: true,
                force: true,
            });
        });
        it('should set state to UPDATED if enableExtensionReloading is true', async () => {
            vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(Promise.resolve({
                name: 'test-extension',
                version: '1.0.0',
            }));
            vi.mocked(mockExtensionManager.installOrUpdateExtension).mockResolvedValue({
                ...mockExtension,
                version: '1.1.0',
            });
            await updateExtension(mockExtension, mockExtensionManager, ExtensionUpdateState.UPDATE_AVAILABLE, mockDispatch, true);
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.UPDATED,
                },
            });
        });
        it('should rollback and set state to ERROR if installation fails', async () => {
            vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(Promise.resolve({
                name: 'test-extension',
                version: '1.0.0',
            }));
            vi.mocked(mockExtensionManager.installOrUpdateExtension).mockRejectedValue(new Error('Install failed'));
            await expect(updateExtension(mockExtension, mockExtensionManager, ExtensionUpdateState.UPDATE_AVAILABLE, mockDispatch)).rejects.toThrow('Updated extension not found after installation');
            expect(copyExtension).toHaveBeenCalledWith('/tmp/mock-dir', mockExtension.path);
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.ERROR,
                },
            });
            expect(fs.promises.rm).toHaveBeenCalled();
        });
    });
    describe('updateAllUpdatableExtensions', () => {
        it('should update all extensions with UPDATE_AVAILABLE status', async () => {
            const extensions = [
                { ...mockExtension, name: 'ext1' },
                { ...mockExtension, name: 'ext2' },
                { ...mockExtension, name: 'ext3' },
            ];
            const extensionsState = new Map([
                ['ext1', { status: ExtensionUpdateState.UPDATE_AVAILABLE }],
                ['ext2', { status: ExtensionUpdateState.UP_TO_DATE }],
                ['ext3', { status: ExtensionUpdateState.UPDATE_AVAILABLE }],
            ]);
            vi.mocked(mockExtensionManager.loadExtensionConfig).mockReturnValue(Promise.resolve({
                name: 'ext',
                version: '1.0.0',
            }));
            vi.mocked(mockExtensionManager.installOrUpdateExtension).mockResolvedValue({ ...mockExtension, version: '1.1.0' });
            const results = await updateAllUpdatableExtensions(extensions, extensionsState, mockExtensionManager, mockDispatch);
            expect(results).toHaveLength(2);
            expect(results.map((r) => r.name)).toEqual(['ext1', 'ext3']);
            expect(mockExtensionManager.installOrUpdateExtension).toHaveBeenCalledTimes(2);
        });
    });
    describe('checkForAllExtensionUpdates', () => {
        it('should dispatch BATCH_CHECK_START and BATCH_CHECK_END', async () => {
            await checkForAllExtensionUpdates([], mockExtensionManager, mockDispatch);
            expect(mockDispatch).toHaveBeenCalledWith({ type: 'BATCH_CHECK_START' });
            expect(mockDispatch).toHaveBeenCalledWith({ type: 'BATCH_CHECK_END' });
        });
        it('should set state to NOT_UPDATABLE if no install metadata', async () => {
            const extensions = [
                { ...mockExtension, installMetadata: undefined },
            ];
            await checkForAllExtensionUpdates(extensions, mockExtensionManager, mockDispatch);
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.NOT_UPDATABLE,
                },
            });
        });
        it('should check for updates and update state', async () => {
            const extensions = [
                { ...mockExtension, installMetadata: { type: 'git', source: '...' } },
            ];
            vi.mocked(checkForExtensionUpdate).mockResolvedValue(ExtensionUpdateState.UPDATE_AVAILABLE);
            await checkForAllExtensionUpdates(extensions, mockExtensionManager, mockDispatch);
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.CHECKING_FOR_UPDATES,
                },
            });
            expect(mockDispatch).toHaveBeenCalledWith({
                type: 'SET_STATE',
                payload: {
                    name: mockExtension.name,
                    state: ExtensionUpdateState.UPDATE_AVAILABLE,
                },
            });
        });
    });
});
//# sourceMappingURL=update.test.js.map