mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 18:55:22 +00:00
仓库规则要求 config/ 为唯一配置来源、不以环境变量作持久默认值: - pi-notify 改读 config/pi-notify.json(逐字段校验回退,复用原 parsers 钳制) - pi-minimal-footer 改读 config/pi-minimal-footer.json - pi-chrome 移除 PI_CHROME_BRIDGE_HOST/PORT,固定 127.0.0.1:17318 (浏览器端 service_worker.js 硬编码同一地址,单侧可配只会断连) Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
108 lines
3.3 KiB
TypeScript
108 lines
3.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, it } from "node:test";
|
|
import { readNotifySettings } from "../src/settings.ts";
|
|
|
|
let tempDir: string | undefined;
|
|
|
|
afterEach(() => {
|
|
if (tempDir) {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
tempDir = undefined;
|
|
}
|
|
});
|
|
|
|
function configPath(contents: string): string {
|
|
tempDir = mkdtempSync(join(tmpdir(), "pi-notify-settings-"));
|
|
const path = join(tempDir, "pi-notify.json");
|
|
writeFileSync(path, contents);
|
|
return path;
|
|
}
|
|
|
|
const expectedDefaults = {
|
|
enabled: true,
|
|
title: "π - {project}",
|
|
body: "{message}",
|
|
messageSource: "assistant",
|
|
messageMax: 80,
|
|
minDurationMs: 3_000,
|
|
visibility: "always",
|
|
action: "focus",
|
|
soundCommand: undefined,
|
|
};
|
|
|
|
describe("readNotifySettings", () => {
|
|
it("returns all defaults when the config file is missing", () => {
|
|
assert.deepEqual(readNotifySettings("/does/not/exist/pi-notify.json"), expectedDefaults);
|
|
});
|
|
|
|
it("returns all defaults for malformed or non-object JSON", () => {
|
|
for (const contents of ["not json", "[1,2]", "\"hi\"", "42"]) {
|
|
assert.deepEqual(readNotifySettings(configPath(contents)), expectedDefaults, contents);
|
|
}
|
|
});
|
|
|
|
it("adopts valid values from the config file", () => {
|
|
const settings = readNotifySettings(configPath(JSON.stringify({
|
|
enabled: false,
|
|
title: "π {status_icon} · {project}",
|
|
body: "{status}",
|
|
messageSource: "none",
|
|
messageMax: 120,
|
|
minSeconds: 30,
|
|
visibility: "unfocused",
|
|
action: "none",
|
|
soundCommand: "afplay /done.aiff",
|
|
})));
|
|
|
|
assert.deepEqual(settings, {
|
|
enabled: false,
|
|
title: "π {status_icon} · {project}",
|
|
body: "{status}",
|
|
messageSource: "none",
|
|
messageMax: 120,
|
|
minDurationMs: 30_000,
|
|
visibility: "unfocused",
|
|
action: "none",
|
|
soundCommand: "afplay /done.aiff",
|
|
});
|
|
});
|
|
|
|
it("falls back per-field when values have the wrong type or value", () => {
|
|
const settings = readNotifySettings(configPath(JSON.stringify({
|
|
enabled: {},
|
|
title: "",
|
|
body: null,
|
|
messageSource: ["assistant"],
|
|
messageMax: "bogus",
|
|
minSeconds: "bogus",
|
|
visibility: "bogus",
|
|
action: {},
|
|
})));
|
|
|
|
assert.deepEqual(settings, expectedDefaults);
|
|
});
|
|
|
|
it("clamps messageMax and minSeconds to their bounded ranges", () => {
|
|
const settings = readNotifySettings(configPath(JSON.stringify({ messageMax: 999, minSeconds: 4000 })));
|
|
assert.equal(settings.messageMax, 500);
|
|
assert.equal(settings.minDurationMs, 3_600_000);
|
|
|
|
const floor = readNotifySettings(configPath(JSON.stringify({ messageMax: -5, minSeconds: -1 })));
|
|
assert.equal(floor.messageMax, 0);
|
|
assert.equal(floor.minDurationMs, 0);
|
|
});
|
|
|
|
it("treats an empty or whitespace soundCommand as absent and trims values", () => {
|
|
for (const value of ["", " "]) {
|
|
assert.equal(readNotifySettings(configPath(JSON.stringify({ soundCommand: value }))).soundCommand, undefined);
|
|
}
|
|
assert.equal(
|
|
readNotifySettings(configPath(JSON.stringify({ soundCommand: " afplay /done.aiff " }))).soundCommand,
|
|
"afplay /done.aiff",
|
|
);
|
|
});
|
|
});
|