delegation-blob-store-l1c.test.mjs
206 lines 7.5 KB
Raw
sha256:c541f54879f8110bb25bb9786cdb8ff7a4aec19f9f2a2789a55bc74e6f7fe095 fix(7C-L1c): persist hosted delegation indexes in Netlify Blobs Human minor ⚠ breaking 33 days ago
1 /**
2 * Phase 7C-L1c — hosted delegation store persistence (Netlify Blobs).
3 *
4 * Tiers: unit, integration, e2e, stress, data-integrity, performance, security.
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11
12 import {
13 DELEGATION_BLOB_FILES,
14 delegationBlobKey,
15 hydrateDelegationStoresFromBlob,
16 persistDelegationStoresToBlob,
17 withDelegationBlobSync,
18 } from '../hub/bridge/delegation-blob-store.mjs';
19 import {
20 DELEGATION_CONSENTS_FILE,
21 handleDelegationGrantMintRequest,
22 seedDelegationFixtures,
23 } from '../lib/agent/delegation.mjs';
24 import { writeDelegationPolicy, makeAgentIdentity, makeDelegationConsent } from './fixtures/agent/delegation-helpers.mjs';
25
26 const __dirname = path.dirname(fileURLToPath(import.meta.url));
27 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-delegation-l1c');
28
29 /** @returns {Map<string, string> & { get: (k: string, o?: { type?: string }) => Promise<string|null>, set: (k: string, v: string) => Promise<void> }} */
30 function makeMockBlobStore() {
31 const map = new Map();
32 return {
33 async get(key, opts) {
34 const v = map.get(key);
35 if (v == null) return null;
36 return opts?.type === 'text' || typeof v === 'string' ? v : null;
37 },
38 async set(key, value) {
39 map.set(key, value);
40 },
41 map,
42 };
43 }
44
45 describe('7C-L1c delegation blob store — unit', () => {
46 it('delegationBlobKey namespaces filenames', () => {
47 assert.equal(delegationBlobKey('hub_delegation_consents.json'), 'delegation/hub_delegation_consents.json');
48 });
49
50 it('DELEGATION_BLOB_FILES includes core index files', () => {
51 assert.ok(DELEGATION_BLOB_FILES.includes(DELEGATION_CONSENTS_FILE));
52 });
53 });
54
55 describe('7C-L1c delegation blob store — integration', () => {
56 const dataDir = path.join(tmpRoot, 'integration', 'data');
57 const vaultId = 'Business';
58
59 beforeEach(() => {
60 fs.rmSync(path.join(tmpRoot, 'integration'), { recursive: true, force: true });
61 fs.mkdirSync(dataDir, { recursive: true });
62 writeDelegationPolicy(dataDir);
63 process.env.DELEGATION_ENABLED = '1';
64 });
65
66 afterEach(() => {
67 delete process.env.DELEGATION_ENABLED;
68 });
69
70 it('persist then hydrate round-trips consent index across cold start', async () => {
71 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
72 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
73 seedDelegationFixtures(dataDir, vaultId, identity, consent);
74 const blob = makeMockBlobStore();
75 await persistDelegationStoresToBlob(blob, dataDir);
76 fs.rmSync(path.join(dataDir, DELEGATION_CONSENTS_FILE));
77 await hydrateDelegationStoresFromBlob(blob, dataDir);
78 assert.ok(fs.existsSync(path.join(dataDir, DELEGATION_CONSENTS_FILE)));
79 const mint = handleDelegationGrantMintRequest({
80 dataDir,
81 vaultId,
82 consentId: 'dcons_l1c_smoke01',
83 actorAgentId: 'agent_l1c_smoke01',
84 taskRef: 'task_hw_week3',
85 });
86 assert.equal(mint.ok, true);
87 });
88 });
89
90 describe('7C-L1c delegation blob store — e2e', () => {
91 const dataDir = path.join(tmpRoot, 'e2e', 'data');
92 const vaultId = 'Business';
93
94 beforeEach(() => {
95 fs.rmSync(path.join(tmpRoot, 'e2e'), { recursive: true, force: true });
96 fs.mkdirSync(dataDir, { recursive: true });
97 writeDelegationPolicy(dataDir);
98 process.env.DELEGATION_ENABLED = '1';
99 });
100
101 afterEach(() => {
102 delete process.env.DELEGATION_ENABLED;
103 });
104
105 it('withDelegationBlobSync persists mint result for next lambda', async () => {
106 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
107 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
108 seedDelegationFixtures(dataDir, vaultId, identity, consent);
109 const blob = makeMockBlobStore();
110 const mint = await withDelegationBlobSync({
111 blobStore: blob,
112 dataDir,
113 run: () =>
114 handleDelegationGrantMintRequest({
115 dataDir,
116 vaultId,
117 consentId: 'dcons_l1c_smoke01',
118 actorAgentId: 'agent_l1c_smoke01',
119 taskRef: 'task_hw_week3',
120 }),
121 });
122 assert.equal(mint.ok, true);
123 fs.rmSync(dataDir, { recursive: true, force: true });
124 fs.mkdirSync(dataDir, { recursive: true });
125 writeDelegationPolicy(dataDir);
126 await hydrateDelegationStoresFromBlob(blob, dataDir);
127 const list = handleDelegationGrantMintRequest({
128 dataDir,
129 vaultId,
130 consentId: 'dcons_l1c_smoke01',
131 actorAgentId: 'agent_l1c_smoke01',
132 taskRef: 'task_hw_week3',
133 });
134 assert.equal(list.ok, true);
135 });
136 });
137
138 describe('7C-L1c delegation blob store — stress', () => {
139 it('hydrate/persist handles empty blob store', async () => {
140 const dataDir = path.join(tmpRoot, 'stress', 'data');
141 fs.rmSync(path.join(tmpRoot, 'stress'), { recursive: true, force: true });
142 fs.mkdirSync(dataDir, { recursive: true });
143 await hydrateDelegationStoresFromBlob(null, dataDir);
144 await persistDelegationStoresToBlob(null, dataDir);
145 assert.ok(true);
146 });
147 });
148
149 describe('7C-L1c delegation blob store — data-integrity', () => {
150 it('blob key matches persisted consent JSON', async () => {
151 const dataDir = path.join(tmpRoot, 'integrity', 'data');
152 fs.rmSync(path.join(tmpRoot, 'integrity'), { recursive: true, force: true });
153 fs.mkdirSync(dataDir, { recursive: true });
154 writeDelegationPolicy(dataDir);
155 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
156 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
157 seedDelegationFixtures(dataDir, 'Business', identity, consent);
158 const blob = makeMockBlobStore();
159 await persistDelegationStoresToBlob(blob, dataDir);
160 const raw = blob.map.get(delegationBlobKey(DELEGATION_CONSENTS_FILE));
161 assert.ok(typeof raw === 'string' && raw.includes('dcons_l1c_smoke01'));
162 });
163 });
164
165 describe('7C-L1c delegation blob store — performance', () => {
166 it('withDelegationBlobSync completes under 500ms for fixture mint', async () => {
167 const dataDir = path.join(tmpRoot, 'perf', 'data');
168 fs.rmSync(path.join(tmpRoot, 'perf'), { recursive: true, force: true });
169 fs.mkdirSync(dataDir, { recursive: true });
170 writeDelegationPolicy(dataDir);
171 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
172 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
173 seedDelegationFixtures(dataDir, 'Business', identity, consent);
174 const blob = makeMockBlobStore();
175 const t0 = Date.now();
176 await withDelegationBlobSync({
177 blobStore: blob,
178 dataDir,
179 run: () =>
180 handleDelegationGrantMintRequest({
181 dataDir,
182 vaultId: 'Business',
183 consentId: 'dcons_l1c_smoke01',
184 actorAgentId: 'agent_l1c_smoke01',
185 taskRef: 'task_hw_week3',
186 }),
187 });
188 assert.ok(Date.now() - t0 < 500);
189 });
190 });
191
192 describe('7C-L1c delegation blob store — security', () => {
193 it('hydrate ignores malformed blob entries without throwing', async () => {
194 const dataDir = path.join(tmpRoot, 'security', 'data');
195 fs.rmSync(path.join(tmpRoot, 'security'), { recursive: true, force: true });
196 fs.mkdirSync(dataDir, { recursive: true });
197 const blob = {
198 async get() {
199 throw new Error('blob read denied');
200 },
201 async set() {},
202 };
203 await hydrateDelegationStoresFromBlob(blob, dataDir);
204 assert.ok(true);
205 });
206 });
File History 1 commit
sha256:c541f54879f8110bb25bb9786cdb8ff7a4aec19f9f2a2789a55bc74e6f7fe095 fix(7C-L1c): persist hosted delegation indexes in Netlify Blobs Human minor 33 days ago