CI / check (push) Successful in 32s
CI / tests-integration (push) Successful in 1m55s
CI / tests-unit (push) Successful in 2m1s
CI / tests-imaging-browsers (push) Successful in 2m7s
CI / tests-ui (push) Successful in 2m44s
CI / preflight (push) Skipped
CI / tests-quality (push) Successful in 1m46s
CI / deploy (push) Successful in 3m28s
Updating the catalog was only possible from the admin "Catalog audit" page, which streams `runCatalogAudit` over SSE and always compares against every enabled clone source. This adds the same engine to the terminal so it can be run by hand or from cron: - `scripts/catalog-sync.ts` — CLI driver around `runCatalogAudit`. Prints the live progress, the per-source "on the source but not in this hotel" counts and a summary block; generated catalog SQL is saved to `storage/catalog-sync/*.sql` (gitignored). Supports `--list-sources`, `--sources=<a,b>`, `--icons`, `--nitros`, `--furnidata`, `--structure`, `--check-ids`, `--sql`, `--apply-sql`, `--organize-sql`, `--all`, `--json` and `--fail-on-errors`. - `scripts/catalog-sync.sh` — interactive menu (`pnpm catalog:sync`). Lists the configured sources (so the menu always matches `clone_sources`), lets Habbo (COM) be taken with Enter, and offers audit-only through full repair + applying the catalog SQL, each with a distinct confirmation. With no arguments it needs a terminal; with any flag it passes straight through to the CLI. - `runCatalogAudit` gains `sourceIds` to restrict which sites are compared; omitting it keeps the previous "compare against everything enabled" behaviour. An audit writes nothing, and `--apply-sql` is the only flag that changes the catalog. Verified against the live DB: 16370 items_base rows, 16479 catalog items, 2871 Habbo (COM) entries missing from this hotel.
115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
DEFAULT_CATALOG_SYNC_FLAGS,
|
|
describeActions,
|
|
parseCatalogSyncArgs,
|
|
writeGeneratedSql,
|
|
} from "./catalog-sync";
|
|
|
|
describe("catalog-sync argument parsing", () => {
|
|
it("defaults to a read-only audit with no filters", () => {
|
|
expect(parseCatalogSyncArgs([])).toEqual({
|
|
...DEFAULT_CATALOG_SYNC_FLAGS,
|
|
sourceIds: [],
|
|
});
|
|
expect(describeActions(parseCatalogSyncArgs([]))).toBe("audit");
|
|
});
|
|
|
|
it("accepts comma-separated source ids", () => {
|
|
const flags = parseCatalogSyncArgs([
|
|
"--sources=default-habbo-com,default-wibbo",
|
|
]);
|
|
expect(flags.sourceIds).toEqual(["default-habbo-com", "default-wibbo"]);
|
|
});
|
|
|
|
it("accepts display names, including ones with spaces", () => {
|
|
const flags = parseCatalogSyncArgs(["--sources=Habbo (COM), Wibbo"]);
|
|
expect(flags.sourceIds).toEqual(["Habbo (COM)", "Wibbo"]);
|
|
});
|
|
|
|
it("trims and drops empty source entries", () => {
|
|
expect(parseCatalogSyncArgs(["--sources= a , ,b,"]).sourceIds).toEqual([
|
|
"a",
|
|
"b",
|
|
]);
|
|
});
|
|
|
|
it("turns --all into every repair step", () => {
|
|
const flags = parseCatalogSyncArgs(["--all"]);
|
|
expect(flags.repairIcons).toBe(true);
|
|
expect(flags.repairNitros).toBe(true);
|
|
expect(flags.repairFurniData).toBe(true);
|
|
expect(flags.repairStructure).toBe(true);
|
|
expect(flags.checkFurniDataIds).toBe(true);
|
|
// Generating/applying SQL is never implied by a repair.
|
|
expect(flags.generateSql).toBe(false);
|
|
expect(flags.applySql).toBe(false);
|
|
});
|
|
|
|
it("treats applying SQL as implying generation", () => {
|
|
expect(parseCatalogSyncArgs(["--apply-sql"]).generateSql).toBe(true);
|
|
expect(parseCatalogSyncArgs(["--organize-sql"]).generateSql).toBe(true);
|
|
expect(describeActions(parseCatalogSyncArgs(["--apply-sql"]))).toBe(
|
|
"audit + apply-sql",
|
|
);
|
|
});
|
|
|
|
it("accepts the alias spellings of the repair flags", () => {
|
|
const flags = parseCatalogSyncArgs([
|
|
"--repair-icons",
|
|
"--repair-nitros",
|
|
"--repair-furnidata",
|
|
"--repair-structure",
|
|
]);
|
|
expect(flags.repairIcons).toBe(true);
|
|
expect(flags.repairNitros).toBe(true);
|
|
expect(flags.repairFurniData).toBe(true);
|
|
expect(flags.repairStructure).toBe(true);
|
|
});
|
|
|
|
it("ignores a bare -- separator", () => {
|
|
expect(parseCatalogSyncArgs(["--", "--icons"]).repairIcons).toBe(true);
|
|
});
|
|
|
|
it("rejects an unknown flag", () => {
|
|
expect(() => parseCatalogSyncArgs(["--nope"])).toThrow(/Unknown flag/);
|
|
});
|
|
|
|
it("rejects --sources without a value", () => {
|
|
expect(() => parseCatalogSyncArgs(["--sources"])).toThrow(/needs a value/);
|
|
});
|
|
});
|
|
|
|
describe("generated catalog SQL output", () => {
|
|
let cwd = "";
|
|
|
|
beforeEach(() => {
|
|
cwd = process.cwd();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
process.chdir(cwd);
|
|
});
|
|
|
|
it("writes the SQL under storage/catalog-sync with a trailing newline", async () => {
|
|
let target = "";
|
|
try {
|
|
const dir = mkdtempSync(join(tmpdir(), "catalog-sync-sql-"));
|
|
target = dir;
|
|
process.chdir(dir);
|
|
const file = await writeGeneratedSql("INSERT INTO `catalog_items` (1);");
|
|
expect(file.startsWith("storage/catalog-sync/")).toBe(true);
|
|
expect(file.endsWith(".sql")).toBe(true);
|
|
expect(readFileSync(join(dir, file), "utf8")).toBe(
|
|
"INSERT INTO `catalog_items` (1);\n",
|
|
);
|
|
} finally {
|
|
if (target) rmSync(target, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|