@enactprotocol/shared 1.2.13 → 2.0.0
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.md +44 -0
- package/package.json +16 -58
- package/src/config.ts +476 -0
- package/src/constants.ts +36 -0
- package/src/execution/command.ts +314 -0
- package/src/execution/index.ts +73 -0
- package/src/execution/runtime.ts +308 -0
- package/src/execution/types.ts +379 -0
- package/src/execution/validation.ts +508 -0
- package/src/index.ts +237 -30
- package/src/manifest/index.ts +36 -0
- package/src/manifest/loader.ts +187 -0
- package/src/manifest/parser.ts +173 -0
- package/src/manifest/validator.ts +309 -0
- package/src/paths.ts +108 -0
- package/src/registry.ts +219 -0
- package/src/resolver.ts +345 -0
- package/src/types/index.ts +30 -0
- package/src/types/manifest.ts +255 -0
- package/src/types.ts +5 -188
- package/src/utils/fs.ts +281 -0
- package/src/utils/logger.ts +270 -59
- package/src/utils/version.ts +304 -36
- package/tests/config.test.ts +515 -0
- package/tests/execution/command.test.ts +317 -0
- package/tests/execution/validation.test.ts +384 -0
- package/tests/fixtures/invalid-tool.yaml +4 -0
- package/tests/fixtures/valid-tool.md +62 -0
- package/tests/fixtures/valid-tool.yaml +40 -0
- package/tests/index.test.ts +8 -0
- package/tests/manifest/loader.test.ts +291 -0
- package/tests/manifest/parser.test.ts +345 -0
- package/tests/manifest/validator.test.ts +394 -0
- package/tests/manifest-types.test.ts +358 -0
- package/tests/paths.test.ts +153 -0
- package/tests/registry.test.ts +231 -0
- package/tests/resolver.test.ts +272 -0
- package/tests/utils/fs.test.ts +388 -0
- package/tests/utils/logger.test.ts +480 -0
- package/tests/utils/version.test.ts +390 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/LocalToolResolver.d.ts +0 -84
- package/dist/LocalToolResolver.js +0 -353
- package/dist/api/enact-api.d.ts +0 -130
- package/dist/api/enact-api.js +0 -428
- package/dist/api/index.d.ts +0 -2
- package/dist/api/index.js +0 -2
- package/dist/api/types.d.ts +0 -103
- package/dist/api/types.js +0 -1
- package/dist/constants.d.ts +0 -7
- package/dist/constants.js +0 -10
- package/dist/core/DaggerExecutionProvider.d.ts +0 -169
- package/dist/core/DaggerExecutionProvider.js +0 -1029
- package/dist/core/DirectExecutionProvider.d.ts +0 -23
- package/dist/core/DirectExecutionProvider.js +0 -406
- package/dist/core/EnactCore.d.ts +0 -162
- package/dist/core/EnactCore.js +0 -597
- package/dist/core/NativeExecutionProvider.d.ts +0 -9
- package/dist/core/NativeExecutionProvider.js +0 -16
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.js +0 -3
- package/dist/exec/index.d.ts +0 -3
- package/dist/exec/index.js +0 -3
- package/dist/exec/logger.d.ts +0 -11
- package/dist/exec/logger.js +0 -57
- package/dist/exec/validate.d.ts +0 -5
- package/dist/exec/validate.js +0 -167
- package/dist/index.d.ts +0 -21
- package/dist/index.js +0 -25
- package/dist/lib/enact-direct.d.ts +0 -150
- package/dist/lib/enact-direct.js +0 -159
- package/dist/lib/index.d.ts +0 -1
- package/dist/lib/index.js +0 -1
- package/dist/security/index.d.ts +0 -3
- package/dist/security/index.js +0 -3
- package/dist/security/security.d.ts +0 -23
- package/dist/security/security.js +0 -137
- package/dist/security/sign.d.ts +0 -103
- package/dist/security/sign.js +0 -666
- package/dist/security/verification-enforcer.d.ts +0 -53
- package/dist/security/verification-enforcer.js +0 -204
- package/dist/services/McpCoreService.d.ts +0 -98
- package/dist/services/McpCoreService.js +0 -124
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/types.d.ts +0 -132
- package/dist/types.js +0 -3
- package/dist/utils/config.d.ts +0 -111
- package/dist/utils/config.js +0 -342
- package/dist/utils/env-loader.d.ts +0 -54
- package/dist/utils/env-loader.js +0 -270
- package/dist/utils/help.d.ts +0 -36
- package/dist/utils/help.js +0 -248
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.js +0 -7
- package/dist/utils/logger.d.ts +0 -35
- package/dist/utils/logger.js +0 -75
- package/dist/utils/silent-monitor.d.ts +0 -67
- package/dist/utils/silent-monitor.js +0 -242
- package/dist/utils/timeout.d.ts +0 -5
- package/dist/utils/timeout.js +0 -23
- package/dist/utils/version.d.ts +0 -4
- package/dist/utils/version.js +0 -35
- package/dist/web/env-manager-server.d.ts +0 -29
- package/dist/web/env-manager-server.js +0 -367
- package/dist/web/index.d.ts +0 -1
- package/dist/web/index.js +0 -1
- package/src/LocalToolResolver.ts +0 -424
- package/src/api/enact-api.ts +0 -604
- package/src/api/index.ts +0 -2
- package/src/api/types.ts +0 -114
- package/src/core/DaggerExecutionProvider.ts +0 -1357
- package/src/core/DirectExecutionProvider.ts +0 -484
- package/src/core/EnactCore.ts +0 -847
- package/src/core/index.ts +0 -3
- package/src/exec/index.ts +0 -3
- package/src/exec/logger.ts +0 -63
- package/src/exec/validate.ts +0 -238
- package/src/lib/enact-direct.ts +0 -254
- package/src/lib/index.ts +0 -1
- package/src/services/McpCoreService.ts +0 -201
- package/src/services/index.ts +0 -1
- package/src/utils/config.ts +0 -438
- package/src/utils/env-loader.ts +0 -370
- package/src/utils/help.ts +0 -257
- package/src/utils/index.ts +0 -7
- package/src/utils/silent-monitor.ts +0 -328
- package/src/utils/timeout.ts +0 -26
- package/src/web/env-manager-server.ts +0 -465
- package/src/web/index.ts +0 -1
- package/src/web/static/app.js +0 -663
- package/src/web/static/index.html +0 -117
- package/src/web/static/style.css +0 -291
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_CONFIG,
|
|
8
|
+
type EnactConfig,
|
|
9
|
+
// Legacy aliases for backwards compatibility
|
|
10
|
+
addTrustedAuditor,
|
|
11
|
+
addTrustedIdentity,
|
|
12
|
+
configExists,
|
|
13
|
+
emailToProviderIdentity,
|
|
14
|
+
getConfigValue,
|
|
15
|
+
getMinimumAttestations,
|
|
16
|
+
getTrustPolicy,
|
|
17
|
+
getTrustedAuditors,
|
|
18
|
+
getTrustedIdentities,
|
|
19
|
+
isAuditorTrusted,
|
|
20
|
+
isIdentityTrusted,
|
|
21
|
+
loadConfig,
|
|
22
|
+
removeTrustedAuditor,
|
|
23
|
+
removeTrustedIdentity,
|
|
24
|
+
resetConfig,
|
|
25
|
+
saveConfig,
|
|
26
|
+
setConfigValue,
|
|
27
|
+
} from "../src/config";
|
|
28
|
+
|
|
29
|
+
// Use a test-specific home directory to avoid affecting real config
|
|
30
|
+
const TEST_HOME = join(import.meta.dir, "fixtures", "config-test-home");
|
|
31
|
+
|
|
32
|
+
describe("configuration manager", () => {
|
|
33
|
+
beforeAll(() => {
|
|
34
|
+
// Mock homedir to use test directory
|
|
35
|
+
// We need to mock the module-level function
|
|
36
|
+
// For this test, we'll manipulate the files directly and test the logic
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
// Clean up test directory before each test
|
|
41
|
+
if (existsSync(TEST_HOME)) {
|
|
42
|
+
rmSync(TEST_HOME, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterAll(() => {
|
|
47
|
+
// Clean up test directory
|
|
48
|
+
if (existsSync(TEST_HOME)) {
|
|
49
|
+
rmSync(TEST_HOME, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("DEFAULT_CONFIG", () => {
|
|
54
|
+
test("has all required top-level keys", () => {
|
|
55
|
+
expect(DEFAULT_CONFIG.version).toBeDefined();
|
|
56
|
+
expect(DEFAULT_CONFIG.trust).toBeDefined();
|
|
57
|
+
expect(DEFAULT_CONFIG.cache).toBeDefined();
|
|
58
|
+
expect(DEFAULT_CONFIG.execution).toBeDefined();
|
|
59
|
+
expect(DEFAULT_CONFIG.registry).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("has sensible default values", () => {
|
|
63
|
+
expect(DEFAULT_CONFIG.trust?.policy).toBe("prompt");
|
|
64
|
+
expect(DEFAULT_CONFIG.trust?.minimum_attestations).toBe(1);
|
|
65
|
+
expect(DEFAULT_CONFIG.cache?.maxSizeMb).toBe(1024);
|
|
66
|
+
expect(DEFAULT_CONFIG.execution?.defaultTimeout).toBe("30s");
|
|
67
|
+
expect(DEFAULT_CONFIG.registry?.url).toBe("https://enact.tools");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("loadConfig", () => {
|
|
72
|
+
test("returns default config if file does not exist", () => {
|
|
73
|
+
// The actual homedir doesn't have config, or we test with mock
|
|
74
|
+
const config = loadConfig();
|
|
75
|
+
expect(config).toBeDefined();
|
|
76
|
+
expect(config.version).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("returns default config structure", () => {
|
|
80
|
+
const config = loadConfig();
|
|
81
|
+
expect(config.trust).toBeDefined();
|
|
82
|
+
expect(config.cache).toBeDefined();
|
|
83
|
+
expect(config.execution).toBeDefined();
|
|
84
|
+
expect(config.registry).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("saveConfig", () => {
|
|
89
|
+
test("creates ~/.enact/ directory if needed", () => {
|
|
90
|
+
const config: EnactConfig = { ...DEFAULT_CONFIG };
|
|
91
|
+
|
|
92
|
+
// This will create in the real home directory
|
|
93
|
+
// For integration test purposes
|
|
94
|
+
saveConfig(config);
|
|
95
|
+
|
|
96
|
+
// Verify config was saved
|
|
97
|
+
expect(configExists()).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("writes valid YAML", () => {
|
|
101
|
+
const config: EnactConfig = {
|
|
102
|
+
version: "1.0.0",
|
|
103
|
+
trust: {
|
|
104
|
+
auditors: ["github:alice"],
|
|
105
|
+
policy: "require_attestation",
|
|
106
|
+
},
|
|
107
|
+
cache: {
|
|
108
|
+
maxSizeMb: 2048,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
saveConfig(config);
|
|
113
|
+
|
|
114
|
+
// Reload and verify
|
|
115
|
+
const loaded = loadConfig();
|
|
116
|
+
expect(loaded.trust?.auditors).toContain("github:alice");
|
|
117
|
+
expect(loaded.trust?.policy).toBe("require_attestation");
|
|
118
|
+
expect(loaded.cache?.maxSizeMb).toBe(2048);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("preserves all config fields", () => {
|
|
122
|
+
const config: EnactConfig = {
|
|
123
|
+
version: "2.0.0",
|
|
124
|
+
trust: {
|
|
125
|
+
auditors: ["github:aud1", "google:aud2"],
|
|
126
|
+
policy: "require_attestation",
|
|
127
|
+
minimum_attestations: 2,
|
|
128
|
+
},
|
|
129
|
+
cache: {
|
|
130
|
+
maxSizeMb: 512,
|
|
131
|
+
ttlSeconds: 3600,
|
|
132
|
+
},
|
|
133
|
+
execution: {
|
|
134
|
+
defaultTimeout: "1m",
|
|
135
|
+
verbose: true,
|
|
136
|
+
},
|
|
137
|
+
registry: {
|
|
138
|
+
url: "https://custom.registry.com",
|
|
139
|
+
authTokenRef: "token-ref",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
saveConfig(config);
|
|
144
|
+
const loaded = loadConfig();
|
|
145
|
+
|
|
146
|
+
expect(loaded.version).toBe("2.0.0");
|
|
147
|
+
expect(loaded.trust?.auditors).toEqual(["github:aud1", "google:aud2"]);
|
|
148
|
+
expect(loaded.trust?.policy).toBe("require_attestation");
|
|
149
|
+
expect(loaded.trust?.minimum_attestations).toBe(2);
|
|
150
|
+
expect(loaded.cache?.maxSizeMb).toBe(512);
|
|
151
|
+
expect(loaded.cache?.ttlSeconds).toBe(3600);
|
|
152
|
+
expect(loaded.execution?.defaultTimeout).toBe("1m");
|
|
153
|
+
expect(loaded.execution?.verbose).toBe(true);
|
|
154
|
+
expect(loaded.registry?.url).toBe("https://custom.registry.com");
|
|
155
|
+
expect(loaded.registry?.authTokenRef).toBe("token-ref");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("getConfigValue", () => {
|
|
160
|
+
test("returns default for missing keys", () => {
|
|
161
|
+
const value = getConfigValue("nonexistent.key", "default");
|
|
162
|
+
expect(value).toBe("default");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("returns value for existing top-level key", () => {
|
|
166
|
+
saveConfig({ ...DEFAULT_CONFIG, version: "test-version" });
|
|
167
|
+
const value = getConfigValue("version", "fallback");
|
|
168
|
+
expect(value).toBe("test-version");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("returns value for nested key", () => {
|
|
172
|
+
saveConfig({
|
|
173
|
+
...DEFAULT_CONFIG,
|
|
174
|
+
trust: { policy: "require_attestation" },
|
|
175
|
+
});
|
|
176
|
+
const value = getConfigValue("trust.policy", "fallback");
|
|
177
|
+
expect(value).toBe("require_attestation");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("returns value for deeply nested key", () => {
|
|
181
|
+
saveConfig({
|
|
182
|
+
...DEFAULT_CONFIG,
|
|
183
|
+
cache: { maxSizeMb: 999 },
|
|
184
|
+
});
|
|
185
|
+
const value = getConfigValue("cache.maxSizeMb", 0);
|
|
186
|
+
expect(value).toBe(999);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("returns default for partially missing path", () => {
|
|
190
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
191
|
+
const value = getConfigValue("trust.nonexistent.deep", "default");
|
|
192
|
+
expect(value).toBe("default");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("setConfigValue", () => {
|
|
197
|
+
test("sets top-level value and persists", () => {
|
|
198
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
199
|
+
setConfigValue("version", "new-version");
|
|
200
|
+
|
|
201
|
+
const loaded = loadConfig();
|
|
202
|
+
expect(loaded.version).toBe("new-version");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("sets nested value and persists", () => {
|
|
206
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
207
|
+
setConfigValue("trust.policy", "allow");
|
|
208
|
+
|
|
209
|
+
const loaded = loadConfig();
|
|
210
|
+
expect(loaded.trust?.policy).toBe("allow");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("creates intermediate objects if needed", () => {
|
|
214
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
215
|
+
setConfigValue("cache.newField", "newValue");
|
|
216
|
+
|
|
217
|
+
const value = getConfigValue("cache.newField", "fallback");
|
|
218
|
+
expect(value).toBe("newValue");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("preserves other values when setting", () => {
|
|
222
|
+
saveConfig({
|
|
223
|
+
...DEFAULT_CONFIG,
|
|
224
|
+
trust: { auditors: ["github:alice"], policy: "prompt" },
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
setConfigValue("trust.policy", "require_attestation");
|
|
228
|
+
|
|
229
|
+
const loaded = loadConfig();
|
|
230
|
+
expect(loaded.trust?.policy).toBe("require_attestation");
|
|
231
|
+
expect(loaded.trust?.auditors).toContain("github:alice");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("throws error for empty key", () => {
|
|
235
|
+
expect(() => setConfigValue("", "value")).toThrow("Invalid configuration key");
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("resetConfig", () => {
|
|
240
|
+
test("resets config to defaults", () => {
|
|
241
|
+
// First, set a custom config
|
|
242
|
+
saveConfig({
|
|
243
|
+
version: "custom",
|
|
244
|
+
trust: { policy: "allow" },
|
|
245
|
+
cache: { maxSizeMb: 1 },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Reset
|
|
249
|
+
resetConfig();
|
|
250
|
+
|
|
251
|
+
// Verify defaults restored
|
|
252
|
+
const loaded = loadConfig();
|
|
253
|
+
expect(loaded.version).toBe(DEFAULT_CONFIG.version);
|
|
254
|
+
expect(loaded.trust?.policy).toBe(DEFAULT_CONFIG.trust?.policy);
|
|
255
|
+
expect(loaded.cache?.maxSizeMb).toBe(DEFAULT_CONFIG.cache?.maxSizeMb);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("configExists", () => {
|
|
260
|
+
test("returns true after saving config", () => {
|
|
261
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
262
|
+
expect(configExists()).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("YAML format", () => {
|
|
267
|
+
test("config file is valid YAML", () => {
|
|
268
|
+
saveConfig({
|
|
269
|
+
...DEFAULT_CONFIG,
|
|
270
|
+
trust: { auditors: ["github:test"] },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Read raw file and parse with yaml
|
|
274
|
+
const configPath = join(homedir(), ".enact", "config.yaml");
|
|
275
|
+
const content = readFileSync(configPath, "utf-8");
|
|
276
|
+
|
|
277
|
+
// Should not throw
|
|
278
|
+
const parsed = yaml.load(content);
|
|
279
|
+
expect(parsed).toBeDefined();
|
|
280
|
+
expect(typeof parsed).toBe("object");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("config file has readable formatting", () => {
|
|
284
|
+
saveConfig({
|
|
285
|
+
...DEFAULT_CONFIG,
|
|
286
|
+
trust: {
|
|
287
|
+
auditors: ["github:alice", "github:bob"],
|
|
288
|
+
policy: "require_attestation",
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const configPath = join(homedir(), ".enact", "config.yaml");
|
|
293
|
+
const content = readFileSync(configPath, "utf-8");
|
|
294
|
+
|
|
295
|
+
// Should have proper indentation (not be on one line)
|
|
296
|
+
expect(content).toContain("\n");
|
|
297
|
+
expect(content).toContain("trust:");
|
|
298
|
+
expect(content).toContain("auditors:");
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe("error handling", () => {
|
|
303
|
+
test("loadConfig handles malformed YAML gracefully", () => {
|
|
304
|
+
// Save valid config first
|
|
305
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
306
|
+
|
|
307
|
+
// Corrupt the file
|
|
308
|
+
const configPath = join(homedir(), ".enact", "config.yaml");
|
|
309
|
+
writeFileSync(configPath, "invalid: yaml: [unclosed", "utf-8");
|
|
310
|
+
|
|
311
|
+
// Should return defaults, not throw
|
|
312
|
+
const config = loadConfig();
|
|
313
|
+
expect(config).toBeDefined();
|
|
314
|
+
expect(config.version).toBe(DEFAULT_CONFIG.version);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("loadConfig handles empty file gracefully", () => {
|
|
318
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
319
|
+
|
|
320
|
+
const configPath = join(homedir(), ".enact", "config.yaml");
|
|
321
|
+
writeFileSync(configPath, "", "utf-8");
|
|
322
|
+
|
|
323
|
+
const config = loadConfig();
|
|
324
|
+
expect(config).toBeDefined();
|
|
325
|
+
expect(config.version).toBe(DEFAULT_CONFIG.version);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("loadConfig handles null content gracefully", () => {
|
|
329
|
+
saveConfig({ ...DEFAULT_CONFIG });
|
|
330
|
+
|
|
331
|
+
const configPath = join(homedir(), ".enact", "config.yaml");
|
|
332
|
+
writeFileSync(configPath, "null", "utf-8");
|
|
333
|
+
|
|
334
|
+
const config = loadConfig();
|
|
335
|
+
expect(config).toBeDefined();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe("merge behavior", () => {
|
|
340
|
+
test("merges partial config with defaults", () => {
|
|
341
|
+
// Save config with only some fields
|
|
342
|
+
const configPath = join(homedir(), ".enact", "config.yaml");
|
|
343
|
+
mkdirSync(join(homedir(), ".enact"), { recursive: true });
|
|
344
|
+
writeFileSync(configPath, yaml.dump({ trust: { policy: "require_attestation" } }), "utf-8");
|
|
345
|
+
|
|
346
|
+
const loaded = loadConfig();
|
|
347
|
+
|
|
348
|
+
// Custom value should be preserved
|
|
349
|
+
expect(loaded.trust?.policy).toBe("require_attestation");
|
|
350
|
+
|
|
351
|
+
// Default values should be filled in
|
|
352
|
+
expect(loaded.cache?.maxSizeMb).toBe(DEFAULT_CONFIG.cache?.maxSizeMb);
|
|
353
|
+
expect(loaded.execution?.defaultTimeout).toBe(DEFAULT_CONFIG.execution?.defaultTimeout);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe("Trust Management", () => {
|
|
358
|
+
describe("Unified Identity Model", () => {
|
|
359
|
+
test("getTrustedIdentities returns empty array by default", () => {
|
|
360
|
+
resetConfig();
|
|
361
|
+
const identities = getTrustedIdentities();
|
|
362
|
+
expect(identities).toEqual([]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("addTrustedIdentity adds identity and returns true", () => {
|
|
366
|
+
resetConfig();
|
|
367
|
+
const added = addTrustedIdentity("github:alice");
|
|
368
|
+
expect(added).toBe(true);
|
|
369
|
+
|
|
370
|
+
const identities = getTrustedIdentities();
|
|
371
|
+
expect(identities).toContain("github:alice");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("addTrustedIdentity returns false for duplicate", () => {
|
|
375
|
+
resetConfig();
|
|
376
|
+
addTrustedIdentity("github:alice");
|
|
377
|
+
const added = addTrustedIdentity("github:alice");
|
|
378
|
+
expect(added).toBe(false);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("removeTrustedIdentity removes identity and returns true", () => {
|
|
382
|
+
resetConfig();
|
|
383
|
+
addTrustedIdentity("github:alice");
|
|
384
|
+
const removed = removeTrustedIdentity("github:alice");
|
|
385
|
+
expect(removed).toBe(true);
|
|
386
|
+
|
|
387
|
+
const identities = getTrustedIdentities();
|
|
388
|
+
expect(identities).not.toContain("github:alice");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("removeTrustedIdentity returns false for non-existent identity", () => {
|
|
392
|
+
resetConfig();
|
|
393
|
+
const removed = removeTrustedIdentity("github:nonexistent");
|
|
394
|
+
expect(removed).toBe(false);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("isIdentityTrusted checks exact match", () => {
|
|
398
|
+
resetConfig();
|
|
399
|
+
addTrustedIdentity("github:alice");
|
|
400
|
+
expect(isIdentityTrusted("github:alice")).toBe(true);
|
|
401
|
+
expect(isIdentityTrusted("github:bob")).toBe(false);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("isIdentityTrusted supports wildcard patterns", () => {
|
|
405
|
+
resetConfig();
|
|
406
|
+
addTrustedIdentity("github:my-org/*");
|
|
407
|
+
expect(isIdentityTrusted("github:my-org/alice")).toBe(true);
|
|
408
|
+
expect(isIdentityTrusted("github:my-org/bob")).toBe(true);
|
|
409
|
+
expect(isIdentityTrusted("github:other-org/alice")).toBe(false);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("isIdentityTrusted supports email wildcards", () => {
|
|
413
|
+
resetConfig();
|
|
414
|
+
addTrustedIdentity("*@company.com");
|
|
415
|
+
expect(isIdentityTrusted("alice@company.com")).toBe(true);
|
|
416
|
+
expect(isIdentityTrusted("bob@company.com")).toBe(true);
|
|
417
|
+
expect(isIdentityTrusted("alice@other.com")).toBe(false);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("Legacy Aliases (Backwards Compatibility)", () => {
|
|
422
|
+
test("getTrustedAuditors works as alias", () => {
|
|
423
|
+
resetConfig();
|
|
424
|
+
const auditors = getTrustedAuditors();
|
|
425
|
+
expect(auditors).toEqual([]);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("addTrustedAuditor works as alias", () => {
|
|
429
|
+
resetConfig();
|
|
430
|
+
const added = addTrustedAuditor("github:alice");
|
|
431
|
+
expect(added).toBe(true);
|
|
432
|
+
|
|
433
|
+
const auditors = getTrustedAuditors();
|
|
434
|
+
expect(auditors).toContain("github:alice");
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("removeTrustedAuditor works as alias", () => {
|
|
438
|
+
resetConfig();
|
|
439
|
+
addTrustedAuditor("github:alice");
|
|
440
|
+
const removed = removeTrustedAuditor("github:alice");
|
|
441
|
+
expect(removed).toBe(true);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("isAuditorTrusted works as alias", () => {
|
|
445
|
+
resetConfig();
|
|
446
|
+
addTrustedAuditor("github:alice");
|
|
447
|
+
expect(isAuditorTrusted("github:alice")).toBe(true);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe("Trust Policy", () => {
|
|
452
|
+
test("getTrustPolicy returns default policy", () => {
|
|
453
|
+
resetConfig();
|
|
454
|
+
const policy = getTrustPolicy();
|
|
455
|
+
expect(policy).toBe("prompt");
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test("getTrustPolicy returns configured policy", () => {
|
|
459
|
+
saveConfig({
|
|
460
|
+
...DEFAULT_CONFIG,
|
|
461
|
+
trust: { policy: "require_attestation" },
|
|
462
|
+
});
|
|
463
|
+
const policy = getTrustPolicy();
|
|
464
|
+
expect(policy).toBe("require_attestation");
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test("getMinimumAttestations returns default", () => {
|
|
468
|
+
resetConfig();
|
|
469
|
+
const min = getMinimumAttestations();
|
|
470
|
+
expect(min).toBe(1);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("getMinimumAttestations returns configured value", () => {
|
|
474
|
+
saveConfig({
|
|
475
|
+
...DEFAULT_CONFIG,
|
|
476
|
+
trust: { minimum_attestations: 3 },
|
|
477
|
+
});
|
|
478
|
+
const min = getMinimumAttestations();
|
|
479
|
+
expect(min).toBe(3);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
describe("emailToProviderIdentity", () => {
|
|
484
|
+
test("converts GitHub noreply email", () => {
|
|
485
|
+
const result = emailToProviderIdentity("alice@users.noreply.github.com");
|
|
486
|
+
expect(result).toBe("github:alice");
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("converts GitHub email", () => {
|
|
490
|
+
const result = emailToProviderIdentity("alice@github.com");
|
|
491
|
+
expect(result).toBe("github:alice");
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("converts Google email", () => {
|
|
495
|
+
const result = emailToProviderIdentity("alice@gmail.com");
|
|
496
|
+
expect(result).toBe("google:alice");
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("converts Microsoft email", () => {
|
|
500
|
+
const result = emailToProviderIdentity("alice@outlook.com");
|
|
501
|
+
expect(result).toBe("microsoft:alice");
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("converts GitHub workflow URL", () => {
|
|
505
|
+
const result = emailToProviderIdentity("https://github.com/my-org/my-workflow");
|
|
506
|
+
expect(result).toBe("github:my-org/my-workflow");
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("returns email as-is for unknown providers", () => {
|
|
510
|
+
const result = emailToProviderIdentity("alice@unknown.com");
|
|
511
|
+
expect(result).toBe("alice@unknown.com");
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
});
|