Fill the SCUM user and vehicle tables from the plugin-declared database read path
The SCUM 用户管理 list stayed empty because the typed scum_user and scum_vehicle tables had no producer: the run ingest endpoint is signed-run only, and the previously registered plugin templates asked for a Run database capability the endpoint never advertises. The plugin now declares bounded, read-only SQLite projections for players and vehicles (sql/scum-db-v57/*.sql with query schemas), and the platform dispatches those templates as durable remote.run.db.sqlite.query jobs on run heartbeat and page open, then projects the returned rows into the typed SCUM tables through the same shared ingest path used by signed run facts.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"id": "game.scum",
|
||||
"name": "SCUM Server",
|
||||
"description": "First-party SCUM game server operations plugin with platform-mediated lifecycle and plugin-owned RCON data flows.",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.22",
|
||||
"kind": "game-plugin",
|
||||
"tags": [
|
||||
"scum",
|
||||
@@ -75,6 +75,7 @@
|
||||
"remote.run.logs.transfer",
|
||||
"remote.run.rcon.command",
|
||||
"remote.run.program.command",
|
||||
"remote.run.db.sqlite.query",
|
||||
"artifacts.read",
|
||||
"artifacts.write",
|
||||
"ai.invoke"
|
||||
@@ -92,10 +93,13 @@
|
||||
"remote.run.process.stop",
|
||||
"remote.run.logs.transfer",
|
||||
"remote.run.rcon.command",
|
||||
"remote.run.program.command"
|
||||
"remote.run.program.command",
|
||||
"remote.run.db.sqlite.query"
|
||||
],
|
||||
"rcon": true,
|
||||
"databaseEngines": [],
|
||||
"databaseEngines": [
|
||||
"sqlite"
|
||||
],
|
||||
"logTransfer": true
|
||||
},
|
||||
"bridge": {
|
||||
@@ -324,6 +328,79 @@
|
||||
"player.intelligence"
|
||||
]
|
||||
}
|
||||
],
|
||||
"queryTemplates": [
|
||||
{
|
||||
"key": "scum.database.players",
|
||||
"title": "Read SCUM player, squad, economy, and position facts",
|
||||
"permission": "server.game-client.read",
|
||||
"engine": "sqlite",
|
||||
"transportKey": "scum-database",
|
||||
"targetKey": "scum-database",
|
||||
"parameterSchemaRef": "schemas/bridge/queries/scum-database-players.parameters.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-database-players.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/players.sql",
|
||||
"pollIntervalSeconds": 60,
|
||||
"maxRows": 180,
|
||||
"timeoutSeconds": 30,
|
||||
"projections": [
|
||||
{
|
||||
"collection": "scum.users",
|
||||
"rowPath": "rows",
|
||||
"upsertKeys": [
|
||||
"steamId"
|
||||
],
|
||||
"fieldMappings": {
|
||||
"steamId": "steamId",
|
||||
"displayName": "displayName",
|
||||
"loginIp": "lastLoginIp",
|
||||
"squadId": "squadId",
|
||||
"online": "online",
|
||||
"login": "freshLogin",
|
||||
"loginObservedAt": "lastLoginTime",
|
||||
"bankBalance": "normalBalance",
|
||||
"goldBars": "goldBalance",
|
||||
"riddenGameVehicleId": "riddenGameVehicleId",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "scum.database.vehicles",
|
||||
"title": "Read SCUM vehicle positions and classes",
|
||||
"permission": "server.game-client.read",
|
||||
"engine": "sqlite",
|
||||
"transportKey": "scum-database",
|
||||
"targetKey": "scum-database",
|
||||
"parameterSchemaRef": "schemas/bridge/queries/scum-database-vehicles.parameters.schema.json",
|
||||
"resultSchemaRef": "schemas/bridge/queries/scum-database-vehicles.result.schema.json",
|
||||
"sqlRef": "sql/scum-db-v57/vehicles.sql",
|
||||
"pollIntervalSeconds": 120,
|
||||
"maxRows": 220,
|
||||
"timeoutSeconds": 30,
|
||||
"projections": [
|
||||
{
|
||||
"collection": "scum.vehicles",
|
||||
"rowPath": "rows",
|
||||
"upsertKeys": [
|
||||
"gameVehicleId"
|
||||
],
|
||||
"fieldMappings": {
|
||||
"gameVehicleId": "gameVehicleId",
|
||||
"vehicleClass": "vehicleClass",
|
||||
"displayName": "displayName",
|
||||
"exists": "existsInGame",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
"z": "z",
|
||||
"observedAt": "observedAt"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"permissions": [
|
||||
@@ -857,6 +934,14 @@
|
||||
{
|
||||
"path": "data-packs/scum-config-v57/map-geometry.json",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "sql/scum-db-v57/players.sql",
|
||||
"mode": 384
|
||||
},
|
||||
{
|
||||
"path": "sql/scum-db-v57/vehicles.sql",
|
||||
"mode": 384
|
||||
}
|
||||
],
|
||||
"productionLifecycle": {
|
||||
@@ -1148,7 +1233,21 @@
|
||||
"retentionDays": 30
|
||||
}
|
||||
],
|
||||
"dataTargets": [],
|
||||
"dataTargets": [
|
||||
{
|
||||
"key": "scum-database",
|
||||
"kind": "sqlite.snapshot",
|
||||
"transportKey": "scum-database",
|
||||
"sourceRootKey": "server-root",
|
||||
"sourcePath": "SCUM/Saved/SaveFiles/SCUM.db",
|
||||
"workspaceKey": "databases/scum/SCUM.db",
|
||||
"refreshPolicy": "on-demand-snapshot",
|
||||
"maxBytes": 1073741824,
|
||||
"platforms": [
|
||||
"windows"
|
||||
]
|
||||
}
|
||||
],
|
||||
"transportProfiles": [
|
||||
{
|
||||
"key": "server-files",
|
||||
@@ -1177,6 +1276,14 @@
|
||||
"remote.rsync.write"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "scum-database",
|
||||
"kind": "sqlite",
|
||||
"targetKey": "scum-database",
|
||||
"capabilities": [
|
||||
"remote.run.db.sqlite.query"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "scum-management",
|
||||
"kind": "rcon",
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "ScumDatabasePlayersParameters",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 500,
|
||||
"description": "Bounded row limit supplied by the platform row cap."
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "ScumDatabasePlayersResult",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["rows"],
|
||||
"properties": {
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"maxItems": 500,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["steamId"],
|
||||
"properties": {
|
||||
"steamId": { "type": "string", "minLength": 1, "maxLength": 32 },
|
||||
"displayName": { "type": ["string", "null"], "maxLength": 80 },
|
||||
"lastLoginIp": { "type": ["string", "null"], "maxLength": 64 },
|
||||
"userProfileId": { "type": ["string", "null"], "maxLength": 32 },
|
||||
"gamePlayerId": { "type": ["string", "null"], "maxLength": 32 },
|
||||
"squadId": { "type": ["string", "null"], "maxLength": 32 },
|
||||
"squadName": { "type": ["string", "null"], "maxLength": 80 },
|
||||
"riddenGameVehicleId": { "type": ["string", "null"], "maxLength": 32 },
|
||||
"x": { "type": ["number", "null"] },
|
||||
"y": { "type": ["number", "null"] },
|
||||
"z": { "type": ["number", "null"] },
|
||||
"moneyBalance": { "type": ["integer", "null"] },
|
||||
"normalBalance": { "type": ["integer", "null"] },
|
||||
"goldBalance": { "type": ["integer", "null"] },
|
||||
"lastLoginTime": { "type": ["string", "null"], "maxLength": 64 },
|
||||
"lastLogoutTime": { "type": ["string", "null"], "maxLength": 64 },
|
||||
"lastSaveTime": { "type": ["string", "null"], "maxLength": 64 },
|
||||
"online": { "type": ["integer", "null"], "enum": [0, 1, null] },
|
||||
"freshLogin": { "type": ["integer", "null"], "enum": [0, 1, null] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "ScumDatabaseVehiclesParameters",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 500,
|
||||
"description": "Bounded row limit supplied by the platform row cap."
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "ScumDatabaseVehiclesResult",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["rows"],
|
||||
"properties": {
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"maxItems": 500,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["gameVehicleId"],
|
||||
"properties": {
|
||||
"gameVehicleId": { "type": "string", "minLength": 1, "maxLength": 32 },
|
||||
"vehicleClass": { "type": ["string", "null"], "maxLength": 120 },
|
||||
"displayName": { "type": ["string", "null"], "maxLength": 120 },
|
||||
"functional": { "type": ["integer", "null"], "enum": [0, 1, null] },
|
||||
"existsInGame": { "type": ["integer", "null"], "enum": [0, 1, null] },
|
||||
"x": { "type": ["number", "null"] },
|
||||
"y": { "type": ["number", "null"] },
|
||||
"z": { "type": ["number", "null"] },
|
||||
"lastAccessTime": { "type": ["string", "null"], "maxLength": 64 },
|
||||
"observedAt": { "type": ["string", "null"], "maxLength": 64 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
SELECT
|
||||
account.id AS steamId,
|
||||
COALESCE(NULLIF(profile.name, ''), NULLIF(account.name, ''), account.id) AS displayName,
|
||||
COALESCE(account.last_direct_connection_address, '') AS lastLoginIp,
|
||||
CAST(member.squad_id AS TEXT) AS squadId,
|
||||
entity.location_x AS x,
|
||||
entity.location_y AS y,
|
||||
entity.location_z AS z,
|
||||
MAX(CASE WHEN currency.currency_type = 1 THEN currency.account_balance END) AS normalBalance,
|
||||
MAX(CASE WHEN currency.currency_type = 2 THEN currency.account_balance END) AS goldBalance,
|
||||
profile.last_login_time AS lastLoginTime,
|
||||
CASE WHEN profile.last_login_time IS NOT NULL AND (profile.last_logout_time IS NULL OR profile.last_login_time > profile.last_logout_time) THEN 1 ELSE 0 END AS online,
|
||||
CASE WHEN profile.last_login_time IS NOT NULL AND profile.last_login_time >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-20 seconds') THEN 1 ELSE 0 END AS freshLogin,
|
||||
CAST(mount.vehicle_entity_id AS TEXT) AS riddenGameVehicleId
|
||||
FROM user account
|
||||
LEFT JOIN user_profile profile ON profile.user_id = account.id
|
||||
LEFT JOIN prisoner ON prisoner.id = profile.prisoner_id
|
||||
LEFT JOIN prisoner_entity ON prisoner_entity.prisoner_id = prisoner.id
|
||||
LEFT JOIN entity ON entity.id = prisoner_entity.entity_id
|
||||
LEFT JOIN squad_member member ON member.user_profile_id = profile.id
|
||||
LEFT JOIN prisoner_vehicle_mountee_info mount ON mount.prisoner_id = prisoner.id
|
||||
LEFT JOIN bank_account_registry bank ON bank.account_owner_user_profile_id = profile.id
|
||||
LEFT JOIN bank_account_registry_currencies currency ON currency.bank_account_id = bank.id
|
||||
GROUP BY account.id
|
||||
ORDER BY profile.last_login_time DESC
|
||||
LIMIT :limit
|
||||
@@ -0,0 +1,13 @@
|
||||
SELECT
|
||||
CAST(spawner.vehicle_entity_id AS TEXT) AS gameVehicleId,
|
||||
entity.class AS vehicleClass,
|
||||
spawner.vehicle_alias AS displayName,
|
||||
1 AS existsInGame,
|
||||
entity.location_x AS x,
|
||||
entity.location_y AS y,
|
||||
entity.location_z AS z,
|
||||
strftime('%Y-%m-%dT%H:%M:%SZ', 'now') AS observedAt
|
||||
FROM vehicle_spawner spawner
|
||||
JOIN entity ON entity.id = spawner.vehicle_entity_id
|
||||
ORDER BY spawner.vehicle_last_access_time DESC
|
||||
LIMIT :limit
|
||||
@@ -140,18 +140,58 @@ describe("plugin manifest validation", () => {
|
||||
|
||||
it("removes raw SQL command surfaces", () => {
|
||||
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as { gameClientBridge: { commands: Array<{ type: string; payloadSchemaRef: string }>; queryTemplates?: Array<{ key: string }> } };
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as {
|
||||
gameClientBridge: {
|
||||
commands: Array<{ type: string; payloadSchemaRef: string }>;
|
||||
queryTemplates?: Array<{ key: string; sqlRef: string }>;
|
||||
pages: Array<{ pageKey: string; queryTemplateKeys?: string[] }>;
|
||||
};
|
||||
};
|
||||
expect(manifest.gameClientBridge.commands.some((command) => command.type === "diagnostic.ping")).toBe(false);
|
||||
expect(manifest.gameClientBridge.commands.map((command) => command.type)).not.toEqual(expect.arrayContaining(["config.read", "config.patch", "database.request", "management.rcon.request", "management.program.request"]));
|
||||
expect(manifest.gameClientBridge.queryTemplates ?? []).toEqual([]);
|
||||
for (const template of manifest.gameClientBridge.queryTemplates ?? []) {
|
||||
expect(template.sqlRef).toMatch(/^sql\/scum-db-v57\/[a-z-]+\.sql$/);
|
||||
}
|
||||
expect(manifest.gameClientBridge.pages.some((page) => (page.queryTemplateKeys ?? []).length > 0)).toBe(false);
|
||||
expect(fs.existsSync(path.join(pluginDir, "schemas/bridge/queries/SCUM_DB_CONTRACT.md"))).toBe(false);
|
||||
});
|
||||
|
||||
it("does not declare SCUM direct database templates or bridge projections", () => {
|
||||
it("declares bounded SCUM database read templates for platform-dispatched projections", () => {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginsRoot, "examples/scum-server-plugin/manifest.json"), "utf8")) as {
|
||||
gameClientBridge: { lifecycleProjections?: unknown[]; queryTemplates?: Array<{ key: string; engine: string; transportKey: string; targetKey: string; projections?: unknown[] }> };
|
||||
gameClientBridge: {
|
||||
lifecycleProjections?: unknown[];
|
||||
queryTemplates?: Array<{
|
||||
key: string;
|
||||
engine: string;
|
||||
transportKey: string;
|
||||
targetKey: string;
|
||||
pollIntervalSeconds: number;
|
||||
maxRows: number;
|
||||
timeoutSeconds: number;
|
||||
projections?: Array<{ collection: string; rowPath: string; upsertKeys: string[] }>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
expect(manifest.gameClientBridge.queryTemplates ?? []).toEqual([]);
|
||||
const templates = manifest.gameClientBridge.queryTemplates ?? [];
|
||||
expect(templates.map((template) => template.key)).toEqual(["scum.database.players", "scum.database.vehicles"]);
|
||||
for (const template of templates) {
|
||||
expect(template.engine).toBe("sqlite");
|
||||
expect(template.transportKey).toBe("scum-database");
|
||||
expect(template.targetKey).toBe("scum-database");
|
||||
expect(template.pollIntervalSeconds).toBeGreaterThan(0);
|
||||
expect(template.pollIntervalSeconds).toBeLessThanOrEqual(300);
|
||||
expect(template.maxRows).toBeGreaterThan(0);
|
||||
expect(template.maxRows).toBeLessThanOrEqual(500);
|
||||
expect(template.timeoutSeconds).toBeGreaterThan(0);
|
||||
expect(template.timeoutSeconds).toBeLessThanOrEqual(60);
|
||||
expect((template.projections ?? []).length).toBe(1);
|
||||
for (const projection of template.projections ?? []) {
|
||||
expect(projection.rowPath).toBe("rows");
|
||||
expect(projection.upsertKeys.length).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
expect(templates.find((template) => template.key === "scum.database.players")?.projections?.[0].collection).toBe("scum.users");
|
||||
expect(templates.find((template) => template.key === "scum.database.vehicles")?.projections?.[0].collection).toBe("scum.vehicles");
|
||||
expect(manifest.gameClientBridge.lifecycleProjections).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -257,7 +297,7 @@ describe("plugin manifest validation", () => {
|
||||
expect(unsafe.some((error) => error.includes("raw host path"))).toBe(true);
|
||||
});
|
||||
|
||||
it("declares management transports without SCUM database access", () => {
|
||||
it("declares a read-only SCUM database transport beside the management transports", () => {
|
||||
const manifestPath = path.join(pluginsRoot, "examples/scum-server-plugin/manifest.json");
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as {
|
||||
remoteAccess?: { databaseEngines?: string[]; runCapabilities?: string[] };
|
||||
@@ -271,14 +311,18 @@ describe("plugin manifest validation", () => {
|
||||
expect(local?.capabilities).toContain("remote.run.rcon.command");
|
||||
expect(local?.transportKeys).toContain("scum-management");
|
||||
expect(local?.transportKeys).not.toContain("scum-database");
|
||||
expect(manifest.remoteAccess?.databaseEngines).toEqual([]);
|
||||
expect(manifest.remoteAccess?.runCapabilities).not.toEqual(expect.arrayContaining(["remote.run.db.sqlite.query", "remote.run.db.sqlite.execute"]));
|
||||
expect(manifest.runtimeProfiles?.dataTargets).toEqual([]);
|
||||
expect(manifest.remoteAccess?.databaseEngines).toEqual(["sqlite"]);
|
||||
expect(manifest.remoteAccess?.runCapabilities).toContain("remote.run.db.sqlite.query");
|
||||
expect(manifest.remoteAccess?.runCapabilities).not.toContain("remote.run.db.sqlite.execute");
|
||||
expect(manifest.runtimeProfiles?.dataTargets).toEqual([
|
||||
expect.objectContaining({ key: "scum-database", kind: "sqlite.snapshot" })
|
||||
]);
|
||||
expect(manifest.runtimeProfiles?.transportProfiles).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ key: "scum-management", kind: "rcon", capabilities: ["remote.run.rcon.command"] }),
|
||||
expect.objectContaining({ key: "scum-program", kind: "program", capabilities: ["remote.run.program.command"] })
|
||||
expect.objectContaining({ key: "scum-program", kind: "program", capabilities: ["remote.run.program.command"] }),
|
||||
expect.objectContaining({ key: "scum-database", kind: "sqlite", targetKey: "scum-database", capabilities: ["remote.run.db.sqlite.query"] })
|
||||
]));
|
||||
expect(manifest.runtimeProfiles?.transportProfiles?.some((profile) => profile.key === "scum-database" || profile.kind === "sqlite")).toBe(false);
|
||||
expect(manifest.runtimeProfiles?.transportProfiles?.flatMap((profile) => profile.capabilities ?? [])).not.toContain("remote.run.db.sqlite.execute");
|
||||
});
|
||||
|
||||
it("covers the SCUM 4.1 bridge and lifecycle declarations", () => {
|
||||
@@ -313,7 +357,7 @@ describe("plugin manifest validation", () => {
|
||||
const serialized = JSON.stringify(manifest).toLowerCase();
|
||||
|
||||
expect(serialized).not.toContain("local-proof");
|
||||
expect(manifest.version).toBe("0.1.19");
|
||||
expect(manifest.version).toBe("0.1.22");
|
||||
expect(installAction.environment?.SERVER_TEMPLATE).toBe("scum-server");
|
||||
expect(manifest.permissions).toEqual(expect.arrayContaining(["server.game-client.read", "server.game-client.command", "server.game-client.maintenance"]));
|
||||
expect(manifest.gameClientBridge.commands.map((command) => command.type)).toEqual(expect.arrayContaining([
|
||||
@@ -328,7 +372,7 @@ describe("plugin manifest validation", () => {
|
||||
expect(manifest.gameClientBridge.snapshots.map((snapshot) => snapshot.type)).toEqual(expect.arrayContaining(["online.sessions", "players", "squads", "vehicles", "flags"]));
|
||||
expect(manifest.runtimeProfiles?.clientManagers).toBeUndefined();
|
||||
expect(manifest.gameClientBridge.lifecycleProjections).toBeUndefined();
|
||||
expect(manifest.gameClientBridge.queryTemplates ?? []).toEqual([]);
|
||||
expect((manifest.gameClientBridge.queryTemplates ?? []).map((template) => template.key)).toEqual(["scum.database.players", "scum.database.vehicles"]);
|
||||
expect(manifest.gameClientBridge.pages.map((page) => page.pageKey)).toEqual(expect.arrayContaining(["players", "squads", "live-map", "gifts", "workflows"]));
|
||||
expect(manifest.gameClientBridge.pages.map((page) => page.pageKey)).not.toContain("files-config");
|
||||
expect(manifest.gameClientBridge.pages.some((page) => page.queryTemplateKeys?.length)).toBe(false);
|
||||
@@ -452,7 +496,7 @@ describe("plugin manifest validation", () => {
|
||||
expect(manifest.gameClientBridge.pages.find((page) => page.pageKey === "live-map")?.snapshotTypes).toEqual(expect.arrayContaining(["players", "vehicles", "flags"]));
|
||||
});
|
||||
|
||||
it("keeps SCUM user and vehicle data off direct database query templates", () => {
|
||||
it("keeps SCUM database reads platform-dispatched, read-only, and package-scoped", () => {
|
||||
const pluginDir = path.join(pluginsRoot, "examples/scum-server-plugin");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginDir, "manifest.json"), "utf8")) as {
|
||||
capabilities: string[];
|
||||
@@ -468,15 +512,32 @@ describe("plugin manifest validation", () => {
|
||||
};
|
||||
assetFiles?: Array<{ path: string }>;
|
||||
};
|
||||
expect(manifest.gameClientBridge.queryTemplates ?? []).toEqual([]);
|
||||
expect(manifest.capabilities).not.toEqual(expect.arrayContaining(["remote.run.db.sqlite.query", "remote.run.db.sqlite.execute"]));
|
||||
expect(manifest.remoteAccess?.runCapabilities).not.toEqual(expect.arrayContaining(["remote.run.db.sqlite.query", "remote.run.db.sqlite.execute"]));
|
||||
expect(manifest.remoteAccess?.databaseEngines).toEqual([]);
|
||||
const templates = manifest.gameClientBridge.queryTemplates ?? [];
|
||||
expect(templates.length).toBe(2);
|
||||
const assetPaths = (manifest.assetFiles ?? []).map((file) => file.path);
|
||||
for (const template of templates) {
|
||||
const sqlRef = String(template.sqlRef);
|
||||
expect(sqlRef.startsWith("sql/scum-db-v57/")).toBe(true);
|
||||
expect(assetPaths).toContain(sqlRef);
|
||||
expect(fs.existsSync(path.join(pluginDir, sqlRef))).toBe(true);
|
||||
}
|
||||
expect(manifest.capabilities).toContain("remote.run.db.sqlite.query");
|
||||
expect(manifest.capabilities).not.toContain("remote.run.db.sqlite.execute");
|
||||
expect(manifest.remoteAccess?.runCapabilities).toContain("remote.run.db.sqlite.query");
|
||||
expect(manifest.remoteAccess?.runCapabilities).not.toContain("remote.run.db.sqlite.execute");
|
||||
expect(manifest.remoteAccess?.databaseEngines).toEqual(["sqlite"]);
|
||||
expect(manifest.remoteAccess?.rcon).toBe(true);
|
||||
expect(manifest.runtimeProfiles?.dataTargets).toEqual([]);
|
||||
expect(manifest.runtimeProfiles?.transportProfiles?.some((profile) => profile.kind === "sqlite" || profile.key === "scum-database")).toBe(false);
|
||||
expect((manifest.assetFiles ?? []).map((file) => file.path).some((assetPath) => assetPath.startsWith("sql/") || assetPath.includes("SCUM_DB_CONTRACT"))).toBe(false);
|
||||
expect(fs.existsSync(path.join(pluginDir, "sql/scum-db-v57"))).toBe(false);
|
||||
expect(manifest.runtimeProfiles?.dataTargets).toEqual([
|
||||
expect.objectContaining({ key: "scum-database", kind: "sqlite.snapshot" })
|
||||
]);
|
||||
expect(manifest.runtimeProfiles?.transportProfiles?.some((profile) => profile.kind === "sqlite" && profile.capabilities.includes("remote.run.db.sqlite.query"))).toBe(true);
|
||||
expect(manifest.gameClientBridge.pages.some((page) => (page.queryTemplateKeys ?? []).length > 0)).toBe(false);
|
||||
for (const template of templates) {
|
||||
const schemaRefs = [String(template.parameterSchemaRef), String(template.resultSchemaRef)];
|
||||
for (const schemaRef of schemaRefs) {
|
||||
expect(fs.existsSync(path.join(pluginDir, schemaRef))).toBe(true);
|
||||
}
|
||||
}
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user