import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import mockFs from "mock-fs"; import { createFakeFetch } from "../../../utils/fetch-utils"; import { FormData } from "@/utils/net/form-data"; import { HttpResponse } from "@/utils/net/http-response"; import { CurseForgeDependencyType } from "@/platforms/curseforge/curseforge-dependency-type"; import { CurseForgeVersionInitMetadata } from "@/platforms/curseforge/curseforge-version"; import { CURSEFORGE_UPLOAD_API_URL, CurseForgeUploadApiClient } from "@/platforms/curseforge/curseforge-upload-api-client"; const FILES = Object.freeze([ "fabric.mod.json", "mods.toml", "quilt.mod.json", ]) as string[]; const DB = Object.freeze({ versionTypes: Object.freeze(JSON.parse( readFileSync(resolve(__dirname, "../../../content/curseforge/version-types.json"), "utf8") )), versions: Object.freeze(JSON.parse( readFileSync(resolve(__dirname, "../../../content/curseforge/versions.json"), "utf8") )), projects: Object.freeze([ { id: 1, slug: "mod", // 1.19, Fabric, Java 17 game_versions: [9186, 7499, 8326], }, { id: 2, slug: "plugin", // 1.19+Bukkit game_versions: [9190], }, { id: 4, slug: "resource-pack", // 1.19 game_versions: [9186], }, { id: 5, slug: "addon", // 1.19+Addon game_versions: [9189], }, ]), files: Object.freeze([ { id: 1, name: "Mod v1.0.0", project_id: 1, url: "https://www.curseforge.com/api/v1/mods/1/files/1/download", version_id: 1, }, { id: 2, name: "mods.toml", project_id: 1, url: "https://www.curseforge.com/api/v1/mods/1/files/2/download", version_id: 1, }, { id: 3, name: "quilt.mod.json", project_id: 1, url: "https://www.curseforge.com/api/v1/mods/1/files/3/download", version_id: 1, }, { id: 4, name: "Plugin v1.0.0", project_id: 2, url: "https://www.curseforge.com/api/v1/mods/2/files/4/download", version_id: 4, }, { id: 5, name: "Resource Pack v1.0.0", project_id: 4, url: "https://www.curseforge.com/api/v1/mods/4/files/5/download", version_id: 5, }, { id: 6, name: "Addon v1.0.0", project_id: 5, url: "https://www.curseforge.com/api/v1/mods/5/files/6/download", version_id: 6, }, ]), }); const CURSEFORGE_FETCH = createFakeFetch({ baseUrl: CURSEFORGE_UPLOAD_API_URL, requiredHeaders: ["X-Api-Token"], GET: { "^\\/game\\/version-types": () => DB.versionTypes, "^\\/game\\/versions": () => DB.versions, }, POST: { "^\\/projects\\/(\\d+)\\/upload-file": ([id], { body }) => { const project = DB.projects.find(x => x.id === +id); if (!project) { return HttpResponse.json({ errorCode: 404, errorMessage: `Project not found: '${id}'` }, { status: 404 }); } const formData = body as FormData; const fileName = formData.get("file")?.["name"] as string; const metadata = JSON.parse(formData.get("metadata") as string) as CurseForgeVersionInitMetadata; const dependencies = metadata.relations?.projects?.map(x => x.slug) || []; const unknownDependency = dependencies.find(x => !DB.projects.find(y => x === y.slug)); const unknownGameVersion = metadata.gameVersions?.find(x => !project.game_versions.includes(x)); if (metadata.relations && !dependencies.length || metadata.parentFileID && dependencies.length) { return HttpResponse.json({ errorCode: 400, errorMessage: "We don't know how to parse an empty array, so just like fuck you, lmao" }, { status: 400 }); } if (!!metadata.gameVersions?.length === !!metadata.parentFileID) { return HttpResponse.json({ errorCode: 400, errorMessage: "At least one game version is required" }, { status: 400 }); } if (unknownGameVersion) { return HttpResponse.json({ errorCode: 1009, errorMessage: `Invalid game version: '${unknownGameVersion}'` }, { status: 400 }); } if (unknownDependency) { return HttpResponse.json({ errorCode: 1018, errorMessage: `Invalid slug in project relations: '${unknownDependency}'` }, { status: 400 }); } const uploadedFile = DB.files.find(x => x.project_id === project.id && [fileName, metadata.displayName].includes(x.name)); expect(uploadedFile).toBeDefined(); return HttpResponse.json(uploadedFile, { status: 200 }); }, }, }); beforeEach(() => { const fakeFiles = FILES.reduce((a, b) => ({ ...a, [b]: "" }), {}); mockFs(fakeFiles); }); afterEach(() => { mockFs.restore(); }); describe("CurseForgeUploadApiClient", () => { describe("getGameVersionTypes", () => { test("returns game version types", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const versionTypes = await api.getGameVersionTypes(); expect(versionTypes?.length).toBeGreaterThan(0); for (const versionType of versionTypes) { expect(versionType).toHaveProperty("id"); expect(versionType).toHaveProperty("name"); expect(versionType).toHaveProperty("slug"); } }); test("returned game version types contain one representing Bukkit plugins", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const versionTypes = await api.getGameVersionTypes(); expect(versionTypes).toContainEqual({ id: 1, slug: "bukkit", name: "Bukkit", }); }); }); describe("getGameVersions", () => { test("returns game versions", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const versions = await api.getGameVersions(); expect(versions?.length).toBeGreaterThan(0); for (const version of versions) { expect(version).toHaveProperty("id"); expect(version).toHaveProperty("gameVersionTypeID"); expect(version).toHaveProperty("name"); expect(version).toHaveProperty("slug"); } }); }); describe("getGameVersionMap", () => { test("returns game version map", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const map = await api.getGameVersionMap(); expect(map.environments?.length).toBeGreaterThan(0); expect(map.game_versions?.length).toBeGreaterThan(0); expect(map.game_versions_for_addons?.length).toBeGreaterThan(0); expect(map.game_versions_for_plugins?.length).toBeGreaterThan(0); expect(map.java_versions?.length).toBeGreaterThan(0); expect(map.loaders?.length).toBeGreaterThan(0); }); }); describe("createVersion", () => { test("creates a new mod version", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const expectedVersionId = 1; const projectId = 1; const name = "Mod v1.0.0"; const fileCount = 1; const version = await api.createVersion({ project_id: projectId, files: FILES.slice(0, fileCount), name, game_versions: ["1.19"], loaders: ["fabric"], java_versions: ["Java 17"], }); expect(version).toEqual({ id: expectedVersionId, project_id: projectId, name, files: DB.files.filter(x => x.project_id === projectId).slice(0, fileCount), }); }); test("creates a new plugin version", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const expectedVersionId = 4; const projectId = 2; const name = "Plugin v1.0.0"; const fileCount = 1; const version = await api.createVersion({ project_id: projectId, files: FILES.slice(0, fileCount), name, game_versions: ["1.19"], loaders: ["fabric"], java_versions: ["Java 17"], }); expect(version).toEqual({ id: expectedVersionId, project_id: projectId, name, files: DB.files.filter(x => x.project_id === projectId).slice(0, fileCount), }); }); test("creates a new resource pack version", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const expectedVersionId = 5; const projectId = 4; const name = "Resource Pack v1.0.0"; const fileCount = 1; const version = await api.createVersion({ project_id: projectId, files: FILES.slice(0, fileCount), name, game_versions: ["1.19"], loaders: ["fabric"], java_versions: ["Java 17"], }); expect(version).toEqual({ id: expectedVersionId, project_id: projectId, name, files: DB.files.filter(x => x.project_id === projectId).slice(0, fileCount), }); }); test("creates a new addon version", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const expectedVersionId = 6; const projectId = 5; const name = "Addon v1.0.0"; const fileCount = 1; const version = await api.createVersion({ project_id: projectId, files: FILES.slice(0, fileCount), name, game_versions: ["1.19"], loaders: ["fabric"], java_versions: ["Java 17"], }); expect(version).toEqual({ id: expectedVersionId, project_id: projectId, name, files: DB.files.filter(x => x.project_id === projectId).slice(0, fileCount), }); }); test("creates a new version with multiple files", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const expectedVersionId = 1; const projectId = 1; const name = "Mod v1.0.0"; const fileCount = 3; const version = await api.createVersion({ project_id: projectId, files: FILES.slice(0, fileCount), name, game_versions: ["1.19"], loaders: ["fabric"], java_versions: ["Java 17"], }); expect(version).toEqual({ id: expectedVersionId, project_id: projectId, name, files: DB.files.filter(x => x.project_id === projectId).slice(0, fileCount), }); }); test("creates a new version with dependencies", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const expectedVersionId = 1; const projectId = 1; const name = "Mod v1.0.0"; const fileCount = 3; const version = await api.createVersion({ project_id: projectId, files: FILES.slice(0, fileCount), name, game_versions: ["1.19"], loaders: ["fabric"], java_versions: ["Java 17"], dependencies: [ { slug: "addon", type: CurseForgeDependencyType.OPTIONAL_DEPENDENCY }, { slug: "resource-pack", type: CurseForgeDependencyType.REQUIRED_DEPENDENCY }, { slug: "plugin", type: CurseForgeDependencyType.EMBEDDED_LIBRARY }, ], }); expect(version).toEqual({ id: expectedVersionId, project_id: projectId, name, files: DB.files.filter(x => x.project_id === projectId).slice(0, fileCount), }); }); test("creates a new version while filtering out invalid dependencies", async () => { const api = new CurseForgeUploadApiClient({ fetch: CURSEFORGE_FETCH, token: "token" }); const expectedVersionId = 1; const projectId = 1; const name = "Mod v1.0.0"; const fileCount = 3; const version = await api.createVersion({ project_id: projectId, files: FILES.slice(0, fileCount), name, game_versions: ["1.19"], loaders: ["fabric"], java_versions: ["Java 17"], dependencies: [ { slug: "addon", type: CurseForgeDependencyType.OPTIONAL_DEPENDENCY }, { slug: "addon-1", type: CurseForgeDependencyType.OPTIONAL_DEPENDENCY }, { slug: "resource-pack", type: CurseForgeDependencyType.REQUIRED_DEPENDENCY }, { slug: "plugin", type: CurseForgeDependencyType.EMBEDDED_LIBRARY }, { slug: "plugin-1", type: CurseForgeDependencyType.EMBEDDED_LIBRARY }, ], }); expect(version).toEqual({ id: expectedVersionId, project_id: projectId, name, files: DB.files.filter(x => x.project_id === projectId).slice(0, fileCount), }); }); }); });