import { describe, expect, test } from 'bun:test' import { PASCAL_AGENT_PROXY_HEADER, validatePascalAgentAccess } from './pascal-agent-access' import { getPascalAgentConfig } from './pascal-agent-config' import { isPascalAgentMutationTool, isPascalAgentToolAllowed } from './pascal-agent-mcp' import { buildPascalAgentPrompt } from './pascal-agent-prompt' import { getPascalHostIdentity } from './pascal-host-identity' describe('Pascal agent configuration', () => { test('stays disabled until a model API key is configured', () => { const config = getPascalAgentConfig({}) expect(config.configured).toBe(false) expect(config.baseUrl).toBe('https://api.openai.com/v1') expect(config.maxOutputTokens).toBe(8192) expect(config.maxSteps).toBe(18) }) test('normalizes endpoints and clamps safety limits', () => { const config = getPascalAgentConfig({ PASCAL_AGENT_API_KEY: ' test-key ', PASCAL_AGENT_BASE_URL: 'https://models.example/v1/', PASCAL_AGENT_MAX_STEPS: '999', PASCAL_AGENT_MAX_OUTPUT_TOKENS: '999999', PASCAL_AGENT_MODEL: ' design-model ', PASCAL_AGENT_STEP_TIMEOUT_MS: '1000', PASCAL_AGENT_TOTAL_TIMEOUT_MS: '9999999', }) expect(config).toMatchObject({ apiKey: 'test-key', baseUrl: 'https://models.example/v1', configured: true, maxSteps: 40, maxOutputTokens: 32_768, model: 'design-model', stepTimeoutMs: 10_000, totalTimeoutMs: 600_000, }) }) }) describe('Pascal agent proxy access', () => { test('allows local development without a proxy secret', () => { expect(validatePascalAgentAccess(new Headers(), { NODE_ENV: 'development' })).toBe('allowed') }) test('requires a configured and matching proxy secret in production', () => { expect(validatePascalAgentAccess(new Headers(), { NODE_ENV: 'production' })).toBe( 'missing_proxy_secret', ) const headers = new Headers({ [PASCAL_AGENT_PROXY_HEADER]: 'proxy-secret' }) expect( validatePascalAgentAccess(headers, { NODE_ENV: 'production', PASCAL_AGENT_PROXY_SECRET: 'proxy-secret', }), ).toBe('allowed') expect( validatePascalAgentAccess(new Headers(), { NODE_ENV: 'production', PASCAL_AGENT_PROXY_SECRET: 'proxy-secret', }), ).toBe('unauthorized') }) }) describe('Pascal agent scene isolation', () => { test('blocks tools that can switch or destroy projects', () => { expect(isPascalAgentToolAllowed('load_scene')).toBe(false) expect(isPascalAgentToolAllowed('delete_scene')).toBe(false) expect(isPascalAgentToolAllowed('create_house_from_brief')).toBe(false) expect(isPascalAgentToolAllowed('create_from_template')).toBe(false) expect(isPascalAgentToolAllowed('photo_to_scene')).toBe(false) expect(isPascalAgentToolAllowed('create_room')).toBe(true) expect(isPascalAgentToolAllowed('save_scene')).toBe(true) }) test('binds the system prompt to the active scene', () => { const prompt = buildPascalAgentPrompt({ sceneId: 'scene-12', sceneName: 'Loft' }) expect(prompt).toContain('scene-12') expect(prompt).toContain('Loft') expect(prompt).toContain('validate_scene') expect(prompt).toContain('check_collisions') }) test('distinguishes scene writes from inspection tools', () => { expect(isPascalAgentMutationTool('create_room')).toBe(true) expect(isPascalAgentMutationTool('add_door')).toBe(true) expect(isPascalAgentMutationTool('get_scene')).toBe(false) expect(isPascalAgentMutationTool('list_levels')).toBe(false) expect(isPascalAgentMutationTool('save_scene')).toBe(false) }) }) describe('Pascal host identity', () => { test('requires a trusted host user in production', () => { const missing = getPascalHostIdentity(new Headers(), { NODE_ENV: 'production' }) const present = getPascalHostIdentity( new Headers({ 'x-pascal-user-id': '0de0ce74-ff6f-48c5-a719-9ad70e688ab8' }), { NODE_ENV: 'production' }, ) expect(missing.allowed).toBe(false) expect(present.allowed).toBe(true) expect(present.userId).toBe('0de0ce74-ff6f-48c5-a719-9ad70e688ab8') }) test('rejects malformed forwarded identities', () => { const identity = getPascalHostIdentity(new Headers({ 'x-pascal-user-id': '../another-user' }), { NODE_ENV: 'production', }) expect(identity.allowed).toBe(false) expect(identity.userId).toBeNull() }) })