mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
192 lines
7.1 KiB
TypeScript
192 lines
7.1 KiB
TypeScript
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
DEFAULT_IMAGE_HOST_PATH,
|
|
DEFAULT_IMAGE_MAX_BYTES,
|
|
DEFAULT_IMAGE_MAX_COUNT,
|
|
DEFAULT_IMAGE_TTL_MS,
|
|
defaultImageLocalDir,
|
|
parseImageHostPath,
|
|
parseImageLocalDir,
|
|
parseTerminalImageStorageMode,
|
|
resolveTerminalImageStorageSettings,
|
|
TERMINAL_IMAGE_STORAGE_KEYS,
|
|
} from "../../../src/backend/images/image-storage-settings.js";
|
|
import { createFakeContext } from "@termix/plugin-sdk/testing";
|
|
import { readImageStorageSettings } from "../../../src/backend/settings.js";
|
|
|
|
// parseImageLocalDir resolves against the host platform, so a POSIX literal
|
|
// becomes a drive-rooted path on Windows. Compare against the same resolution.
|
|
function localDir(posixPath: string): string {
|
|
return path.resolve(posixPath);
|
|
}
|
|
|
|
function stubSettings(values: Record<string, string> = {}) {
|
|
return {
|
|
get: async (key: string) => values[key] ?? null,
|
|
};
|
|
}
|
|
|
|
const EMPTY_ENV: NodeJS.ProcessEnv = {};
|
|
|
|
describe("terminal image storage settings", () => {
|
|
describe("mode parsing", () => {
|
|
it("accepts the three documented modes case-insensitively", () => {
|
|
expect(parseTerminalImageStorageMode("auto")).toBe("auto");
|
|
expect(parseTerminalImageStorageMode("LOCAL")).toBe("local");
|
|
expect(parseTerminalImageStorageMode(" remote-sftp ")).toBe(
|
|
"remote-sftp",
|
|
);
|
|
});
|
|
|
|
it("rejects unknown modes and non-strings", () => {
|
|
expect(parseTerminalImageStorageMode("s3")).toBeNull();
|
|
expect(parseTerminalImageStorageMode("")).toBeNull();
|
|
expect(parseTerminalImageStorageMode(undefined)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("path validation", () => {
|
|
it("accepts absolute local directories and normalizes them", () => {
|
|
expect(parseImageLocalDir("/var/lib/termix/images")).toBe(
|
|
localDir("/var/lib/termix/images"),
|
|
);
|
|
expect(parseImageLocalDir("/var/lib/termix/../termix/images")).toBeNull();
|
|
});
|
|
|
|
it("rejects relative, empty and NUL-containing local directories", () => {
|
|
expect(parseImageLocalDir("images")).toBeNull();
|
|
expect(parseImageLocalDir("./db/data/images")).toBeNull();
|
|
expect(parseImageLocalDir("")).toBeNull();
|
|
expect(parseImageLocalDir("/tmp/a\0b")).toBeNull();
|
|
});
|
|
|
|
it("requires the agent-visible host path to be POSIX-absolute", () => {
|
|
expect(parseImageHostPath("/tmp/termix-image-v0")).toBe(
|
|
"/tmp/termix-image-v0",
|
|
);
|
|
expect(parseImageHostPath("tmp/termix-image-v0")).toBeNull();
|
|
expect(parseImageHostPath("C:\\images")).toBeNull();
|
|
expect(parseImageHostPath("/tmp/a\0b")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("resolution precedence", () => {
|
|
it("uses built-in defaults when neither the database nor env has values", async () => {
|
|
const resolved = await resolveTerminalImageStorageSettings(
|
|
stubSettings(),
|
|
EMPTY_ENV,
|
|
);
|
|
expect(resolved.mode).toBe("auto");
|
|
expect(resolved.localDir).toBe(defaultImageLocalDir(EMPTY_ENV));
|
|
expect(resolved.hostPath).toBe(DEFAULT_IMAGE_HOST_PATH);
|
|
expect(resolved.ttlMs).toBe(DEFAULT_IMAGE_TTL_MS);
|
|
expect(resolved.maxCount).toBe(DEFAULT_IMAGE_MAX_COUNT);
|
|
expect(resolved.maxBytes).toBe(DEFAULT_IMAGE_MAX_BYTES);
|
|
});
|
|
|
|
it("seeds defaults from legacy TERMIX_IMAGE_* env when no DB value exists", async () => {
|
|
const resolved = await resolveTerminalImageStorageSettings(
|
|
stubSettings(),
|
|
{
|
|
TERMIX_IMAGE_DIR: "/host-tmp/images",
|
|
TERMIX_IMAGE_HOST_PATH: "/tmp/images",
|
|
TERMIX_IMAGE_TTL_MS: "60000",
|
|
TERMIX_MAX_IMAGE_COUNT: "5",
|
|
TERMIX_MAX_IMAGE_STORAGE_BYTES: "10485760",
|
|
},
|
|
);
|
|
expect(resolved.localDir).toBe(localDir("/host-tmp/images"));
|
|
expect(resolved.hostPath).toBe("/tmp/images");
|
|
expect(resolved.ttlMs).toBe(60_000);
|
|
expect(resolved.maxCount).toBe(5);
|
|
expect(resolved.maxBytes).toBe(10_485_760);
|
|
});
|
|
|
|
it("lets persisted DB values win over legacy env values", async () => {
|
|
const resolved = await resolveTerminalImageStorageSettings(
|
|
stubSettings({
|
|
[TERMINAL_IMAGE_STORAGE_KEYS.localDir]: "/db/images",
|
|
[TERMINAL_IMAGE_STORAGE_KEYS.ttlMs]: "1000",
|
|
[TERMINAL_IMAGE_STORAGE_KEYS.maxCount]: "7",
|
|
}),
|
|
{
|
|
TERMIX_IMAGE_DIR: "/host-tmp/images",
|
|
TERMIX_IMAGE_TTL_MS: "60000",
|
|
TERMIX_MAX_IMAGE_COUNT: "5",
|
|
},
|
|
);
|
|
expect(resolved.localDir).toBe(localDir("/db/images"));
|
|
expect(resolved.ttlMs).toBe(1_000);
|
|
expect(resolved.maxCount).toBe(7);
|
|
});
|
|
|
|
it("falls through to env and defaults when the DB value is invalid", async () => {
|
|
const resolved = await resolveTerminalImageStorageSettings(
|
|
stubSettings({
|
|
[TERMINAL_IMAGE_STORAGE_KEYS.localDir]: "relative/path",
|
|
[TERMINAL_IMAGE_STORAGE_KEYS.ttlMs]: "not-a-number",
|
|
}),
|
|
{ TERMIX_IMAGE_DIR: "/host-tmp/images" },
|
|
);
|
|
expect(resolved.localDir).toBe(localDir("/host-tmp/images"));
|
|
expect(resolved.ttlMs).toBe(DEFAULT_IMAGE_TTL_MS);
|
|
});
|
|
|
|
it("keeps legacy explicit local mappings on local mode", async () => {
|
|
const resolved = await resolveTerminalImageStorageSettings(
|
|
stubSettings(),
|
|
{ TERMIX_IMAGE_DIR: "/host-tmp/images" },
|
|
);
|
|
expect(resolved.mode).toBe("local");
|
|
// hostPath falls back to the legacy local dir, which resolves natively.
|
|
expect(resolved.hostPath).toBe(localDir("/host-tmp/images"));
|
|
expect(resolved.localMappingConfigured).toBe(true);
|
|
});
|
|
|
|
it("lets a persisted DB mode override the legacy local mapping", async () => {
|
|
const resolved = await resolveTerminalImageStorageSettings(
|
|
stubSettings({
|
|
[TERMINAL_IMAGE_STORAGE_KEYS.mode]: "remote-sftp",
|
|
}),
|
|
{ TERMIX_IMAGE_DIR: "/host-tmp/images" },
|
|
);
|
|
expect(resolved.mode).toBe("remote-sftp");
|
|
});
|
|
|
|
it("clamps out-of-range numeric values like the legacy env parsing did", async () => {
|
|
const resolved = await resolveTerminalImageStorageSettings(
|
|
stubSettings(),
|
|
{
|
|
TERMIX_IMAGE_TTL_MS: "-5",
|
|
TERMIX_MAX_IMAGE_COUNT: "0",
|
|
TERMIX_MAX_IMAGE_STORAGE_BYTES: "10",
|
|
},
|
|
);
|
|
expect(resolved.ttlMs).toBe(0);
|
|
expect(resolved.maxCount).toBe(1);
|
|
expect(resolved.maxBytes).toBe(1_048_576);
|
|
});
|
|
|
|
it("reads the plugin's admin settings, and treats unset fields as unset", async () => {
|
|
const { ctx } = createFakeContext({
|
|
settings: {
|
|
imageStorageMode: "local",
|
|
imageLocalDir: "/persisted/images",
|
|
},
|
|
});
|
|
const resolved = await readImageStorageSettings(ctx);
|
|
expect(resolved.mode).toBe("local");
|
|
expect(resolved.localDir).toBe(localDir("/persisted/images"));
|
|
// Nothing stored and no environment: the built-in default.
|
|
expect(resolved.maxCount).toBe(DEFAULT_IMAGE_MAX_COUNT);
|
|
});
|
|
|
|
it("keeps TERMINAL_IMAGE_STORAGE_KEYS as the lookup names", () => {
|
|
expect(TERMINAL_IMAGE_STORAGE_KEYS.mode).toBe(
|
|
"terminal_image_storage_mode",
|
|
);
|
|
});
|
|
});
|
|
});
|