mirror of
https://github.com/Corfirean/coa-server-manager.git
synced 2026-11-11 03:51:01 +00:00
107 lines
5.9 KiB
JavaScript
107 lines
5.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { registerHooks } from "node:module";
|
|
|
|
let applyFailure = false;
|
|
let stateFailure = false;
|
|
let pending = null;
|
|
let serverRunning = true;
|
|
const calls = [];
|
|
globalThis.__hotfixApi = {
|
|
status: async () => ({ observed: Object.fromEntries(["mysql", "auth", "world"].map(key => [key, { state: serverRunning ? "running" : "stopped" }])) }),
|
|
stop: async () => { calls.push("stop"); },
|
|
applyUpdate: async () => { calls.push("apply"); if (applyFailure) throw new Error("SQL failure"); return { txn: { state: "committed", to_version: "2" } }; },
|
|
pendingUpdate: async () => { calls.push("disk"); if (stateFailure) throw new Error("damaged journal"); return pending; },
|
|
checkUpdate: async () => ({ from_version: "2", to_version: "2", items: [], conflicts: [], migrations: 1, pending_migrations: 0 }),
|
|
};
|
|
registerHooks({ resolve(specifier, context, next) {
|
|
if (specifier === "react") return { shortCircuit: true, url: "data:text/javascript,export const useSyncExternalStore=(subscribe,getSnapshot)=>getSnapshot();export const useState=()=>{throw new Error('React state is not used in this test');};" };
|
|
if (specifier === "@/lib/serverLifecycle") return { shortCircuit: true, url: new URL("../src/lib/serverLifecycle.ts", import.meta.url).href };
|
|
if (specifier === "@/lib/api") return { shortCircuit: true, url: "data:text/javascript,export const api=globalThis.__hotfixApi;export const asUiError=e=>({human:{code:'unknown'},technical:e.message});" };
|
|
if (specifier === "@tauri-apps/api/event") return { shortCircuit: true, url: "data:text/javascript,export const listen=async()=>()=>{};" };
|
|
if (specifier === "./serverUpdateStatus" && context.parentURL?.endsWith("/serverUpdate.ts")) return { shortCircuit: true, url: new URL("../src/lib/serverUpdateStatus.ts", import.meta.url).href };
|
|
if (specifier === "./updatePrefs" && context.parentURL?.endsWith("/serverUpdate.ts")) return { shortCircuit: true, url: new URL("../src/lib/updatePrefs.ts", import.meta.url).href };
|
|
return next(specifier, context);
|
|
} });
|
|
const { applyServerUpdate, refreshServerUpdateState, checkServerUpdate, useServerUpdate } = await import("../src/lib/serverUpdate.ts");
|
|
|
|
test("a failed update rereads its unfinished transaction from disk", async () => {
|
|
calls.length = 0;
|
|
applyFailure = true;
|
|
pending = { id: "interrupted", state: "failed", message: "Recovery required" };
|
|
assert.equal(await applyServerUpdate("failed-case", { choices: {} }), false);
|
|
assert.deepEqual(calls, ["apply", "disk"]);
|
|
assert.equal(await refreshServerUpdateState("failed-case"), pending);
|
|
});
|
|
test("unreadable state blocks another apply until a successful reread", async () => {
|
|
calls.length = 0;
|
|
applyFailure = false;
|
|
stateFailure = true;
|
|
await applyServerUpdate("corrupt-case", { choices: {} });
|
|
assert.equal(await applyServerUpdate("corrupt-case", { choices: {} }), false);
|
|
assert.deepEqual(calls, ["apply", "disk"]);
|
|
stateFailure = false;
|
|
pending = null;
|
|
assert.equal(await refreshServerUpdateState("corrupt-case"), null);
|
|
assert.equal(await applyServerUpdate("corrupt-case", { choices: {} }), true);
|
|
assert.deepEqual(calls, ["apply", "disk", "disk", "apply", "disk"]);
|
|
});
|
|
test("refresh after recovery returns the latest disk state, then its absence", async () => {
|
|
pending = { id: "recovery", state: "failed", message: "Restore failed at database verification" };
|
|
assert.equal(await refreshServerUpdateState("recovery-case"), pending);
|
|
pending = null;
|
|
assert.equal(await refreshServerUpdateState("recovery-case"), null);
|
|
});
|
|
|
|
test("successful update keeps the existing-character warning across disk refresh", async () => {
|
|
const original = globalThis.__hotfixApi.applyUpdate;
|
|
try {
|
|
applyFailure = false;
|
|
stateFailure = false;
|
|
pending = null;
|
|
globalThis.__hotfixApi.applyUpdate = async () => ({ txn: { state: "committed", to_version: "2" }, preexisting_character_count: 174 });
|
|
assert.equal(await applyServerUpdate("old-character-warnings", { choices: {} }), true);
|
|
await refreshServerUpdateState("old-character-warnings");
|
|
assert.deepEqual(useServerUpdate("old-character-warnings").result, { committed: "2", preexistingCharacters: 174 });
|
|
} finally { globalThis.__hotfixApi.applyUpdate = original; }
|
|
});
|
|
|
|
test("a stopped server is left stopped after a successful update by default", async () => {
|
|
calls.length = 0;
|
|
serverRunning = false;
|
|
applyFailure = false;
|
|
stateFailure = false;
|
|
pending = null;
|
|
try {
|
|
assert.equal(await applyServerUpdate("stopped-case", { choices: {} }), true);
|
|
assert.deepEqual(calls, ["apply", "stop", "disk"]);
|
|
} finally { serverRunning = true; }
|
|
});
|
|
|
|
test("a rejected recheck removes an earlier cached update offer", async () => {
|
|
const original = globalThis.__hotfixApi.checkUpdate;
|
|
try {
|
|
globalThis.__hotfixApi.checkUpdate = async () => ({ from_version: "0.261005.22", to_version: "0.261005.24", items: [], migrations: 1, pending_migrations: 1 });
|
|
await checkServerUpdate("stale-offer");
|
|
assert.equal(useServerUpdate("stale-offer").available, true);
|
|
globalThis.__hotfixApi.checkUpdate = async () => { throw new Error("Installed version is newer than package"); };
|
|
await checkServerUpdate("stale-offer");
|
|
assert.equal(useServerUpdate("stale-offer").available, false);
|
|
assert.equal(useServerUpdate("stale-offer").preview, null);
|
|
} finally { globalThis.__hotfixApi.checkUpdate = original; }
|
|
});
|
|
|
|
test("a successful recheck keeps the last failed update details visible", async () => {
|
|
applyFailure = true;
|
|
stateFailure = false;
|
|
pending = null;
|
|
try {
|
|
assert.equal(await applyServerUpdate("visible-error", { choices: {} }), false);
|
|
await checkServerUpdate("visible-error");
|
|
const state = useServerUpdate("visible-error");
|
|
assert.equal(state.applying, false);
|
|
assert.equal(state.available, false);
|
|
assert.equal(state.uiError.technical, "SQL failure");
|
|
} finally { applyFailure = false; }
|
|
});
|