新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、 時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙 測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
92 lines
3.6 KiB
JavaScript
92 lines
3.6 KiB
JavaScript
import { prisma } from "@kokorone/db";
|
||
|
||
const API_PORT = process.env.PORT_API ?? "3001";
|
||
const WEB_PORT = process.env.PORT_WEB ?? "3100";
|
||
const CHARACTER_ID = "seed-character-genki";
|
||
const USER_ID = "seed-user-primary";
|
||
|
||
async function post(path, body) {
|
||
const res = await fetch(`http://localhost:${API_PORT}${path}`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(body ?? {}),
|
||
});
|
||
if (!res.ok) {
|
||
throw new Error(`POST ${path} 回傳 ${res.status}`);
|
||
}
|
||
return res.json();
|
||
}
|
||
|
||
async function get(path) {
|
||
const res = await fetch(`http://localhost:${API_PORT}${path}`);
|
||
if (!res.ok) {
|
||
throw new Error(`GET ${path} 回傳 ${res.status}`);
|
||
}
|
||
return res.json();
|
||
}
|
||
|
||
export default async function smokeH() {
|
||
// T38:1:1 聊天併入 SOLO 房,roomId 取代舊版由呼叫端自行產生的 sessionId。
|
||
const room = await post("/room/solo", { userId: USER_ID, characterId: CHARACTER_ID });
|
||
const sessionId = room.id; // 沿用既有 /memory/:characterId/sessions/:sessionId/messages 端點(sessionId 本就是不透明鍵)
|
||
|
||
await prisma.emotionState.update({
|
||
where: { characterId: CHARACTER_ID },
|
||
data: { calm: 100, joy: 0, sad: 0, alert: 0, shy: 0, grumpy: 0, updatedAt: new Date() },
|
||
});
|
||
const before = await get(`/relationships/${CHARACTER_ID}/${USER_ID}`);
|
||
const intimacyBefore = before.relationship.intimacy;
|
||
|
||
// H-1 對話 API:一輪完整對話回傳回應、情緒狀態與親密度變化
|
||
const result = await post(`/room/${room.id}/message`, {
|
||
userId: USER_ID,
|
||
text: "謝謝你,我今天好開心!",
|
||
});
|
||
if (!result.text || typeof result.text !== "string") {
|
||
throw new Error("對話 API 未回傳文字內容");
|
||
}
|
||
if (result.context.emotion.dominant !== "JOY") {
|
||
throw new Error(`對話後情緒狀態應變為愉悅,實際為 ${result.context.emotion.dominant}`);
|
||
}
|
||
if (result.context.relationship.intimacy <= intimacyBefore) {
|
||
throw new Error("對話後親密度應有變化(累積日常互動)");
|
||
}
|
||
|
||
// H-1 房間生命週期:POST /room/:roomId/end 觸發睡眠固化,工作記憶應被清空
|
||
const contextBefore = await get(`/memory/${CHARACTER_ID}/sessions/${sessionId}/messages`);
|
||
if (contextBefore.context.length === 0) {
|
||
throw new Error("對話後工作記憶應保留本輪內容,房間結束前不應為空");
|
||
}
|
||
await post(`/room/${room.id}/end`, {});
|
||
const contextAfter = await get(`/memory/${CHARACTER_ID}/sessions/${sessionId}/messages`);
|
||
if (contextAfter.context.length !== 0) {
|
||
throw new Error("房間結束後應觸發睡眠固化並清空工作記憶");
|
||
}
|
||
|
||
// H-2~H-5:對話頁面應包含情緒晶片、親密度顯示、心跳波形、立繪佔位區塊的結構標記
|
||
const pageRes = await fetch(`http://localhost:${WEB_PORT}/room/solo`);
|
||
if (!pageRes.ok) {
|
||
throw new Error(`GET /room/solo 回傳 ${pageRes.status}`);
|
||
}
|
||
const html = await pageRes.text();
|
||
if (!html.includes("親密度")) {
|
||
throw new Error("對話頁面缺少親密度顯示(H-2)");
|
||
}
|
||
if (!html.includes("heartbeat-wave-line")) {
|
||
throw new Error("對話頁面缺少心跳波形母題(H-4)");
|
||
}
|
||
if (!html.includes("立繪")) {
|
||
throw new Error("對話頁面缺少立繪佔位區塊(H-5)");
|
||
}
|
||
|
||
// 清理本次測試造成的狀態變化,避免影響其他群組或人工檢視
|
||
await prisma.relationship.update({
|
||
where: { characterId_userId: { characterId: CHARACTER_ID, userId: USER_ID } },
|
||
data: { intimacy: 25 },
|
||
});
|
||
await prisma.emotionState.update({
|
||
where: { characterId: CHARACTER_ID },
|
||
data: { calm: 70, joy: 20, sad: 0, alert: 0, shy: 0, grumpy: 0, updatedAt: new Date() },
|
||
});
|
||
}
|