import { describe, expect, it } from "vitest"; import { migrateConfigurationRecord, migrateGiftGrantRecord, migratePlayerProfileRecord, migratePlayerRecord, migrateStatePatchRecord, migrateTrajectoryHistoryRecord, migrateTrajectoryRecord, migrationStatus } from "../examples/scum-server-plugin/features/migration.js"; import { renderPluginPage } from "../examples/scum-server-plugin/page-bundle/index.js"; import { configurationCatalog, validateConfigPatch, validateStatePatch, validateVehicleSpawn, vehicleSpawnCatalog } from "../examples/scum-server-plugin/features/schemas.js"; import { scumMigrationParityFixtures } from "./fixtures/scum-migration-parity.js"; describe("SCUM plugin feature module", () => { it("owns the versioned configuration and state field catalogs", () => { expect(configurationCatalog("0.9.700.90357").map((field) => field.key)).toContain("welcome-message"); expect(validateConfigPatch({ version: "0.9.700.90357", reason: "adjust capacity", idempotencyKey: "cfg-1", changes: [{ key: "max-players", value: "129" }] })).toContain("超出允许范围"); expect(validateStatePatch("0.9.700.90357", [{ fieldKey: "skills.running", before: 1, after: 2 }])).toBeNull(); expect(validateStatePatch("unknown", [{ fieldKey: "skills.running", before: 1, after: 2 }])).toContain("未受当前版本支持"); expect(vehicleSpawnCatalog("0.9.700.90357").map((vehicle) => vehicle.code)).toEqual(["BPC_Laika_C", "BPC_WolfsWagen_C"]); expect(validateVehicleSpawn({ vehicleCode: "BPC_Laika_C" })).toBeNull(); expect(validateVehicleSpawn({ vehicleCode: "#spawnvehicle BPC_Laika_C" })).toContain("格式无效"); expect(validateVehicleSpawn({ vehicleCode: "BPC_Unknown_C" })).toContain("受控目录"); }); it("maps transitional records only as read-only provenance", () => { expect(migratePlayerRecord({ id: "p-1", gamePlayerId: "steam-1", displayName: "Mira", updatedAt: "2026-07-29T00:00:00Z" })).toMatchObject({ provenance: "transitional-read-only", readOnly: true, payload: { gamePlayerId: "steam-1" } }); expect(migrateTrajectoryRecord({ playerRecordId: "p-1", points: [{ recordedAt: "2026-07-29T00:00:00Z", mapX: 10, mapY: 20 }] })).toMatchObject({ provenance: "transitional-read-only", points: [{ x: 10, y: 20 }] }); }); it("preserves only allowlisted transitional history for every feature area", () => { expect(migrateConfigurationRecord({ id: "cfg-1", version: "0.9.700.90357", fields: { MaxPlayers: 64 }, observedAt: "2026-07-29T00:00:00Z", hostPath: "C:/secret" })).toMatchObject({ readOnly: true, payload: { fields: { MaxPlayers: "64" } } }); expect(migratePlayerProfileRecord({ player: { id: "p-1", gamePlayerId: "steam-1", displayName: "Mira", updatedAt: "2026-07-29T00:00:00Z" }, sessions: [{ id: "s-1", gamePlayerRecordId: "p-1", startedAt: "2026-07-29T00:00:00Z", networkFingerprint: "never-copy" }], accessAttempts: [{ occurredAt: "2026-07-29T00:01:00Z", outcome: "review", reason: "manual" }] })).toMatchObject({ payload: { sessions: [{ kind: "login" }], risks: [{ summary: "manual" }] } }); expect(migrateGiftGrantRecord({ id: "gift-1", revisionId: "r-1", gamePlayerRecordId: "p-1", status: "unknown", createdAt: "2026-07-29T00:00:00Z" })).toMatchObject({ payload: { status: "unknown" }, readOnly: true }); expect(migrateStatePatchRecord({ id: "patch-1", gamePlayerRecordId: "p-1", gameVersion: "0.9.700.90357", expectedStateVersion: "state-1", safetyWindow: "maintenance", status: "confirmed", createdAt: "2026-07-29T00:00:00Z", changes: [{ fieldKey: "skills.running", before: 1, after: 2 }] })).toMatchObject({ payload: { status: "succeeded" }, readOnly: true }); expect(migrateTrajectoryHistoryRecord({ id: "track-1", playerRecordId: "p-1", points: [{ recordedAt: "2026-07-29T00:00:00Z", mapX: 10, mapY: 20 }] })).toMatchObject({ sourceRecordId: "track-1", readOnly: true }); }); it("matches controlled transitional fixtures without carrying sensitive fields into plugin history", () => { expect(migrateConfigurationRecord(scumMigrationParityFixtures.configuration.source)).toEqual(scumMigrationParityFixtures.configuration.expected); expect(migratePlayerProfileRecord(scumMigrationParityFixtures.playerHistory.source)).toEqual(scumMigrationParityFixtures.playerHistory.expected); expect(migrateGiftGrantRecord(scumMigrationParityFixtures.gift.source)).toEqual(scumMigrationParityFixtures.gift.expected); expect(migrateStatePatchRecord(scumMigrationParityFixtures.statePatch.source)).toEqual(scumMigrationParityFixtures.statePatch.expected); expect(migrateTrajectoryHistoryRecord(scumMigrationParityFixtures.trajectory.source)).toEqual(scumMigrationParityFixtures.trajectory.expected); expect(migrateConfigurationRecord({ version: "0.9.700.90357", observedAt: "2026-07-29T00:00:00Z", fields: { hostPath: "/srv/scum" } })).toBeNull(); }); it("enables plugin authority only for one exact server-version feature flag", () => { const flags = [{ serverInstanceId: "server-1", serverVersion: "0.9.700.90357", feature: "configuration" as const, authority: "plugin" as const }]; expect(migrationStatus(flags, "server-1", "0.9.700.90357", "configuration")).toMatchObject({ authority: "plugin", pluginWritesEnabled: true, readOnlyHistory: true }); expect(migrationStatus(flags, "server-2", "0.9.700.90357", "configuration")).toMatchObject({ authority: "transitional-read-only", pluginWritesEnabled: false }); expect(migrationStatus([...flags, flags[0]], "server-1", "0.9.700.90357", "configuration")).toMatchObject({ authority: "transitional-read-only", pluginWritesEnabled: false }); }); it("renders plugin-owned configuration, player, reward, state, and trajectory panels with scoped permissions", () => { const nodes: string[] = []; const buttons = new Map(); const react = { createElement: (type: unknown, props: Record | null, ...children: unknown[]) => { if (typeof type === "string") nodes.push(`${type}:${String(props?.["aria-label"] ?? "")}`); if (type === "button") buttons.set(String(children[0]), Boolean(props?.disabled)); return { type, props, children }; } }; renderPluginPage(react, { context: { serverInstanceId: "server-1", permissions: ["server.game-client.read", "server.game-client.command", "server.game-client.maintenance"] }, availability: { available: true, features: [{ key: "config.manage", available: true }, { key: "player.intelligence", available: false, reason: "no event producer" }, { key: "reward.delivery", available: false, reason: "no delivery handler" }, { key: "state.patch", available: false, reason: "no state handler" }, { key: "vehicle.spawn", available: false, reason: "no vehicle handler" }, { key: "trajectory.collect", available: false, reason: "no position producer" }] }, workspace: {} }); expect(nodes).toContain("section:SCUM 配置工作台"); expect(nodes).toContain("section:SCUM 玩家档案"); expect(nodes).toContain("section:SCUM 礼物与通知"); expect(nodes).toContain("section:SCUM 受控状态修改"); expect(nodes).toContain("section:SCUM 受限载具生成"); expect(nodes).toContain("section:SCUM 地图轨迹"); expect(buttons.get("读取配置")).toBe(false); expect(buttons.get("查询玩家")).toBe(true); expect(buttons.get("申请投递")).toBe(true); expect(buttons.get("创建修改申请")).toBe(true); expect(buttons.get("生成载具")).toBe(true); expect(buttons.get("读取轨迹")).toBe(true); }); it("fails closed when a generally online Companion omits feature availability", () => { const buttons = new Map(); const react = { createElement: (type: unknown, props: Record | null, ...children: unknown[]) => { if (type === "button") buttons.set(String(children[0]), Boolean(props?.disabled)); return { type, props, children }; } }; renderPluginPage(react, { context: { serverInstanceId: "server-1", permissions: ["server.game-client.read", "server.game-client.command", "server.game-client.maintenance"] }, availability: { available: true }, workspace: {} }); for (const label of ["读取配置", "查询玩家", "申请投递", "创建修改申请", "生成载具", "读取轨迹"]) expect(buttons.get(label)).toBe(true); }); it("opens vehicle spawning only for the declared Companion handler", () => { const buttons = new Map(); const react = { createElement: (type: unknown, props: Record | null, ...children: unknown[]) => { if (type === "button") buttons.set(String(children[0]), Boolean(props?.disabled)); return { type, props, children }; } }; renderPluginPage(react, { context: { serverInstanceId: "server-1", permissions: ["server.game-client.command"] }, availability: { available: true, features: [{ key: "vehicle.spawn", available: true }] }, workspace: {} }); expect(buttons.get("生成载具")).toBe(false); }); });