import test from 'node:test'; import assert from 'node:assert/strict'; import { verifyOrganization } from '../scripts/verify-org-context.mjs'; const ROOT = '038326b6-954a-48a7-a85f-efd83f62b82a'; const USER = '01900000-0000-7000-8000-000000000001'; let sequence = 0; const settings = () => ({ ORG_CONTEXT_ENABLED: 'true', ORG_CONTEXT_TENANT_IDS: JSON.stringify([ROOT]), ORG_CONTEXT_KEY_ID: 'fixture-verification', ORG_CONTEXT_KEY_SECRET: 'fixture-secret-' + ++sequence }); const directory = () => ({ scope: { tenantId: ROOT }, tree: { id: ROOT, name: '기준 조직', members: [{ id: USER }] } }); const response = (body, status = 200) => Response.json(body, { status }); for (const status of [429, 502, 503, 504]) { test(`deployment verification recovers from HTTP ${status} and reports only verified directory counts`, async () => { let calls = 0; const logs = [], pauses = []; const result = await verifyOrganization(settings(), { fetchImpl: async () => ++calls === 1 ? response({}, status) : response(directory()), pause: async (ms) => pauses.push(ms), log: (line) => logs.push(line) }); assert.equal(calls, 2); assert.deepEqual(pauses, [2000]); assert.equal(result.status, 'verified'); assert.equal(result.userCount, 1); assert.ok(!JSON.stringify(logs).includes('fixture-secret')); assert.ok(!JSON.stringify(logs).includes(USER)); }); } test('deployment verification allows 15 seconds per request and retries an actual abort', async (t) => { const originalTimer = globalThis.setTimeout; const timeouts = []; t.mock.method(globalThis, 'setTimeout', (callback, delay) => { timeouts.push(delay); return originalTimer(callback, delay === 15000 ? 5 : delay); }); let calls = 0; const result = await verifyOrganization(settings(), { fetchImpl: async (_url, { signal }) => { if (++calls === 1) return new Promise((_resolve, reject) => { signal.addEventListener('abort', () => reject(new DOMException('Aborted', 'AbortError')), { once: true }); }); return response(directory()); }, pause: async () => {}, log() {} }); assert.equal(calls, 2); assert.deepEqual(timeouts, [15000, 15000]); assert.equal(result.status, 'verified'); }); test('persistent network failure still blocks deployment after three attempts without exposing upstream errors', async () => { let calls = 0, pauses = 0; const logs = []; await assert.rejects(verifyOrganization(settings(), { fetchImpl: async () => { calls++; throw new Error('private-network-detail'); }, pause: async () => { pauses++; }, log: (line) => logs.push(line) }), /org_context_request_failed/); assert.equal(calls, 3); assert.equal(pauses, 2); assert.ok(!JSON.stringify(logs).includes('private-network-detail')); }); for (const [name, body, status, error] of [ ['unauthorized', {}, 401, 'http_401'], ['forbidden', {}, 403, 'http_403'], ['wrong scope', { scope: { tenantId: USER } }, 200, 'scope_mismatch'], ['missing members', { scope: { tenantId: ROOT }, tree: { id: ROOT, name: 'Empty' } }, 200, 'members_missing'] ]) { test(`deployment verification rejects ${name} immediately`, async () => { let calls = 0; await assert.rejects(verifyOrganization(settings(), { fetchImpl: async () => { calls++; return response(body, status); }, pause: async () => assert.fail('must not retry a configuration or data error'), log() {} }), new RegExp('org_context_' + error)); assert.equal(calls, 1); }); } test('disabled verification makes no upstream requests', async () => { await verifyOrganization({ ORG_CONTEXT_ENABLED: 'false' }, { fetchImpl: async () => assert.fail('disabled lookup'), log() {} }); });