openpetswithchatandmcp/apps/desktop/tests/plugin-config.test.ts
OpenPets Dev 6ab3bb64d8 feat(rebrand): rename OpenPets to FamiliarOS and pets to familiars
- Rename all user-facing and technical identifiers from OpenPets/Pet to FamiliarOS/Familiar.
- Rename packages from @open-pets/* to @familiaros/*; rename install-pet/pet-format packages.
- Rename plugin IDs and directories from openpets.* to familiaros.*.
- Rename IPC namespace from openpets:* to familiaros:* and state filenames from openpets-* to familiaros-* with legacy migration.
- Rename source files (pet-window, built-in-pet, default-pet-controller, etc.) to familiar equivalents.
- Update locales (en, es-419, ja, ko, pt-BR, zh-Hans, zh-Hant) and tray/pet context menu strings.
- Add Familiar naming feature: preference, settings input, tray menu display.
- Update assets and packaging config; all desktop tests pass.
2026-06-17 01:42:08 +00:00

78 lines
4.5 KiB
TypeScript

import assert from "node:assert/strict";
import { getEffectivePluginConfig, getPluginDefaultConfig, resolvePluginNumericConfig, resolvePluginStringConfig, validatePluginConfigReplacement } from "../src/plugin-config.js";
import type { FamiliarOSPluginManifest } from "../src/plugin-manifest.js";
const base = manifest();
assert.deepEqual(getPluginDefaultConfig(base), { enabled: true, intervalMinutes: 10, message: "Stretch", mood: "calm" });
assertInvalidReplacement({ message: null }, "invalid_config_value");
assertInvalidReplacement({ message: ["x"] }, "invalid_config_value");
assertInvalidReplacement({ message: { text: "x" } }, "invalid_config_value");
assertInvalidReplacement({ missing: true }, "unknown_config_key");
assertInvalidReplacement({ intervalMinutes: Number.POSITIVE_INFINITY }, "invalid_config_value");
assertInvalidReplacement({ mood: "unknown" }, "invalid_select_value");
assertInvalidReplacement(new Date(), "invalid_config");
assertInvalidReplacement(new Map(), "invalid_config");
assertInvalidReplacement([], "invalid_config");
const soundManifest = manifest({ configSchema: { sound: { type: "sound" } } });
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: "alert" }).ok, true);
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: { kind: "user-sound", id: "a".repeat(32), name: "Ding" } }).ok, true);
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: { kind: "user-sound", id: "../secret" } }).ok, false);
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: { kind: "user-sound", id: "not-a-hash" } }).ok, false);
assert.equal(validatePluginConfigReplacement(soundManifest, { sound: "/tmp/ding.wav" }).ok, false);
assert.deepEqual(validatePluginConfigReplacement(base, { message: "Move", details: "Now", intervalMinutes: 12, enabled: false, mood: "active" }), {
ok: true,
config: { details: "Now", enabled: false, intervalMinutes: 12, message: "Move", mood: "active" },
errors: [],
});
assert.deepEqual(getEffectivePluginConfig(base, { stale: { old: true }, message: "Hydrate" }), {
ok: true,
config: { enabled: true, intervalMinutes: 10, message: "Hydrate", mood: "calm" },
errors: [],
});
const invalidPersisted = getEffectivePluginConfig(base, { intervalMinutes: "10" });
assert.equal(invalidPersisted.ok, false);
assert.equal(invalidPersisted.errors[0]?.code, "invalid_config_value");
assert.equal(resolvePluginNumericConfig(base, {}, "intervalMinutes", { min: 5 }), 10);
assert.equal(resolvePluginNumericConfig(base, { intervalMinutes: 7, stale: true }, "intervalMinutes", { min: 5 }), 7);
assert.throws(() => resolvePluginNumericConfig(base, { intervalMinutes: 4 }, "intervalMinutes", { min: 5 }), /at least 5/);
assert.throws(() => resolvePluginNumericConfig(manifest({ configSchema: { intervalMinutes: { type: "number" } } }), {}, "intervalMinutes", { min: 5 }), /must resolve to an integer/);
assert.throws(() => resolvePluginNumericConfig(base, { intervalMinutes: 6.5 }, "intervalMinutes", { min: 5 }), /must resolve to an integer/);
assert.equal(resolvePluginStringConfig(base, {}, "message", "text"), "Stretch");
assert.equal(resolvePluginStringConfig(base, { message: "Move" }, "message", "text"), "Move");
assert.equal(resolvePluginStringConfig(base, {}, "mood", "select"), "calm");
assert.throws(() => resolvePluginStringConfig(base, { message: 1 }, "message", "text"), /invalid/);
assert.throws(() => resolvePluginStringConfig(manifest({ configSchema: { message: { type: "text" } } }), {}, "message", "text"), /resolve to a value/);
assert.throws(() => resolvePluginStringConfig(base, {}, "intervalMinutes", "text"), /text config field/);
console.error("Plugin config validation passed.");
function assertInvalidReplacement(config: unknown, code: string): void {
const result = validatePluginConfigReplacement(base, config);
assert.equal(result.ok, false);
assert.ok(result.errors.some((error) => error.code === code), JSON.stringify(result.errors));
}
function manifest(patch: Partial<FamiliarOSPluginManifest> = {}): FamiliarOSPluginManifest {
return {
manifestVersion: 1,
id: "plug",
name: "Plug",
version: "1.0.0",
runtime: "declarative",
permissions: ["timer"],
configSchema: patch.configSchema ?? {
message: { type: "text", default: "Stretch" },
details: { type: "textarea" },
intervalMinutes: { type: "number", default: 10 },
enabled: { type: "boolean", default: true },
mood: { type: "select", default: "calm", options: [{ label: "Calm", value: "calm" }, { label: "Active", value: "active" }] },
},
triggers: [],
};
}