forked from wangbo/easyai
198 lines
6.3 KiB
JavaScript
198 lines
6.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import {
|
|
assertNoMissingModels,
|
|
buildRelease,
|
|
checkArtifacts,
|
|
prepareArtifacts,
|
|
publicBaseUrl,
|
|
validateSource,
|
|
} from "./catalog.mjs";
|
|
|
|
const directory = path.dirname(new URL(import.meta.url).pathname);
|
|
const source = JSON.parse(
|
|
fs.readFileSync(path.join(directory, "catalog.source.json"), "utf8"),
|
|
);
|
|
|
|
function fixture(t) {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "easyai-git-catalog-"));
|
|
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
|
const data = structuredClone(source);
|
|
data.baseModels = data.baseModels.slice(0, 2);
|
|
const save = () =>
|
|
fs.writeFileSync(
|
|
path.join(dir, "catalog.source.json"),
|
|
JSON.stringify(data, null, 2) + "\n",
|
|
);
|
|
save();
|
|
return { dir, data, save };
|
|
}
|
|
|
|
test("唯一维护源通过完整 Schema 和规范格式校验", () => {
|
|
const data = validateSource();
|
|
assert.ok(data.baseModels.length >= 220);
|
|
assert.equal(data.providers.length, 27);
|
|
});
|
|
|
|
test("Nano Banana 2.1 保留唯一原身份和图像能力", () => {
|
|
const rows = source.baseModels.filter(
|
|
(model) => model.defaultRealModelName === "gemini-nano-banana-2.1",
|
|
);
|
|
assert.equal(rows.length, 1);
|
|
assert.equal(
|
|
rows[0].catalogKey,
|
|
"base:runtime:google-gemini:Nano Banana 2.1:gemini-nano-banana-2.1",
|
|
);
|
|
assert.equal(rows[0].alias, "Nano Banana 2.1");
|
|
assert.equal(rows[0].status, "active");
|
|
assert.deepEqual(rows[0].modelTypes, ["image_generate", "image_edit"]);
|
|
assert.deepEqual(rows[0].capabilities.image_generate.output_resolutions, [
|
|
"1K",
|
|
"2K",
|
|
"4K",
|
|
]);
|
|
assert.equal(rows[0].capabilities.image_edit.input_max_images_count, 14);
|
|
});
|
|
|
|
test("重复模型身份拒绝保存", (t) => {
|
|
const { dir, data, save } = fixture(t);
|
|
data.baseModels.push(structuredClone(data.baseModels[0]));
|
|
save();
|
|
assert.throws(
|
|
() => validateSource(path.join(dir, "catalog.source.json")),
|
|
/重复/,
|
|
);
|
|
});
|
|
|
|
test("能力字段拼写和类型受 Schema 约束", (t) => {
|
|
const { dir, data, save } = fixture(t);
|
|
const type = data.baseModels[0].modelTypes[0];
|
|
data.baseModels[0].capabilities[type].misspelledCapability = true;
|
|
save();
|
|
assert.throws(
|
|
() => validateSource(path.join(dir, "catalog.source.json")),
|
|
/additional properties/,
|
|
);
|
|
});
|
|
|
|
test("准备版本时同步生成公开指针和校验和匹配的快照", (t) => {
|
|
const { dir } = fixture(t);
|
|
const { pointer } = prepareArtifacts(dir, { version: "test-v1" });
|
|
assert.equal(
|
|
pointer.catalogUrl,
|
|
publicBaseUrl + "/releases/test-v1/catalog.json",
|
|
);
|
|
assert.equal(checkArtifacts(dir).version, "test-v1");
|
|
});
|
|
|
|
test("只改维护源而未重新生成快照会被检查拒绝", (t) => {
|
|
const { dir, data, save } = fixture(t);
|
|
prepareArtifacts(dir, { version: "test-v1" });
|
|
data.baseModels[0].displayName = "修改后的名称";
|
|
save();
|
|
assert.throws(() => checkArtifacts(dir), /不一致/);
|
|
});
|
|
|
|
test("篡改版本快照会被拒绝", (t) => {
|
|
const { dir } = fixture(t);
|
|
prepareArtifacts(dir, { version: "test-v1" });
|
|
const file = path.join(dir, "releases/test-v1/catalog.json");
|
|
const release = JSON.parse(fs.readFileSync(file));
|
|
release.baseModels[0].displayName = "篡改";
|
|
fs.writeFileSync(file, JSON.stringify(release));
|
|
assert.throws(() => checkArtifacts(dir), /不一致/);
|
|
});
|
|
|
|
test("遗漏已发布模型默认拒绝,正常下线保留原身份", (t) => {
|
|
const { data } = fixture(t);
|
|
const previous = buildRelease(data, { version: "test-v1" });
|
|
const reduced = structuredClone(data);
|
|
reduced.baseModels.pop();
|
|
assert.throws(
|
|
() => assertNoMissingModels(previous, reduced),
|
|
/遗漏已发布模型/,
|
|
);
|
|
data.baseModels[0].status = "offline";
|
|
assert.deepEqual(assertNoMissingModels(previous, data), []);
|
|
});
|
|
|
|
test("物理移除必须同时声明允许和原因,并记录在新版本中", (t) => {
|
|
const { dir, data, save } = fixture(t);
|
|
const { release: previous } = prepareArtifacts(dir, { version: "test-v1" });
|
|
data.baseModels.pop();
|
|
save();
|
|
assert.throws(
|
|
() => prepareArtifacts(dir, { previous, allowRemovals: true }),
|
|
/遗漏/,
|
|
);
|
|
assert.throws(
|
|
() => prepareArtifacts(dir, { previous, removalReason: "迁移" }),
|
|
/遗漏/,
|
|
);
|
|
const { release } = prepareArtifacts(dir, {
|
|
previous,
|
|
version: "test-v2",
|
|
allowRemovals: true,
|
|
removalReason: "明确移除",
|
|
});
|
|
assert.equal(release.removals.models.length, 1);
|
|
assert.equal(release.removals.reason, "明确移除");
|
|
assert.equal(checkArtifacts(dir).version, "test-v2");
|
|
});
|
|
|
|
test("历史快照不能覆盖且版本不能包含目录穿越", (t) => {
|
|
const { dir } = fixture(t);
|
|
prepareArtifacts(dir, { version: "test-v1" });
|
|
assert.throws(
|
|
() => prepareArtifacts(dir, { version: "test-v1" }),
|
|
/不能覆盖/,
|
|
);
|
|
assert.throws(
|
|
() => prepareArtifacts(dir, { version: "../outside" }),
|
|
/版本格式/,
|
|
);
|
|
});
|
|
|
|
test("公开指针不能重新指向原 CDN 或其他仓库", (t) => {
|
|
const { dir } = fixture(t);
|
|
prepareArtifacts(dir, { version: "test-v1" });
|
|
const file = path.join(dir, "latest.json");
|
|
const pointer = JSON.parse(fs.readFileSync(file));
|
|
pointer.catalogUrl =
|
|
"https://aliyuncdn.51easyai.com/model-catalog/official/v1/catalog.json";
|
|
fs.writeFileSync(file, JSON.stringify(pointer));
|
|
assert.throws(() => checkArtifacts(dir), /easyai 仓库公开地址/);
|
|
});
|
|
|
|
test("恢复 Git 历史定义后生成新回退版本,保留全部快照", (t) => {
|
|
const { dir, data, save } = fixture(t);
|
|
const original = structuredClone(data);
|
|
const first = prepareArtifacts(dir, { version: "test-v1" });
|
|
data.baseModels[0].displayName = "新版";
|
|
save();
|
|
const second = prepareArtifacts(dir, {
|
|
previous: first.release,
|
|
version: "test-v2",
|
|
});
|
|
fs.writeFileSync(
|
|
path.join(dir, "catalog.source.json"),
|
|
JSON.stringify(original, null, 2) + "\n",
|
|
);
|
|
const third = prepareArtifacts(dir, {
|
|
previous: second.release,
|
|
version: "test-v3",
|
|
note: "恢复 test-v1 的定义",
|
|
});
|
|
assert.equal(third.release.previousVersion, "test-v2");
|
|
assert.equal(third.release.checksum, first.release.checksum);
|
|
assert.equal(checkArtifacts(dir).version, "test-v3");
|
|
assert.deepEqual(fs.readdirSync(path.join(dir, "releases")).sort(), [
|
|
"test-v1",
|
|
"test-v2",
|
|
"test-v3",
|
|
]);
|
|
});
|