Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 39s
CI / tests-unit (push) Successful in 2m3s
CI / tests-integration (push) Successful in 2m7s
CI / tests-ui (push) Successful in 2m50s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 1m45s
An uploaded .nitro was written to disk byte-for-byte, so a bundle from a third-party tool that ships a WebP member while still pointing spritesheet.meta.image at a .png was accepted and stored as-is. The client resolves the spritesheet through that pointer, so the result was a file that validates fine and then renders nothing. Re-write the bundle through createNitroBundle on import, which labels the member from the actual bytes and repairs the pointer. No texture is re-encoded, so the bytes stay identical, and the member keeps the base name it arrived with so `chair*2` colour variants that share the `chair` library are not renamed.
169 lines
4.6 KiB
TypeScript
169 lines
4.6 KiB
TypeScript
// @ts-nocheck
|
|
// @vitest-environment node
|
|
import { promises as fs } from "node:fs";
|
|
import os from "node:os";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { getRuntimePath } from "@/lib/utils/runtime-path";
|
|
|
|
const {
|
|
appendFurniEntryMock,
|
|
assetTargets,
|
|
dbExecuteMock,
|
|
getOrCreateCategoryPageMock,
|
|
} = vi.hoisted(() => ({
|
|
appendFurniEntryMock: vi.fn(),
|
|
assetTargets: {
|
|
swfDir: "",
|
|
iconDir: "",
|
|
nitroDir: "",
|
|
mirrorDirs: [] as Array<{
|
|
swfDir: string;
|
|
iconDir: string;
|
|
nitroDir: string;
|
|
}>,
|
|
},
|
|
dbExecuteMock: vi.fn(),
|
|
getOrCreateCategoryPageMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("drizzle-orm", () => ({
|
|
eq: vi.fn(() => ({})),
|
|
sql: (strings: TemplateStringsArray, ...values: unknown[]) => ({
|
|
strings,
|
|
values,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/lib/db", async () => ({
|
|
...(await import("@/test/db-helpers")).createDbHelpers(dbExecuteMock),
|
|
ItemsBase: { id: "id", itemName: "item_name" },
|
|
db: {
|
|
execute: dbExecuteMock,
|
|
select: () => ({
|
|
from: () => ({
|
|
where: () => ({
|
|
limit: async () => [],
|
|
}),
|
|
}),
|
|
}),
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/lib/services/furni-asset-dirs", () => ({
|
|
getFurniAssetDirs: vi.fn(async () => assetTargets),
|
|
getFurniAssetWriteTargets: vi.fn(async () => assetTargets),
|
|
}));
|
|
|
|
vi.mock("@/lib/services/furni-data", () => ({
|
|
appendFurniEntry: appendFurniEntryMock,
|
|
buildFurniEntry: vi.fn(() => ({ id: 501 })),
|
|
}));
|
|
|
|
vi.mock("@/lib/services/furni-import", () => ({
|
|
allocateCatalogItemId: vi.fn(
|
|
async (insertFn: (nextId: number) => Promise<unknown>) => insertFn(701),
|
|
),
|
|
autoPriceFurni: vi.fn(() => ({ credits: 3, points: 0, pointsType: 0 })),
|
|
CATEGORY_PAGE: {},
|
|
classifyFurni: vi.fn(() => "other"),
|
|
ensureDirectories: vi.fn(async () => {
|
|
const dirs = [
|
|
assetTargets.swfDir,
|
|
assetTargets.iconDir,
|
|
assetTargets.nitroDir,
|
|
...assetTargets.mirrorDirs.flatMap((target) => [
|
|
target.swfDir,
|
|
target.iconDir,
|
|
target.nitroDir,
|
|
]),
|
|
];
|
|
await Promise.all(dirs.map((dir) => fs.mkdir(dir, { recursive: true })));
|
|
}),
|
|
getOrCreateCategoryPage: getOrCreateCategoryPageMock,
|
|
IMPORTED_PAGE_CAPTION: "Imported Furniture",
|
|
IMPORTED_PAGE_CAPTION_SAVE: "imported_furni",
|
|
}));
|
|
|
|
const nitroBuilderMock = vi.hoisted(() => ({
|
|
createNitroBundle: vi.fn((json: object) =>
|
|
Buffer.from(`rebuilt:${JSON.stringify(json)}`, "utf-8"),
|
|
),
|
|
}));
|
|
|
|
vi.mock("@/lib/services/swf/nitro-builder", () => ({
|
|
parseNitroBundle: vi.fn(() => ({
|
|
json: { xdim: 1, ydim: 1 },
|
|
texture: Buffer.from("texture-bytes"),
|
|
textureFileName: "chair.png",
|
|
})),
|
|
createNitroBundle: nitroBuilderMock.createNitroBundle,
|
|
}));
|
|
|
|
import {
|
|
__resetUploadItemsBaseAllocatorForTests,
|
|
uploadSingleFurni,
|
|
} from "./upload-import";
|
|
|
|
describe("uploadSingleFurni live asset mirrors", () => {
|
|
let tempDir: string;
|
|
|
|
beforeEach(async () => {
|
|
__resetUploadItemsBaseAllocatorForTests();
|
|
tempDir = await fs.mkdtemp(getRuntimePath(os.tmpdir(), "upload-furni-"));
|
|
assetTargets.swfDir = getRuntimePath(tempDir, "cms", "swf");
|
|
assetTargets.iconDir = getRuntimePath(tempDir, "cms", "icons");
|
|
assetTargets.nitroDir = getRuntimePath(tempDir, "cms", "furniture");
|
|
assetTargets.mirrorDirs = [
|
|
{
|
|
swfDir: assetTargets.swfDir,
|
|
iconDir: getRuntimePath(tempDir, "Gamedata", "icons"),
|
|
nitroDir: getRuntimePath(tempDir, "Gamedata", "bundled", "furniture"),
|
|
},
|
|
];
|
|
dbExecuteMock.mockReset();
|
|
dbExecuteMock
|
|
.mockResolvedValueOnce([[{ next: 501 }], undefined])
|
|
.mockResolvedValue(undefined);
|
|
getOrCreateCategoryPageMock.mockResolvedValue("imp_other");
|
|
appendFurniEntryMock.mockResolvedValue(undefined);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("copies the Nitro bundle and icon into the directories served by production", async () => {
|
|
const nitroBuffer = Buffer.from("nitro-bundle");
|
|
const iconBuffer = Buffer.from("png-icon");
|
|
|
|
const result = await uploadSingleFurni({
|
|
classname: "chair",
|
|
name: "Chair",
|
|
itemType: "s",
|
|
nitroBuffer,
|
|
iconBuffer,
|
|
});
|
|
|
|
expect(result.ok).toBe(true);
|
|
// The bundle is re-written through the builder so `meta.image` can never
|
|
// disagree with the texture member, while the member keeps the name it
|
|
// arrived with (colour variants share a library name).
|
|
expect(nitroBuilderMock.createNitroBundle).toHaveBeenCalledWith(
|
|
{ xdim: 1, ydim: 1 },
|
|
Buffer.from("texture-bytes"),
|
|
"chair",
|
|
);
|
|
await expect(
|
|
fs.readFile(
|
|
getRuntimePath(assetTargets.mirrorDirs[0].nitroDir, "chair.nitro"),
|
|
),
|
|
).resolves.toEqual(Buffer.from('rebuilt:{"xdim":1,"ydim":1}', "utf-8"));
|
|
await expect(
|
|
fs.readFile(
|
|
getRuntimePath(assetTargets.mirrorDirs[0].iconDir, "chair_icon.png"),
|
|
),
|
|
).resolves.toEqual(iconBuffer);
|
|
});
|
|
});
|