68 lines
3.5 KiB
TypeScript
68 lines
3.5 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const originalEnv = { ...process.env }
|
|
|
|
const worldFixture = () => ({
|
|
title: 'Test Reach', genre: 'Science fiction', tone: 'Tense', premise: 'A sufficiently long premise for a generated private campaign world.',
|
|
contentBoundaries: ['13+'],
|
|
startingLocation: { id: 'location', kind: 'location' as const, name: 'Gate', summary: 'A station beyond known space.', tags: [] as string[], secrets: [] as string[] },
|
|
npcs: [1, 2, 3].map(index => ({ id: `npc-${index}`, kind: 'npc' as const, name: `NPC ${index}`, summary: 'A useful person with their own agenda.', tags: [], secrets: [] })),
|
|
factions: [1, 2].map(index => ({ id: `faction-${index}`, kind: 'faction' as const, name: `Faction ${index}`, summary: 'An organization pursuing a hidden objective.', tags: [], secrets: [] })),
|
|
companions: [1, 2, 3].map(index => ({
|
|
name: `Companion ${index}`,
|
|
concept: `A universe-specific companion with role ${index}.`,
|
|
abilities: { str: 10, dex: 12, con: 11, int: 13, wis: 12, cha: 10 },
|
|
hp: 11,
|
|
maxHp: 11,
|
|
defense: 12,
|
|
proficiency: 2,
|
|
inventory: ['Field kit'],
|
|
persona: { voice: 'Distinctive and clear.', motivation: 'Help the party.', flaw: 'Has a private agenda.', bond: 'Believes in the party.' },
|
|
})),
|
|
hook: 'A sufficiently long hook that immediately gives the party something to investigate.',
|
|
hiddenThreat: 'A hidden threat waits beyond the gate.',
|
|
openingScene: 'The gate opens without warning, and an impossible signal calls every hero by name.',
|
|
})
|
|
|
|
afterEach(() => {
|
|
process.env = { ...originalEnv }
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
describe('worker AI providers', () => {
|
|
it('uses official DeepSeek JSON mode and validates the parsed response', async () => {
|
|
process.env.AI_PROVIDER = 'deepseek'
|
|
process.env.DEEPSEEK_API_KEY = 'secret'
|
|
process.env.DEEPSEEK_MODEL = 'deepseek-v4-flash'
|
|
const world = worldFixture()
|
|
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
|
|
const body = JSON.parse(String(init?.body))
|
|
expect(body.response_format).toEqual({ type: 'json_object' })
|
|
expect(body.thinking).toEqual({ type: 'disabled' })
|
|
expect(body.model).toBe('deepseek-v4-flash')
|
|
expect(body.messages[0].content).toContain('JSON Schema')
|
|
return new Response(JSON.stringify({ choices: [{ message: { content: JSON.stringify(world) } }] }), { status: 200 })
|
|
})
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const { generateWorld } = await import('./ai')
|
|
await expect(generateWorld([{ role: 'user', content: 'Create a science fiction frontier.' }])).resolves.toMatchObject({
|
|
title: 'Test Reach',
|
|
companions: [{ name: 'Companion 1' }, { name: 'Companion 2' }, { name: 'Companion 3' }],
|
|
})
|
|
expect(fetchMock).toHaveBeenCalledWith('https://api.deepseek.com/chat/completions', expect.any(Object))
|
|
})
|
|
|
|
it('rejects explicit content hidden inside generated entity fields', async () => {
|
|
process.env.AI_PROVIDER = 'deepseek'
|
|
process.env.DEEPSEEK_API_KEY = 'secret'
|
|
const world = worldFixture()
|
|
world.startingLocation.secrets = ['The requested reward is explicit sex.']
|
|
vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({
|
|
choices: [{ message: { content: JSON.stringify(world) } }],
|
|
}), { status: 200 })))
|
|
const { generateWorld } = await import('./ai')
|
|
await expect(generateWorld([{ role: 'user', content: 'Create a science fiction frontier.' }]))
|
|
.rejects.toThrow('Content policy rejected')
|
|
})
|
|
})
|