mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-09-02 09:30:23 +08:00
Settings saved in the panel are written to data/settings.json and overlaid onto process.env, taking precedence over environment variables and applying immediately without restart. Enable by setting ADMIN_PASSWORD; on serverless platforms without persistent disk the panel degrades to read-only.
125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
import fs from "fs"
|
|
import os from "os"
|
|
import path from "path"
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest"
|
|
import {
|
|
_resetForTests,
|
|
applyToEnv,
|
|
getEnvFallback,
|
|
getValueSource,
|
|
isSettingsWritable,
|
|
loadSettings,
|
|
saveSettings,
|
|
} from "@/lib/admin/settings"
|
|
|
|
let tmpDir: string
|
|
|
|
beforeEach(() => {
|
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "admin-settings-"))
|
|
process.env.SETTINGS_FILE = path.join(tmpDir, "settings.json")
|
|
_resetForTests()
|
|
})
|
|
|
|
afterEach(() => {
|
|
_resetForTests()
|
|
delete process.env.SETTINGS_FILE
|
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
delete process.env.TEST_ADMIN_VAR
|
|
})
|
|
|
|
describe("loadSettings", () => {
|
|
it("returns empty object when file does not exist", () => {
|
|
expect(loadSettings()).toEqual({})
|
|
})
|
|
|
|
it("reads values from the settings file", () => {
|
|
fs.writeFileSync(
|
|
process.env.SETTINGS_FILE!,
|
|
JSON.stringify({ version: 1, values: { TEST_ADMIN_VAR: "abc" } }),
|
|
)
|
|
expect(loadSettings()).toEqual({ TEST_ADMIN_VAR: "abc" })
|
|
})
|
|
})
|
|
|
|
describe("applyToEnv / saveSettings", () => {
|
|
it("overlays file values onto process.env", () => {
|
|
saveSettings({ TEST_ADMIN_VAR: "from-file" })
|
|
expect(process.env.TEST_ADMIN_VAR).toBe("from-file")
|
|
})
|
|
|
|
it("file value wins over pre-existing env value", () => {
|
|
process.env.TEST_ADMIN_VAR = "from-env"
|
|
saveSettings({ TEST_ADMIN_VAR: "from-file" })
|
|
expect(process.env.TEST_ADMIN_VAR).toBe("from-file")
|
|
})
|
|
|
|
it("deleting a key restores the original env value", () => {
|
|
process.env.TEST_ADMIN_VAR = "from-env"
|
|
saveSettings({ TEST_ADMIN_VAR: "from-file" })
|
|
saveSettings({ TEST_ADMIN_VAR: null })
|
|
expect(process.env.TEST_ADMIN_VAR).toBe("from-env")
|
|
})
|
|
|
|
it("deleting a key unsets env when there was no original value", () => {
|
|
saveSettings({ TEST_ADMIN_VAR: "from-file" })
|
|
saveSettings({ TEST_ADMIN_VAR: null })
|
|
expect(process.env.TEST_ADMIN_VAR).toBeUndefined()
|
|
})
|
|
|
|
it("persists across cache reset (file round-trip)", () => {
|
|
saveSettings({ TEST_ADMIN_VAR: "persisted" })
|
|
_resetForTests()
|
|
applyToEnv()
|
|
expect(process.env.TEST_ADMIN_VAR).toBe("persisted")
|
|
})
|
|
})
|
|
|
|
describe("getValueSource / getEnvFallback", () => {
|
|
it("reports file source when key is in settings", () => {
|
|
saveSettings({ TEST_ADMIN_VAR: "x" })
|
|
expect(getValueSource("TEST_ADMIN_VAR")).toBe("file")
|
|
})
|
|
|
|
it("reports env source when only env is set", () => {
|
|
process.env.TEST_ADMIN_VAR = "from-env"
|
|
applyToEnv()
|
|
expect(getValueSource("TEST_ADMIN_VAR")).toBe("env")
|
|
})
|
|
|
|
it("reports default when neither is set", () => {
|
|
expect(getValueSource("TEST_ADMIN_VAR")).toBe("default")
|
|
})
|
|
|
|
it("returns the shadowed env value as fallback", () => {
|
|
process.env.TEST_ADMIN_VAR = "from-env"
|
|
saveSettings({ TEST_ADMIN_VAR: "from-file" })
|
|
expect(getEnvFallback("TEST_ADMIN_VAR")).toBe("from-env")
|
|
})
|
|
})
|
|
|
|
describe("isSettingsWritable", () => {
|
|
it("returns true for a writable temp dir", () => {
|
|
expect(isSettingsWritable()).toBe(true)
|
|
})
|
|
|
|
it("returns false for an unwritable path", () => {
|
|
_resetForTests()
|
|
process.env.SETTINGS_FILE = "/nonexistent-root-dir/settings.json"
|
|
expect(isSettingsWritable()).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("settings file on disk", () => {
|
|
it("writes valid JSON with restrictive permissions", () => {
|
|
saveSettings({ TEST_ADMIN_VAR: "secret" })
|
|
const filePath = process.env.SETTINGS_FILE!
|
|
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"))
|
|
expect(parsed).toEqual({
|
|
version: 1,
|
|
values: { TEST_ADMIN_VAR: "secret" },
|
|
})
|
|
const mode = fs.statSync(filePath).mode & 0o777
|
|
expect(mode).toBe(0o600)
|
|
})
|
|
})
|