/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { MacOsSandboxManager } from './MacOsSandboxManager.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
describe('MacOsSandboxManager', () => {
    let mockWorkspace;
    let mockAllowedPaths;
    const mockNetworkAccess = true;
    let mockPolicy;
    let manager;
    beforeEach(() => {
        mockWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-macos-test-'));
        mockAllowedPaths = [
            path.join(os.tmpdir(), 'gemini-cli-macos-test-allowed'),
        ];
        if (!fs.existsSync(mockAllowedPaths[0])) {
            fs.mkdirSync(mockAllowedPaths[0]);
        }
        mockPolicy = {
            allowedPaths: mockAllowedPaths,
            networkAccess: mockNetworkAccess,
        };
        manager = new MacOsSandboxManager({ workspace: mockWorkspace });
        // Mock realpathSync to just return the path for testing
        vi.spyOn(fs, 'realpathSync').mockImplementation((p) => p);
    });
    afterEach(() => {
        vi.restoreAllMocks();
        fs.rmSync(mockWorkspace, { recursive: true, force: true });
        if (mockAllowedPaths && mockAllowedPaths[0]) {
            fs.rmSync(mockAllowedPaths[0], { recursive: true, force: true });
        }
    });
    describe('prepareCommand', () => {
        it('should build a strict allowlist profile allowing the workspace via param', async () => {
            const result = await manager.prepareCommand({
                command: 'echo',
                args: ['hello'],
                cwd: mockWorkspace,
                env: {},
                policy: { networkAccess: false },
            });
            expect(result.program).toBe('/usr/bin/sandbox-exec');
            const profile = result.args[1];
            expect(profile).toContain('(version 1)');
            expect(profile).toContain('(deny default)');
            expect(profile).toContain('(allow process-exec)');
            expect(profile).toContain('(subpath (param "WORKSPACE"))');
            expect(profile).not.toContain('(allow network-outbound)');
            expect(result.args).toContain('-D');
            expect(result.args).toContain(`WORKSPACE=${mockWorkspace}`);
            expect(result.args).toContain(`TMPDIR=${os.tmpdir()}`);
            // Governance files should be protected
            expect(profile).toContain('(deny file-write* (literal (param "GOVERNANCE_FILE_0")))'); // .gitignore
            expect(profile).toContain('(deny file-write* (literal (param "GOVERNANCE_FILE_1")))'); // .geminiignore
            expect(profile).toContain('(deny file-write* (subpath (param "GOVERNANCE_FILE_2")))'); // .git
        });
        it('should allow network when networkAccess is true in policy', async () => {
            const result = await manager.prepareCommand({
                command: 'curl',
                args: ['example.com'],
                cwd: mockWorkspace,
                env: {},
                policy: { networkAccess: true },
            });
            const profile = result.args[1];
            expect(profile).toContain('(allow network-outbound)');
        });
        it('should parameterize allowed paths and normalize them', async () => {
            vi.spyOn(fs, 'realpathSync').mockImplementation((p) => {
                if (p === '/test/symlink')
                    return '/test/real_path';
                return p;
            });
            const result = await manager.prepareCommand({
                command: 'ls',
                args: ['/custom/path1'],
                cwd: mockWorkspace,
                env: {},
                policy: {
                    allowedPaths: ['/custom/path1', '/test/symlink'],
                },
            });
            const profile = result.args[1];
            expect(profile).toContain('(subpath (param "ALLOWED_PATH_0"))');
            expect(profile).toContain('(subpath (param "ALLOWED_PATH_1"))');
            expect(result.args).toContain('-D');
            expect(result.args).toContain('ALLOWED_PATH_0=/custom/path1');
            expect(result.args).toContain('ALLOWED_PATH_1=/test/real_path');
        });
        it('should format the executable and arguments correctly for sandbox-exec', async () => {
            const result = await manager.prepareCommand({
                command: 'echo',
                args: ['hello'],
                cwd: mockWorkspace,
                env: {},
                policy: mockPolicy,
            });
            expect(result.program).toBe('/usr/bin/sandbox-exec');
            expect(result.args.slice(-3)).toEqual(['--', 'echo', 'hello']);
        });
        it('should correctly pass through the cwd to the resulting command', async () => {
            const result = await manager.prepareCommand({
                command: 'echo',
                args: ['hello'],
                cwd: '/test/different/cwd',
                env: {},
                policy: mockPolicy,
            });
            expect(result.cwd).toBe('/test/different/cwd');
        });
        it('should apply environment sanitization via the default mechanisms', async () => {
            const result = await manager.prepareCommand({
                command: 'echo',
                args: ['hello'],
                cwd: mockWorkspace,
                env: {
                    SAFE_VAR: '1',
                    GITHUB_TOKEN: 'sensitive',
                },
                policy: mockPolicy,
            });
            expect(result.env['SAFE_VAR']).toBe('1');
            expect(result.env['GITHUB_TOKEN']).toBeUndefined();
        });
        it('should resolve parent directories if a file does not exist', async () => {
            const baseTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-macos-realpath-test-'));
            const realPath = path.join(baseTmpDir, 'real_path');
            const nonexistentFile = path.join(realPath, 'nonexistent.txt');
            vi.spyOn(fs, 'realpathSync').mockImplementation((p) => {
                if (p === nonexistentFile) {
                    const error = new Error('ENOENT');
                    Object.assign(error, { code: 'ENOENT' });
                    throw error;
                }
                if (p === realPath) {
                    return path.join(baseTmpDir, 'resolved_path');
                }
                return p;
            });
            try {
                const dynamicManager = new MacOsSandboxManager({
                    workspace: nonexistentFile,
                });
                const dynamicResult = await dynamicManager.prepareCommand({
                    command: 'echo',
                    args: ['hello'],
                    cwd: nonexistentFile,
                    env: {},
                });
                expect(dynamicResult.args).toContain(`WORKSPACE=${path.join(baseTmpDir, 'resolved_path', 'nonexistent.txt')}`);
            }
            finally {
                fs.rmSync(baseTmpDir, { recursive: true, force: true });
            }
        });
        it('should throw if realpathSync throws a non-ENOENT error', async () => {
            vi.spyOn(fs, 'realpathSync').mockImplementation(() => {
                const error = new Error('Permission denied');
                Object.assign(error, { code: 'EACCES' });
                throw error;
            });
            const errorManager = new MacOsSandboxManager({
                workspace: mockWorkspace,
            });
            await expect(errorManager.prepareCommand({
                command: 'echo',
                args: ['hello'],
                cwd: mockWorkspace,
                env: {},
            })).rejects.toThrow('Permission denied');
        });
    });
});
//# sourceMappingURL=MacOsSandboxManager.test.js.map