mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-21 16:00:59 -05:00
Implemented generic metadata reader logic
This commit is contained in:
parent
f029df1567
commit
5a20150e0a
2 changed files with 191 additions and 0 deletions
79
src/loaders/loader-metadata-reader.ts
Normal file
79
src/loaders/loader-metadata-reader.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { $i } from "@/utils/collections";
|
||||
import { PathLike } from "node:fs";
|
||||
import { FabricMetadataReader } from "./fabric/fabric-metadata-reader";
|
||||
import { ForgeMetadataReader } from "./forge/forge-metadata-reader";
|
||||
import { LoaderMetadata } from "./loader-metadata";
|
||||
import { LoaderType } from "./loader-type";
|
||||
import { QuiltMetadataReader } from "./quilt/quilt-metadata-reader";
|
||||
|
||||
/**
|
||||
* Defines a structure for reading metadata files.
|
||||
*
|
||||
* @template T - The type of the metadata this reader is able to process.
|
||||
*/
|
||||
export interface LoaderMetadataReader<T extends LoaderMetadata = LoaderMetadata> {
|
||||
/**
|
||||
* Reads the metadata file from a given path.
|
||||
*
|
||||
* @param path - The path to the metadata file.
|
||||
*
|
||||
* @returns The metadata object, or `undefined` if the file cannot be read.
|
||||
*/
|
||||
readMetadataFile(path: PathLike): Promise<T | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple metadata readers into a single reader
|
||||
* that tries each reader in order until one successfully reads the metadata.
|
||||
*
|
||||
* @param readers - A collection of metadata readers to be combined.
|
||||
*
|
||||
* @returns A new metadata reader instance that represents the combined readers.
|
||||
*/
|
||||
export function combineLoaderMetadataReaders(readers: Iterable<LoaderMetadataReader>): LoaderMetadataReader {
|
||||
const readerArray = [...readers];
|
||||
|
||||
const readMetadataFile = async (path: PathLike) => {
|
||||
for (const reader of readerArray) {
|
||||
const metadata = await reader.readMetadataFile(path).catch(() => undefined as LoaderMetadata);
|
||||
if (metadata) {
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return { readMetadataFile };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a metadata reader for the specified well-known loader.
|
||||
*
|
||||
* @param loader - The loader the metadata for which needs to be read.
|
||||
*
|
||||
* @returns A metadata reader for the given loader.
|
||||
*/
|
||||
export function createLoaderMetadataReader(loader: LoaderType): LoaderMetadataReader {
|
||||
switch (loader) {
|
||||
case LoaderType.FABRIC:
|
||||
return new FabricMetadataReader();
|
||||
|
||||
case LoaderType.FORGE:
|
||||
return new ForgeMetadataReader();
|
||||
|
||||
case LoaderType.QUILT:
|
||||
return new QuiltMetadataReader();
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown mod loader '${LoaderType.format(loader)}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a metadata reader that is a combination of readers for all known loaders.
|
||||
*
|
||||
* @returns A metadata reader that can read metadata from all known loaders.
|
||||
*/
|
||||
export function createDefaultLoaderMetadataReader(): LoaderMetadataReader {
|
||||
return combineLoaderMetadataReaders($i(LoaderType.values()).map(createLoaderMetadataReader));
|
||||
}
|
112
tests/unit/loaders/loader-metadata-reader.spec.ts
Normal file
112
tests/unit/loaders/loader-metadata-reader.spec.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { zipFile } from "@/../tests/utils/zip-utils";
|
||||
import { LoaderType } from "@/loaders/loader-type";
|
||||
import mockFs from "mock-fs";
|
||||
import {
|
||||
LoaderMetadataReader,
|
||||
combineLoaderMetadataReaders,
|
||||
createLoaderMetadataReader,
|
||||
createDefaultLoaderMetadataReader,
|
||||
} from "@/loaders/loader-metadata-reader";
|
||||
|
||||
beforeEach(async () => {
|
||||
mockFs({
|
||||
"fabric.jar": await zipFile([__dirname, "../../content/fabric/fabric.mod.json"]),
|
||||
"quilt.jar": await zipFile([__dirname, "../../content/quilt/quilt.mod.json"]),
|
||||
"forge.jar": await zipFile([__dirname, "../../content/forge/mods.toml"], "META-INF/mods.toml"),
|
||||
"text.txt": "",
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe("combineLoaderMetadataReaders", () => {
|
||||
test("combined reader returns metadata from the first underlying reader that successfully reads the metadata", async () => {
|
||||
const reader1 = { readMetadataFile: jest.fn().mockImplementation(x => x === "1" ? Promise.resolve({ id: "1" }) : Promise.reject(new Error("Unknown id"))) } as LoaderMetadataReader;
|
||||
const reader2 = { readMetadataFile: jest.fn().mockImplementation(x => Promise.resolve(x === "2" ? { id: "2" } : undefined)) } as LoaderMetadataReader;
|
||||
|
||||
const combined = combineLoaderMetadataReaders([reader1, reader2]);
|
||||
|
||||
const metadata1 = await combined.readMetadataFile("1");
|
||||
expect(metadata1).toEqual({ id: "1" });
|
||||
expect(reader1.readMetadataFile).toHaveBeenCalledTimes(1);
|
||||
expect(reader1.readMetadataFile).toHaveBeenCalledWith("1");
|
||||
expect(reader2.readMetadataFile).not.toHaveBeenCalled();
|
||||
|
||||
const metadata2 = await combined.readMetadataFile("2");
|
||||
expect(metadata2).toEqual({ id: "2" });
|
||||
expect(reader1.readMetadataFile).toHaveBeenCalledTimes(2);
|
||||
expect(reader1.readMetadataFile).toHaveBeenCalledWith("2");
|
||||
expect(reader2.readMetadataFile).toHaveBeenCalledTimes(1);
|
||||
expect(reader2.readMetadataFile).toHaveBeenCalledWith("2");
|
||||
});
|
||||
|
||||
test("combined reader returns undefined when no reader can read the metadata", async () => {
|
||||
const combined = combineLoaderMetadataReaders([]);
|
||||
const metadata = await combined.readMetadataFile("test");
|
||||
|
||||
expect(metadata).toBeUndefined();
|
||||
});
|
||||
|
||||
test("combined reader returns undefined instead of throwing", async () => {
|
||||
const reader1 = { readMetadataFile: jest.fn().mockRejectedValue(new Error("Cannot read the metadata file")) } as LoaderMetadataReader;
|
||||
|
||||
const combined = combineLoaderMetadataReaders([reader1]);
|
||||
const metadata = await combined.readMetadataFile("test");
|
||||
|
||||
expect(metadata).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createLoaderMetadataReader", () => {
|
||||
test("creates a reader for every known loader", () => {
|
||||
for (const loader of LoaderType.values()) {
|
||||
expect(createLoaderMetadataReader(loader)).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("created reader is able to read metadata of its supported loader", async () => {
|
||||
for (const loader of LoaderType.values()) {
|
||||
const reader = createLoaderMetadataReader(loader);
|
||||
const metadata = await reader.readMetadataFile(`${loader}.jar`);
|
||||
|
||||
expect(metadata).toBeDefined();
|
||||
expect(metadata.version).toBe("0.1.0");
|
||||
}
|
||||
});
|
||||
|
||||
test("created reader returns undefined for unsupported metadata files", async () => {
|
||||
for (const loader of LoaderType.values()) {
|
||||
const reader = createLoaderMetadataReader(loader);
|
||||
const metadata = await reader.readMetadataFile("text.txt");
|
||||
|
||||
expect(metadata).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("created reader returns undefined for non-existing files", async () => {
|
||||
for (const loader of LoaderType.values()) {
|
||||
const reader = createLoaderMetadataReader(loader);
|
||||
const metadata = await reader.readMetadataFile("text.json");
|
||||
|
||||
expect(metadata).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("throws an error when unknown loader is provided", () => {
|
||||
expect(() => createLoaderMetadataReader("unknown" as LoaderType)).toThrow("Unknown mod loader 'unknown'.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("createDefaultLoaderMetadataReader", () => {
|
||||
test("creates a reader that can read metadata from all known loaders", async () => {
|
||||
const reader = createDefaultLoaderMetadataReader();
|
||||
for (const loader of LoaderType.values()) {
|
||||
const metadata = await reader.readMetadataFile(`${loader}.jar`);
|
||||
|
||||
expect(metadata).toBeDefined();
|
||||
expect(metadata.version).toBe("0.1.0");
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue