kit.program/kit/test/remote_manu_test.js
2026-06-17 10:25:27 +08:00

261 lines
11 KiB
JavaScript

import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import fs from "fs";
import os from "os";
import path from "path";
const RUN_MANUAL = process.env.KIT_REMOTE_MANU_TEST === "1";
const manualDescribe = RUN_MANUAL ? describe : describe.skip;
const manualTest = RUN_MANUAL ? test : test.skip;
let Remote;
if (RUN_MANUAL) {
require("../src/init");
Remote = require("../src/remote");
}
async function createRemote() {
const remote = new Remote();
await remote.init();
return remote;
}
function writeFixtureProject(tempDir, version, content, options = {}) {
const projectId = options.id || "myapp";
const group = options.group === undefined ? "/modules" : options.group;
const projectDir = path.resolve(tempDir, `fixture-${projectId}-${version || "auto"}-${Math.random().toString(16).slice(2)}`);
fs.mkdirSync(projectDir, { recursive: true });
const filePath = path.resolve(projectDir, "myapp.txt");
fs.writeFileSync(filePath, content, "utf-8");
fs.writeFileSync(path.resolve(projectDir, "meta.json"), JSON.stringify({
id: projectId,
group,
version,
desc: "test app",
official_url: "https://example.test/myapp",
dist: [
{
path: filePath,
os: "any",
platform: "any",
filename: "myapp.txt",
type: "data"
}
]
}, null, 4), "utf-8");
return projectDir;
}
function normalizeRemotePath(value) {
if (!value || value === "/") {
return "/";
}
return path.posix.normalize("/" + String(value).replace(/\\/g, "/")).replace(/\/+$/g, "") || "/";
}
function remoteProjectPath(group, projectId) {
const normalizedGroup = group === undefined ? "/modules" : normalizeRemotePath(group);
return normalizeRemotePath(path.posix.join(normalizedGroup, projectId));
}
async function deleteRemotePath(remote, remotePath) {
try {
if (remote.client && remote.client.deleteFile && await remote.client.exists(remotePath)) {
await remote.client.deleteFile(remotePath);
}
} catch (e) {
console.warn(`cleanup remote failed ${remotePath}: ${e.message}`);
}
}
let tempDir;
let localStore;
let remote;
let runId;
let counter;
let projectIds;
let remoteRoots;
beforeEach(async () => {
if (!RUN_MANUAL) {
return;
}
tempDir = fs.mkdtempSync(path.resolve(os.tmpdir(), "kit-remote-manu-test-"));
localStore = $sConfig.getLocalStorePath();
remote = await createRemote();
runId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
counter = 0;
projectIds = [];
remoteRoots = [];
});
afterEach(async () => {
if (!RUN_MANUAL) {
return;
}
for (const remoteRoot of remoteRoots.reverse()) {
await deleteRemotePath(remote, remoteRoot);
}
for (const projectId of projectIds) {
fs.rmSync(path.resolve(localStore, "modules", projectId), { recursive: true, force: true });
}
fs.rmSync(tempDir, { recursive: true, force: true });
});
function nextProjectId() {
const projectId = `myapp-${runId}-${++counter}`.toLowerCase();
projectIds.push(projectId);
return projectId;
}
function trackRemote(group, projectId) {
const root = remoteProjectPath(group, projectId);
remoteRoots.push(root);
return root;
}
manualDescribe("remote module public API manual webdav", () => {
manualTest("1-1 exposes documented public methods", async () => {
expect(typeof remote.check).toBe("function");
expect(typeof remote.publish).toBe("function");
expect(typeof remote.install).toBe("function");
expect(typeof remote.list).toBe("function");
expect(typeof remote.use).toBe("function");
expect(typeof remote.rollback).toBe("function");
expect(typeof remote.remove).toBe("function");
});
manualTest("1-2 check validates an available webdav client", async () => {
expect(await remote.check()).toBe(true);
});
manualTest("1-3 publish stores files under default group /modules/id", async () => {
const projectId = nextProjectId();
trackRemote("/modules", projectId);
const projectDir = writeFixtureProject(tempDir, "1.0", "hello v1", { id: projectId });
await remote.publish(projectDir);
const metadata = JSON.parse(await remote.client.getFileContents(`/modules/${projectId}/metadata.json`, { format: "text" }));
const version = JSON.parse(await remote.client.getFileContents(`/modules/${projectId}/1.0/version.json`, { format: "text" }));
const artifact = await remote.client.getFileContents(`/modules/${projectId}/1.0/any/any/myapp.txt`, { format: "text" });
expect(metadata.project_id).toBe(projectId);
expect(metadata.group).toBe("/modules");
expect(metadata.version).toBe("1.0");
expect(metadata.versions).toBeUndefined();
expect(metadata.latest_version).toBeUndefined();
expect(version.artifacts[0].path).toBe("any/any/myapp.txt");
expect(artifact).toBe("hello v1");
}, 30000);
manualTest("1-4 publish supports custom nested group", async () => {
const projectId = nextProjectId();
const group = `/kit-manual-test/${runId}/docker/net`;
trackRemote(group, projectId);
await remote.publish(writeFixtureProject(tempDir, "2.0", "docker net", { id: projectId, group }));
const metadata = JSON.parse(await remote.client.getFileContents(`${group}/${projectId}/metadata.json`, { format: "text" }));
const artifact = await remote.client.getFileContents(`${group}/${projectId}/2.0/any/any/myapp.txt`, { format: "text" });
expect(metadata.group).toBe(group);
expect(metadata.version).toBe("2.0");
expect(artifact).toBe("docker net");
}, 30000);
manualTest("1-5 empty group stores project at repository root", async () => {
const projectId = nextProjectId();
trackRemote("", projectId);
await remote.publish(writeFixtureProject(tempDir, "1.0", "root group", { id: projectId, group: "" }));
const metadata = JSON.parse(await remote.client.getFileContents(`/${projectId}/metadata.json`, { format: "text" }));
expect(metadata.group).toBe("/");
expect(await remote.client.getFileContents(`/${projectId}/1.0/any/any/myapp.txt`, { format: "text" })).toBe("root group");
}, 30000);
manualTest("1-6 explicit version fails when remote version already exists", async () => {
const projectId = nextProjectId();
trackRemote("/modules", projectId);
await remote.publish(writeFixtureProject(tempDir, "1.0", "hello v1", { id: projectId }));
await expect(remote.publish(writeFixtureProject(tempDir, "1.0", "hello again", { id: projectId }))).rejects.toThrow("远程版本已存在");
}, 30000);
manualTest("1-7 blank version increments remote version tail or starts at 1.0", async () => {
const projectId = nextProjectId();
trackRemote("/modules", projectId);
await remote.publish(writeFixtureProject(tempDir, "", "first auto", { id: projectId }));
let metadata = JSON.parse(await remote.client.getFileContents(`/modules/${projectId}/metadata.json`, { format: "text" }));
expect(metadata.version).toBe("1.0");
await remote.publish(writeFixtureProject(tempDir, "", "second auto", { id: projectId }));
metadata = JSON.parse(await remote.client.getFileContents(`/modules/${projectId}/metadata.json`, { format: "text" }));
expect(metadata.version).toBe("1.1");
expect(await remote.client.getFileContents(`/modules/${projectId}/1.1/any/any/myapp.txt`, { format: "text" })).toBe("second auto");
}, 30000);
manualTest("1-8 install downloads latest version from default group", async () => {
const projectId = nextProjectId();
trackRemote("/modules", projectId);
await remote.publish(writeFixtureProject(tempDir, "1.0", "hello v1", { id: projectId }));
const installed = await remote.install(projectId);
const moduleRoot = path.resolve(localStore, "modules", projectId);
const installJson = JSON.parse(fs.readFileSync(path.resolve(moduleRoot, ".meta", "install.json"), "utf-8"));
const installedFile = fs.readFileSync(path.resolve(moduleRoot, "versions", "1.0", "any", "any", "myapp.txt"), "utf-8");
expect(installed).toBe("1.0");
expect(installedFile).toBe("hello v1");
expect(installJson.current_version).toBe("1.0");
expect(installJson.installed_versions).toContain("1.0");
expect(fs.existsSync(path.resolve(moduleRoot, "current"))).toBe(true);
}, 30000);
manualTest("1-9 install can target a custom group path", async () => {
const projectId = nextProjectId();
const group = `/kit-manual-test/${runId}/docker/net`;
trackRemote(group, projectId);
await remote.publish(writeFixtureProject(tempDir, "1.0", "docker install", { id: projectId, group }));
const installed = await remote.install(`${group}/${projectId}`);
const installedFile = fs.readFileSync(path.resolve(localStore, "modules", projectId, "versions", "1.0", "any", "any", "myapp.txt"), "utf-8");
expect(installed).toBe("1.0");
expect(installedFile).toBe("docker install");
}, 30000);
manualTest("1-10 list use rollback and remove manage installed versions", async () => {
const projectId = nextProjectId();
trackRemote("/modules", projectId);
await remote.publish(writeFixtureProject(tempDir, "1.0", "hello v1", { id: projectId }));
await remote.install(projectId);
await remote.publish(writeFixtureProject(tempDir, "2.0", "hello v2", { id: projectId }));
await remote.install(projectId);
expect(await remote.list(projectId)).toEqual(["1.0", "2.0"]);
await remote.rollback(projectId);
let installJson = JSON.parse(fs.readFileSync(path.resolve(localStore, "modules", projectId, ".meta", "install.json"), "utf-8"));
expect(installJson.current_version).toBe("1.0");
await remote.use(`${projectId}@2.0`);
installJson = JSON.parse(fs.readFileSync(path.resolve(localStore, "modules", projectId, ".meta", "install.json"), "utf-8"));
expect(installJson.current_version).toBe("2.0");
await remote.remove(`${projectId}@1.0`);
installJson = JSON.parse(fs.readFileSync(path.resolve(localStore, "modules", projectId, ".meta", "install.json"), "utf-8"));
expect(installJson.installed_versions).toEqual(["2.0"]);
expect(fs.existsSync(path.resolve(localStore, "modules", projectId, "versions", "1.0"))).toBe(false);
}, 30000);
});
if (!RUN_MANUAL) {
describe("remote manual webdav tests", () => {
test.skip("set KIT_REMOTE_MANU_TEST=1 and run bun test test/remote_manu_test.js", () => {});
});
}