/**
 * @license
 * Copyright 2025 Vybestack LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { QwenOAuthProvider } from './qwen-oauth-provider.js';
// Mock the ClipboardService class - do this before importing it
vi.mock('../services/ClipboardService.js', () => ({
    ClipboardService: {
        copyToClipboard: vi.fn().mockResolvedValue(undefined),
    },
}));
// Import the mocked ClipboardService for test assertions
import { ClipboardService } from '../services/ClipboardService.js';
// Mock runtimeSettings to provide getEphemeralSetting
vi.mock('../runtime/runtimeSettings.js', () => ({
    getEphemeralSetting: vi.fn().mockReturnValue(undefined),
}));
// Mock the device flow implementation
vi.mock('@vybestack/llxprt-code-core', async () => {
    const actual = await vi.importActual('@vybestack/llxprt-code-core');
    return {
        ...actual,
        QwenDeviceFlow: vi.fn().mockImplementation(() => ({
            initiateDeviceFlow: vi.fn().mockResolvedValue({
                device_code: 'mock-device-code',
                user_code: 'mock-user-code',
                verification_uri: 'https://chat.qwen.ai/activate',
                verification_uri_complete: 'https://chat.qwen.ai/activate?user_code=mock-user-code',
            }),
            pollForToken: vi.fn().mockResolvedValue({
                access_token: 'mock-access-token',
                refresh_token: 'mock-refresh-token',
                expiry: Math.floor(Date.now() / 1000) + 3600,
                token_type: 'Bearer',
                scope: 'openid profile email model.completion',
            }),
        })),
        // Mock shouldLaunchBrowser to return true for tests
        shouldLaunchBrowser: vi.fn().mockReturnValue(true),
        // Mock openBrowserSecurely to prevent actual browser opening
        openBrowserSecurely: vi.fn().mockResolvedValue(undefined),
    };
});
describe('QwenOAuthProvider', () => {
    let provider;
    let mockTokenStore;
    let mockAddItem;
    beforeEach(() => {
        vi.clearAllMocks();
        mockTokenStore = {
            getToken: vi.fn().mockResolvedValue(null),
            saveToken: vi.fn().mockResolvedValue(undefined),
            removeToken: vi.fn().mockResolvedValue(undefined),
            listProviders: vi.fn().mockResolvedValue([]),
            listBuckets: vi.fn().mockResolvedValue(['default']),
            getBucketStats: vi.fn().mockResolvedValue(null),
            acquireRefreshLock: vi.fn().mockResolvedValue(true),
            releaseRefreshLock: vi.fn().mockResolvedValue(undefined),
            acquireAuthLock: vi.fn().mockResolvedValue(true),
            releaseAuthLock: vi.fn().mockResolvedValue(undefined),
        };
        mockAddItem = vi.fn();
        provider = new QwenOAuthProvider(mockTokenStore, mockAddItem);
        // Mock console.log to prevent output during tests
        vi.spyOn(console, 'log').mockImplementation(() => { });
        // Mock ClipboardService - make sure to clear any previous calls
        vi.mocked(ClipboardService.copyToClipboard).mockResolvedValue(undefined);
    });
    it('should call addItem with type "oauth_url" when initiating auth', async () => {
        await provider.initiateAuth();
        expect(mockAddItem).toHaveBeenCalledWith(expect.objectContaining({
            type: 'oauth_url', // Now expecting 'oauth_url' type
            text: expect.stringContaining('Please visit the following URL to authorize with Qwen'),
        }), expect.any(Number));
    });
    it('should call addItem with both text and url fields when initiating auth', async () => {
        await provider.initiateAuth();
        const addItemCall = mockAddItem.mock.calls[0][0];
        expect(addItemCall).toHaveProperty('text');
        // This should now pass - we expect the item to have a url property
        expect(addItemCall).toHaveProperty('url');
        expect(addItemCall.text).toContain('Please visit the following URL to authorize with Qwen');
        expect(addItemCall.url).toBe('https://chat.qwen.ai/activate?user_code=mock-user-code');
    });
    it('should copy auth URL to clipboard when initiating auth', async () => {
        await provider.initiateAuth();
        // This should now pass - we expect ClipboardService.copyToClipboard to be called
        expect(ClipboardService.copyToClipboard).toHaveBeenCalledWith('https://chat.qwen.ai/activate?user_code=mock-user-code');
    });
    it('should call addItem with type "oauth_url" when browser launch is disabled', async () => {
        // Mock shouldLaunchBrowser to return false
        vi.doMock('@vybestack/llxprt-code-core', async () => {
            const actual = await vi.importActual('@vybestack/llxprt-code-core');
            return {
                ...actual,
                shouldLaunchBrowser: vi.fn().mockReturnValue(false),
            };
        });
        await provider.initiateAuth();
        expect(mockAddItem).toHaveBeenCalledWith(expect.objectContaining({
            type: 'oauth_url', // Now expecting 'oauth_url' type
            text: expect.stringContaining('Please visit the following URL to authorize with Qwen'),
        }), expect.any(Number));
    });
});
describe('Qwen Provider Refactor Tests (Issue #1652 Phase 3)', () => {
    let provider;
    let mockTokenStore;
    beforeEach(() => {
        vi.clearAllMocks();
        mockTokenStore = {
            getToken: vi.fn().mockResolvedValue(null),
            saveToken: vi.fn().mockResolvedValue(undefined),
            removeToken: vi.fn().mockResolvedValue(undefined),
            listProviders: vi.fn().mockResolvedValue([]),
            listBuckets: vi.fn().mockResolvedValue(['default']),
            getBucketStats: vi.fn().mockResolvedValue(null),
            acquireRefreshLock: vi.fn().mockResolvedValue(true),
            releaseRefreshLock: vi.fn().mockResolvedValue(undefined),
            acquireAuthLock: vi.fn().mockResolvedValue(true),
            releaseAuthLock: vi.fn().mockResolvedValue(undefined),
        };
        provider = new QwenOAuthProvider(mockTokenStore);
    });
    describe('Test 3.9: initiateAuth returns token', () => {
        it('GIVEN device flow completes, WHEN initiateAuth() called, THEN returns OAuthToken AND saveToken NOT called by provider', async () => {
            const result = await provider.initiateAuth();
            expect(result).toBeDefined();
            expect(result).toHaveProperty('access_token');
            expect(result).toHaveProperty('token_type');
            expect(result).toHaveProperty('expiry');
            expect(typeof result?.access_token).toBe('string');
            expect(typeof result?.token_type).toBe('string');
            expect(typeof result?.expiry).toBe('number');
            expect(mockTokenStore.saveToken).not.toHaveBeenCalled();
        });
    });
    describe('Test 3.10: getToken is read-only with expired token', () => {
        it('GIVEN expired token, WHEN getToken() called, THEN NO writes AND NO device-flow activity', async () => {
            const expiredToken = {
                access_token: 'expired-qwen-token',
                refresh_token: 'qwen-refresh',
                expiry: Math.floor(Date.now() / 1000) - 100,
                token_type: 'Bearer',
            };
            mockTokenStore.getToken.mockResolvedValue(expiredToken);
            const result = await provider.getToken();
            // Should return expired token as-is (read-only, no refresh attempt)
            expect(result).toEqual(expiredToken);
            expect(mockTokenStore.saveToken).not.toHaveBeenCalled();
            expect(mockTokenStore.removeToken).not.toHaveBeenCalled();
        });
    });
    describe('Test 3.11: refreshIfNeeded does not write', () => {
        it('GIVEN refreshIfNeeded() called, THEN saveToken and removeToken NOT called', async () => {
            await provider.refreshIfNeeded();
            expect(mockTokenStore.saveToken).not.toHaveBeenCalled();
            expect(mockTokenStore.removeToken).not.toHaveBeenCalled();
        });
    });
});
//# sourceMappingURL=qwen-oauth-provider.test.js.map