Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions __tests__/client/restore.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, test } from 'bun:test'
import { restoreConversations } from '@/client/lib/restore'
import type { SessionHistory } from '@/client/lib/schemas'
import { sessionHistorySchema } from '@/client/lib/schemas'

const history = (histories: SessionHistory['histories']): SessionHistory => ({
sessionId: '8571c162-a7d4-4be9-a14c-2d4ea2780d4f',
Expand Down Expand Up @@ -145,3 +146,34 @@ describe('restoreConversations', () => {
expect(Object.keys(result)).toEqual(['a'])
})
})

/*
* 相手のIDは三種類ある。uuid の人物、決め打ちの `victim`、そして作者が書いた場所の
* ローカルID。履歴は聞き込みの画面へ入った瞬間に読むので、ここで受けそこねると
* 一手も打たないうちに画面ごと落ちる——実際、場所を足したときに `victim` までしか
* 許しておらず、場所を持つ事件が開いた時点で必ず落ちた。
*/
describe('sessionHistorySchema の相手ID', () => {
const body = (characterId: string) => ({
sessionId: '8571c162-a7d4-4be9-a14c-2d4ea2780d4f',
histories: [{ characterId, exchanges: [] }],
})

test('人物の uuid を受ける', () => {
expect(
sessionHistorySchema.safeParse(body('7f97837b-ef8f-46ff-a199-377926e8fb75')).success,
).toBe(true)
})

test('遺体の victim を受ける', () => {
expect(sessionHistorySchema.safeParse(body('victim')).success).toBe(true)
})

test('場所のローカルIDを受ける', () => {
expect(sessionHistorySchema.safeParse(body('choba')).success).toBe(true)
})

test('三者のどれでもない文字列は弾く', () => {
expect(sessionHistorySchema.safeParse(body('帳場')).success).toBe(false)
})
})
13 changes: 9 additions & 4 deletions src/client/lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { floorPlanSchema } from '~/db/floor-plan'
import { gameModeSchema, hintSchema } from '~/db/game-mode'
import { llmProviderSchema, settableLlmRoleSchema } from '~/db/llm-catalog'
import { investigablePlaceSchema } from '~/db/place'
import { VICTIM_ID } from '~/db/scenario-definition'
import { placeIdSchema, VICTIM_ID } from '~/db/scenario-definition'

/**
* サーバのレスポンスは fetch の時点では unknown。
Expand Down Expand Up @@ -303,10 +303,15 @@ export const historyExchangeSchema = z.object({
/**
* 話しかけた相手のID。
*
* 登場人物は uuid だが、被害者だけは決め打ちの `victim`(採番する先が一人しか無い)。
* ここを uuid で縛ると、遺体を調べたセッションが復元できずに画面ごと落ちる。
* 登場人物は uuid、被害者は決め打ちの `victim`、場所は作者が書いたローカルID。
* 三者は形で見分けられるので、どれを指しているかは常に決まる(`placeIdSchema`)。
*
* ここを狭く縛ると、その相手を含むセッションが復元できずに**画面ごと落ちる**。
* 履歴は聞き込みの画面へ入った瞬間に読むので、一手も打たないうちに落ちる。
* 実際、場所を足したとき `victim` までしか許しておらず、場所を持つ事件が
* 開いた時点で必ず落ちた。相手を増やしたらここも必ず増やすこと。
*/
const subjectIdSchema = z.union([z.uuid(), z.literal(VICTIM_ID)])
const subjectIdSchema = z.union([z.uuid(), z.literal(VICTIM_ID), placeIdSchema])

export const sessionHistorySchema = z.object({
sessionId: z.uuid(),
Expand Down
Loading