116 lines
4.2 KiB
TypeScript
116 lines
4.2 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.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_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,
|
|
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()
|
|
})
|
|
})
|