@vellumai/credential-executor 0.4.55
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/Dockerfile +55 -0
- package/bun.lock +37 -0
- package/package.json +32 -0
- package/src/__tests__/command-executor.test.ts +1333 -0
- package/src/__tests__/command-validator.test.ts +708 -0
- package/src/__tests__/command-workspace.test.ts +997 -0
- package/src/__tests__/grant-store.test.ts +467 -0
- package/src/__tests__/http-executor.test.ts +1251 -0
- package/src/__tests__/http-policy.test.ts +970 -0
- package/src/__tests__/local-materializers.test.ts +826 -0
- package/src/__tests__/managed-materializers.test.ts +961 -0
- package/src/__tests__/toolstore.test.ts +539 -0
- package/src/__tests__/transport.test.ts +388 -0
- package/src/audit/store.ts +188 -0
- package/src/commands/auth-adapters.ts +169 -0
- package/src/commands/executor.ts +840 -0
- package/src/commands/output-scan.ts +157 -0
- package/src/commands/profiles.ts +282 -0
- package/src/commands/validator.ts +438 -0
- package/src/commands/workspace.ts +512 -0
- package/src/grants/index.ts +17 -0
- package/src/grants/persistent-store.ts +247 -0
- package/src/grants/rpc-handlers.ts +269 -0
- package/src/grants/temporary-store.ts +219 -0
- package/src/http/audit.ts +84 -0
- package/src/http/executor.ts +540 -0
- package/src/http/path-template.ts +179 -0
- package/src/http/policy.ts +256 -0
- package/src/http/response-filter.ts +233 -0
- package/src/index.ts +106 -0
- package/src/main.ts +263 -0
- package/src/managed-main.ts +420 -0
- package/src/materializers/local.ts +300 -0
- package/src/materializers/managed-platform.ts +270 -0
- package/src/paths.ts +137 -0
- package/src/server.ts +636 -0
- package/src/subjects/local.ts +177 -0
- package/src/subjects/managed.ts +290 -0
- package/src/toolstore/integrity.ts +94 -0
- package/src/toolstore/manifest.ts +154 -0
- package/src/toolstore/publish.ts +342 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for CES grant stores.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Persistent store: initialization, duplicate prevention by canonical grant
|
|
6
|
+
* hash, fail-closed behavior on corrupt/unreadable files.
|
|
7
|
+
* - Temporary store: allow_once consumption, allow_10m expiry, allow_thread
|
|
8
|
+
* scoping by conversation ID, clearConversation cleanup.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
12
|
+
import {
|
|
13
|
+
existsSync,
|
|
14
|
+
mkdirSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
rmSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { randomUUID } from "node:crypto";
|
|
22
|
+
|
|
23
|
+
import { PersistentGrantStore, type PersistentGrant } from "../grants/persistent-store.js";
|
|
24
|
+
import { TemporaryGrantStore } from "../grants/temporary-store.js";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Test helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
function makeTmpDir(): string {
|
|
31
|
+
const dir = join(tmpdir(), `ces-grant-test-${randomUUID()}`);
|
|
32
|
+
mkdirSync(dir, { recursive: true });
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeGrant(overrides?: Partial<PersistentGrant>): PersistentGrant {
|
|
37
|
+
return {
|
|
38
|
+
id: overrides?.id ?? randomUUID(),
|
|
39
|
+
tool: overrides?.tool ?? "host_bash",
|
|
40
|
+
pattern: overrides?.pattern ?? "npm install",
|
|
41
|
+
scope: overrides?.scope ?? "/project",
|
|
42
|
+
createdAt: overrides?.createdAt ?? Date.now(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Persistent store
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
describe("PersistentGrantStore", () => {
|
|
51
|
+
let tmpDir: string;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
tmpDir = makeTmpDir();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("initialization", () => {
|
|
62
|
+
test("creates grants.json when file does not exist", () => {
|
|
63
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
64
|
+
store.init();
|
|
65
|
+
|
|
66
|
+
const filePath = join(tmpDir, "grants.json");
|
|
67
|
+
expect(existsSync(filePath)).toBe(true);
|
|
68
|
+
|
|
69
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
70
|
+
expect(data.version).toBe(1);
|
|
71
|
+
expect(data.grants).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("creates parent directory if missing", () => {
|
|
75
|
+
const nestedDir = join(tmpDir, "nested", "grants");
|
|
76
|
+
const store = new PersistentGrantStore(nestedDir);
|
|
77
|
+
store.init();
|
|
78
|
+
|
|
79
|
+
expect(existsSync(join(nestedDir, "grants.json"))).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("loads existing valid grants file", () => {
|
|
83
|
+
const grant = makeGrant();
|
|
84
|
+
writeFileSync(
|
|
85
|
+
join(tmpDir, "grants.json"),
|
|
86
|
+
JSON.stringify({ version: 1, grants: [grant] }, null, 2),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
90
|
+
store.init();
|
|
91
|
+
|
|
92
|
+
const grants = store.getAll();
|
|
93
|
+
expect(grants).toHaveLength(1);
|
|
94
|
+
expect(grants[0].id).toBe(grant.id);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("add and deduplication", () => {
|
|
99
|
+
test("adds a new grant and persists it", () => {
|
|
100
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
101
|
+
store.init();
|
|
102
|
+
|
|
103
|
+
const grant = makeGrant();
|
|
104
|
+
const added = store.add(grant);
|
|
105
|
+
expect(added).toBe(true);
|
|
106
|
+
|
|
107
|
+
// Verify persisted by creating a new store instance
|
|
108
|
+
const store2 = new PersistentGrantStore(tmpDir);
|
|
109
|
+
const grants = store2.getAll();
|
|
110
|
+
expect(grants).toHaveLength(1);
|
|
111
|
+
expect(grants[0].id).toBe(grant.id);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("deduplicates by canonical grant id", () => {
|
|
115
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
116
|
+
store.init();
|
|
117
|
+
|
|
118
|
+
const grant = makeGrant({ id: "canonical-hash-123" });
|
|
119
|
+
expect(store.add(grant)).toBe(true);
|
|
120
|
+
expect(store.add(grant)).toBe(false); // duplicate
|
|
121
|
+
|
|
122
|
+
expect(store.getAll()).toHaveLength(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("allows grants with different IDs", () => {
|
|
126
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
127
|
+
store.init();
|
|
128
|
+
|
|
129
|
+
store.add(makeGrant({ id: "grant-a" }));
|
|
130
|
+
store.add(makeGrant({ id: "grant-b" }));
|
|
131
|
+
|
|
132
|
+
expect(store.getAll()).toHaveLength(2);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("getById and has", () => {
|
|
137
|
+
test("returns grant by ID", () => {
|
|
138
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
139
|
+
store.init();
|
|
140
|
+
|
|
141
|
+
const grant = makeGrant({ id: "lookup-id" });
|
|
142
|
+
store.add(grant);
|
|
143
|
+
|
|
144
|
+
expect(store.getById("lookup-id")).toBeDefined();
|
|
145
|
+
expect(store.getById("lookup-id")!.tool).toBe(grant.tool);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("returns undefined for missing ID", () => {
|
|
149
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
150
|
+
store.init();
|
|
151
|
+
|
|
152
|
+
expect(store.getById("nonexistent")).toBeUndefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("has() returns true for existing grant", () => {
|
|
156
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
157
|
+
store.init();
|
|
158
|
+
|
|
159
|
+
store.add(makeGrant({ id: "exists" }));
|
|
160
|
+
expect(store.has("exists")).toBe(true);
|
|
161
|
+
expect(store.has("missing")).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("remove", () => {
|
|
166
|
+
test("removes an existing grant", () => {
|
|
167
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
168
|
+
store.init();
|
|
169
|
+
|
|
170
|
+
store.add(makeGrant({ id: "to-remove" }));
|
|
171
|
+
expect(store.remove("to-remove")).toBe(true);
|
|
172
|
+
expect(store.has("to-remove")).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("returns false for non-existent grant", () => {
|
|
176
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
177
|
+
store.init();
|
|
178
|
+
|
|
179
|
+
expect(store.remove("nonexistent")).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("clear", () => {
|
|
184
|
+
test("removes all grants", () => {
|
|
185
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
186
|
+
store.init();
|
|
187
|
+
|
|
188
|
+
store.add(makeGrant({ id: "a" }));
|
|
189
|
+
store.add(makeGrant({ id: "b" }));
|
|
190
|
+
store.clear();
|
|
191
|
+
|
|
192
|
+
expect(store.getAll()).toHaveLength(0);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("fail-closed behavior", () => {
|
|
197
|
+
test("throws on malformed JSON", () => {
|
|
198
|
+
writeFileSync(join(tmpDir, "grants.json"), "not json at all");
|
|
199
|
+
|
|
200
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
201
|
+
expect(() => store.init()).toThrow();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("throws on missing version field", () => {
|
|
205
|
+
writeFileSync(
|
|
206
|
+
join(tmpDir, "grants.json"),
|
|
207
|
+
JSON.stringify({ grants: [] }),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
211
|
+
expect(() => store.init()).toThrow(/malformed/i);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("throws on missing grants field", () => {
|
|
215
|
+
writeFileSync(
|
|
216
|
+
join(tmpDir, "grants.json"),
|
|
217
|
+
JSON.stringify({ version: 1 }),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
221
|
+
expect(() => store.init()).toThrow(/malformed/i);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("throws on unsupported version", () => {
|
|
225
|
+
writeFileSync(
|
|
226
|
+
join(tmpDir, "grants.json"),
|
|
227
|
+
JSON.stringify({ version: 999, grants: [] }),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
231
|
+
expect(() => store.init()).toThrow(/unsupported version/i);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("blocks all operations after corruption is detected", () => {
|
|
235
|
+
writeFileSync(join(tmpDir, "grants.json"), "corrupt");
|
|
236
|
+
|
|
237
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
238
|
+
expect(() => store.init()).toThrow();
|
|
239
|
+
|
|
240
|
+
// Subsequent operations should also fail
|
|
241
|
+
expect(() => store.getAll()).toThrow(/corrupt/i);
|
|
242
|
+
expect(() => store.add(makeGrant())).toThrow(/corrupt/i);
|
|
243
|
+
expect(() => store.remove("any")).toThrow(/corrupt/i);
|
|
244
|
+
expect(() => store.clear()).toThrow(/corrupt/i);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("throws on grants field being non-array", () => {
|
|
248
|
+
writeFileSync(
|
|
249
|
+
join(tmpDir, "grants.json"),
|
|
250
|
+
JSON.stringify({ version: 1, grants: "not-an-array" }),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
254
|
+
expect(() => store.init()).toThrow(/not an array/i);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("atomic writes", () => {
|
|
259
|
+
test("grants.json is valid after write", () => {
|
|
260
|
+
const store = new PersistentGrantStore(tmpDir);
|
|
261
|
+
store.init();
|
|
262
|
+
|
|
263
|
+
store.add(makeGrant({ id: "atom-1" }));
|
|
264
|
+
store.add(makeGrant({ id: "atom-2" }));
|
|
265
|
+
|
|
266
|
+
const raw = readFileSync(join(tmpDir, "grants.json"), "utf-8");
|
|
267
|
+
const data = JSON.parse(raw);
|
|
268
|
+
expect(data.version).toBe(1);
|
|
269
|
+
expect(data.grants).toHaveLength(2);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Temporary store
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
describe("TemporaryGrantStore", () => {
|
|
279
|
+
let store: TemporaryGrantStore;
|
|
280
|
+
|
|
281
|
+
beforeEach(() => {
|
|
282
|
+
store = new TemporaryGrantStore();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("allow_once", () => {
|
|
286
|
+
test("grant is consumed on first check", () => {
|
|
287
|
+
store.add("allow_once", "hash-abc");
|
|
288
|
+
|
|
289
|
+
expect(store.check("allow_once", "hash-abc")).toBe(true);
|
|
290
|
+
// Second check should fail — consumed
|
|
291
|
+
expect(store.check("allow_once", "hash-abc")).toBe(false);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("different proposal hashes are independent", () => {
|
|
295
|
+
store.add("allow_once", "hash-1");
|
|
296
|
+
store.add("allow_once", "hash-2");
|
|
297
|
+
|
|
298
|
+
expect(store.check("allow_once", "hash-1")).toBe(true);
|
|
299
|
+
expect(store.check("allow_once", "hash-2")).toBe(true);
|
|
300
|
+
// Both consumed
|
|
301
|
+
expect(store.check("allow_once", "hash-1")).toBe(false);
|
|
302
|
+
expect(store.check("allow_once", "hash-2")).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe("allow_10m", () => {
|
|
307
|
+
test("grant is active before expiry", () => {
|
|
308
|
+
store.add("allow_10m", "hash-timed");
|
|
309
|
+
|
|
310
|
+
expect(store.check("allow_10m", "hash-timed")).toBe(true);
|
|
311
|
+
// Can be checked multiple times (not consumed)
|
|
312
|
+
expect(store.check("allow_10m", "hash-timed")).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("grant expires after TTL", () => {
|
|
316
|
+
// Add with a very short duration (1ms)
|
|
317
|
+
store.add("allow_10m", "hash-expire", { durationMs: 1 });
|
|
318
|
+
|
|
319
|
+
// Wait for expiry — use a synchronous busy-wait to avoid flakes
|
|
320
|
+
const start = Date.now();
|
|
321
|
+
while (Date.now() - start < 5) {
|
|
322
|
+
// spin
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
expect(store.check("allow_10m", "hash-expire")).toBe(false);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("expired grant is lazily purged", () => {
|
|
329
|
+
store.add("allow_10m", "hash-lazy", { durationMs: 1 });
|
|
330
|
+
|
|
331
|
+
const start = Date.now();
|
|
332
|
+
while (Date.now() - start < 5) {
|
|
333
|
+
// spin
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check triggers lazy purge
|
|
337
|
+
store.check("allow_10m", "hash-lazy");
|
|
338
|
+
expect(store.size).toBe(0);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe("allow_thread", () => {
|
|
343
|
+
test("requires conversationId", () => {
|
|
344
|
+
expect(() => store.add("allow_thread", "hash-t", {})).toThrow(
|
|
345
|
+
/conversationId/,
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("scoped to conversation ID", () => {
|
|
350
|
+
store.add("allow_thread", "hash-t", { conversationId: "conv-1" });
|
|
351
|
+
|
|
352
|
+
expect(store.check("allow_thread", "hash-t", "conv-1")).toBe(true);
|
|
353
|
+
// Different conversation should not match
|
|
354
|
+
expect(store.check("allow_thread", "hash-t", "conv-2")).toBe(false);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("same proposal hash in different conversations are independent", () => {
|
|
358
|
+
store.add("allow_thread", "hash-t", { conversationId: "conv-a" });
|
|
359
|
+
store.add("allow_thread", "hash-t", { conversationId: "conv-b" });
|
|
360
|
+
|
|
361
|
+
expect(store.check("allow_thread", "hash-t", "conv-a")).toBe(true);
|
|
362
|
+
expect(store.check("allow_thread", "hash-t", "conv-b")).toBe(true);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("not consumed on check (persists within thread)", () => {
|
|
366
|
+
store.add("allow_thread", "hash-t", { conversationId: "conv-1" });
|
|
367
|
+
|
|
368
|
+
expect(store.check("allow_thread", "hash-t", "conv-1")).toBe(true);
|
|
369
|
+
expect(store.check("allow_thread", "hash-t", "conv-1")).toBe(true);
|
|
370
|
+
expect(store.check("allow_thread", "hash-t", "conv-1")).toBe(true);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe("checkAny", () => {
|
|
375
|
+
test("finds allow_once grant", () => {
|
|
376
|
+
store.add("allow_once", "hash-any");
|
|
377
|
+
expect(store.checkAny("hash-any")).toBe("allow_once");
|
|
378
|
+
// Consumed — should not match again
|
|
379
|
+
expect(store.checkAny("hash-any")).toBeUndefined();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("finds allow_10m grant", () => {
|
|
383
|
+
store.add("allow_10m", "hash-any-t");
|
|
384
|
+
expect(store.checkAny("hash-any-t")).toBe("allow_10m");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("finds allow_thread grant with conversationId", () => {
|
|
388
|
+
store.add("allow_thread", "hash-any-th", { conversationId: "conv-x" });
|
|
389
|
+
expect(store.checkAny("hash-any-th", "conv-x")).toBe("allow_thread");
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("returns undefined when no grants match", () => {
|
|
393
|
+
expect(store.checkAny("no-such-hash")).toBeUndefined();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("prefers allow_once over allow_10m", () => {
|
|
397
|
+
store.add("allow_once", "hash-prio");
|
|
398
|
+
store.add("allow_10m", "hash-prio");
|
|
399
|
+
|
|
400
|
+
expect(store.checkAny("hash-prio")).toBe("allow_once");
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("remove", () => {
|
|
405
|
+
test("removes a specific grant", () => {
|
|
406
|
+
store.add("allow_10m", "hash-rm");
|
|
407
|
+
expect(store.remove("allow_10m", "hash-rm")).toBe(true);
|
|
408
|
+
expect(store.check("allow_10m", "hash-rm")).toBe(false);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("returns false for non-existent grant", () => {
|
|
412
|
+
expect(store.remove("allow_once", "nope")).toBe(false);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe("clearConversation", () => {
|
|
417
|
+
test("removes all thread grants for a conversation", () => {
|
|
418
|
+
store.add("allow_thread", "hash-1", { conversationId: "conv-clear" });
|
|
419
|
+
store.add("allow_thread", "hash-2", { conversationId: "conv-clear" });
|
|
420
|
+
store.add("allow_thread", "hash-3", { conversationId: "conv-keep" });
|
|
421
|
+
|
|
422
|
+
store.clearConversation("conv-clear");
|
|
423
|
+
|
|
424
|
+
expect(store.check("allow_thread", "hash-1", "conv-clear")).toBe(false);
|
|
425
|
+
expect(store.check("allow_thread", "hash-2", "conv-clear")).toBe(false);
|
|
426
|
+
// Other conversation unaffected
|
|
427
|
+
expect(store.check("allow_thread", "hash-3", "conv-keep")).toBe(true);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("does not affect non-thread grants", () => {
|
|
431
|
+
store.add("allow_once", "hash-non-thread");
|
|
432
|
+
store.add("allow_10m", "hash-non-thread-t");
|
|
433
|
+
|
|
434
|
+
store.clearConversation("any-conv");
|
|
435
|
+
|
|
436
|
+
// Non-thread grants unaffected
|
|
437
|
+
expect(store.check("allow_once", "hash-non-thread")).toBe(true);
|
|
438
|
+
expect(store.check("allow_10m", "hash-non-thread-t")).toBe(true);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe("clear", () => {
|
|
443
|
+
test("removes all grants", () => {
|
|
444
|
+
store.add("allow_once", "h1");
|
|
445
|
+
store.add("allow_10m", "h2");
|
|
446
|
+
store.add("allow_thread", "h3", { conversationId: "c1" });
|
|
447
|
+
|
|
448
|
+
store.clear();
|
|
449
|
+
expect(store.size).toBe(0);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe("process restart simulation", () => {
|
|
454
|
+
test("grants do not survive new store instance", () => {
|
|
455
|
+
store.add("allow_once", "hash-restart");
|
|
456
|
+
store.add("allow_10m", "hash-restart-t");
|
|
457
|
+
store.add("allow_thread", "hash-restart-th", {
|
|
458
|
+
conversationId: "conv-r",
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// "Restart" — new instance
|
|
462
|
+
const newStore = new TemporaryGrantStore();
|
|
463
|
+
expect(newStore.size).toBe(0);
|
|
464
|
+
expect(newStore.checkAny("hash-restart")).toBeUndefined();
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|