Files
doktainer-upstream/tests/backend/app-catalog-validation.test.ts
T
2026-06-02 00:17:50 +07:00

114 lines
3.3 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import AdmZip from "adm-zip";
import { parseArchiveResponse } from "../../src/server/services/app-catalog/archive";
import { normalizeSingleManifest } from "../../src/server/services/app-catalog/manifest-parsers";
import {
CatalogSourceValidationError,
isLikelyHtmlDocument,
parseStructuredText,
} from "../../src/server/services/app-catalog/utils";
test("app catalog parser rejects HTML responses with a friendly error", () => {
const html = `
<!doctype html>
<html>
<head>
<style type="text/css">
:root { --tab-size-preference: 4; }
</style>
</head>
</html>
`;
assert.equal(isLikelyHtmlDocument(html), true);
assert.throws(
() => parseStructuredText(html),
(error) =>
error instanceof CatalogSourceValidationError &&
error.message.includes("raw JSON/YAML app manifest"),
);
});
test("generic app template keeps its explicit category", () => {
const templates = normalizeSingleManifest(
{
id: "beszel",
name: "Beszel",
desc: "Lightweight server monitoring",
category: "Monitoring",
image: "henrygd/beszel",
tags: ["monitoring"],
presentation: {
icon: "icon.png",
color: "#f59e0b",
installs: 42,
},
},
"beszel",
"https://raw.githubusercontent.com/DoktainerApp/templates/main/templates/beszel/template.json",
);
assert.equal(templates[0]?.category, "Monitoring");
assert.equal(templates[0]?.desc, "Lightweight server monitoring");
assert.deepEqual(templates[0]?.tags, ["monitoring"]);
assert.deepEqual(templates[0]?.presentation, {
icon: "https://raw.githubusercontent.com/DoktainerApp/templates/main/templates/beszel/icon.png",
color: "#f59e0b",
installs: 42,
});
});
test("community manifest fallback does not expose CasaOS as a user category", () => {
const templates = normalizeSingleManifest(
{
name: "Community App",
image: "example/community-app",
},
"community-app",
);
assert.equal(templates[0]?.category, "Community");
assert.equal(templates[0]?.desc, "Community app config");
assert.deepEqual(templates[0]?.tags, ["community"]);
});
test("app catalog parser hides raw YAML parser details", () => {
assert.throws(
() => parseStructuredText("name: Example\n bad-indent: true"),
(error) =>
error instanceof CatalogSourceValidationError &&
error.message.includes("could not be parsed as JSON or YAML") &&
!error.message.includes("bad indentation"),
);
});
test("app catalog archive resolves relative icon paths to GitHub raw URLs", async () => {
const zip = new AdmZip();
zip.addFile(
"templates-main/templates/beszel/template.json",
Buffer.from(
JSON.stringify({
id: "beszel",
name: "Beszel",
desc: "Monitoring",
image: "henrygd/beszel",
icon: "icon.png",
}),
),
);
const result = await parseArchiveResponse(
new Response(zip.toBuffer()),
"github-archive",
"GitHub Archive ZIP",
"https://codeload.github.com/DoktainerApp/templates/zip/refs/heads/main",
);
assert.equal(
result.templates[0]?.icon,
"https://raw.githubusercontent.com/DoktainerApp/templates/main/templates/beszel/icon.png",
);
});