/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { openBrowserSecurely } from './secure-browser-launcher.js';
// Create mock function using vi.hoisted
const mockExecFile = vi.hoisted(() => vi.fn());
// Mock modules
vi.mock('node:child_process');
vi.mock('node:util', () => ({
    promisify: () => mockExecFile,
}));
describe('secure-browser-launcher', () => {
    let originalPlatform;
    beforeEach(() => {
        vi.clearAllMocks();
        mockExecFile.mockResolvedValue({ stdout: '', stderr: '' });
        originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
    });
    afterEach(() => {
        if (originalPlatform) {
            Object.defineProperty(process, 'platform', originalPlatform);
        }
    });
    function setPlatform(platform) {
        Object.defineProperty(process, 'platform', {
            value: platform,
            configurable: true,
        });
    }
    describe('URL validation', () => {
        it('should allow valid HTTP URLs', async () => {
            setPlatform('darwin');
            await openBrowserSecurely('http://example.com');
            expect(mockExecFile).toHaveBeenCalledWith('open', ['http://example.com'], expect.any(Object));
        });
        it('should allow valid HTTPS URLs', async () => {
            setPlatform('darwin');
            await openBrowserSecurely('https://example.com');
            expect(mockExecFile).toHaveBeenCalledWith('open', ['https://example.com'], expect.any(Object));
        });
        it('should reject non-HTTP(S) protocols', async () => {
            await expect(openBrowserSecurely('file:///etc/passwd')).rejects.toThrow('Unsafe protocol');
            await expect(openBrowserSecurely('javascript:alert(1)')).rejects.toThrow('Unsafe protocol');
            await expect(openBrowserSecurely('ftp://example.com')).rejects.toThrow('Unsafe protocol');
        });
        it('should reject invalid URLs', async () => {
            await expect(openBrowserSecurely('not-a-url')).rejects.toThrow('Invalid URL');
            await expect(openBrowserSecurely('')).rejects.toThrow('Invalid URL');
        });
        it('should reject URLs with control characters', async () => {
            await expect(openBrowserSecurely('http://example.com\nmalicious-command')).rejects.toThrow('invalid characters');
            await expect(openBrowserSecurely('http://example.com\rmalicious-command')).rejects.toThrow('invalid characters');
            await expect(openBrowserSecurely('http://example.com\x00')).rejects.toThrow('invalid characters');
        });
    });
    describe('Command injection prevention', () => {
        it('should prevent PowerShell command injection on Windows', async () => {
            setPlatform('win32');
            // The POC from the vulnerability report
            const maliciousUrl = "http://127.0.0.1:8080/?param=example#$(Invoke-Expression([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('Y2FsYy5leGU='))))";
            await openBrowserSecurely(maliciousUrl);
            // Verify that execFile was called (not exec) and the URL is passed safely
            expect(mockExecFile).toHaveBeenCalledWith('powershell.exe', [
                '-NoProfile',
                '-NonInteractive',
                '-WindowStyle',
                'Hidden',
                '-Command',
                `Start-Process '${maliciousUrl.replace(/'/g, "''")}'`,
            ], expect.any(Object));
        });
        it('should handle URLs with special shell characters safely', async () => {
            setPlatform('darwin');
            const urlsWithSpecialChars = [
                'http://example.com/path?param=value&other=$value',
                'http://example.com/path#fragment;command',
                'http://example.com/$(whoami)',
                'http://example.com/`command`',
                'http://example.com/|pipe',
                'http://example.com/>redirect',
            ];
            for (const url of urlsWithSpecialChars) {
                await openBrowserSecurely(url);
                // Verify the URL is passed as an argument, not interpreted by shell
                expect(mockExecFile).toHaveBeenCalledWith('open', [url], expect.any(Object));
            }
        });
        it('should properly escape single quotes in URLs on Windows', async () => {
            setPlatform('win32');
            const urlWithSingleQuotes = "http://example.com/path?name=O'Brien&test='value'";
            await openBrowserSecurely(urlWithSingleQuotes);
            // Verify that single quotes are escaped by doubling them
            expect(mockExecFile).toHaveBeenCalledWith('powershell.exe', [
                '-NoProfile',
                '-NonInteractive',
                '-WindowStyle',
                'Hidden',
                '-Command',
                `Start-Process 'http://example.com/path?name=O''Brien&test=''value'''`,
            ], expect.any(Object));
        });
    });
    describe('Platform-specific behavior', () => {
        it('should use correct command on macOS', async () => {
            setPlatform('darwin');
            await openBrowserSecurely('https://example.com');
            expect(mockExecFile).toHaveBeenCalledWith('open', ['https://example.com'], expect.any(Object));
        });
        it('should use PowerShell on Windows', async () => {
            setPlatform('win32');
            await openBrowserSecurely('https://example.com');
            expect(mockExecFile).toHaveBeenCalledWith('powershell.exe', expect.arrayContaining([
                '-Command',
                `Start-Process 'https://example.com'`,
            ]), expect.any(Object));
        });
        it('should use xdg-open on Linux', async () => {
            setPlatform('linux');
            await openBrowserSecurely('https://example.com');
            expect(mockExecFile).toHaveBeenCalledWith('xdg-open', ['https://example.com'], expect.any(Object));
        });
        it('should throw on unsupported platforms', async () => {
            setPlatform('aix');
            await expect(openBrowserSecurely('https://example.com')).rejects.toThrow('Unsupported platform');
        });
    });
    describe('Error handling', () => {
        it('should handle browser launch failures gracefully', async () => {
            setPlatform('darwin');
            mockExecFile.mockRejectedValueOnce(new Error('Command not found'));
            await expect(openBrowserSecurely('https://example.com')).rejects.toThrow('Failed to open browser');
        });
        it('should try fallback browsers on Linux', async () => {
            setPlatform('linux');
            // First call to xdg-open fails
            mockExecFile.mockRejectedValueOnce(new Error('Command not found'));
            // Second call to gnome-open succeeds
            mockExecFile.mockResolvedValueOnce({ stdout: '', stderr: '' });
            await openBrowserSecurely('https://example.com');
            expect(mockExecFile).toHaveBeenCalledTimes(2);
            expect(mockExecFile).toHaveBeenNthCalledWith(1, 'xdg-open', ['https://example.com'], expect.any(Object));
            expect(mockExecFile).toHaveBeenNthCalledWith(2, 'gnome-open', ['https://example.com'], expect.any(Object));
        });
    });
});
//# sourceMappingURL=secure-browser-launcher.test.js.map