Some changes to the narrative intergation
This commit is contained in:
@@ -9,6 +9,7 @@ import jwt from 'jsonwebtoken'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import type { CaseState, DocumentExhibit, EventExhibit, FolderExhibit, NoteExhibit, PartyExhibit, TimelineView } from '../src/types.js'
|
||||
import { runMigrations } from './migrations.js'
|
||||
import type { StoryGraphDto } from './storyGraphRepository.js'
|
||||
|
||||
const { Client } = pg
|
||||
const baseDatabaseUrl = process.env.TEST_DATABASE_URL
|
||||
@@ -252,6 +253,7 @@ suite('normalized level persistence API', () => {
|
||||
const { importMysteryTemplate } = await import('../scripts/importMysteryTemplate.js')
|
||||
const manifestPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'mysteries', 'barricelli-scene-7', 'mystery.json')
|
||||
const imported = await importMysteryTemplate(manifestPath, baseUrl, adminAuthorization.replace(/^Bearer /, ''))
|
||||
expect(imported.mystery).toEqual({ slug:'barricelli-inventor-proof' })
|
||||
expect(imported.playableLevel).toMatchObject({ title:'The Barricelli Files',exhibits:[expect.objectContaining({ type:'claim',statement:'Nils Aall Barricelli was an inventor.' })],report:expect.objectContaining({
|
||||
title:'Barricelli Inventor Finding',requiredForCompletion:true,status:'draft',
|
||||
}),goals:[expect.objectContaining({
|
||||
@@ -270,6 +272,12 @@ suite('normalized level persistence API', () => {
|
||||
expect.objectContaining({ goalKey:'barricelli.inventor-proof',targetSubject:'Nils Aall Barricelli',
|
||||
relatedFlagKey:'scene7.father_inventor_discovered' }),
|
||||
])
|
||||
const mysteries = await (await adminFetch(`${baseUrl}/api/admin/mysteries`)).json() as { id:string;slug:string }[]
|
||||
const mysteryId = mysteries.find(mystery => mystery.slug === 'barricelli-inventor-proof')!.id
|
||||
const graph = await (await adminFetch(`${baseUrl}/api/admin/mysteries/${mysteryId}/graph`)).json() as StoryGraphDto
|
||||
expect(graph.nodes).toHaveLength(2)
|
||||
expect(graph.nodes.find(node => node.nodeType === 'level')).toMatchObject({ label:'Demonstrate OSINT skill',levelTemplateVersionId:expect.any(String) })
|
||||
expect(graph.nodes.find(node => node.nodeType === 'merit')).toMatchObject({ label:'The Barricelli Luggage',awardsFlag:'barricelli_luggage' })
|
||||
|
||||
const fixtureDir = path.join(path.dirname(manifestPath), 'fixtures')
|
||||
const fatherUpload = new FormData()
|
||||
|
||||
@@ -144,8 +144,12 @@ suite('narrative graph runtime', () => {
|
||||
const mysteries = await (await authFetch(`${baseUrl}/api/admin/mysteries`, adminAuthorization)).json() as { id:string;slug:string }[]
|
||||
const mysteryId = mysteries.find(mystery => mystery.slug === 'goal-mystery')!.id
|
||||
expect((await authFetch(`${baseUrl}/api/admin/mysteries/${mysteryId}/graph`, adminAuthorization, { method:'POST',headers:json,
|
||||
body:JSON.stringify({ entry:'level',nodes:[{ key:'level',type:'level',label:'Prove it',templateSlug:'goal-chapter',x:0,y:0,
|
||||
terminals:[{ key:'continue',label:'Continue',to:null }] }] }) })).status).toBe(201)
|
||||
body:JSON.stringify({ entry:'level',nodes:[
|
||||
{ key:'level',type:'level',label:'Prove it',templateSlug:'goal-chapter',x:0,y:0,
|
||||
terminals:[{ key:'continue',label:'Continue',to:'merit' }] },
|
||||
{ key:'merit',type:'merit',label:'Inventor Merit',awardsFlag:'demo.inventor-merit',x:200,y:0,
|
||||
terminals:[{ key:'continue',label:'Accept',to:null }] },
|
||||
] }) })).status).toBe(201)
|
||||
|
||||
const created = await authFetch(`${baseUrl}/api/playthroughs`, undefined, { method:'POST',headers:json,body:JSON.stringify({ mystery:'goal-mystery' }) })
|
||||
expect(created.status).toBe(201)
|
||||
@@ -167,10 +171,15 @@ suite('narrative graph runtime', () => {
|
||||
|
||||
const completed = await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/advance`, undefined, { method:'POST',headers:json,body:'{}' })
|
||||
expect(completed.status).toBe(200)
|
||||
expect((await completed.json() as PlaythroughState).playthrough.status).toBe('finished')
|
||||
expect(await (await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/achievements`, undefined)).json()).toContain('demo.inventor-proved')
|
||||
expect(await completed.json()).toMatchObject({ playthrough:{ status:'active' },node:{ kind:'merit',label:'Inventor Merit',awardsFlag:'demo.inventor-merit' } })
|
||||
expect(await (await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/achievements`, undefined)).json())
|
||||
.toEqual(expect.arrayContaining(['demo.inventor-proved','demo.inventor-merit']))
|
||||
const achievement = await appPool.query<{ awarded_by_node_id:string | null }>(
|
||||
'SELECT awarded_by_node_id FROM osint.achievements WHERE playthrough_id=$1 AND flag_key=$2', [playthroughId,'demo.inventor-proved'])
|
||||
expect(achievement.rows[0].awarded_by_node_id).toBe(atLevel.node!.id)
|
||||
|
||||
const finished = await authFetch(`${baseUrl}/api/playthroughs/${playthroughId}/advance`, undefined, { method:'POST',headers:json,body:'{}' })
|
||||
expect(finished.status).toBe(200)
|
||||
expect((await finished.json() as PlaythroughState).playthrough.status).toBe('finished')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Pool, PoolClient } from 'pg'
|
||||
|
||||
export type GraphSpecNode = {
|
||||
key: string; type: StoryNodeType; label?: string; x: number; y: number
|
||||
componentKey?: string; templateSlug?: string; version?: number
|
||||
componentKey?: string; templateSlug?: string; version?: number; awardsFlag?: string
|
||||
terminals?: { key: string; label?: string; to?: string | null }[]
|
||||
utterances?: { npc?: string; pose?: string; text: string; utterer?: Utterer }[]
|
||||
}
|
||||
@@ -261,8 +261,8 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
||||
versionId = version.rows[0]?.id ?? null
|
||||
if (!versionId) throw new Error(`Graph node ${node.key}: unknown level template ${node.templateSlug}`)
|
||||
}
|
||||
await client.query('INSERT INTO osint.story_nodes (id,mystery_id,node_type,label,xpos,ypos,has_utterances,level_template_version_id,component_key) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)',
|
||||
[id, mysteryId, node.type, node.label || node.type, node.x, node.y, Boolean(node.utterances?.length), versionId, node.componentKey || null])
|
||||
await client.query('INSERT INTO osint.story_nodes (id,mystery_id,node_type,label,xpos,ypos,has_utterances,level_template_version_id,component_key,awards_flag) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)',
|
||||
[id, mysteryId, node.type, node.label || node.type, node.x, node.y, Boolean(node.utterances?.length), versionId, node.componentKey || null, node.awardsFlag?.trim() || null])
|
||||
for (const [index, terminal] of (node.terminals || []).entries()) {
|
||||
const terminalId = randomUUID(); terminalIds.set(`${node.key}:${terminal.key}`, terminalId)
|
||||
await client.query('INSERT INTO osint.story_node_terminals (id,parent_node_id,terminal_key,label,sort_order) VALUES ($1,$2,$3,$4,$5)',
|
||||
|
||||
Reference in New Issue
Block a user