ai-chatbot/services/pascal-editor/apps/editor/lib/pascal-agent-config.test.ts

119 lines
4.4 KiB
TypeScript

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()
})
})