@digitalpaws/starr 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.rst +75 -0
- package/data/.nfs.811bbb0d.43c34.4 +0 -0
- package/data/.nfs.81ba4cd0.ffff87804.4 +0 -0
- package/data/.nfs.82d127f7.fffff5704.4 +0 -0
- package/data/.nfs.82d127fe.fffff5704.4 +0 -0
- package/data/.nfs.82d159a5.fffff5aa4.4 +0 -0
- package/data/.nfs.82d159aa.fffff5aa4.4 +0 -0
- package/data/.nfs.82d15a9c.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15aa1.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15c65.fffff5c24.4 +0 -0
- package/data/.nfs.82d1852c.fffff5e24.4 +0 -0
- package/data/.nfs.82d18536.fffff5e24.4 +0 -0
- package/data/.nfs.82d18dbe.fffff5e44.4 +0 -0
- package/data/.nfs.82d18dc6.fffff5e44.4 +0 -0
- package/data/.nfs.8309a3f5.fffff82f4.4 +0 -0
- package/data/.nfs.8309a3fe.fffff82f4.4 +0 -0
- package/data/.nfs.8314122e.fffffb044.4 +0 -0
- package/data/.nfs.83141236.fffffb044.4 +0 -0
- package/data/.nfs.833c4fe9.fffffbc34.4 +0 -0
- package/data/.nfs.833c4ff3.fffffbc34.4 +0 -0
- package/data/logs/starr.log +66244 -0
- package/data/starr.db +0 -0
- package/data/starr.db-shm +0 -0
- package/data/starr.db-wal +0 -0
- package/data/starr.jwt +1 -0
- package/data/starr.key +1 -0
- package/dist/auth/auth-service.d.ts +42 -0
- package/dist/auth/auth-service.d.ts.map +1 -0
- package/dist/auth/auth-service.js +226 -0
- package/dist/auth/auth-service.js.map +1 -0
- package/dist/auth/http-server.d.ts +16 -0
- package/dist/auth/http-server.d.ts.map +1 -0
- package/dist/auth/http-server.js +207 -0
- package/dist/auth/http-server.js.map +1 -0
- package/dist/auth/jwt-secret.d.ts +6 -0
- package/dist/auth/jwt-secret.d.ts.map +1 -0
- package/dist/auth/jwt-secret.js +51 -0
- package/dist/auth/jwt-secret.js.map +1 -0
- package/dist/auth/oidc/oidc-flow.d.ts +60 -0
- package/dist/auth/oidc/oidc-flow.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-flow.js +201 -0
- package/dist/auth/oidc/oidc-flow.js.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts +59 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.js +124 -0
- package/dist/auth/oidc/oidc-provider-service.js.map +1 -0
- package/dist/auth/oidc/user-service.d.ts +67 -0
- package/dist/auth/oidc/user-service.d.ts.map +1 -0
- package/dist/auth/oidc/user-service.js +177 -0
- package/dist/auth/oidc/user-service.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +31 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +70 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/config/config-service.d.ts +83 -0
- package/dist/config/config-service.d.ts.map +1 -0
- package/dist/config/config-service.js +309 -0
- package/dist/config/config-service.js.map +1 -0
- package/dist/config/validate.d.ts +12 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +140 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/content-types/registry.d.ts +75 -0
- package/dist/content-types/registry.d.ts.map +1 -0
- package/dist/content-types/registry.js +171 -0
- package/dist/content-types/registry.js.map +1 -0
- package/dist/datastore/db.d.ts +9 -0
- package/dist/datastore/db.d.ts.map +1 -0
- package/dist/datastore/db.js +27 -0
- package/dist/datastore/db.js.map +1 -0
- package/dist/datastore/encrypt.d.ts +9 -0
- package/dist/datastore/encrypt.d.ts.map +1 -0
- package/dist/datastore/encrypt.js +84 -0
- package/dist/datastore/encrypt.js.map +1 -0
- package/dist/datastore/migrations.d.ts +10 -0
- package/dist/datastore/migrations.d.ts.map +1 -0
- package/dist/datastore/migrations.js +340 -0
- package/dist/datastore/migrations.js.map +1 -0
- package/dist/datastore/storage.d.ts +4 -0
- package/dist/datastore/storage.d.ts.map +1 -0
- package/dist/datastore/storage.js +60 -0
- package/dist/datastore/storage.js.map +1 -0
- package/dist/events/event-bus.d.ts +15 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +140 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/spine.d.ts +31 -0
- package/dist/events/spine.d.ts.map +1 -0
- package/dist/events/spine.js +68 -0
- package/dist/events/spine.js.map +1 -0
- package/dist/events/trace.d.ts +24 -0
- package/dist/events/trace.d.ts.map +1 -0
- package/dist/events/trace.js +33 -0
- package/dist/events/trace.js.map +1 -0
- package/dist/host.d.ts +65 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +94 -0
- package/dist/host.js.map +1 -0
- package/dist/library/importer.d.ts +29 -0
- package/dist/library/importer.d.ts.map +1 -0
- package/dist/library/importer.js +145 -0
- package/dist/library/importer.js.map +1 -0
- package/dist/library/library-cache.d.ts +46 -0
- package/dist/library/library-cache.d.ts.map +1 -0
- package/dist/library/library-cache.js +152 -0
- package/dist/library/library-cache.js.map +1 -0
- package/dist/library/library-config.d.ts +12 -0
- package/dist/library/library-config.d.ts.map +1 -0
- package/dist/library/library-config.js +10 -0
- package/dist/library/library-config.js.map +1 -0
- package/dist/library/library-scanner.d.ts +62 -0
- package/dist/library/library-scanner.d.ts.map +1 -0
- package/dist/library/library-scanner.js +336 -0
- package/dist/library/library-scanner.js.map +1 -0
- package/dist/library/library-service.d.ts +65 -0
- package/dist/library/library-service.d.ts.map +1 -0
- package/dist/library/library-service.js +197 -0
- package/dist/library/library-service.js.map +1 -0
- package/dist/library/metadata-matcher.d.ts +56 -0
- package/dist/library/metadata-matcher.d.ts.map +1 -0
- package/dist/library/metadata-matcher.js +207 -0
- package/dist/library/metadata-matcher.js.map +1 -0
- package/dist/library/naming.d.ts +8 -0
- package/dist/library/naming.d.ts.map +1 -0
- package/dist/library/naming.js +47 -0
- package/dist/library/naming.js.map +1 -0
- package/dist/library/scan-controller.d.ts +95 -0
- package/dist/library/scan-controller.d.ts.map +1 -0
- package/dist/library/scan-controller.js +379 -0
- package/dist/library/scan-controller.js.map +1 -0
- package/dist/library/source-resolver.d.ts +17 -0
- package/dist/library/source-resolver.d.ts.map +1 -0
- package/dist/library/source-resolver.js +75 -0
- package/dist/library/source-resolver.js.map +1 -0
- package/dist/logging/logger.d.ts +3 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +107 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/main.d.ts +36 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +229 -0
- package/dist/main.js.map +1 -0
- package/dist/plugins/context.d.ts +10 -0
- package/dist/plugins/context.d.ts.map +1 -0
- package/dist/plugins/context.js +110 -0
- package/dist/plugins/context.js.map +1 -0
- package/dist/plugins/discovery.d.ts +2 -0
- package/dist/plugins/discovery.d.ts.map +1 -0
- package/dist/plugins/discovery.js +32 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/loader.d.ts +9 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +245 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/state/request-intake.d.ts +26 -0
- package/dist/state/request-intake.d.ts.map +1 -0
- package/dist/state/request-intake.js +74 -0
- package/dist/state/request-intake.js.map +1 -0
- package/dist/state/state-store.d.ts +146 -0
- package/dist/state/state-store.d.ts.map +1 -0
- package/dist/state/state-store.js +465 -0
- package/dist/state/state-store.js.map +1 -0
- package/dist/state/trace-store.d.ts +12 -0
- package/dist/state/trace-store.d.ts.map +1 -0
- package/dist/state/trace-store.js +24 -0
- package/dist/state/trace-store.js.map +1 -0
- package/dist/types/plugin.d.ts +93 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +4 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/web/index.d.ts +10 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +153 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/routes/activity.d.ts +6 -0
- package/dist/web/routes/activity.d.ts.map +1 -0
- package/dist/web/routes/activity.js +31 -0
- package/dist/web/routes/activity.js.map +1 -0
- package/dist/web/routes/auth.d.ts +6 -0
- package/dist/web/routes/auth.d.ts.map +1 -0
- package/dist/web/routes/auth.js +142 -0
- package/dist/web/routes/auth.js.map +1 -0
- package/dist/web/routes/config.d.ts +7 -0
- package/dist/web/routes/config.d.ts.map +1 -0
- package/dist/web/routes/config.js +204 -0
- package/dist/web/routes/config.js.map +1 -0
- package/dist/web/routes/content-types.d.ts +6 -0
- package/dist/web/routes/content-types.d.ts.map +1 -0
- package/dist/web/routes/content-types.js +29 -0
- package/dist/web/routes/content-types.js.map +1 -0
- package/dist/web/routes/discovery.d.ts +6 -0
- package/dist/web/routes/discovery.d.ts.map +1 -0
- package/dist/web/routes/discovery.js +111 -0
- package/dist/web/routes/discovery.js.map +1 -0
- package/dist/web/routes/library-import.d.ts +6 -0
- package/dist/web/routes/library-import.d.ts.map +1 -0
- package/dist/web/routes/library-import.js +120 -0
- package/dist/web/routes/library-import.js.map +1 -0
- package/dist/web/routes/library-management.d.ts +6 -0
- package/dist/web/routes/library-management.d.ts.map +1 -0
- package/dist/web/routes/library-management.js +120 -0
- package/dist/web/routes/library-management.js.map +1 -0
- package/dist/web/routes/library.d.ts +6 -0
- package/dist/web/routes/library.d.ts.map +1 -0
- package/dist/web/routes/library.js +28 -0
- package/dist/web/routes/library.js.map +1 -0
- package/dist/web/routes/oidc.d.ts +9 -0
- package/dist/web/routes/oidc.d.ts.map +1 -0
- package/dist/web/routes/oidc.js +203 -0
- package/dist/web/routes/oidc.js.map +1 -0
- package/dist/web/routes/requests.d.ts +6 -0
- package/dist/web/routes/requests.d.ts.map +1 -0
- package/dist/web/routes/requests.js +183 -0
- package/dist/web/routes/requests.js.map +1 -0
- package/dist/web/routes/sse.d.ts +6 -0
- package/dist/web/routes/sse.d.ts.map +1 -0
- package/dist/web/routes/sse.js +54 -0
- package/dist/web/routes/sse.js.map +1 -0
- package/package.json +33 -0
- package/package.json.license +2 -0
- package/src/auth/auth-service.ts +333 -0
- package/src/auth/http-server.ts +249 -0
- package/src/auth/jwt-secret.ts +65 -0
- package/src/auth/oidc/oidc-flow.ts +266 -0
- package/src/auth/oidc/oidc-provider-service.ts +174 -0
- package/src/auth/oidc/user-service.ts +227 -0
- package/src/auth/rate-limiter.ts +85 -0
- package/src/config/config-service.ts +434 -0
- package/src/config/validate.ts +153 -0
- package/src/content-types/registry.ts +194 -0
- package/src/datastore/db.ts +38 -0
- package/src/datastore/encrypt.ts +107 -0
- package/src/datastore/migrations.ts +367 -0
- package/src/datastore/storage.ts +83 -0
- package/src/events/event-bus.ts +180 -0
- package/src/events/spine.ts +87 -0
- package/src/events/trace.ts +38 -0
- package/src/host.ts +130 -0
- package/src/library/importer.ts +174 -0
- package/src/library/library-cache.ts +212 -0
- package/src/library/library-config.ts +22 -0
- package/src/library/library-scanner.ts +404 -0
- package/src/library/library-service.ts +282 -0
- package/src/library/metadata-matcher.ts +285 -0
- package/src/library/naming.ts +59 -0
- package/src/library/scan-controller.ts +490 -0
- package/src/library/source-resolver.ts +92 -0
- package/src/logging/logger.ts +115 -0
- package/src/main.ts +364 -0
- package/src/plugins/context.ts +138 -0
- package/src/plugins/discovery.ts +29 -0
- package/src/plugins/loader.ts +309 -0
- package/src/state/request-intake.ts +101 -0
- package/src/state/state-store.ts +621 -0
- package/src/state/trace-store.ts +27 -0
- package/src/types/plugin.ts +110 -0
- package/src/web/index.ts +182 -0
- package/src/web/routes/activity.ts +41 -0
- package/src/web/routes/auth.ts +170 -0
- package/src/web/routes/config.ts +282 -0
- package/src/web/routes/content-types.ts +42 -0
- package/src/web/routes/discovery.ts +156 -0
- package/src/web/routes/library-import.ts +196 -0
- package/src/web/routes/library-management.ts +140 -0
- package/src/web/routes/library.ts +46 -0
- package/src/web/routes/oidc.ts +260 -0
- package/src/web/routes/requests.ts +250 -0
- package/src/web/routes/sse.ts +92 -0
- package/tests/auth.test.ts +510 -0
- package/tests/config-route.test.ts +345 -0
- package/tests/config.test.ts +612 -0
- package/tests/content-types.test.ts +376 -0
- package/tests/datastore.test.ts +345 -0
- package/tests/e2e.test.ts +430 -0
- package/tests/event-bus.test.ts +925 -0
- package/tests/host.test.ts +449 -0
- package/tests/library-cache.test.ts +171 -0
- package/tests/library-scanner.test.ts +177 -0
- package/tests/library.test.ts +210 -0
- package/tests/loader-context.test.ts +644 -0
- package/tests/logger-jsonl.test.ts +154 -0
- package/tests/metadata-matcher.test.ts +198 -0
- package/tests/oidc.test.ts +218 -0
- package/tests/scan-controller.test.ts +328 -0
- package/tests/source-resolver.test.ts +100 -0
- package/tests/starr-video-integration.test.ts +153 -0
- package/tests/state-store.test.ts +700 -0
- package/tests/trace-history.test.ts +356 -0
- package/tsconfig.json +8 -0
- package/tsconfig.json.license +2 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
+
import Database from "better-sqlite3";
|
|
6
|
+
import { mkdtempSync, writeFileSync, existsSync, unlinkSync, rmdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
|
|
11
|
+
import { runMigrations, CORE_MIGRATIONS } from "../src/datastore/migrations.js";
|
|
12
|
+
import { loadEncryptionKey, resetEncryptionKey, encrypt, decrypt } from "../src/datastore/encrypt.js";
|
|
13
|
+
import { validateConfig, type ValidationResult } from "../src/config/validate.js";
|
|
14
|
+
import { ConfigService, SECRET_UNCHANGED_SENTINEL } from "../src/config/config-service.js";
|
|
15
|
+
import { createLogger } from "../src/logging/logger.js";
|
|
16
|
+
import type { ConfigSchema, ConfigField } from "../src/types/plugin.js";
|
|
17
|
+
|
|
18
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
function createTestDb(): Database.Database {
|
|
21
|
+
const db = new Database(":memory:");
|
|
22
|
+
db.pragma("journal_mode = WAL");
|
|
23
|
+
db.pragma("foreign_keys = ON");
|
|
24
|
+
return db;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const testLogger = createLogger("test", "silent");
|
|
28
|
+
|
|
29
|
+
function createTestConfigService(
|
|
30
|
+
db: Database.Database,
|
|
31
|
+
onReload?: (pluginId: string) => void,
|
|
32
|
+
): ConfigService {
|
|
33
|
+
return new ConfigService(db, testLogger, onReload);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── 7a. Validation Tests ──────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
describe("validateConfig", () => {
|
|
39
|
+
it("returns valid: false with error for missing required field", () => {
|
|
40
|
+
const schema: ConfigSchema<{ x: string }> = {
|
|
41
|
+
x: { type: "string", label: "X", required: true },
|
|
42
|
+
};
|
|
43
|
+
const result = validateConfig(schema, {});
|
|
44
|
+
expect(result.valid).toBe(false);
|
|
45
|
+
expect(result.errors).toHaveLength(1);
|
|
46
|
+
expect(result.errors[0].field).toBe("x");
|
|
47
|
+
expect(result.errors[0].message).toContain("required");
|
|
48
|
+
expect(result.values).toEqual({});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns valid: true when required field is present", () => {
|
|
52
|
+
const schema: ConfigSchema<{ x: string }> = {
|
|
53
|
+
x: { type: "string", label: "X", required: true },
|
|
54
|
+
};
|
|
55
|
+
const result = validateConfig(schema, { x: "hello" });
|
|
56
|
+
expect(result.valid).toBe(true);
|
|
57
|
+
expect(result.errors).toHaveLength(0);
|
|
58
|
+
expect(result.values.x).toBe("hello");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("applies default value when field is missing", () => {
|
|
62
|
+
const schema: ConfigSchema<{ port: number }> = {
|
|
63
|
+
port: { type: "number", label: "Port", default: 8080 },
|
|
64
|
+
};
|
|
65
|
+
const result = validateConfig(schema, {});
|
|
66
|
+
expect(result.valid).toBe(true);
|
|
67
|
+
expect(result.values.port).toBe(8080);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("does NOT apply default when user provides a value", () => {
|
|
71
|
+
const schema: ConfigSchema<{ port: number }> = {
|
|
72
|
+
port: { type: "number", label: "Port", default: 8080 },
|
|
73
|
+
};
|
|
74
|
+
const result = validateConfig(schema, { port: 9090 });
|
|
75
|
+
expect(result.valid).toBe(true);
|
|
76
|
+
expect(result.values.port).toBe(9090);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("coerces string '42' to number 42", () => {
|
|
80
|
+
const schema: ConfigSchema<{ val: number }> = {
|
|
81
|
+
val: { type: "number", label: "Val" },
|
|
82
|
+
};
|
|
83
|
+
const result = validateConfig(schema, { val: "42" });
|
|
84
|
+
expect(result.valid).toBe(true);
|
|
85
|
+
expect(result.values.val).toBe(42);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("rejects 'abc' for number field", () => {
|
|
89
|
+
const schema: ConfigSchema<{ val: number }> = {
|
|
90
|
+
val: { type: "number", label: "Val" },
|
|
91
|
+
};
|
|
92
|
+
const result = validateConfig(schema, { val: "abc" });
|
|
93
|
+
expect(result.valid).toBe(false);
|
|
94
|
+
expect(result.errors).toHaveLength(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("coerces 'true' and 'false' strings to booleans", () => {
|
|
98
|
+
const schema: ConfigSchema<{ a: boolean; b: boolean }> = {
|
|
99
|
+
a: { type: "boolean", label: "A" },
|
|
100
|
+
b: { type: "boolean", label: "B" },
|
|
101
|
+
};
|
|
102
|
+
const result = validateConfig(schema, { a: "true", b: "false" });
|
|
103
|
+
expect(result.valid).toBe(true);
|
|
104
|
+
expect(result.values.a).toBe(true);
|
|
105
|
+
expect(result.values.b).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("rejects enum value not in options", () => {
|
|
109
|
+
const schema: ConfigSchema<{ color: string }> = {
|
|
110
|
+
color: { type: "enum", label: "Color", options: ["red", "green", "blue"] },
|
|
111
|
+
};
|
|
112
|
+
const result = validateConfig(schema, { color: "yellow" });
|
|
113
|
+
expect(result.valid).toBe(false);
|
|
114
|
+
expect(result.errors).toHaveLength(1);
|
|
115
|
+
expect(result.errors[0].message).toContain("not in allowed options");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("accepts enum value in options", () => {
|
|
119
|
+
const schema: ConfigSchema<{ color: string }> = {
|
|
120
|
+
color: { type: "enum", label: "Color", options: ["red", "green", "blue"] },
|
|
121
|
+
};
|
|
122
|
+
const result = validateConfig(schema, { color: "green" });
|
|
123
|
+
expect(result.valid).toBe(true);
|
|
124
|
+
expect(result.values.color).toBe("green");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("accepts any string for enum with empty options", () => {
|
|
128
|
+
const schema: ConfigSchema<{ val: string }> = {
|
|
129
|
+
val: { type: "enum", label: "Val", options: [] },
|
|
130
|
+
};
|
|
131
|
+
const result = validateConfig(schema, { val: "anything" });
|
|
132
|
+
expect(result.valid).toBe(true);
|
|
133
|
+
expect(result.values.val).toBe("anything");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("validates URL type", () => {
|
|
137
|
+
const schema: ConfigSchema<{ url: string }> = {
|
|
138
|
+
url: { type: "url", label: "URL" },
|
|
139
|
+
};
|
|
140
|
+
const valid = validateConfig(schema, { url: "https://example.com" });
|
|
141
|
+
expect(valid.valid).toBe(true);
|
|
142
|
+
expect(valid.values.url).toBe("https://example.com");
|
|
143
|
+
|
|
144
|
+
const invalid = validateConfig(schema, { url: "not-a-url" });
|
|
145
|
+
expect(invalid.valid).toBe(false);
|
|
146
|
+
expect(invalid.errors).toHaveLength(1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("validates path type (non-empty string)", () => {
|
|
150
|
+
const schema: ConfigSchema<{ p: string }> = {
|
|
151
|
+
p: { type: "path", label: "Path" },
|
|
152
|
+
};
|
|
153
|
+
const valid = validateConfig(schema, { p: "/some/path" });
|
|
154
|
+
expect(valid.valid).toBe(true);
|
|
155
|
+
expect(valid.values.p).toBe("/some/path");
|
|
156
|
+
|
|
157
|
+
// Empty string is treated as "missing" per spec — optional field stores undefined
|
|
158
|
+
const empty = validateConfig(schema, { p: "" });
|
|
159
|
+
expect(empty.valid).toBe(true);
|
|
160
|
+
expect(empty.values.p).toBeUndefined();
|
|
161
|
+
|
|
162
|
+
const notString = validateConfig(schema, { p: 42 });
|
|
163
|
+
expect(notString.valid).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("validates secret type same as string", () => {
|
|
167
|
+
const schema: ConfigSchema<{ token: string }> = {
|
|
168
|
+
token: { type: "secret", label: "Token" },
|
|
169
|
+
};
|
|
170
|
+
const result = validateConfig(schema, { token: "my-secret" });
|
|
171
|
+
expect(result.valid).toBe(true);
|
|
172
|
+
expect(result.values.token).toBe("my-secret");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("collects multiple errors (not just first)", () => {
|
|
176
|
+
const schema: ConfigSchema<{ a: string; b: string; c: string }> = {
|
|
177
|
+
a: { type: "string", label: "A", required: true },
|
|
178
|
+
b: { type: "number", label: "B", required: true },
|
|
179
|
+
c: { type: "string", label: "C", required: true },
|
|
180
|
+
};
|
|
181
|
+
const result = validateConfig(schema, { b: "not-a-number" });
|
|
182
|
+
expect(result.valid).toBe(false);
|
|
183
|
+
// a missing, b invalid, c missing = 3 errors
|
|
184
|
+
expect(result.errors).toHaveLength(3);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("ignores unknown fields in input", () => {
|
|
188
|
+
const schema: ConfigSchema<{ name: string }> = {
|
|
189
|
+
name: { type: "string", label: "Name" },
|
|
190
|
+
};
|
|
191
|
+
const result = validateConfig(schema, { name: "hello", extra: "ignored" });
|
|
192
|
+
expect(result.valid).toBe(true);
|
|
193
|
+
expect(result.values.name).toBe("hello");
|
|
194
|
+
expect((result.values as Record<string, unknown>).extra).toBeUndefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("treats field.default: null as valid default", () => {
|
|
198
|
+
const schema: ConfigSchema<{ val: string | null }> = {
|
|
199
|
+
val: { type: "string", label: "Val", default: null },
|
|
200
|
+
};
|
|
201
|
+
const result = validateConfig(schema, {});
|
|
202
|
+
expect(result.valid).toBe(true);
|
|
203
|
+
expect(result.values.val).toBeNull();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("empty schema returns valid: true with empty values", () => {
|
|
207
|
+
const result = validateConfig({}, {});
|
|
208
|
+
expect(result.valid).toBe(true);
|
|
209
|
+
expect(result.values).toEqual({});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("optional field with no default stores undefined", () => {
|
|
213
|
+
const schema: ConfigSchema<{ name: string }> = {
|
|
214
|
+
name: { type: "string", label: "Name" },
|
|
215
|
+
};
|
|
216
|
+
const result = validateConfig(schema, {});
|
|
217
|
+
expect(result.valid).toBe(true);
|
|
218
|
+
expect(result.values.name).toBeUndefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("required field with default does not trigger error when missing", () => {
|
|
222
|
+
const schema: ConfigSchema<{ port: number }> = {
|
|
223
|
+
port: { type: "number", label: "Port", required: true, default: 8080 },
|
|
224
|
+
};
|
|
225
|
+
const result = validateConfig(schema, {});
|
|
226
|
+
expect(result.valid).toBe(true);
|
|
227
|
+
expect(result.values.port).toBe(8080);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("number rejects empty string", () => {
|
|
231
|
+
const schema: ConfigSchema<{ n: number }> = {
|
|
232
|
+
n: { type: "number", label: "N", required: true },
|
|
233
|
+
};
|
|
234
|
+
const result = validateConfig(schema, { n: "" });
|
|
235
|
+
expect(result.valid).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ── 7b. ConfigService Tests ────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
describe("ConfigService", () => {
|
|
242
|
+
let db: Database.Database;
|
|
243
|
+
let dataDir: string;
|
|
244
|
+
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
db = createTestDb();
|
|
247
|
+
runMigrations(db, CORE_MIGRATIONS, testLogger);
|
|
248
|
+
dataDir = mkdtempSync(path.join(tmpdir(), "starr-config-"));
|
|
249
|
+
resetEncryptionKey();
|
|
250
|
+
loadEncryptionKey(dataDir, testLogger);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
afterEach(() => {
|
|
254
|
+
db.close();
|
|
255
|
+
try {
|
|
256
|
+
const keyPath = path.join(dataDir, "starr.key");
|
|
257
|
+
if (existsSync(keyPath)) unlinkSync(keyPath);
|
|
258
|
+
rmdirSync(dataDir);
|
|
259
|
+
} catch {
|
|
260
|
+
// ignore cleanup failures
|
|
261
|
+
}
|
|
262
|
+
resetEncryptionKey();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("register on first call persists schema and returns defaults", () => {
|
|
266
|
+
const cs = createTestConfigService(db);
|
|
267
|
+
const schema: ConfigSchema<{ port: number }> = {
|
|
268
|
+
port: { type: "number", label: "Port", default: 8080 },
|
|
269
|
+
};
|
|
270
|
+
const values = cs.register("test-plugin", schema);
|
|
271
|
+
expect(values).toEqual({ port: 8080 });
|
|
272
|
+
|
|
273
|
+
// Verify it was persisted
|
|
274
|
+
const entry = cs.get("test-plugin");
|
|
275
|
+
expect(entry).toBeDefined();
|
|
276
|
+
expect(entry!.schema).toEqual(schema);
|
|
277
|
+
expect(entry!.values.port).toBe(8080);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("register on second call returns persisted values", () => {
|
|
281
|
+
const cs = createTestConfigService(db);
|
|
282
|
+
const schema: ConfigSchema<{ name: string }> = {
|
|
283
|
+
name: { type: "string", label: "Name", default: "default" },
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// First call
|
|
287
|
+
cs.register("test-plugin", schema);
|
|
288
|
+
|
|
289
|
+
// Second call (simulating restart) - should return persisted value
|
|
290
|
+
// But since we never updated, it should still be the default
|
|
291
|
+
const values = cs.register("test-plugin", schema);
|
|
292
|
+
expect(values).toEqual({ name: "default" });
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("register returns persisted values after update", () => {
|
|
296
|
+
const cs = createTestConfigService(db);
|
|
297
|
+
const schema: ConfigSchema<{ name: string }> = {
|
|
298
|
+
name: { type: "string", label: "Name" },
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
cs.register("test-plugin", schema);
|
|
302
|
+
cs.update("test-plugin", schema, { name: "updated" });
|
|
303
|
+
|
|
304
|
+
// Re-register (simulating restart) - should get persisted value
|
|
305
|
+
const values = cs.register("test-plugin", schema);
|
|
306
|
+
expect(values).toEqual({ name: "updated" });
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("register with required field missing marks needsConfiguration", () => {
|
|
310
|
+
const cs = createTestConfigService(db);
|
|
311
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
312
|
+
apiKey: { type: "secret", label: "API Key", required: true },
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
cs.register("test-plugin", schema);
|
|
316
|
+
|
|
317
|
+
expect(cs.needsConfiguration("test-plugin")).toBe(true);
|
|
318
|
+
const entry = cs.get("test-plugin");
|
|
319
|
+
expect(entry!.needsConfiguration).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("register with all required fields present does not need configuration", () => {
|
|
323
|
+
const cs = createTestConfigService(db);
|
|
324
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
325
|
+
apiKey: { type: "secret", label: "API Key", required: true },
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
cs.register("test-plugin", schema);
|
|
329
|
+
cs.update("test-plugin", schema, { apiKey: "sk-123" });
|
|
330
|
+
|
|
331
|
+
expect(cs.needsConfiguration("test-plugin")).toBe(false);
|
|
332
|
+
const entry = cs.get("test-plugin");
|
|
333
|
+
expect(entry!.needsConfiguration).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("update with valid input succeeds and persists", () => {
|
|
337
|
+
const cs = createTestConfigService(db);
|
|
338
|
+
const schema: ConfigSchema<{ port: number; host: string }> = {
|
|
339
|
+
port: { type: "number", label: "Port", default: 8080 },
|
|
340
|
+
host: { type: "string", label: "Host", default: "localhost" },
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
cs.register("test-plugin", schema);
|
|
344
|
+
const result = cs.update("test-plugin", schema, { port: 9090, host: "example.com" });
|
|
345
|
+
|
|
346
|
+
expect(result.valid).toBe(true);
|
|
347
|
+
expect(result.values.port).toBe(9090);
|
|
348
|
+
expect(result.values.host).toBe("example.com");
|
|
349
|
+
|
|
350
|
+
// Verify persisted
|
|
351
|
+
const entry = cs.get("test-plugin");
|
|
352
|
+
expect(entry!.values.port).toBe(9090);
|
|
353
|
+
expect(entry!.values.host).toBe("example.com");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("update with invalid input returns errors and does NOT persist", () => {
|
|
357
|
+
const cs = createTestConfigService(db);
|
|
358
|
+
const schema: ConfigSchema<{ port: number }> = {
|
|
359
|
+
port: { type: "number", label: "Port", required: true },
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
cs.register("test-plugin", schema);
|
|
363
|
+
|
|
364
|
+
// First set a valid value
|
|
365
|
+
cs.update("test-plugin", schema, { port: 8080 });
|
|
366
|
+
|
|
367
|
+
// Now try invalid
|
|
368
|
+
const result = cs.update("test-plugin", schema, { port: "not-a-number" });
|
|
369
|
+
expect(result.valid).toBe(false);
|
|
370
|
+
expect(result.errors).toHaveLength(1);
|
|
371
|
+
|
|
372
|
+
// Verify the good value is still there
|
|
373
|
+
const entry = cs.get("test-plugin");
|
|
374
|
+
expect(entry!.values.port).toBe(8080);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("update triggers the onReload callback on success", () => {
|
|
378
|
+
const reloaded: string[] = [];
|
|
379
|
+
const cs = createTestConfigService(db, (pluginId) => {
|
|
380
|
+
reloaded.push(pluginId);
|
|
381
|
+
});
|
|
382
|
+
const schema: ConfigSchema<{ port: number }> = {
|
|
383
|
+
port: { type: "number", label: "Port" },
|
|
384
|
+
};
|
|
385
|
+
cs.register("test-plugin", schema);
|
|
386
|
+
cs.update("test-plugin", schema, { port: 9090 });
|
|
387
|
+
expect(reloaded).toContain("test-plugin");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("update does NOT call onReload when validation fails", () => {
|
|
391
|
+
const reloaded: string[] = [];
|
|
392
|
+
const cs = createTestConfigService(db, (pluginId) => {
|
|
393
|
+
reloaded.push(pluginId);
|
|
394
|
+
});
|
|
395
|
+
const schema: ConfigSchema<{ port: number }> = {
|
|
396
|
+
port: { type: "number", label: "Port", required: true },
|
|
397
|
+
};
|
|
398
|
+
cs.register("test-plugin", schema);
|
|
399
|
+
|
|
400
|
+
// Attempt invalid update
|
|
401
|
+
cs.update("test-plugin", schema, { port: "bad" });
|
|
402
|
+
expect(reloaded).toHaveLength(0);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("get returns schema + values for a registered plugin", () => {
|
|
406
|
+
const cs = createTestConfigService(db);
|
|
407
|
+
const schema: ConfigSchema<{ name: string }> = {
|
|
408
|
+
name: { type: "string", label: "Name", default: "test" },
|
|
409
|
+
};
|
|
410
|
+
cs.register("test-plugin", schema);
|
|
411
|
+
cs.update("test-plugin", schema, { name: "hello" });
|
|
412
|
+
|
|
413
|
+
const entry = cs.get("test-plugin");
|
|
414
|
+
expect(entry).toBeDefined();
|
|
415
|
+
expect(entry!.schema).toEqual(schema);
|
|
416
|
+
expect(entry!.values.name).toBe("hello");
|
|
417
|
+
expect(entry!.updatedAt).toBeDefined();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("get returns undefined for unknown plugin", () => {
|
|
421
|
+
const cs = createTestConfigService(db);
|
|
422
|
+
const entry = cs.get("nonexistent");
|
|
423
|
+
expect(entry).toBeUndefined();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it("get masks secret values (returns sentinel, not plaintext)", () => {
|
|
427
|
+
const cs = createTestConfigService(db);
|
|
428
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
429
|
+
apiKey: { type: "secret", label: "API Key" },
|
|
430
|
+
};
|
|
431
|
+
cs.register("test-plugin", schema);
|
|
432
|
+
cs.update("test-plugin", schema, { apiKey: "my-secret-key" });
|
|
433
|
+
|
|
434
|
+
const entry = cs.get("test-plugin");
|
|
435
|
+
expect(entry!.values.apiKey).toBe(SECRET_UNCHANGED_SENTINEL);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("get returns needsConfiguration: true for plugins with missing required fields", () => {
|
|
439
|
+
const cs = createTestConfigService(db);
|
|
440
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
441
|
+
apiKey: { type: "secret", label: "API Key", required: true },
|
|
442
|
+
};
|
|
443
|
+
cs.register("test-plugin", schema);
|
|
444
|
+
|
|
445
|
+
const entry = cs.get("test-plugin");
|
|
446
|
+
expect(entry!.needsConfiguration).toBe(true);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("list returns all registered plugins", () => {
|
|
450
|
+
const cs = createTestConfigService(db);
|
|
451
|
+
const schemaA: ConfigSchema<{ name: string }> = {
|
|
452
|
+
name: { type: "string", label: "Name" },
|
|
453
|
+
};
|
|
454
|
+
const schemaB: ConfigSchema<{ port: number }> = {
|
|
455
|
+
port: { type: "number", label: "Port" },
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
cs.register("plugin-a", schemaA);
|
|
459
|
+
cs.register("plugin-b", schemaB);
|
|
460
|
+
|
|
461
|
+
const list = cs.list();
|
|
462
|
+
expect(list).toHaveLength(2);
|
|
463
|
+
expect(list.map((s) => s.pluginId)).toEqual(["plugin-a", "plugin-b"]);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("secret encryption round-trip: update with secret value, get returns masked, update with sentinel preserves original", () => {
|
|
467
|
+
const cs = createTestConfigService(db);
|
|
468
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
469
|
+
apiKey: { type: "secret", label: "API Key" },
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
cs.register("test-plugin", schema);
|
|
473
|
+
cs.update("test-plugin", schema, { apiKey: "my-secret-key" });
|
|
474
|
+
|
|
475
|
+
// get returns masked
|
|
476
|
+
const entry1 = cs.get("test-plugin");
|
|
477
|
+
expect(entry1!.values.apiKey).toBe(SECRET_UNCHANGED_SENTINEL);
|
|
478
|
+
|
|
479
|
+
// update with sentinel preserves original secret
|
|
480
|
+
cs.update("test-plugin", schema, { apiKey: SECRET_UNCHANGED_SENTINEL });
|
|
481
|
+
|
|
482
|
+
// register again (simulating restart) - should still have the original secret
|
|
483
|
+
const values = cs.register("test-plugin", schema);
|
|
484
|
+
expect(values).toEqual({ apiKey: "my-secret-key" });
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("secret in database is encrypted (not plaintext)", () => {
|
|
488
|
+
const cs = createTestConfigService(db);
|
|
489
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
490
|
+
apiKey: { type: "secret", label: "API Key" },
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
cs.register("test-plugin", schema);
|
|
494
|
+
cs.update("test-plugin", schema, { apiKey: "my-secret-key" });
|
|
495
|
+
|
|
496
|
+
// Directly query the DB
|
|
497
|
+
const row = db.prepare("SELECT \"values\" FROM plugin_config WHERE plugin_id = ?").get("test-plugin") as { values: string } | undefined;
|
|
498
|
+
expect(row).toBeDefined();
|
|
499
|
+
|
|
500
|
+
const parsed = JSON.parse(row!.values);
|
|
501
|
+
// The secret field should NOT be the plaintext
|
|
502
|
+
expect(parsed.apiKey).not.toBe("my-secret-key");
|
|
503
|
+
// It should be a base64 string (encrypted blob)
|
|
504
|
+
expect(typeof parsed.apiKey).toBe("string");
|
|
505
|
+
expect(parsed.apiKey.length).toBeGreaterThan(0);
|
|
506
|
+
|
|
507
|
+
// Verify we can decrypt it
|
|
508
|
+
const decrypted = decrypt(parsed.apiKey);
|
|
509
|
+
expect(decrypted).toBe("my-secret-key");
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("schema changes between register calls are handled (new required field)", () => {
|
|
513
|
+
const cs = createTestConfigService(db);
|
|
514
|
+
|
|
515
|
+
// First registration with simple schema
|
|
516
|
+
const schema1: ConfigSchema<{ name: string }> = {
|
|
517
|
+
name: { type: "string", label: "Name", default: "test" },
|
|
518
|
+
};
|
|
519
|
+
cs.register("test-plugin", schema1);
|
|
520
|
+
|
|
521
|
+
// Update with a value
|
|
522
|
+
cs.update("test-plugin", schema1, { name: "original" });
|
|
523
|
+
|
|
524
|
+
// Now simulate a plugin upgrade with a new required field
|
|
525
|
+
const schema2: ConfigSchema<{ name: string; apiKey: string }> = {
|
|
526
|
+
name: { type: "string", label: "Name", default: "test" },
|
|
527
|
+
apiKey: { type: "secret", label: "API Key", required: true },
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// Re-register with new schema
|
|
531
|
+
const values = cs.register("test-plugin", schema2);
|
|
532
|
+
|
|
533
|
+
// The old value "original" should be preserved for name
|
|
534
|
+
// But apiKey is required with no default → needs-configuration
|
|
535
|
+
expect(values).toBeDefined();
|
|
536
|
+
expect(cs.needsConfiguration("test-plugin")).toBe(true);
|
|
537
|
+
|
|
538
|
+
// The get should also reflect that
|
|
539
|
+
const entry = cs.get("test-plugin");
|
|
540
|
+
expect(entry!.needsConfiguration).toBe(true);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("update with new secret value overwrites the old one", () => {
|
|
544
|
+
const cs = createTestConfigService(db);
|
|
545
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
546
|
+
apiKey: { type: "secret", label: "API Key" },
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
cs.register("test-plugin", schema);
|
|
550
|
+
cs.update("test-plugin", schema, { apiKey: "old-secret" });
|
|
551
|
+
cs.update("test-plugin", schema, { apiKey: "new-secret" });
|
|
552
|
+
|
|
553
|
+
const values = cs.register("test-plugin", schema);
|
|
554
|
+
expect(values).toEqual({ apiKey: "new-secret" });
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it("list returns needsConfiguration status", () => {
|
|
558
|
+
const cs = createTestConfigService(db);
|
|
559
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
560
|
+
apiKey: { type: "secret", label: "API Key", required: true },
|
|
561
|
+
};
|
|
562
|
+
cs.register("plugin-a", schema);
|
|
563
|
+
cs.update("plugin-a", schema, { apiKey: "set" });
|
|
564
|
+
|
|
565
|
+
cs.register("plugin-b", schema); // missing required
|
|
566
|
+
|
|
567
|
+
const list = cs.list();
|
|
568
|
+
const a = list.find((s) => s.pluginId === "plugin-a");
|
|
569
|
+
const b = list.find((s) => s.pluginId === "plugin-b");
|
|
570
|
+
expect(a!.needsConfiguration).toBe(false);
|
|
571
|
+
expect(b!.needsConfiguration).toBe(true);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("getDecryptedValues returns undefined for unknown plugin", () => {
|
|
575
|
+
const cs = createTestConfigService(db);
|
|
576
|
+
const result = cs.getDecryptedValues("nonexistent");
|
|
577
|
+
expect(result).toBeUndefined();
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it("getDecryptedValues returns decrypted secret values", () => {
|
|
581
|
+
const cs = createTestConfigService(db);
|
|
582
|
+
const schema: ConfigSchema<{ apiKey: string }> = {
|
|
583
|
+
apiKey: { type: "secret", label: "API Key" },
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
cs.register("test-plugin", schema);
|
|
587
|
+
cs.update("test-plugin", schema, { apiKey: "my-secret-key" });
|
|
588
|
+
|
|
589
|
+
const decrypted = cs.getDecryptedValues("test-plugin");
|
|
590
|
+
expect(decrypted).toBeDefined();
|
|
591
|
+
expect(decrypted!.apiKey).toBe("my-secret-key");
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it("getDecryptedValues returns non-secret fields as stored", () => {
|
|
595
|
+
const cs = createTestConfigService(db);
|
|
596
|
+
const schema: ConfigSchema<{ endpoint: string; apiKey: string }> = {
|
|
597
|
+
endpoint: { type: "url", label: "Endpoint" },
|
|
598
|
+
apiKey: { type: "secret", label: "API Key" },
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
cs.register("test-plugin", schema);
|
|
602
|
+
cs.update("test-plugin", schema, {
|
|
603
|
+
endpoint: "http://jackett:9117",
|
|
604
|
+
apiKey: "secret-123",
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const decrypted = cs.getDecryptedValues("test-plugin");
|
|
608
|
+
expect(decrypted).toBeDefined();
|
|
609
|
+
expect(decrypted!.endpoint).toBe("http://jackett:9117");
|
|
610
|
+
expect(decrypted!.apiKey).toBe("secret-123");
|
|
611
|
+
});
|
|
612
|
+
});
|