diff --git a/packages/fs-bridge/src/index.ts b/packages/fs-bridge/src/index.ts index 52de88ce2..27f93e715 100644 --- a/packages/fs-bridge/src/index.ts +++ b/packages/fs-bridge/src/index.ts @@ -11,6 +11,7 @@ export { export type { FileSystemBridge, + FileSystemBridgeCapabilities, FileSystemBridgeOperations, FileSystemBridgeRmOptions, FSEntry, diff --git a/packages/ucd-store/package.json b/packages/ucd-store/package.json index 7a255b77c..bf60e11e9 100644 --- a/packages/ucd-store/package.json +++ b/packages/ucd-store/package.json @@ -36,10 +36,7 @@ "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", - "typecheck": "tsc --noEmit", - "playground:http": "tsx --tsconfig=./tsconfig.json ./playgrounds/http-playground.ts", - "playground:node": "tsx --tsconfig=./tsconfig.json ./playgrounds/node-playground.ts", - "playground:memory": "tsx --tsconfig=./tsconfig.json ./playgrounds/memory-playground.ts" + "typecheck": "tsc --noEmit" }, "dependencies": { "@luxass/unicode-utils-new": "catalog:prod", diff --git a/packages/ucd-store/playgrounds/__utils.ts b/packages/ucd-store/playgrounds/__utils.ts deleted file mode 100644 index 6f6b111b0..000000000 --- a/packages/ucd-store/playgrounds/__utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -// eslint-disable-next-line ts/explicit-function-return-type -export function createLogger(name: string) { - return { - // eslint-disable-next-line no-console - info: (message?: any, ...optionalParams: any[]) => console.info(`[${name}] ${message}`, ...optionalParams), - warn: (message?: any, ...optionalParams: any[]) => console.warn(`[${name}] ${message}`, ...optionalParams), - error: (message?: any, ...optionalParams: any[]) => console.error(`[${name}] ${message}`, ...optionalParams), - // eslint-disable-next-line no-console - debug: (message?: any, ...optionalParams: any[]) => console.debug(`[${name}] ${message}`, ...optionalParams), - }; -} diff --git a/packages/ucd-store/playgrounds/http-playground.ts b/packages/ucd-store/playgrounds/http-playground.ts deleted file mode 100644 index b36309493..000000000 --- a/packages/ucd-store/playgrounds/http-playground.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createLogger } from "./__utils"; - -const log = createLogger("http-playground"); - -log.info("Starting HTTP Playground for UCD Store"); diff --git a/packages/ucd-store/playgrounds/memory-playground.ts b/packages/ucd-store/playgrounds/memory-playground.ts deleted file mode 100644 index 7cf9264d7..000000000 --- a/packages/ucd-store/playgrounds/memory-playground.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createLogger } from "./__utils"; - -const log = createLogger("memory-playground"); - -log.info("Starting memory playground..."); diff --git a/packages/ucd-store/playgrounds/node-playground.ts b/packages/ucd-store/playgrounds/node-playground.ts deleted file mode 100644 index db91a171b..000000000 --- a/packages/ucd-store/playgrounds/node-playground.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-disable antfu/no-top-level-await */ -import assert from "node:assert"; -import { join } from "node:path"; -import { assertCapability } from "@ucdjs/fs-bridge"; -import NodeFileSystemBridge from "@ucdjs/fs-bridge/bridges/node"; -import { createUCDStore } from "../src/factory"; -import { UCDStore } from "../src/store"; -import { createLogger } from "./__utils"; - -const log = createLogger("node-playground"); - -const basePath = join(import.meta.dirname, ".local"); -log.info("Base path for UCD Store:", basePath); - -log.info("Starting Node Playground for UCD Store"); -const store = createUCDStore({ - basePath, - fs: NodeFileSystemBridge({ - basePath, - }), - versions: [ - "15.1.0", - ], -}); - -log.info("UCD Store created successfully"); - -assert(store instanceof UCDStore, "store should be an instance of UCDStore"); - -log.info("Store basePath:", store.basePath); -assert(store.basePath === basePath, "store basePath should be set correctly"); - -// fs capabilities -assert(store.fs.capabilities != null, "store should have file system capabilities"); -assert(store.fs.capabilities.read, "store should support reading files"); -assert(store.fs.capabilities.write, "store should support writing files"); -assert(store.fs.capabilities.listdir, "store should support listing directories"); -assert(store.fs.capabilities.mkdir, "store should support creating directories"); -assert(store.fs.capabilities.exists, "store should support checking file existence"); -assert(store.fs.capabilities.rm, "store should support removing files"); - -assertCapability(store.fs, ["read", "write", "listdir", "mkdir", "exists", "rm"]); - -log.info("All capability assertions passed"); - -// check if local directory exists -const localDirExists = await store.fs.exists("./ucd-data"); -log.info("Local UCD data directory exists:", localDirExists); - -if (!localDirExists) { - log.info("Creating local UCD data directory..."); - await store.fs.mkdir("./ucd-data"); - log.info("Local UCD data directory created"); -} - -// test basic file operations -try { - const testFile = "./ucd-data/test.txt"; - await store.fs.write(testFile, "Hello, UCD Store!"); - const content = await store.fs.read(testFile); - assert(content === "Hello, UCD Store!", "file content should match"); - log.info("File write/read test passed"); - - // Clean up test file - await store.fs.rm(testFile); - log.info("Test file cleaned up"); -} catch (error) { - log.error("File operation test failed:", error); -} - -// initialize the store with a single version -await store.init(); - -// analyze the store -const [analyze, error] = await store.analyze({ - versions: ["15.1.0"], - checkOrphaned: true, -}); - -assert(analyze != null, "store analysis should succeed"); -assert(error == null, "store analysis should not return an error"); -assert(analyze[0], "analysis should contain data"); - -const [analysis] = analyze; -log.info("Store analysis results:", analysis); - -assert(analysis.version === "15.1.0", "analysis should contain the correct version"); -assert(analysis.orphanedFiles.length === 0, "there should be no orphaned files"); -log.info("Store initialized and analyzed successfully"); - -// write orphaned files to the store -await store.fs.write("./15.1.0/orphaned.txt", "This is an orphaned file"); -log.info("Orphaned file written to the store"); - -const [newAnalyzes, newError] = await store.analyze({ - versions: ["15.1.0"], - checkOrphaned: true, -}); - -assert(newAnalyzes != null, "new store analysis should succeed"); -assert(newError == null, "new store analysis should not return an error"); -assert(newAnalyzes[0], "new analysis should contain data"); - -const [newAnalysis] = newAnalyzes; -log.info("New store analysis results after writing orphaned file:", newAnalysis); -assert(newAnalysis.orphanedFiles.length === 1, "there should be one orphaned file"); - -log.info("Node playground completed successfully"); diff --git a/packages/ucd-store/test/playgrounds/__shared.ts b/packages/ucd-store/test/playgrounds/__shared.ts new file mode 100644 index 000000000..d5d6e6fac --- /dev/null +++ b/packages/ucd-store/test/playgrounds/__shared.ts @@ -0,0 +1,136 @@ +import type { FileSystemBridgeCapabilities } from "@ucdjs/fs-bridge"; +import { assertCapability } from "@ucdjs/fs-bridge"; +import { expect, it } from "vitest"; +import { UCDStore } from "../../src/store"; + +export interface PlaygroundTestOptions { + /** + * The UCDStore instance to be tested. + */ + store: UCDStore; + + /** + * List of required capabilities that the store's filesystem bridge must support. + */ + requiredCapabilities?: (keyof FileSystemBridgeCapabilities)[]; + + /** + * Whether to run read operation tests. + */ + read?: boolean; + + /** + * Whether to run write operation tests. + */ + write?: boolean; + + /** + * Whether to run analysis tests. + */ + analyze?: boolean; + + /** + * Whether to run directory listing tests. + */ + listdir?: boolean; + + /** + * Whether to run mirroring tests. + */ + mirror?: boolean; + + /** + * Whether to run cleaning tests. + */ + clean?: boolean; + + /** + * Whether to run repair tests. + */ + repair?: boolean; +} + +export function runPlaygroundTests(options: PlaygroundTestOptions): void { + const { + store, + read: shouldRunReadTests = true, + write: shouldRunWriteTests = true, + analyze: shouldRunAnalyzeTests = true, + listdir: shouldRunListDirTests = true, + mirror: shouldRunMirrorTests = true, + clean: shouldRunCleanTests = true, + repair: shouldRunRepairTests = true, + } = options; + + it("should create a valid UCDStore instance", () => { + expect(store).toBeInstanceOf(UCDStore); + }); + + it("should have basePath configured", () => { + expect(store.basePath).toBeDefined(); + }); + + it("should have capabilities defined", () => { + expect(store.fs.capabilities).toBeDefined(); + + if (options.requiredCapabilities != null && options.requiredCapabilities.length > 0) { + for (const cap of options.requiredCapabilities) { + expect(store.fs.capabilities[cap]).toBe(true); + } + } + }); + + it("should initialize the store", async () => { + expect(store.initialized).toBe(false); + + await expect(store.init()).resolves.not.toThrowError(); + + expect(store.initialized).toBe(true); + }); + + it.runIf(store.initialized && shouldRunReadTests)("should support read operations", async () => { + try { + assertCapability(store.fs, ["read", "exists", "listdir"]); + + await store.init(); + + const [arabicShaping, arabicShapingError] = await store.getFile("15.1.0", "ArabicShaping.txt"); + expect(arabicShaping).toBeDefined(); + expect(arabicShapingError).toBeNull(); + + expect(arabicShaping).not.toBeNull(); + } catch (err) { + expect.fail((err as Error).message); + } + }); + + it.runIf(store.initialized && shouldRunWriteTests)("should support write operations", async () => { + try { + assertCapability(store.fs, ["exists", "mkdir", "write", "read", "rm"]); + + expect.fail("Write operations test not implemented yet"); + } catch (err) { + expect.fail((err as Error).message); + } + }); + + it.runIf(store.initialized && shouldRunAnalyzeTests)("should analyze the store", async () => { + expect.fail("Analyze test not implemented yet"); + }); + + it.runIf(store.initialized && shouldRunListDirTests)("should list files in a version directory", async () => { + expect.fail("List directory test not implemented yet"); + }); + + it.runIf(store.initialized && shouldRunMirrorTests)("should mirror files from remote", async () => { + expect.fail("Mirror test not implemented yet"); + }); + + it.runIf(store.initialized && shouldRunCleanTests)("should clean orphaned files", async () => { + expect.fail("Clean test not implemented yet"); + }); + + it.runIf(store.initialized && shouldRunRepairTests)("should repair missing files", async () => { + expect.fail("Repair test not implemented yet"); + }); +} diff --git a/packages/ucd-store/test/playgrounds/http.test.ts b/packages/ucd-store/test/playgrounds/http.test.ts new file mode 100644 index 000000000..8e6367a5a --- /dev/null +++ b/packages/ucd-store/test/playgrounds/http.test.ts @@ -0,0 +1,39 @@ +import { mockStoreApi } from "#internal/test-utils/mock-store"; +import { UNICODE_VERSION_METADATA } from "@luxass/unicode-utils-new"; +import { beforeEach, describe } from "vitest"; +import { createHTTPUCDStore } from "../../src/factory"; +import { runPlaygroundTests } from "./__shared"; + +describe("http playground", async () => { + beforeEach(() => { + mockStoreApi({ + responses: { + "/api/v1/versions": [...UNICODE_VERSION_METADATA], + "/api/v1/versions/:version/file-tree": [{ + type: "file", + name: "ArabicShaping.txt", + path: "ArabicShaping.txt", + lastModified: 1724601900000, + }], + }, + }); + }); + + const store = await createHTTPUCDStore({ + baseUrl: "https://api.ucdjs.dev", + versions: ["15.1.0"], + }); + + runPlaygroundTests({ + store, + requiredCapabilities: [ + "read", + "exists", + "listdir", + ], + repair: false, + mirror: false, + write: false, + clean: false, + }); +}); diff --git a/packages/ucd-store/test/playgrounds/memory.test.ts b/packages/ucd-store/test/playgrounds/memory.test.ts new file mode 100644 index 000000000..3e2261a9b --- /dev/null +++ b/packages/ucd-store/test/playgrounds/memory.test.ts @@ -0,0 +1,42 @@ +import { mockStoreApi } from "#internal/test-utils/mock-store"; +import { UNICODE_VERSION_METADATA } from "@luxass/unicode-utils-new"; +import { beforeEach, describe } from "vitest"; +import { createUCDStore } from "../../src/factory"; +import { createMemoryMockFS } from "../__shared"; +import { runPlaygroundTests } from "./__shared"; + +describe("memory playground", () => { + beforeEach(() => { + mockStoreApi({ + responses: { + "/api/v1/versions": [...UNICODE_VERSION_METADATA], + "/api/v1/versions/:version/file-tree": [{ + type: "file", + name: "ArabicShaping.txt", + path: "ArabicShaping.txt", + lastModified: 1724601900000, + }], + }, + }); + }); + + const memoryFS = createMemoryMockFS(); + + const store = createUCDStore({ + basePath: "/test", + fs: memoryFS, + versions: ["15.1.0"], + }); + + runPlaygroundTests({ + store, + requiredCapabilities: [ + "write", + "read", + "mkdir", + "exists", + "listdir", + "rm", + ], + }); +}); diff --git a/packages/ucd-store/test/playgrounds/node.test.ts b/packages/ucd-store/test/playgrounds/node.test.ts new file mode 100644 index 000000000..189ca8433 --- /dev/null +++ b/packages/ucd-store/test/playgrounds/node.test.ts @@ -0,0 +1,41 @@ +import { mockStoreApi } from "#internal/test-utils/mock-store"; +import { UNICODE_VERSION_METADATA } from "@luxass/unicode-utils-new"; +import { beforeEach, describe } from "vitest"; +import { testdir } from "vitest-testdirs"; +import { createNodeUCDStore } from "../../src/factory"; +import { runPlaygroundTests } from "./__shared"; + +describe("node playground", async () => { + beforeEach(() => { + mockStoreApi({ + responses: { + "/api/v1/versions": [...UNICODE_VERSION_METADATA], + "/api/v1/versions/:version/file-tree": [{ + type: "file", + name: "ArabicShaping.txt", + path: "ArabicShaping.txt", + lastModified: 1724601900000, + }], + }, + }); + }); + + const basePath = await testdir(); + + const store = await createNodeUCDStore({ + basePath, + versions: ["15.1.0"], + }); + + runPlaygroundTests({ + store, + requiredCapabilities: [ + "write", + "read", + "mkdir", + "exists", + "listdir", + "rm", + ], + }); +});