import { describe, expect, it, vi } from "vitest"; import { validateElderAiAnalysisOutput, validateKnowledgeEntryInput } from "@/modules/ai/types"; import { getDatabase, type AppDatabase } from "@/modules/core/server/db"; import { DEFAULT_AI_KNOWLEDGE_ENTRIES, DEFAULT_ALERT_RULES, DEFAULT_ALERT_TRIGGERS, DEFAULT_DEVICE_ASSETS, DEFAULT_FAMILY_CONTACTS, DEFAULT_FAMILY_FEEDBACK, DEFAULT_FAMILY_VISITS, DEFAULT_MAINTENANCE_TICKETS, DEFAULT_NOTICES, DEFAULT_PREPARED_AI_ANALYSES, seedDefaultCollaborationWorkspaceData, } from "@/modules/core/server/default-workspace-data"; const FORBIDDEN_DEMO_WORDING_PATTERN = /mock|simulation|simulated|demo|sample|placeholder|fixture|fake|test data|模拟|仿真|演示|样例|示例|占位|测试数据/i; vi.mock("server-only", () => ({})); vi.mock("@/modules/core/server/db", () => ({ getDatabase: vi.fn(), })); function expectStableUniqueNaturalKeys(items: T[], getKey: (item: T) => string, expectedKeys: string[]): void { const keys = items.map(getKey); expect(keys).toEqual(expectedKeys); expect(new Set(keys)).toHaveLength(items.length); for (const key of keys) { expect(key.trim()).toBe(key); expect(key).not.toBe(""); } } type SeedInsertRow = Record; function isSeedInsertRow(value: unknown): value is SeedInsertRow { return typeof value === "object" && value !== null && !Array.isArray(value); } function toSeedInsertRows(values: unknown): SeedInsertRow[] { return Array.isArray(values) ? values.filter(isSeedInsertRow) : []; } function rowsWithKey(rows: SeedInsertRow[], key: string): SeedInsertRow[] { return rows.filter((row) => key in row); } function createCollaborationSeedDatabaseMock(params: { missingDeviceCode: string; missingTicketTitle: string; missingNoticeTitle: string; missingAlertRuleName: string; missingAlertTriggerTitle: string; missingFamilyContactName: string; missingFamilyVisitNotes: string; missingFamilyFeedbackContent: string; }): { database: AppDatabase; insertCalls: Array<{ values: SeedInsertRow[] }> } { const elderNames = Array.from( new Set([ ...DEFAULT_FAMILY_CONTACTS.map((contact) => contact.elderName), ...DEFAULT_ALERT_TRIGGERS.flatMap((trigger) => (trigger.elderName ? [trigger.elderName] : [])), ]), ); const elderRows = elderNames.map((name, index) => ({ id: `elder-${index}`, name })); const getElderId = (name: string): string => { const elder = elderRows.find((row) => row.name === name); if (!elder) { throw new Error(`Missing test elder row for ${name}`); } return elder.id; }; const allFamilyContactRows = DEFAULT_FAMILY_CONTACTS.map((contact, index) => ({ id: `contact-${index}`, elderId: getElderId(contact.elderName), name: contact.name, })); const getContactId = (elderName: string, contactName: string): string => { const elderId = getElderId(elderName); const contact = allFamilyContactRows.find((row) => row.elderId === elderId && row.name === contactName); if (!contact) { throw new Error(`Missing test contact row for ${elderName}/${contactName}`); } return contact.id; }; const selectResults = [ elderRows, DEFAULT_DEVICE_ASSETS.filter((device) => device.code !== params.missingDeviceCode).map((device) => ({ id: `device-${device.code}`, code: device.code })), DEFAULT_MAINTENANCE_TICKETS.filter((ticket) => ticket.title !== params.missingTicketTitle).map((ticket) => ({ title: ticket.title })), DEFAULT_NOTICES.filter((notice) => notice.title !== params.missingNoticeTitle).map((notice) => ({ title: notice.title })), DEFAULT_ALERT_RULES.filter((rule) => rule.name !== params.missingAlertRuleName).map((rule) => ({ id: `rule-${rule.name}`, name: rule.name })), DEFAULT_ALERT_TRIGGERS.filter((trigger) => trigger.title !== params.missingAlertTriggerTitle).map((trigger) => ({ title: trigger.title })), allFamilyContactRows.filter((contact) => contact.name !== params.missingFamilyContactName), DEFAULT_FAMILY_VISITS.filter((visit) => visit.notes !== params.missingFamilyVisitNotes).map((visit) => ({ elderId: getElderId(visit.elderName), contactId: getContactId(visit.elderName, visit.contactName), notes: visit.notes, })), DEFAULT_FAMILY_FEEDBACK.filter((feedback) => feedback.content !== params.missingFamilyFeedbackContent).map((feedback) => ({ elderId: getElderId(feedback.elderName), contactId: getContactId(feedback.elderName, feedback.contactName), content: feedback.content, })), ]; let selectIndex = 0; const insertCalls: Array<{ values: SeedInsertRow[] }> = []; const transaction = { insert: vi.fn(() => ({ values: vi.fn((values: unknown) => { const rows = toSeedInsertRows(values); insertCalls.push({ values: rows }); return { returning: vi.fn(() => rows.map((row, index) => ({ ...row, id: `inserted-${index}` }))), }; }), })), }; const database = { select: vi.fn(() => ({ from: vi.fn(() => ({ where: vi.fn(() => { const result = selectResults[selectIndex] ?? []; selectIndex += 1; return result; }), })), })), transaction: vi.fn(async (callback: (transactionClient: typeof transaction) => Promise | void) => { await callback(transaction); }), }; return { database: database as unknown as AppDatabase, insertCalls }; } describe("default collaboration seed data", () => { it("keeps idempotent seed natural keys stable, populated, and unique per collaboration table", () => { expectStableUniqueNaturalKeys(DEFAULT_DEVICE_ASSETS, (device) => device.code, [ "DEV-CALL-A102-2", "DEV-SPO2-S101", "DEV-OXY-A302-2", "DEV-REHAB-R101", ]); expectStableUniqueNaturalKeys(DEFAULT_MAINTENANCE_TICKETS, (ticket) => ticket.title, [ "床头呼叫器按键失灵", "氧气接口保养延期", "康复步行带日常校准", ]); expectStableUniqueNaturalKeys(DEFAULT_NOTICES, (notice) => notice.title, [ "本周家属探访安排", "夜间重点巡房提醒", "设备巡检培训材料", ]); expectStableUniqueNaturalKeys(DEFAULT_ALERT_RULES, (rule) => rule.name, [ "血氧低值连续告警", "高优先级护理任务逾期", "设备维修工单未派单", ]); expectStableUniqueNaturalKeys(DEFAULT_ALERT_TRIGGERS, (trigger) => trigger.title, [ "S101 血氧低值需复核", "重点照护夜间巡检逾期", "氧气接口保养待派单", ]); expectStableUniqueNaturalKeys(DEFAULT_FAMILY_CONTACTS, (contact) => `${contact.elderName}/${contact.name}`, [ "王桂兰/张敏", "钱松柏/钱宁", "林玉琴/林晓", "赵国华/赵蕾", ]); expectStableUniqueNaturalKeys(DEFAULT_FAMILY_VISITS, (visit) => `${visit.elderName}/${visit.contactName}/${visit.notes}`, [ "王桂兰/张敏/安排护理楼一层会客区。", "钱松柏/钱宁/需医生确认夜间血氧情况后再回复。", "林玉琴/林晓/已完成探访和短住事项确认。", ]); expectStableUniqueNaturalKeys(DEFAULT_FAMILY_FEEDBACK, (feedback) => `${feedback.elderName}/${feedback.contactName}/${feedback.content}`, [ "赵国华/赵蕾/希望夜间巡房后能同步重点观察结果。", "林玉琴/林晓/短住房间清洁和呼叫器说明比较清楚。", ]); }); it("keeps default collaboration records resolvable through their natural-key dependencies", () => { const deviceCodes = new Set(DEFAULT_DEVICE_ASSETS.map((device) => device.code)); const alertRuleNames = new Set(DEFAULT_ALERT_RULES.map((rule) => rule.name)); const elderNames = new Set(DEFAULT_FAMILY_CONTACTS.map((contact) => contact.elderName)); const contactKeys = new Set(DEFAULT_FAMILY_CONTACTS.map((contact) => `${contact.elderName}/${contact.name}`)); expect(DEFAULT_DEVICE_ASSETS.length).toBeGreaterThanOrEqual(3); expect(DEFAULT_MAINTENANCE_TICKETS.length).toBeGreaterThanOrEqual(1); expect(DEFAULT_NOTICES.length).toBeGreaterThanOrEqual(1); expect(DEFAULT_ALERT_RULES.length).toBeGreaterThanOrEqual(1); expect(DEFAULT_ALERT_TRIGGERS.length).toBeGreaterThanOrEqual(1); expect(DEFAULT_FAMILY_CONTACTS.length).toBeGreaterThanOrEqual(1); expect(DEFAULT_FAMILY_VISITS.length).toBeGreaterThanOrEqual(1); expect(DEFAULT_FAMILY_FEEDBACK.length).toBeGreaterThanOrEqual(1); for (const ticket of DEFAULT_MAINTENANCE_TICKETS) { expect(deviceCodes.has(ticket.deviceCode)).toBe(true); } for (const trigger of DEFAULT_ALERT_TRIGGERS) { expect(alertRuleNames.has(trigger.ruleName)).toBe(true); if (trigger.elderName) { expect(elderNames.has(trigger.elderName)).toBe(true); } } for (const visit of DEFAULT_FAMILY_VISITS) { expect(contactKeys.has(`${visit.elderName}/${visit.contactName}`)).toBe(true); } for (const feedback of DEFAULT_FAMILY_FEEDBACK) { expect(contactKeys.has(`${feedback.elderName}/${feedback.contactName}`)).toBe(true); } }); it("keeps user-visible collaboration defaults realistic", () => { const userVisibleContent = [ ...DEFAULT_DEVICE_ASSETS.map((device) => [device.name, device.category, device.location, device.notes].join("\n")), ...DEFAULT_MAINTENANCE_TICKETS.map((ticket) => [ticket.title, ticket.description, ticket.assigneeLabel, ticket.resolutionNotes].join("\n")), ...DEFAULT_NOTICES.map((notice) => [notice.title, notice.content, notice.audience].join("\n")), ...DEFAULT_ALERT_RULES.map((rule) => [rule.name, rule.ruleType, rule.conditionSummary, rule.suggestion].join("\n")), ...DEFAULT_ALERT_TRIGGERS.map((trigger) => [trigger.title, trigger.description, trigger.source, trigger.handlingNotes].join("\n")), ...DEFAULT_FAMILY_CONTACTS.map((contact) => [contact.elderName, contact.name, contact.relationship, contact.phone, contact.notes].join("\n")), ...DEFAULT_FAMILY_VISITS.map((visit) => [visit.elderName, visit.contactName, visit.status, visit.notes].join("\n")), ...DEFAULT_FAMILY_FEEDBACK.map((feedback) => [feedback.elderName, feedback.contactName, feedback.feedbackType, feedback.content, feedback.responseNotes].join("\n")), ].join("\n"); expect(userVisibleContent).not.toMatch(FORBIDDEN_DEMO_WORDING_PATTERN); }); it("inserts only collaboration defaults missing from existing natural-key sets", async () => { const missingDevice = DEFAULT_DEVICE_ASSETS.find((device) => device.code === "DEV-CALL-A102-2"); const missingTicket = DEFAULT_MAINTENANCE_TICKETS.find((ticket) => ticket.title === "床头呼叫器按键失灵"); const missingNotice = DEFAULT_NOTICES.find((notice) => notice.title === "设备巡检培训材料"); const missingAlertRule = DEFAULT_ALERT_RULES.find((rule) => rule.name === "血氧低值连续告警"); const missingAlertTrigger = DEFAULT_ALERT_TRIGGERS.find((trigger) => trigger.title === "S101 血氧低值需复核"); const missingFamilyContact = DEFAULT_FAMILY_CONTACTS.find((contact) => contact.name === "张敏"); const missingFamilyVisit = DEFAULT_FAMILY_VISITS.find((visit) => visit.notes === "安排护理楼一层会客区。"); const missingFamilyFeedback = DEFAULT_FAMILY_FEEDBACK.find((feedback) => feedback.content === "希望夜间巡房后能同步重点观察结果。"); if (!missingDevice || !missingTicket || !missingNotice || !missingAlertRule || !missingAlertTrigger || !missingFamilyContact || !missingFamilyVisit || !missingFamilyFeedback) { throw new Error("Missing collaboration seed fixture for behavior test"); } const { database, insertCalls } = createCollaborationSeedDatabaseMock({ missingDeviceCode: missingDevice.code, missingTicketTitle: missingTicket.title, missingNoticeTitle: missingNotice.title, missingAlertRuleName: missingAlertRule.name, missingAlertTriggerTitle: missingAlertTrigger.title, missingFamilyContactName: missingFamilyContact.name, missingFamilyVisitNotes: missingFamilyVisit.notes, missingFamilyFeedbackContent: missingFamilyFeedback.content, }); vi.mocked(getDatabase).mockReturnValue(database); await seedDefaultCollaborationWorkspaceData("organization-1"); const insertedRows = insertCalls.flatMap((call) => call.values); expect(insertedRows).toHaveLength(8); expect(rowsWithKey(insertedRows, "code")).toEqual([expect.objectContaining({ code: missingDevice.code })]); expect(rowsWithKey(insertedRows, "priority")).toEqual([expect.objectContaining({ title: missingTicket.title })]); expect(rowsWithKey(insertedRows, "audience")).toEqual([expect.objectContaining({ title: missingNotice.title })]); expect(rowsWithKey(insertedRows, "conditionSummary")).toEqual([expect.objectContaining({ name: missingAlertRule.name })]); expect(rowsWithKey(insertedRows, "source")).toEqual([expect.objectContaining({ title: missingAlertTrigger.title })]); expect(rowsWithKey(insertedRows, "relationship")).toEqual([expect.objectContaining({ name: missingFamilyContact.name })]); expect(rowsWithKey(insertedRows, "scheduledAt")).toEqual([expect.objectContaining({ notes: missingFamilyVisit.notes })]); expect(rowsWithKey(insertedRows, "feedbackType")).toEqual([expect.objectContaining({ content: missingFamilyFeedback.content })]); }); }); describe("default AI workspace seed data", () => { it("keeps user-visible AI knowledge content realistic and validator-ready", () => { expect(DEFAULT_AI_KNOWLEDGE_ENTRIES.length).toBeGreaterThanOrEqual(3); const scopes = new Set(DEFAULT_AI_KNOWLEDGE_ENTRIES.map((entry) => entry.scope)); expect(scopes).toEqual(new Set(["platform", "organization"])); for (const entry of DEFAULT_AI_KNOWLEDGE_ENTRIES) { expect(validateKnowledgeEntryInput(entry)).toEqual({ success: true, input: entry }); expect(entry.status).toBe("enabled"); expect(entry.body.length).toBeGreaterThan(80); expect([entry.title, entry.category, entry.tags, entry.body].join("\n")).not.toMatch(FORBIDDEN_DEMO_WORDING_PATTERN); } }); it("provides completed elder AI analyses that current validators can display", () => { expect(DEFAULT_PREPARED_AI_ANALYSES.length).toBeGreaterThanOrEqual(1); for (const analysis of DEFAULT_PREPARED_AI_ANALYSES) { const validated = validateElderAiAnalysisOutput(analysis.result); expect(validated).toEqual(analysis.result); expect(analysis.elderName).toBeTruthy(); expect(analysis.dataScopes).toContain("elder"); expect(analysis.dataScopes).toContain("knowledge"); expect(analysis.createdHoursAgo).toBeGreaterThan(0); expect(analysis.result.citations.length).toBeGreaterThan(0); expect(analysis.result.keyFindings.length).toBeGreaterThan(0); expect(analysis.result.recommendations.length).toBeGreaterThan(0); expect(JSON.stringify(analysis)).not.toMatch(FORBIDDEN_DEMO_WORDING_PATTERN); } }); it("keeps every prepared analysis citation reference displayable", () => { for (const analysis of DEFAULT_PREPARED_AI_ANALYSES) { const citationIds = new Set(analysis.result.citations.map((citation) => citation.id)); const referencedIds = [ ...analysis.result.keyFindings.flatMap((finding) => finding.citationIds), ...analysis.result.recommendations.flatMap((recommendation) => recommendation.citationIds), ]; expect(referencedIds.length).toBeGreaterThan(0); expect(new Set(referencedIds)).toEqual(citationIds); for (const citationId of referencedIds) { expect(citationIds.has(citationId)).toBe(true); } } }); });