Adding working board
This commit is contained in:
+44
-197
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import pg from 'pg'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import type { CaseState } from '../src/types.js'
|
||||
import type { CaseState, DocumentExhibit, EventExhibit, FolderExhibit, NoteExhibit, PartyExhibit, TimelineView } from '../src/types.js'
|
||||
import { runMigrations } from './migrations.js'
|
||||
|
||||
const { Client } = pg
|
||||
@@ -36,7 +36,9 @@ async function availablePort() {
|
||||
})
|
||||
}
|
||||
|
||||
suite('level persistence API', () => {
|
||||
const placed = (x: number, y: number, width: number, height: number, zIndex = 1) => ({ x, y, width, height, rotation: 0, zIndex, hidden: false })
|
||||
|
||||
suite('normalized level persistence API', () => {
|
||||
beforeAll(async () => {
|
||||
const adminUrl = new URL(baseDatabaseUrl!)
|
||||
adminUrl.pathname = '/postgres'
|
||||
@@ -46,13 +48,13 @@ suite('level persistence API', () => {
|
||||
const testUrl = new URL(baseDatabaseUrl!)
|
||||
testUrl.pathname = `/${databaseName}`
|
||||
const databaseUrl = testUrl.toString()
|
||||
const migrationsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'migrations')
|
||||
await runMigrations(databaseUrl, migrationsDir, () => undefined)
|
||||
await runMigrations(databaseUrl, path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'migrations'), () => undefined)
|
||||
|
||||
const port = await availablePort()
|
||||
process.env.DATABASE_URL = databaseUrl
|
||||
process.env.LEVEL_EDITING_ENABLED = 'true'
|
||||
process.env.JWT_SECRET = 'osint-integration-jwt-secret'
|
||||
process.env.ASSET_STORAGE_DRIVER = 'memory'
|
||||
process.env.PORT = String(port)
|
||||
const serverModule = await import('./index.js')
|
||||
appServer = serverModule.server
|
||||
@@ -69,217 +71,62 @@ suite('level persistence API', () => {
|
||||
await adminClient.end()
|
||||
})
|
||||
|
||||
it('persists one normalized level across authoring and play views', async () => {
|
||||
it('round-trips exhibits, relations, board views, private objects, and template clones', async () => {
|
||||
expect(await (await fetch(`${baseUrl}/api/session`)).json()).toEqual({ authenticated: false, isAdmin: false })
|
||||
expect(await (await adminFetch(`${baseUrl}/api/session`)).json()).toEqual({ authenticated: true, isAdmin: true })
|
||||
expect((await fetch(`${baseUrl}/api/levels`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })).status).toBe(403)
|
||||
const createResponse = await adminFetch(`${baseUrl}/api/levels`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ id: 'api-smoke-level', title: 'API Smoke Level' }),
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'api-smoke-level', title: 'API Smoke Level' }),
|
||||
})
|
||||
expect(createResponse.status).toBe(201)
|
||||
const state = await createResponse.json() as CaseState
|
||||
const timeline = state.views.find((view): view is TimelineView => view.type === 'timeline')!
|
||||
timeline.rangeMode = 'fixed'
|
||||
timeline.range = { start: '2021-04-01', end: '2021-04-30' }
|
||||
state.viewport = { x: 91, y: -42, zoom: 0.85 }
|
||||
state.timelineRange = { start: '2021-04-01', end: '2021-04-30' }
|
||||
const documentId = randomUUID()
|
||||
const folderId = randomUUID()
|
||||
const noteId = randomUUID()
|
||||
const eventId = randomUUID()
|
||||
const personConceptId = randomUUID()
|
||||
const organizationConceptId = randomUUID()
|
||||
state.brief = { body: 'Identify Ada Lovelace and Analytical Engines Ltd in the source material.', concepts: [
|
||||
{ id: personConceptId, label: 'Ada Lovelace', context: 'Named as the correspondent.', expectedPartyKind: 'person' },
|
||||
{ id: organizationConceptId, label: 'Analytical Engines Ltd', context: 'Issued the filing.', expectedPartyKind: 'organization' },
|
||||
] }
|
||||
state.documents = [{ id: documentId, title: 'Evidence', kind: 'IMAGE', date: '2021-04-17', publishedAt: '2021-04-17T12:00:00Z', body: ['Extracted body'], regions: [{ id: 'stamp', label: 'Date stamp', excerpt: '17 April 2021', date: '2021-04-17T09:00:00Z' }], fileType: 'image', metadata: {} }]
|
||||
state.evidence = [
|
||||
{ id: folderId, type: 'folder', title: 'Folder', content: 'Evidence folder', x: 685, y: 417, width: 260, config: { open: true }, containedDocumentIds: [documentId] },
|
||||
{ id: noteId, type: 'note', title: 'Extract', content: 'Date matters', sourceDocumentId: documentId, sourceRegionId: 'stamp', x: 420, y: 300, width: 108 },
|
||||
{ id: eventId, type: 'event', title: 'The meeting occurred', content: 'The evidence places the meeting on 18 April.', eventDate: '2021-04-18T14:30:00Z', supportingEvidenceIds: [documentId, noteId], x: 520, y: 610, width: 270 },
|
||||
|
||||
const document: DocumentExhibit = { id: randomUUID(), type: 'document', title: 'Evidence', publishedAt: '2021-04-17T12:00:00Z', body: ['Extracted body'], regions: [{ id: 'stamp', label: 'Date stamp', excerpt: '17 April 2021', date: '2021-04-17T09:00:00Z' }], fileType: 'image', metadata: {}, ...placed(1051, 417, 174, 145, 2) }
|
||||
const folder: FolderExhibit = { id: randomUUID(), type: 'folder', title: 'Folder', content: 'Evidence folder', isOpen: true, ...placed(685, 417, 260, 166) }
|
||||
const note: NoteExhibit = { id: randomUUID(), type: 'note', title: 'Extract', content: 'Date matters', ...placed(420, 300, 108, 154) }
|
||||
const event: EventExhibit = { id: randomUUID(), type: 'event', title: 'The meeting occurred', content: 'The evidence places the meeting on 18 April.', eventDate: '2021-04-18T14:30:00Z', ...placed(520, 610, 270, 174) }
|
||||
const party: PartyExhibit = { id: randomUUID(), type: 'party', partyKind: 'person', title: 'Ada Lovelace', content: 'Named as correspondent.', aliases: ['A. A. L.'], ...placed(720, 250, 280, 190) }
|
||||
state.exhibits = [document, folder, note, event, party]
|
||||
state.relations = [
|
||||
{ id: randomUUID(), fromExhibitId: folder.id, toExhibitId: document.id, type: 'contains', sortOrder: 0 },
|
||||
{ id: randomUUID(), fromExhibitId: note.id, toExhibitId: document.id, type: 'source', sourceRegionId: 'stamp', sortOrder: 0 },
|
||||
{ id: randomUUID(), fromExhibitId: event.id, toExhibitId: document.id, type: 'supports', sortOrder: 0 },
|
||||
{ id: randomUUID(), fromExhibitId: event.id, toExhibitId: note.id, type: 'supports', sortOrder: 1 },
|
||||
{ id: randomUUID(), fromExhibitId: party.id, toExhibitId: document.id, type: 'concerns', sortOrder: 0 },
|
||||
]
|
||||
state.relations = [{ id: `contains:${folderId}:${documentId}`, fromWidgetId: folderId, toWidgetId: documentId, type: 'contains', sortOrder: 0, config: { x: 1051, y: 417 } }]
|
||||
state.connections = [{ id: randomUUID(), fromEvidenceId: folderId, toEvidenceId: documentId, label: 'Primary source', tightness: 85, tagStyle: 'compact', tagPosition: 72, tagOffset: -12 }]
|
||||
|
||||
const saveResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(state),
|
||||
})
|
||||
expect(await saveResponse.json()).toEqual({ ok: true, mode: 'author' })
|
||||
state.connections = [{ id: randomUUID(), fromExhibitId: folder.id, toExhibitId: document.id, label: 'Primary source', tightness: 85, tagStyle: 'compact', tagPosition: 72, tagOffset: -12 }]
|
||||
state.brief = { body: 'Identify Ada Lovelace.', concepts: [{ id: randomUUID(), label: 'Ada Lovelace', context: 'Named in evidence.', expectedPartyKind: 'person', resolvedPartyExhibitId: party.id }] }
|
||||
|
||||
const save = await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(state) })
|
||||
expect(await save.json()).toEqual({ ok: true, mode: 'author' })
|
||||
const loaded = await (await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`)).json() as CaseState
|
||||
expect(loaded.viewport).toEqual(state.viewport)
|
||||
expect(loaded.timelineRange).toEqual(state.timelineRange)
|
||||
expect(loaded.evidence[0]).toMatchObject({ id: folderId, x: 685, y: 417, config: { open: true } })
|
||||
expect(loaded.relations[0]).toMatchObject({ id: `contains:${folderId}:${documentId}`, config: { x: 1051, y: 417 } })
|
||||
expect(loaded.connections).toContainEqual(expect.objectContaining({ fromEvidenceId: folderId, toEvidenceId: documentId, label: 'Primary source', tightness: 85, tagStyle: 'compact', tagPosition: 72, tagOffset: -12 }))
|
||||
expect(loaded.brief.concepts).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ label: 'Ada Lovelace', expectedPartyKind: 'person' }),
|
||||
expect.objectContaining({ label: 'Analytical Engines Ltd', expectedPartyKind: 'organization' }),
|
||||
]))
|
||||
const legacyClientState = structuredClone(loaded)
|
||||
delete legacyClientState.timelineRange
|
||||
const legacySave = await fetch(`${baseUrl}/api/levels/${state.id}`, {
|
||||
method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(legacyClientState),
|
||||
})
|
||||
expect(legacySave.ok).toBe(true)
|
||||
expect((await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState).timelineRange).toEqual(state.timelineRange)
|
||||
expect(loaded.views[0]).toMatchObject({ type: 'timeline', rangeMode: 'fixed', range: timeline.range })
|
||||
expect(loaded.exhibits.find(item => item.id === folder.id)).toMatchObject({ x: 685, y: 417, isOpen: true })
|
||||
expect(loaded.relations).toEqual(expect.arrayContaining([expect.objectContaining({ type: 'supports', fromExhibitId: event.id, toExhibitId: note.id })]))
|
||||
expect(loaded.connections[0]).toMatchObject({ fromExhibitId: folder.id, toExhibitId: document.id, label: 'Primary source' })
|
||||
|
||||
const upload = new FormData()
|
||||
upload.append('file', new Blob(['OSINT smoke evidence'], { type: 'text/plain' }), 'smoke-evidence.txt')
|
||||
const uploadResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/documents?edit=1`, { method: 'POST', body: upload })
|
||||
expect(uploadResponse.status).toBe(201)
|
||||
const uploaded = await uploadResponse.json() as CaseState['documents'][number]
|
||||
expect(uploaded).toMatchObject({ title: 'smoke-evidence.txt', fileName: 'smoke-evidence.txt', mimeType: 'text/plain', fileType: 'text' })
|
||||
expect(uploaded.assetId).toBeTruthy()
|
||||
const uploaded = await uploadResponse.json() as DocumentExhibit
|
||||
expect(uploaded).toMatchObject({ type: 'document', fileName: 'smoke-evidence.txt', fileType: 'text' })
|
||||
expect(await (await fetch(`${baseUrl}/api/assets/${uploaded.assetId}`)).text()).toBe('OSINT smoke evidence')
|
||||
const assetRow = await appPool.query<{ storage_provider: string; content: Buffer | null; object_key: string | null }>('SELECT storage_provider,content,object_key FROM osint.assets WHERE id=$1', [uploaded.assetId])
|
||||
expect(assetRow.rows[0]).toMatchObject({ storage_provider: 's3', content: null, object_key: expect.stringMatching(/^assets\//) })
|
||||
|
||||
const withUpload = await (await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`)).json() as CaseState
|
||||
const uploadedDocument = withUpload.documents.find(document => document.id === uploaded.id)!
|
||||
uploadedDocument.title = 'Renamed smoke evidence'
|
||||
uploadedDocument.publishedAt = '2022-06-15T10:30:00.000Z'
|
||||
uploadedDocument.metadata = { witness: 'Integration test', confidence: 'high' }
|
||||
const metadataSave = await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(withUpload),
|
||||
})
|
||||
expect(metadataSave.ok).toBe(true)
|
||||
const afterMetadataSave = await (await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`)).json() as CaseState
|
||||
expect(afterMetadataSave.documents.find(document => document.id === uploaded.id)).toMatchObject({
|
||||
title: 'Renamed smoke evidence',
|
||||
publishedAt: '2022-06-15T10:30:00.000Z',
|
||||
metadata: { witness: 'Integration test', confidence: 'high' },
|
||||
})
|
||||
|
||||
const undatedState = structuredClone(afterMetadataSave)
|
||||
const undatedEvent = undatedState.evidence.find(exhibit => exhibit.id === eventId)!
|
||||
delete undatedEvent.eventDate
|
||||
const undatedSave = await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`, {
|
||||
method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(undatedState),
|
||||
})
|
||||
expect(undatedSave.ok).toBe(true)
|
||||
const loadedUndated = await (await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`)).json() as CaseState
|
||||
expect(loadedUndated.evidence.find(exhibit => exhibit.id === eventId)?.eventDate).toBeUndefined()
|
||||
expect((await appPool.query<{ occurred_at: Date | null }>('SELECT occurred_at FROM osint.event_exhibits WHERE exhibit_id=$1', [eventId])).rows[0].occurred_at).toBeNull()
|
||||
loadedUndated.evidence.find(exhibit => exhibit.id === eventId)!.eventDate = '2021-04-18T14:30:00Z'
|
||||
expect((await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`, {
|
||||
method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(loadedUndated),
|
||||
})).ok).toBe(true)
|
||||
|
||||
const playerState = await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState
|
||||
expect(playerState.brief.concepts.every(concept => concept.expectedPartyKind === undefined)).toBe(true)
|
||||
const personPartyId = randomUUID()
|
||||
const organizationPartyId = randomUUID()
|
||||
playerState.evidence.push(
|
||||
{ id: personPartyId, type: 'party', partyKind: 'person', title: 'Ada Lovelace', content: 'Named as the correspondent.', aliases: ['A. A. L.'], relatedEvidenceIds: [documentId, noteId], x: 720, y: 250, width: 280 },
|
||||
{ id: organizationPartyId, type: 'party', partyKind: 'organization', organizationKind: 'business', title: 'Analytical Engines Ltd', content: 'Issued the filing.', aliases: ['AEL'], relatedEvidenceIds: [documentId], x: 1020, y: 250, width: 280 },
|
||||
)
|
||||
playerState.brief.concepts = playerState.brief.concepts.map(concept => ({ ...concept,
|
||||
resolvedPartyExhibitId: concept.id === personConceptId ? personPartyId : organizationPartyId }))
|
||||
playerState.viewport = { x: -150, y: 88, zoom: 1.1 }
|
||||
playerState.evidence[0] = { ...playerState.evidence[0], x: 812, y: 533 }
|
||||
const playerSave = await fetch(`${baseUrl}/api/levels/${state.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(playerState),
|
||||
})
|
||||
expect(await playerSave.json()).toEqual({ ok: true, mode: 'play' })
|
||||
const savedPlayerState = await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState
|
||||
expect(savedPlayerState.viewport).toEqual(playerState.viewport)
|
||||
expect(savedPlayerState.evidence[0]).toMatchObject({ id: folderId, x: 812, y: 533 })
|
||||
const sameLevelInEditView = await (await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`)).json() as CaseState
|
||||
expect(sameLevelInEditView.viewport).toEqual(playerState.viewport)
|
||||
expect(sameLevelInEditView.evidence[0]).toMatchObject({ id: folderId, x: 812, y: 533 })
|
||||
|
||||
const normalized = await appPool.query<{ exhibits: string; documents: string; folders: string; memberships: string; metadata: string; sources: string; connections: string; events: string; event_evidence: string; parties: string; people: string; organizations: string; party_evidence: string; concepts: string; hidden_answers: string }>(`SELECT
|
||||
(SELECT COUNT(*) FROM osint.exhibits)::text AS exhibits,
|
||||
(SELECT COUNT(*) FROM osint.document_exhibits)::text AS documents,
|
||||
(SELECT COUNT(*) FROM osint.folder_exhibits)::text AS folders,
|
||||
(SELECT COUNT(*) FROM osint.folder_memberships)::text AS memberships,
|
||||
(SELECT COUNT(*) FROM osint.exhibit_metadata_text_values)::text AS metadata,
|
||||
(SELECT COUNT(*) FROM osint.exhibit_sources)::text AS sources,
|
||||
(SELECT COUNT(*) FROM osint.exhibit_connections)::text AS connections,
|
||||
(SELECT COUNT(*) FROM osint.event_exhibits)::text AS events,
|
||||
(SELECT COUNT(*) FROM osint.event_evidence)::text AS event_evidence,
|
||||
(SELECT COUNT(*) FROM osint.party_exhibits)::text AS parties,
|
||||
(SELECT COUNT(*) FROM osint.person_parties)::text AS people,
|
||||
(SELECT COUNT(*) FROM osint.organization_parties)::text AS organizations,
|
||||
(SELECT COUNT(*) FROM osint.party_evidence)::text AS party_evidence,
|
||||
(SELECT COUNT(*) FROM osint.brief_concepts)::text AS concepts,
|
||||
(SELECT COUNT(*) FROM osint.brief_concepts WHERE expected_party_kind IS NOT NULL)::text AS hidden_answers`)
|
||||
expect(normalized.rows[0]).toEqual({ exhibits: '7', documents: '2', folders: '1', memberships: '1', metadata: '2', sources: '1', connections: '1', events: '1', event_evidence: '2', parties: '2', people: '1', organizations: '1', party_evidence: '3', concepts: '2', hidden_answers: '2' })
|
||||
|
||||
const resetResponse = await fetch(`${baseUrl}/api/levels/${state.id}/reset`, { method: 'POST' })
|
||||
expect(resetResponse.ok).toBe(true)
|
||||
const resetState = await resetResponse.json() as CaseState
|
||||
expect(resetState.viewport).toEqual(playerState.viewport)
|
||||
expect(resetState.timelineRange).toEqual(state.timelineRange)
|
||||
expect(resetState.evidence[0]).toMatchObject({ id: folderId, x: 812, y: 533 })
|
||||
|
||||
const templateResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/templates?edit=1`, {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Smoke Template' }),
|
||||
})
|
||||
const templateResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/templates?edit=1`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Smoke Template' }) })
|
||||
expect(templateResponse.status).toBe(201)
|
||||
expect(await templateResponse.json()).toMatchObject({ slug: 'smoke-template', currentVersion: 1, versionCount: 1 })
|
||||
expect(await (await fetch(`${baseUrl}/api/templates`)).json()).toEqual([
|
||||
expect.objectContaining({ slug: 'smoke-template', currentVersion: 1, versionCount: 1 }),
|
||||
])
|
||||
|
||||
const changedSource = structuredClone(savedPlayerState)
|
||||
changedSource.title = 'Changed after template freeze'
|
||||
changedSource.evidence[0].x = 999
|
||||
await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`, {
|
||||
method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(changedSource),
|
||||
})
|
||||
const cloneResponse = await adminFetch(`${baseUrl}/api/templates/smoke-template/levels?edit=1`, {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'smoke-template-copy', title: 'Playable copy' }),
|
||||
})
|
||||
const cloneResponse = await adminFetch(`${baseUrl}/api/templates/smoke-template/levels?edit=1`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'smoke-template-copy', title: 'Playable copy' }) })
|
||||
expect(cloneResponse.status).toBe(201)
|
||||
const clone = await cloneResponse.json() as CaseState
|
||||
expect(clone).toMatchObject({ id: 'smoke-template-copy', title: 'Playable copy', sourceTemplateVersionId: expect.any(String) })
|
||||
expect(clone.timelineRange).toEqual(state.timelineRange)
|
||||
expect(clone.evidence.find(item => item.type === 'folder')).toMatchObject({ x: 812, y: 533 })
|
||||
expect(clone.documents.find(item => item.title === 'Renamed smoke evidence')?.assetId).toBe(uploaded.assetId)
|
||||
expect(clone.documents[0].id).not.toBe(savedPlayerState.documents[0].id)
|
||||
expect(clone.evidence.map(item => item.id)).not.toContain(folderId)
|
||||
expect(clone.connections).toHaveLength(1)
|
||||
expect(clone.connections[0]).toMatchObject({ label: 'Primary source', tightness: 85, tagStyle: 'compact', tagPosition: 72, tagOffset: -12, toEvidenceId: clone.documents[0].id })
|
||||
expect(clone.evidence.find(item => item.type === 'note')).toMatchObject({ sourceRegionId: 'stamp' })
|
||||
const clonedEvent = clone.evidence.find(item => item.type === 'event')!
|
||||
expect(clonedEvent).toMatchObject({ title: 'The meeting occurred', eventDate: '2021-04-18T14:30:00.000Z' })
|
||||
expect(clonedEvent.supportingEvidenceIds).toHaveLength(2)
|
||||
expect(clonedEvent.supportingEvidenceIds).not.toContain(documentId)
|
||||
expect(clonedEvent.supportingEvidenceIds).not.toContain(noteId)
|
||||
expect(clone.evidence.filter(item => item.type === 'party')).toHaveLength(2)
|
||||
expect(clone.brief.concepts.every(concept => Boolean(concept.resolvedPartyExhibitId))).toBe(true)
|
||||
expect(clone.brief.concepts.map(concept => concept.resolvedPartyExhibitId)).not.toContain(personPartyId)
|
||||
const authoredClone = await (await adminFetch(`${baseUrl}/api/levels/${clone.id}?edit=1`)).json() as CaseState
|
||||
expect(authoredClone.brief.concepts.map(concept => concept.expectedPartyKind).sort()).toEqual(['organization', 'person'])
|
||||
|
||||
const clonedFolder = clone.evidence.find(item => item.type === 'folder')!
|
||||
clonedFolder.x = 1234
|
||||
clone.viewport = { x: 333, y: 222, zoom: 1.2 }
|
||||
await fetch(`${baseUrl}/api/levels/${clone.id}`, {
|
||||
method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(clone),
|
||||
})
|
||||
const cloneReset = await (await fetch(`${baseUrl}/api/levels/${clone.id}/reset`, { method: 'POST' })).json() as CaseState
|
||||
expect(cloneReset).toMatchObject({ title: 'API Smoke Level', viewport: { x: 0, y: 28, zoom: 0.7 } })
|
||||
expect(cloneReset.evidence.find(item => item.type === 'folder')).toMatchObject({ x: 812, y: 533 })
|
||||
expect(cloneReset.evidence.map(item => item.id)).not.toContain(clonedFolder.id)
|
||||
|
||||
const versionTwoResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/templates?edit=1`, {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Smoke Template' }),
|
||||
})
|
||||
expect(await versionTwoResponse.json()).toMatchObject({ currentVersion: 2, versionCount: 2 })
|
||||
const oldVersion = await (await adminFetch(`${baseUrl}/api/templates/smoke-template/levels?edit=1`, {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'old-version-copy', version: 1 }),
|
||||
})).json() as CaseState
|
||||
const currentVersion = await (await adminFetch(`${baseUrl}/api/templates/smoke-template/levels?edit=1`, {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'current-version-copy' }),
|
||||
})).json() as CaseState
|
||||
expect(oldVersion.evidence.find(item => item.type === 'folder')).toMatchObject({ x: 812 })
|
||||
expect(currentVersion.evidence.find(item => item.type === 'folder')).toMatchObject({ x: 999 })
|
||||
expect(clone.views[0]).toMatchObject({ type: 'timeline', rangeMode: 'fixed', range: timeline.range })
|
||||
expect(clone.exhibits.map(item => item.id)).not.toContain(folder.id)
|
||||
expect(clone.exhibits.find(item => item.type === 'folder')).toMatchObject({ x: 685, y: 417 })
|
||||
expect(clone.relations.filter(relation => relation.type === 'supports')).toHaveLength(2)
|
||||
expect(clone.connections[0]).toMatchObject({ label: 'Primary source', tightness: 85 })
|
||||
expect(clone.brief.concepts[0].resolvedPartyExhibitId).not.toBe(party.id)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user