Rebuild SCUM plugin data ownership

This commit is contained in:
npc0-hue
2026-08-14 10:03:58 +08:00
parent c8b49c711c
commit a6c4cdac5d
79 changed files with 532 additions and 1842 deletions
+4 -42
View File
@@ -617,7 +617,7 @@ describe("plugin manifest validation", () => {
expect(manifest.gameClientBridge.pages.find((page) => page.pageKey === "live-map")?.snapshotTypes).toEqual(expect.arrayContaining(["players", "vehicles", "flags"]));
});
it("declares packaged SCUM v57 query templates without browser-provided SQL", () => {
it("declares typed SCUM.db query templates without browser-visible SQL", () => {
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as {
permissions: string[];
@@ -633,10 +633,6 @@ describe("plugin manifest validation", () => {
targetKey: string;
parameterSchemaRef: string;
resultSchemaRef: string;
sqlRef: string;
targetTable: string;
upsertKeys: string[];
columnMappings: Record<string, string>;
maxRows: number;
timeoutSeconds: number;
}>;
@@ -645,7 +641,7 @@ describe("plugin manifest validation", () => {
pages: Array<{ key: string; permissions?: string[]; bridgeActions?: string[] }>;
runtimeProfiles?: { transportProfiles?: Array<{ key: string; kind: string; targetKey?: string; capabilities: string[] }> };
};
const expectedKeys = ["scum.player.profile", "scum.squads", "scum.squad-members", "scum.vehicles", "scum.flags", "scum.positions", "scum.activity", "scum.gifts"];
const expectedKeys = ["scum.player.profile", "scum.squads", "scum.squad-members", "scum.vehicles", "scum.flags", "scum.positions"];
const templatesByKey = new Map(manifest.gameClientBridge.queryTemplates.map((template) => [template.key, template]));
expect([...templatesByKey.keys()]).toEqual(expect.arrayContaining(expectedKeys));
expect(manifest.capabilities).toContain("remote.run.db.sqlite.query");
@@ -659,14 +655,7 @@ describe("plugin manifest validation", () => {
expect(template.engine).toBe("sqlite");
expect(template.transportKey).toBe("scum-database");
expect(template.targetKey).toBe("scum-database");
expect(template.sqlRef).toMatch(/^sql\/scum-db-v57\/.+\.sql$/);
expect(template.targetTable).toMatch(/^scum_/);
expect(template.upsertKeys.length).toBeGreaterThan(0);
expect(Object.keys(template.columnMappings).length).toBeGreaterThan(0);
expect(template.upsertKeys.every((key) => key in template.columnMappings)).toBe(true);
const sql = fs.readFileSync(path.join(pluginDir, template.sqlRef), "utf8");
expect(sql).toMatch(/^SELECT\b/i);
expect(sql).not.toMatch(/\b(?:INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|ATTACH|PRAGMA)\b/i);
expect(JSON.stringify(template).toLowerCase()).not.toMatch(/select\s|from\s|sqlite:|scum\.db|databasepath|hostpath|dsn/);
const parameters = JSON.parse(fs.readFileSync(path.join(pluginDir, template.parameterSchemaRef), "utf8"));
const result = JSON.parse(fs.readFileSync(path.join(pluginDir, template.resultSchemaRef), "utf8"));
expect(parameters).toMatchObject({ type: "object", additionalProperties: false });
@@ -676,7 +665,7 @@ describe("plugin manifest validation", () => {
const playersPage = manifest.gameClientBridge.pages.find((page) => page.pageKey === "players");
const squadsPage = manifest.gameClientBridge.pages.find((page) => page.pageKey === "squads");
const mapPage = manifest.gameClientBridge.pages.find((page) => page.pageKey === "live-map");
expect(playersPage?.queryTemplateKeys).toEqual(expect.arrayContaining(["scum.player.profile", "scum.positions", "scum.activity"]));
expect(playersPage?.queryTemplateKeys).toEqual(expect.arrayContaining(["scum.player.profile", "scum.positions"]));
expect(squadsPage?.queryTemplateKeys).toEqual(expect.arrayContaining(["scum.squads", "scum.squad-members", "scum.flags"]));
expect(mapPage?.queryTemplateKeys).toEqual(expect.arrayContaining(["scum.vehicles", "scum.flags", "scum.positions"]));
for (const pageKey of ["players", "squads", "live-map"]) {
@@ -686,19 +675,6 @@ describe("plugin manifest validation", () => {
}
});
it("packages SCUM v57 UTF-16LE log and config maps", () => {
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as { gameClientBridge: { dataPacks: Array<{ key: string; databaseUserVersion: number; logParserRefs: string[]; configMapRefs: string[] }> } };
const pack = manifest.gameClientBridge.dataPacks.find((candidate) => candidate.key === "scum-db-v57");
expect(pack).toMatchObject({ databaseUserVersion: 57 });
const logParsers = JSON.parse(fs.readFileSync(path.join(pluginDir, pack!.logParserRefs[0]), "utf8"));
const configMaps = JSON.parse(fs.readFileSync(path.join(pluginDir, pack!.configMapRefs[0]), "utf8"));
expect(logParsers.encoding).toBe("utf-16le");
expect(logParsers.parsers.map((parser: { key: string }) => parser.key)).toEqual(expect.arrayContaining(["login", "logout", "chat", "admin", "vehicle-destruction"]));
const serverSettings = configMaps.maps.find((map: { key: string }) => map.key === "server-settings");
expect(serverSettings.fields).toMatchObject({ "scum.WelcomeMessage": "welcomeMessage", "scum.MessageOfTheDay": "motd" });
});
it("declares typed SCUM RCON operations without arbitrary command inputs", () => {
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as {
@@ -929,20 +905,6 @@ describe("plugin manifest validation", () => {
expect(errors.some((error) => error.includes("timeoutSeconds"))).toBe(true);
});
it("rejects inline SQL and incomplete SCUM row declarations", () => {
const inlineErrors = validateTemporaryBridgeManifest((manifest) => {
Object.assign(manifest.gameClientBridge.queryTemplates![0], { sqlRef: "SELECT * FROM user_profile", targetTable: "scum_users", upsertKeys: ["userProfileId"], columnMappings: { userProfileId: "userProfileId" } });
});
expect(inlineErrors.some((error) => error.includes("sqlRef"))).toBe(true);
const incompleteErrors = validateTemporaryBridgeManifest((manifest) => {
Object.assign(manifest.gameClientBridge.queryTemplates![0], { sqlRef: "sql/scum-db-v57/users.sql", targetTable: "users", upsertKeys: [], columnMappings: {} });
});
expect(incompleteErrors.some((error) => error.includes("targetTable"))).toBe(true);
expect(incompleteErrors.some((error) => error.includes("upsertKeys"))).toBe(true);
expect(incompleteErrors.some((error) => error.includes("columnMappings"))).toBe(true);
});
it("requires query templates to match a declared sqlite transport target and capability", () => {
const targetErrors = validateTemporaryBridgeManifest((manifest) => {
manifest.gameClientBridge.queryTemplates![0].targetKey = "db/other";