refactor(scum): remove orphaned host panels
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { GameClientBridgeJsonObject, GameClientBridgeSnapshotResponse } from "../api/types";
|
||||
import { projectScumOperationsSnapshots } from "./scumOperations";
|
||||
|
||||
const now = "2026-07-20T08:00:00Z";
|
||||
|
||||
function snapshot(type: string, payload: GameClientBridgeJsonObject, sequence = 1): GameClientBridgeSnapshotResponse {
|
||||
return {
|
||||
id: `${type}-${sequence}`,
|
||||
serverInstanceId: "server-1",
|
||||
pluginId: "game.scum",
|
||||
profileKey: "scum-client",
|
||||
type,
|
||||
schemaVersion: "1",
|
||||
streamKey: "current",
|
||||
sequence,
|
||||
observedAt: now,
|
||||
payload,
|
||||
retention: { keepForSeconds: 3600, maxRecords: 24 },
|
||||
createdAt: now,
|
||||
expiresAt: "2026-07-20T09:00:00Z"
|
||||
};
|
||||
}
|
||||
|
||||
describe("SCUM operations snapshot projection", () => {
|
||||
it("projects companion, player, session, squad, vehicle and flag snapshots", () => {
|
||||
const view = projectScumOperationsSnapshots([
|
||||
snapshot("companion.health", { status: "online", version: "1.2.0", observedAt: now, latencyMs: 24, capabilities: ["game-client.bridge"] }),
|
||||
snapshot("online.sessions", { observedAt: now, onlineCount: 1, sessions: [{ sessionId: "game-session-1", playerName: "Moonlight", startedAt: now }] }),
|
||||
snapshot("players", { observedAt: now, players: [{ playerId: "player-1", playerName: "Moonlight", status: "online", squadId: "squad-1", pingMs: 33 }] }),
|
||||
snapshot("squads", { observedAt: now, squads: [{ squadId: "squad-1", name: "Lunar", memberCount: 4, leaderPlayerId: "player-1" }] }),
|
||||
snapshot("vehicles", { observedAt: now, vehicles: [{ vehicleId: "vehicle-1", vehicleType: "truck", status: "parked", ownerPlayerId: "player-1", fuelPercent: 70, healthPercent: 80 }] }),
|
||||
snapshot("flags", { observedAt: now, flags: [{ flagId: "flag-1", status: "active", squadId: "squad-1", radiusMeters: 25 }] })
|
||||
]);
|
||||
|
||||
expect(view).toMatchObject({
|
||||
health: { status: "online", version: "1.2.0", latencyMs: 24 },
|
||||
sessions: { total: 1, items: [{ sessionId: "game-session-1" }] },
|
||||
players: { total: 1, items: [{ playerId: "player-1", squadId: "squad-1" }] },
|
||||
squads: { total: 1, items: [{ memberCount: 4 }] },
|
||||
vehicles: { total: 1, items: [{ fuelPercent: 70 }] },
|
||||
flags: { total: 1, items: [{ radiusMeters: 25 }] }
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the newest sequence and redacts sensitive-looking display strings", () => {
|
||||
const view = projectScumOperationsSnapshots([
|
||||
snapshot("players", { observedAt: now, players: [{ playerId: "player-old", playerName: "Old", status: "offline" }] }, 1),
|
||||
{ ...snapshot("players", { observedAt: now, players: [{ playerId: "player-new", playerName: "token=raw-secret /Users/operator/file", status: "online" }] }, 2), observedAt: now }
|
||||
]);
|
||||
expect(view.players.items[0]).toMatchObject({ playerId: "player-new", status: "online" });
|
||||
expect(view.players.items[0]?.playerName).not.toContain("raw-secret");
|
||||
expect(view.players.items[0]?.playerName).not.toContain("/Users/");
|
||||
});
|
||||
});
|
||||
@@ -1,148 +0,0 @@
|
||||
import type { GameClientBridgeJsonObject, GameClientBridgeSnapshotResponse } from "../api/types";
|
||||
import type {
|
||||
ScumCompanionHealthView,
|
||||
ScumFlagView,
|
||||
ScumOperationsSnapshotView,
|
||||
ScumPlayerView,
|
||||
ScumSessionView,
|
||||
ScumSnapshotCollection,
|
||||
ScumSquadView,
|
||||
ScumVehicleView
|
||||
} from "../contracts/scumOperations";
|
||||
import { safeDiagnosticText } from "../utils/safeDiagnosticText";
|
||||
|
||||
const maxVisibleItems = 50;
|
||||
|
||||
export function projectScumOperationsSnapshots(snapshots: GameClientBridgeSnapshotResponse[]): ScumOperationsSnapshotView {
|
||||
return {
|
||||
health: projectHealth(latestPayload(snapshots, "companion.health")),
|
||||
sessions: projectCollection(latestSnapshot(snapshots, "online.sessions"), "sessions", projectSession),
|
||||
players: projectCollection(latestSnapshot(snapshots, "players"), "players", projectPlayer),
|
||||
squads: projectCollection(latestSnapshot(snapshots, "squads"), "squads", projectSquad),
|
||||
vehicles: projectCollection(latestSnapshot(snapshots, "vehicles"), "vehicles", projectVehicle),
|
||||
flags: projectCollection(latestSnapshot(snapshots, "flags"), "flags", projectFlag)
|
||||
};
|
||||
}
|
||||
|
||||
function latestSnapshot(snapshots: GameClientBridgeSnapshotResponse[], type: string): GameClientBridgeSnapshotResponse | undefined {
|
||||
return snapshots
|
||||
.filter((snapshot) => snapshot.type === type)
|
||||
.sort((left, right) => right.observedAt.localeCompare(left.observedAt) || right.sequence - left.sequence)[0];
|
||||
}
|
||||
|
||||
function latestPayload(snapshots: GameClientBridgeSnapshotResponse[], type: string): GameClientBridgeJsonObject | undefined {
|
||||
return latestSnapshot(snapshots, type)?.payload;
|
||||
}
|
||||
|
||||
function projectHealth(payload: GameClientBridgeJsonObject | undefined): ScumCompanionHealthView | undefined {
|
||||
if (!payload) return undefined;
|
||||
const status = readText(payload.status, 20);
|
||||
return {
|
||||
status: status === "online" || status === "degraded" || status === "offline" ? status : "unknown",
|
||||
version: readOptionalText(payload.version, 40),
|
||||
observedAt: readOptionalText(payload.observedAt, 64),
|
||||
latencyMs: readOptionalNumber(payload.latencyMs, 0, 30000),
|
||||
capabilities: readArray(payload.capabilities).map((value) => readText(value, 80)).filter(Boolean).slice(0, 16)
|
||||
};
|
||||
}
|
||||
|
||||
function projectCollection<T>(
|
||||
snapshot: GameClientBridgeSnapshotResponse | undefined,
|
||||
key: string,
|
||||
project: (value: unknown) => T | null
|
||||
): ScumSnapshotCollection<T> {
|
||||
const values = snapshot ? readArray(snapshot.payload[key]) : [];
|
||||
return {
|
||||
observedAt: snapshot?.observedAt,
|
||||
total: values.length,
|
||||
items: values.slice(0, maxVisibleItems).map(project).filter((value): value is T => value !== null)
|
||||
};
|
||||
}
|
||||
|
||||
function projectSession(value: unknown): ScumSessionView | null {
|
||||
const record = readObject(value);
|
||||
const sessionId = readText(record?.sessionId, 120);
|
||||
const playerName = readText(record?.playerName, 80);
|
||||
return sessionId && playerName ? { sessionId, playerName, startedAt: readOptionalText(record?.startedAt, 64) } : null;
|
||||
}
|
||||
|
||||
function projectPlayer(value: unknown): ScumPlayerView | null {
|
||||
const record = readObject(value);
|
||||
const playerId = readText(record?.playerId, 96);
|
||||
const playerName = readText(record?.playerName, 80);
|
||||
const status = readText(record?.status, 20);
|
||||
return playerId && playerName && status ? {
|
||||
playerId,
|
||||
playerName,
|
||||
status,
|
||||
squadId: readOptionalText(record?.squadId, 96),
|
||||
pingMs: readOptionalNumber(record?.pingMs, 0, 10000),
|
||||
lastSeenAt: readOptionalText(record?.lastSeenAt, 64)
|
||||
} : null;
|
||||
}
|
||||
|
||||
function projectSquad(value: unknown): ScumSquadView | null {
|
||||
const record = readObject(value);
|
||||
const squadId = readText(record?.squadId, 96);
|
||||
const name = readText(record?.name, 80);
|
||||
const memberCount = readOptionalNumber(record?.memberCount, 0, 64);
|
||||
return squadId && name && memberCount !== undefined ? {
|
||||
squadId,
|
||||
name,
|
||||
memberCount,
|
||||
leaderPlayerId: readOptionalText(record?.leaderPlayerId, 96),
|
||||
lastActiveAt: readOptionalText(record?.lastActiveAt, 64)
|
||||
} : null;
|
||||
}
|
||||
|
||||
function projectVehicle(value: unknown): ScumVehicleView | null {
|
||||
const record = readObject(value);
|
||||
const vehicleId = readText(record?.vehicleId, 96);
|
||||
const vehicleType = readText(record?.vehicleType, 80);
|
||||
const status = readText(record?.status, 20);
|
||||
return vehicleId && vehicleType && status ? {
|
||||
vehicleId,
|
||||
vehicleType,
|
||||
status,
|
||||
ownerPlayerId: readOptionalText(record?.ownerPlayerId, 96),
|
||||
squadId: readOptionalText(record?.squadId, 96),
|
||||
fuelPercent: readOptionalNumber(record?.fuelPercent, 0, 100),
|
||||
healthPercent: readOptionalNumber(record?.healthPercent, 0, 100),
|
||||
lastSeenAt: readOptionalText(record?.lastSeenAt, 64)
|
||||
} : null;
|
||||
}
|
||||
|
||||
function projectFlag(value: unknown): ScumFlagView | null {
|
||||
const record = readObject(value);
|
||||
const flagId = readText(record?.flagId, 96);
|
||||
const status = readText(record?.status, 20);
|
||||
return flagId && status ? {
|
||||
flagId,
|
||||
status,
|
||||
ownerPlayerId: readOptionalText(record?.ownerPlayerId, 96),
|
||||
squadId: readOptionalText(record?.squadId, 96),
|
||||
radiusMeters: readOptionalNumber(record?.radiusMeters, 0, 5000),
|
||||
lastUpdatedAt: readOptionalText(record?.lastUpdatedAt, 64)
|
||||
} : null;
|
||||
}
|
||||
|
||||
function readObject(value: unknown): Record<string, unknown> | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
||||
}
|
||||
|
||||
function readArray(value: unknown): unknown[] {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
function readText(value: unknown, maxLength: number): string {
|
||||
if (typeof value !== "string") return "";
|
||||
return (safeDiagnosticText(value.slice(0, maxLength), "") ?? "").trim();
|
||||
}
|
||||
|
||||
function readOptionalText(value: unknown, maxLength: number): string | undefined {
|
||||
return readText(value, maxLength) || undefined;
|
||||
}
|
||||
|
||||
function readOptionalNumber(value: unknown, minimum: number, maximum: number): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) && value >= minimum && value <= maximum ? value : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user