From f61c63794216317bdc118438f69def7a02fc8920 Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Sun, 7 Jan 2024 15:07:22 +0000 Subject: [PATCH] Added tests for `curseforge-upload-api-client` --- .../curseforge-upload-api-client.spec.ts | 414 ++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 tests/unit/platforms/curseforge/curseforge-upload-api-client.spec.ts diff --git a/tests/unit/platforms/curseforge/curseforge-upload-api-client.spec.ts b/tests/unit/platforms/curseforge/curseforge-upload-api-client.spec.ts new file mode 100644 index 0000000..c5b4b18 --- /dev/null +++ b/tests/unit/platforms/curseforge/curseforge-upload-api-client.spec.ts @@ -0,0 +1,414 @@ +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), + }); + }); + }); +});