Support Gitea configuration and safe Catalog Studio furniture completion
This commit is contained in:
1 parent
26f071117b
commit
9f791ba284
22 files changed
+1120
-166
No files matched your search
@@ -0,0 +1,120 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { MySqlDialect } from "drizzle-orm/mysql-core";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
execute: vi.fn(),
|
||||
select: vi.fn(),
|
||||
stat: vi.fn(),
|
||||
conflict: vi.fn(),
|
||||
build: vi.fn(),
|
||||
append: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/db", () => ({
|
||||
db: { select: mocks.select, execute: mocks.execute },
|
||||
ItemsBase: {
|
||||
id: "id",
|
||||
itemName: "item_name",
|
||||
spriteId: "sprite_id",
|
||||
type: "type",
|
||||
},
|
||||
CatalogPages: {},
|
||||
}));
|
||||
vi.mock("node:fs", () => ({
|
||||
existsSync: () => true,
|
||||
promises: {
|
||||
stat: mocks.stat,
|
||||
readFile: vi.fn().mockResolvedValue(Buffer.alloc(256)),
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/services/furni-asset-dirs", () => ({
|
||||
DEFAULT_FURNI_NITRO_DIR: "/nitro",
|
||||
getFurniAssetWriteTargets: async () => ({
|
||||
swfDir: "/swf",
|
||||
nitroDir: "/nitro",
|
||||
iconDir: "/icons",
|
||||
mirrorDirs: [],
|
||||
}),
|
||||
}));
|
||||
vi.mock("@/lib/services/furni-data", () => ({
|
||||
findFurniDataIdConflict: mocks.conflict,
|
||||
buildFurniEntry: mocks.build,
|
||||
appendFurniEntry: mocks.append,
|
||||
}));
|
||||
vi.mock("@/lib/services/habbo-furnidata-cache", () => ({
|
||||
lookupOfficialHabboFurni: async () => null,
|
||||
}));
|
||||
vi.mock("@/lib/services/habbo-gamedata-hotel", () => ({
|
||||
getHabboGamedataHotel: async () => "it",
|
||||
}));
|
||||
vi.mock("@/lib/services/import/core/download", () => ({
|
||||
downloadFile: vi.fn(),
|
||||
tryDownloadCandidates: async () => false,
|
||||
}));
|
||||
vi.mock("@/lib/services/swf-to-nitro", () => ({
|
||||
parseNitroBundle: () => ({ json: {} }),
|
||||
extractIconFromSwfAsync: async () => null,
|
||||
}));
|
||||
|
||||
import { importSingleFurni } from "./furni-import";
|
||||
|
||||
const input = {
|
||||
id: 42,
|
||||
classname: "recycler_scrapbookA",
|
||||
name: "Scrapbook",
|
||||
description: "Book",
|
||||
type: "flooritem",
|
||||
revision: 1,
|
||||
category: "unknown",
|
||||
repairExisting: true,
|
||||
};
|
||||
const dialect = new MySqlDialect();
|
||||
function queries() {
|
||||
return mocks.execute.mock.calls.map(([query]) => dialect.sqlToQuery(query));
|
||||
}
|
||||
describe("completion of existing furniture", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
const rows = [
|
||||
[{ id: 2000037257, spriteId: 9001, type: "s" }],
|
||||
[{ itemName: input.classname }],
|
||||
];
|
||||
mocks.select.mockImplementation(() => ({
|
||||
from: () => ({ where: () => ({ limit: async () => rows.shift() }) }),
|
||||
}));
|
||||
mocks.stat.mockResolvedValue({ size: 256 });
|
||||
mocks.conflict.mockResolvedValue(null);
|
||||
mocks.execute.mockResolvedValue([[{ id: 123, page_id: "15" }], []]);
|
||||
mocks.build.mockImplementation((value) => value);
|
||||
});
|
||||
it("reuses local item and sprite IDs without rewriting SQL settings or offers", async () => {
|
||||
const result = await importSingleFurni(input);
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.itemId).toBe(2000037257);
|
||||
expect(mocks.conflict).toHaveBeenCalledWith(9001, input.classname);
|
||||
expect(mocks.build).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 9001, spriteId: 9001 }),
|
||||
);
|
||||
expect(mocks.append).toHaveBeenCalled();
|
||||
expect(
|
||||
queries().every((query) => query.sql.trim().startsWith("SELECT")),
|
||||
).toBe(true);
|
||||
expect(queries()[0].params).toContain("2000037257");
|
||||
});
|
||||
it("never deletes existing SQL when the nitro cannot be recovered", async () => {
|
||||
mocks.stat.mockRejectedValue(new Error("missing"));
|
||||
const result = await importSingleFurni(input);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("preserved");
|
||||
expect(mocks.execute).not.toHaveBeenCalled();
|
||||
expect(mocks.append).not.toHaveBeenCalled();
|
||||
});
|
||||
it("rejects an occupied furnidata sprite before any mutation", async () => {
|
||||
mocks.conflict.mockResolvedValue("other_furniture");
|
||||
const result = await importSingleFurni(input);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(mocks.execute).not.toHaveBeenCalled();
|
||||
expect(mocks.append).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user