Integrate SCUM real ops workflows

This commit is contained in:
npc0-hue
2026-08-10 21:12:53 +08:00
parent 1063330710
commit a770bc6250
88 changed files with 6375 additions and 2719 deletions
+29 -86
View File
@@ -244,61 +244,18 @@ describe("PlatformApiClient AI providers", () => {
count: 1
});
}
if (url.endsWith("/api/v1/server-instances/server-1/config")) {
return jsonResponse({
serverInstanceId: server.id,
configVersion: 1,
format: "properties",
key: "server.properties",
content: "server.name=Example Survival #1\n",
source: "platform-derived",
updatedAt: "2026-07-03T00:00:00Z"
});
}
if (url.endsWith("/api/v1/server-instances/server-1/config/diff") && init?.method === "POST") {
expect(JSON.parse(String(init.body))).toEqual({
expectedConfigVersion: 1,
key: "server.properties",
proposedContent: "server.name=Example Survival #2\n"
});
return jsonResponse({
serverInstanceId: server.id,
configVersion: 1,
key: "server.properties",
currentContent: "server.name=Example Survival #1\n",
proposedContent: "server.name=Example Survival #2\n",
diff: [
{ kind: "removed", oldNumber: 1, content: "server.name=Example Survival #1" },
{ kind: "added", newNumber: 1, content: "server.name=Example Survival #2" }
],
hasChanges: true,
source: "platform-review",
reviewedAt: "2026-07-03T00:00:00Z"
});
}
if (url.endsWith("/api/v1/server-instances/server-1/config/approve") && init?.method === "POST") {
expect(JSON.parse(String(init.body))).toEqual({
expectedConfigVersion: 1,
key: "server.properties",
proposedContent: "server.name=Example Survival #2\n",
idempotencyKey: "idem-config"
});
return jsonResponse({
status: "queued",
preview: {
serverInstanceId: server.id,
configVersion: 1,
key: "server.properties",
currentContent: "server.name=Example Survival #1\n",
proposedContent: "server.name=Example Survival #2\n",
diff: [{ kind: "added", newNumber: 1, content: "server.name=Example Survival #2" }],
hasChanges: true,
source: "platform-review",
reviewedAt: "2026-07-03T00:00:00Z"
},
job: { ...job, id: "job-config-write", capability: "config.write", targetKey: "server.properties", inputRef: "input://server-config/server-1/server.properties/v1" }
});
}
if (url.endsWith("/api/v1/server-instances/server-1/scum/players")) return jsonResponse({ items: [{ id: "scum-player-1", gamePlayerId: "steam-1", displayName: "Prisoner One", online: true }], count: 1 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/squads")) return jsonResponse({ items: [{ id: "squad-1", squadId: "squad-1", name: "Alpha" }], count: 1 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/squad-members")) return jsonResponse({ items: [{ id: "member-1", squadId: "squad-1", gamePlayerId: "steam-1" }], count: 1 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/vehicles")) return jsonResponse({ items: [{ id: "vehicle-1", vehicleId: "vehicle-1", label: "SUV" }], count: 1 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/flags")) return jsonResponse({ items: [{ id: "flag-1", flagId: "flag-1", ownerSquadId: "squad-1" }], count: 1 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/positions")) return jsonResponse({ items: [{ id: "position-1", subjectType: "player", subjectId: "steam-1", x: 1, y: 2, z: 3 }], count: 1 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/operations") && (!init?.method || init.method === "GET")) return jsonResponse({ items: [], count: 0 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/operations") && init?.method === "POST") return jsonResponse({ id: "op-1", serverInstanceId: server.id, pluginId: plugin.id, templateKey: "player.fame.set", status: "waiting", approvalLevel: "operator", createdAt: "2026-07-03T00:00:00Z", updatedAt: "2026-07-03T00:00:00Z" });
if (url.endsWith("/api/v1/server-instances/server-1/scum/operations/op-1/approve") && init?.method === "POST") return jsonResponse({ id: "op-1", serverInstanceId: server.id, pluginId: plugin.id, templateKey: "player.fame.set", status: "queued", approvalLevel: "operator", createdAt: "2026-07-03T00:00:00Z", updatedAt: "2026-07-03T00:00:00Z" });
if (url.endsWith("/api/v1/server-instances/server-1/scum/workflows") && (!init?.method || init.method === "GET")) return jsonResponse({ items: [], count: 0 });
if (url.endsWith("/api/v1/server-instances/server-1/scum/workflows") && init?.method === "POST") return jsonResponse({ id: "workflow-1", serverInstanceId: server.id, pluginId: plugin.id, templateKey: "scum.world-refresh", status: "queued", createdAt: "2026-07-03T00:00:00Z", updatedAt: "2026-07-03T00:00:00Z" });
if (url.endsWith("/api/v1/server-instances/server-1/scum/workflow-steps?workflowId=workflow-1")) return jsonResponse({ items: [{ id: "step-1", workflowId: "workflow-1", serverInstanceId: server.id, stepKey: "read-positions", status: "queued", createdAt: "2026-07-03T00:00:00Z", updatedAt: "2026-07-03T00:00:00Z" }], count: 1 });
if (url.endsWith("/api/v1/file-operations/dispatch") && init?.method === "POST") {
expect(JSON.parse(String(init.body))).toEqual({
serverInstanceId: server.id,
@@ -536,13 +493,6 @@ describe("PlatformApiClient AI providers", () => {
expect(JSON.parse(String(init.body))).toEqual({ probeKey: "java-21", installPlanKey: "install-java-linux", planDigest: runtimeDigest, idempotencyKey: "idem-dep-install" });
return jsonResponse({ ...job, id: "job-dep-install", capability: "dependencies.install", targetKey: "dependencies/install/install-java-linux" });
}
if (url.endsWith("/api/v1/server-instances/server-1/logs/live")) {
return jsonResponse({ items: [], count: 0 });
}
if (url.endsWith("/api/v1/server-instances/server-1/logs/backfill") && init?.method === "POST") {
expect(JSON.parse(String(init.body))).toEqual({ sourceKey: "latest-log", checkpointRef: "artifact://logs/checkpoint/1", limit: 200, idempotencyKey: "idem-log-backfill" });
return jsonResponse({ ...job, id: "job-log-backfill", capability: "logs.backfill", targetKey: "logs/latest-log", inputRef: "artifact://logs/checkpoint/1" });
}
if (url.endsWith("/api/v1/plugin-bridge/authorize") && init?.method === "POST") {
return jsonResponse({
pluginId: plugin.id,
@@ -604,13 +554,18 @@ describe("PlatformApiClient AI providers", () => {
await expect(client.deleteServerInstance(server.id, { password: "secret-password", force: true, confirmation: "FORCE DELETE" })).resolves.toBeUndefined();
await expect(client.getPlatformResourceUsage()).resolves.toMatchObject({ source: "platform-derived", cpuPercent: 28 });
await expect(client.listServerMetrics()).resolves.toMatchObject({ count: 1, items: [{ serverInstanceId: server.id, online: true }] });
await expect(client.getServerConfig(server.id)).resolves.toMatchObject({ content: "server.name=Example Survival #1\n" });
await expect(
client.previewServerConfigDiff(server.id, { expectedConfigVersion: 1, key: "server.properties", proposedContent: "server.name=Example Survival #2\n" })
).resolves.toMatchObject({ hasChanges: true, source: "platform-review" });
await expect(
client.approveServerConfigWrite(server.id, { expectedConfigVersion: 1, key: "server.properties", proposedContent: "server.name=Example Survival #2\n", idempotencyKey: "idem-config" })
).resolves.toMatchObject({ status: "queued", job: { capability: "config.write", targetKey: "server.properties" } });
await expect(client.listSCUMPlayers(server.id)).resolves.toMatchObject({ count: 1, items: [{ gamePlayerId: "steam-1" }] });
await expect(client.listSCUMSquads(server.id)).resolves.toMatchObject({ count: 1 });
await expect(client.listSCUMSquadMembers(server.id)).resolves.toMatchObject({ count: 1 });
await expect(client.listSCUMVehicles(server.id)).resolves.toMatchObject({ count: 1 });
await expect(client.listSCUMFlags(server.id)).resolves.toMatchObject({ count: 1 });
await expect(client.listSCUMPositions(server.id)).resolves.toMatchObject({ count: 1 });
await expect(client.listSCUMOperations(server.id)).resolves.toMatchObject({ count: 0 });
await expect(client.createSCUMOperation(server.id, { templateKey: "player.fame.set", playerId: "steam-1", payload: { fame: 100 }, reason: "typed correction", idempotencyKey: "idem-scum-op" })).resolves.toMatchObject({ id: "op-1", status: "waiting" });
await expect(client.approveSCUMOperation(server.id, "op-1")).resolves.toMatchObject({ id: "op-1", status: "queued" });
await expect(client.listSCUMWorkflows(server.id)).resolves.toMatchObject({ count: 0 });
await expect(client.createSCUMWorkflow(server.id, { templateKey: "scum.world-refresh", idempotencyKey: "idem-scum-workflow" })).resolves.toMatchObject({ id: "workflow-1", status: "queued" });
await expect(client.listSCUMWorkflowSteps(server.id, "workflow-1")).resolves.toMatchObject({ count: 1, items: [{ stepKey: "read-positions" }] });
await expect(client.dispatchFileOperation({ serverInstanceId: server.id, operation: "read", key: "logs/latest.log", idempotencyKey: "idem-file" })).resolves.toMatchObject({
status: "queued",
job: { capability: "files.read", targetKey: "logs/latest.log" }
@@ -665,21 +620,11 @@ describe("PlatformApiClient AI providers", () => {
await expect(client.installDependencies(server.id, { probeKey: "java-21", installPlanKey: "install-java-linux", planDigest: runtimeDigest, idempotencyKey: "idem-dep-install" })).resolves.toMatchObject({
capability: "dependencies.install"
});
await expect(client.listServerLiveLogs(server.id)).resolves.toMatchObject({ count: 0 });
await expect(client.requestLogBackfill(server.id, { sourceKey: "latest-log", checkpointRef: "artifact://logs/checkpoint/1", limit: 200, idempotencyKey: "idem-log-backfill" })).resolves.toMatchObject({
capability: "logs.backfill"
});
await expect(client.authorizePluginBridge({ pluginId: plugin.id, routeKey: "logs", action: "logs.query" })).resolves.toMatchObject({
allowed: true
});
await expect(
client.executePluginBridge({ requestId: "req-bridge", pluginId: plugin.id, routeKey: "logs", serverInstanceId: server.id, action: "logs.query", payload: { logStreamId: "log-1" } })
).resolves.toMatchObject({ status: "ok", result: { entryCount: "0" } });
await expect(
client.invokeAI({ requestId: "ai-1", serverInstanceId: server.id, purpose: "config.suggest", prompt: "Tune PVP safely", currentConfig: "server.name=Example Survival #1\n" })
).resolves.toMatchObject({ status: "ok", usage: { mocked: true }, configRecommendation: { diffSummary: "review required" } });
expect(fetchMock).toHaveBeenCalledTimes(43);
expect(fetchMock).toHaveBeenCalledTimes(48);
});
it("calls plugin marketplace endpoints with filter and state contracts", async () => {
@@ -708,11 +653,11 @@ describe("PlatformApiClient AI providers", () => {
expect(fetchMock).toHaveBeenCalledTimes(3);
});
it("surfaces config diff preview failures from the platform", async () => {
it("surfaces SCUM typed operation failures from the platform", async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url.endsWith("/api/v1/server-instances/server-1/config/diff") && init?.method === "POST") {
return new Response(JSON.stringify({ code: "validation", message: "expectedConfigVersion must match server instance" }), {
if (url.endsWith("/api/v1/server-instances/server-1/scum/operations") && init?.method === "POST") {
return new Response(JSON.stringify({ code: "validation", message: "SCUM operation template is not declared" }), {
status: 400,
headers: { "Content-Type": "application/json" }
});
@@ -723,9 +668,7 @@ describe("PlatformApiClient AI providers", () => {
const client = new PlatformApiClient();
await expect(client.previewServerConfigDiff(server.id, { expectedConfigVersion: 0, key: "server.properties", proposedContent: "changed=true\n" })).rejects.toThrow(
"expectedConfigVersion must match server instance"
);
await expect(client.createSCUMOperation(server.id, { templateKey: "raw.sql", reason: "unsafe", idempotencyKey: "bad-scum-op" })).rejects.toThrow("SCUM operation template is not declared");
});
it("keeps raw key and base URL fields out of provider responses", () => {