Files
api/plugin/builder/utils/manifest-validator.test.ts
Eli Bosley af5ca11860 Feat/vue (#1655)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Introduced Docker management UI components: Overview, Logs, Console,
Preview, and Edit.
- Added responsive Card/Detail layouts with grouping, bulk actions, and
tabs.
  - New UnraidToaster component and global toaster configuration.
- Component auto-mounting improved with async loading and multi-selector
support.
- UI/UX
- Overhauled theme system (light/dark tokens, primary/orange accents)
and added theme variants.
  - Header OS version now includes integrated changelog modal.
- Registration displays warning states; multiple visual polish updates.
- API
  - CPU load now includes percentGuest and percentSteal metrics.
- Chores
  - Migrated web app to Vite; updated artifacts and manifests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
Co-authored-by: Michael Datelle <mdatelle@icloud.com>
2025-09-08 10:04:49 -04:00

290 lines
9.6 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdir, writeFile, rm } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
import {
validateStandaloneManifest,
getStandaloneManifestPath,
type StandaloneManifest
} from "./manifest-validator";
describe("manifest-validator", () => {
let testDir: string;
let manifestPath: string;
beforeEach(async () => {
// Create a temporary test directory
testDir = join(tmpdir(), `manifest-test-${Date.now()}`);
await mkdir(testDir, { recursive: true });
manifestPath = join(testDir, "standalone.manifest.json");
});
afterEach(async () => {
// Clean up test directory
await rm(testDir, { recursive: true, force: true });
});
describe("validateStandaloneManifest", () => {
it("should fail when manifest file does not exist", async () => {
const result = await validateStandaloneManifest(join(testDir, "nonexistent.json"));
expect(result.isValid).toBe(false);
expect(result.errors).toHaveLength(1);
expect(result.errors[0]).toContain("Manifest file does not exist");
});
it("should fail when manifest has invalid JSON", async () => {
await writeFile(manifestPath, "{ invalid json");
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(false);
expect(result.errors).toHaveLength(1);
expect(result.errors[0]).toContain("Failed to parse manifest JSON");
});
it("should pass for valid manifest with existing files", async () => {
// Create the referenced files
await writeFile(join(testDir, "app.js"), "console.log('app');");
await writeFile(join(testDir, "app.css"), "body { color: red; }");
// Create valid manifest
const manifest: StandaloneManifest = {
"app.js": {
file: "app.js",
src: "app.js",
isEntry: true,
},
"app.css": {
file: "app.css",
src: "app.css",
},
ts: Date.now(),
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
expect(result.warnings).toHaveLength(0);
});
it("should fail when referenced files are missing", async () => {
const manifest: StandaloneManifest = {
"app.js": {
file: "app.js",
src: "app.js",
},
"app.css": {
file: "app.css",
src: "app.css",
},
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(false);
expect(result.errors).toHaveLength(2);
expect(result.errors).toContain("Missing file referenced in manifest: app.js");
expect(result.errors).toContain("Missing file referenced in manifest: app.css");
});
it("should fail when CSS files in array are missing", async () => {
await writeFile(join(testDir, "app.js"), "console.log('app');");
const manifest: StandaloneManifest = {
"app.js": {
file: "app.js",
css: ["style1.css", "style2.css"],
},
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(false);
expect(result.errors).toHaveLength(2);
expect(result.errors).toContain("Missing CSS file referenced in manifest: style1.css");
expect(result.errors).toContain("Missing CSS file referenced in manifest: style2.css");
});
it("should fail when asset files are missing", async () => {
await writeFile(join(testDir, "app.js"), "console.log('app');");
const manifest: StandaloneManifest = {
"app.js": {
file: "app.js",
assets: ["image.png", "font.woff2"],
},
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(false);
expect(result.errors).toHaveLength(2);
expect(result.errors).toContain("Missing asset file referenced in manifest: image.png");
expect(result.errors).toContain("Missing asset file referenced in manifest: font.woff2");
});
it("should warn for missing imports but not fail", async () => {
await writeFile(join(testDir, "app.js"), "console.log('app');");
const manifest: StandaloneManifest = {
"app.js": {
file: "app.js",
imports: ["virtual-module"],
},
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
expect(result.warnings).toHaveLength(1);
expect(result.warnings[0]).toContain("Missing import file referenced in manifest: virtual-module");
});
it("should skip timestamp field", async () => {
await writeFile(join(testDir, "app.js"), "console.log('app');");
const manifest = {
"app.js": {
file: "app.js",
},
ts: 1234567890,
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it("should warn for non-entry fields", async () => {
await writeFile(join(testDir, "app.js"), "console.log('app');");
const manifest = {
"app.js": {
file: "app.js",
},
"invalid": "not an entry",
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(true);
expect(result.warnings).toHaveLength(1);
expect(result.warnings[0]).toContain("Skipping non-entry field: invalid");
});
it("should fail when no JavaScript entry exists", async () => {
await writeFile(join(testDir, "app.css"), "body { color: red; }");
const manifest: StandaloneManifest = {
"app.css": {
file: "app.css",
},
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(false);
expect(result.errors).toHaveLength(1);
expect(result.errors[0]).toContain("Manifest must contain at least one JavaScript entry file");
});
it("should not check duplicate files multiple times", async () => {
await writeFile(join(testDir, "app.js"), "console.log('app');");
await writeFile(join(testDir, "shared.css"), "body { color: red; }");
const manifest: StandaloneManifest = {
"entry1": {
file: "app.js",
css: ["shared.css"],
},
"entry2": {
file: "app.js",
css: ["shared.css"],
},
};
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
const result = await validateStandaloneManifest(manifestPath);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
});
describe("getStandaloneManifestPath", () => {
it("should find manifest in standalone subdirectory (preferred)", async () => {
const standaloneDir = join(testDir, "standalone");
await mkdir(standaloneDir, { recursive: true });
const standaloneManifestPath = join(standaloneDir, "standalone.manifest.json");
await writeFile(standaloneManifestPath, "{}");
const path = getStandaloneManifestPath(testDir);
expect(path).toBe(standaloneManifestPath);
});
it("should find manifest in root directory", async () => {
await writeFile(manifestPath, "{}");
const path = getStandaloneManifestPath(testDir);
expect(path).toBe(manifestPath);
});
it("should find manifest in nuxt subdirectory for backwards compatibility", async () => {
const nuxtDir = join(testDir, "nuxt");
await mkdir(nuxtDir, { recursive: true });
const nuxtManifestPath = join(nuxtDir, "standalone.manifest.json");
await writeFile(nuxtManifestPath, "{}");
const path = getStandaloneManifestPath(testDir);
expect(path).toBe(nuxtManifestPath);
});
it("should prefer standalone subdirectory over root and nuxt", async () => {
// Create manifest in all locations
const standaloneDir = join(testDir, "standalone");
await mkdir(standaloneDir, { recursive: true });
const standaloneManifestPath = join(standaloneDir, "standalone.manifest.json");
await writeFile(standaloneManifestPath, "{}");
await writeFile(manifestPath, "{}");
const nuxtDir = join(testDir, "nuxt");
await mkdir(nuxtDir, { recursive: true });
await writeFile(join(nuxtDir, "standalone.manifest.json"), "{}");
const path = getStandaloneManifestPath(testDir);
expect(path).toBe(standaloneManifestPath);
});
it("should return null when no manifest exists", async () => {
const path = getStandaloneManifestPath(testDir);
expect(path).toBeNull();
});
});
});