154 lines
5.7 KiB
TypeScript
154 lines
5.7 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { loadConfig } from "../src/config.js";
|
|
|
|
test("loadConfig accepts container listening and runtime paths", () => {
|
|
expect(loadConfig({
|
|
HOST: "0.0.0.0",
|
|
PORT: "9000",
|
|
THT_HARNESS_DIR: "/app/harness",
|
|
THT_BIN: "/opt/venv/bin/tht",
|
|
PI_BIN: "/usr/local/bin/pi",
|
|
SETTINGS_FILE: "/data/settings/settings.json",
|
|
THT_DATA_ROOT: "/data",
|
|
})).toMatchObject({
|
|
host: "0.0.0.0",
|
|
port: 9000,
|
|
harnessDir: "/app/harness",
|
|
thtBin: "/opt/venv/bin/tht",
|
|
piBin: "/usr/local/bin/pi",
|
|
settingsFile: "/data/settings/settings.json",
|
|
maintenanceFile: "/data/settings/maintenance.json",
|
|
dataRoot: "/data",
|
|
});
|
|
});
|
|
|
|
test("loadConfig keeps local development defaults", () => {
|
|
expect(loadConfig({})).toMatchObject({
|
|
host: "127.0.0.1",
|
|
port: 8787,
|
|
harnessDir: "../harness",
|
|
thtBin: "tht",
|
|
piBin: "pi",
|
|
settingsFile: "data/settings.json",
|
|
maintenanceFile: "data/maintenance.json",
|
|
workspaceRegistry: {
|
|
root: "/data/workspace-registry",
|
|
branch: "main",
|
|
maxImportBytes: 10 * 1024 * 1024,
|
|
maxImportEntries: 32,
|
|
},
|
|
internalQdrantUrl: "http://qdrant:6333",
|
|
internalEmbeddingUrl: "http://embedding:11434",
|
|
internalEmbeddingModel: "qwen3-embedding:0.6b",
|
|
internalEmbeddingDimensions: 1024,
|
|
});
|
|
expect(loadConfig({}).dataRoot).toBeUndefined();
|
|
});
|
|
|
|
test("loadConfig accepts only the allowed internal semantic runtime hosts", () => {
|
|
expect(loadConfig({
|
|
THT_INTERNAL_QDRANT_URL: "http://localhost:6333",
|
|
THT_INTERNAL_EMBEDDING_URL: "http://127.0.0.1:11434",
|
|
})).toMatchObject({
|
|
internalQdrantUrl: "http://localhost:6333",
|
|
internalEmbeddingUrl: "http://127.0.0.1:11434",
|
|
});
|
|
|
|
expect(() => loadConfig({ THT_INTERNAL_QDRANT_URL: "http://qdrant.internal:6333" }))
|
|
.toThrow(/internal.*qdrant|host validation|invalid/i);
|
|
expect(() => loadConfig({ THT_INTERNAL_EMBEDDING_URL: "http://example.com:11434" }))
|
|
.toThrow(/internal.*embedding|host validation|invalid/i);
|
|
});
|
|
|
|
test("loadConfig enables the legacy workspace request only through explicit local mode", () => {
|
|
expect(loadConfig({ THT_LEGACY_WORKSPACE_MODE: "local" }).legacyWorkspaceMode).toBe(true);
|
|
|
|
expect(() => loadConfig({
|
|
THT_LEGACY_WORKSPACE_MODE: "local",
|
|
AUTH_MODE: "upstream",
|
|
THT_SESSION_STORAGE: "postgres",
|
|
THT_SESSION_DB_HOST: "db.internal",
|
|
THT_SESSION_DB_NAME: "thoth",
|
|
THT_SESSION_RUNTIME_USER: "thoth_sessions_app",
|
|
THT_SESSION_RUNTIME_PASSWORD_FILE: "/run/secrets/session_runtime_password",
|
|
THT_SESSION_DB_SSLMODE: "verify-full",
|
|
THT_SESSION_DB_SSLROOTCERT: "/run/secrets/session_ca.pem",
|
|
})).toThrow(/legacy workspace mode requires local session storage/);
|
|
expect(() => loadConfig({ THT_LEGACY_WORKSPACE_MODE: "true" }))
|
|
.toThrow(/legacy workspace mode configuration is invalid/);
|
|
});
|
|
|
|
test("loadConfig rejects unauthenticated public exposure", () => {
|
|
expect(() => loadConfig({
|
|
THOTH_PUBLIC_EXPOSURE: "true",
|
|
AUTH_MODE: "none",
|
|
})).toThrow(/public exposure requires AUTH_MODE=upstream/);
|
|
});
|
|
|
|
test("loadConfig accepts an authenticated upstream trust boundary", () => {
|
|
expect(loadConfig({
|
|
THOTH_PUBLIC_EXPOSURE: "true",
|
|
AUTH_MODE: "upstream",
|
|
THT_SESSION_STORAGE: "postgres",
|
|
THT_SESSION_DB_HOST: "db.internal",
|
|
THT_SESSION_DB_NAME: "thoth",
|
|
THT_SESSION_RUNTIME_USER: "thoth_sessions_app",
|
|
THT_SESSION_RUNTIME_PASSWORD_FILE: "/run/secrets/session_runtime_password",
|
|
THT_SESSION_DB_SSLMODE: "verify-full",
|
|
THT_SESSION_DB_SSLROOTCERT: "/run/secrets/session_ca.pem",
|
|
}).authMode).toBe("upstream");
|
|
});
|
|
|
|
test("loadConfig requires direct PostgreSQL TLS inputs for the public server session store", () => {
|
|
const env = {
|
|
THOTH_PUBLIC_EXPOSURE: "true",
|
|
AUTH_MODE: "upstream",
|
|
THT_SESSION_STORAGE: "postgres",
|
|
THT_SESSION_DB_HOST: "db.internal",
|
|
THT_SESSION_DB_NAME: "thoth",
|
|
THT_SESSION_RUNTIME_USER: "thoth_sessions_app",
|
|
THT_SESSION_RUNTIME_PASSWORD_FILE: "/run/secrets/session_runtime_password",
|
|
THT_SESSION_DB_SSLMODE: "verify-full",
|
|
THT_SESSION_DB_SSLROOTCERT: "/run/secrets/session_ca.pem",
|
|
};
|
|
|
|
expect(loadConfig(env).sessionStorage).toMatchObject({
|
|
mode: "postgres",
|
|
host: "db.internal",
|
|
port: 5432,
|
|
database: "thoth",
|
|
runtimeUser: "thoth_sessions_app",
|
|
runtimePasswordFile: "/run/secrets/session_runtime_password",
|
|
sslmode: "verify-full",
|
|
sslrootcert: "/run/secrets/session_ca.pem",
|
|
});
|
|
for (const required of [
|
|
"THT_SESSION_DB_HOST", "THT_SESSION_DB_NAME", "THT_SESSION_RUNTIME_USER",
|
|
"THT_SESSION_RUNTIME_PASSWORD_FILE", "THT_SESSION_DB_SSLMODE", "THT_SESSION_DB_SSLROOTCERT",
|
|
]) {
|
|
const missing = { ...env, [required]: undefined };
|
|
expect(() => loadConfig(missing)).toThrow(/server session storage configuration is invalid/);
|
|
}
|
|
});
|
|
|
|
test("loadConfig rejects public local storage and server storage without upstream auth", () => {
|
|
expect(() => loadConfig({
|
|
THOTH_PUBLIC_EXPOSURE: "true",
|
|
AUTH_MODE: "upstream",
|
|
THT_SESSION_STORAGE: "local",
|
|
})).toThrow(/local session storage requires loopback-only deployment/);
|
|
expect(() => loadConfig({
|
|
THT_SESSION_STORAGE: "postgres",
|
|
AUTH_MODE: "none",
|
|
})).toThrow(/server session storage requires AUTH_MODE=upstream/);
|
|
});
|
|
|
|
test("loadConfig accepts only an absolute generic model key file", () => {
|
|
expect(loadConfig({ THT_MODEL_API_KEY_FILE: "/run/secrets/model_api_key" }).modelApiKeyFile)
|
|
.toBe("/run/secrets/model_api_key");
|
|
expect(() => loadConfig({ THT_MODEL_API_KEY_FILE: "relative/key" }))
|
|
.toThrow(/model credential configuration is invalid/);
|
|
expect(() => loadConfig({ THT_MODEL_API_KEY_FILE: " /run/secrets/key" }))
|
|
.toThrow(/model credential configuration is invalid/);
|
|
});
|