| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 |
- import test from 'node:test';
- import assert from 'node:assert/strict';
- import fs from 'node:fs';
- import os from 'node:os';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import { buildIdentityFunctions } from '../server/identity/build-release.mjs';
- import { manage } from '../server/identity/manage.mjs';
- const sha = (v) => crypto.createHash('sha256').update(v).digest('hex');
- function setup(t) {
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ksf-identity-test-')); t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
- const base = { name: 'ksfGetPublicBoard', path: '/ksfGetPublicBoard', objectId: 'public-id', ACL: { '*': { read: true } }, code: 'function bootstrap(){attachWorkshopHandlers(app,{store,data:DATA});}\nfunction handler(request,response){return runDatabaseFunction("ksfGetPublicBoard",request,response,{});}' };
- const built = buildIdentityFunctions(base);
- for (const entry of built) fs.writeFileSync(path.join(dir, entry.name + '.js'), entry.code);
- fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify({ version: 'ksf.wecom-identity.20261103.v1', functions: built.map(({ code, ...r }) => r) }));
- const rows = [structuredClone(base)], calls = [];
- const request = async (method, route, body) => {
- calls.push({ method, route });
- if (method === 'GET' && route.includes('?')) { const name = JSON.parse(new URL('https://test' + route).searchParams.get('where')).name; return { results: structuredClone(rows.filter((r) => r.name === name)) }; }
- const id = route.split('/').at(-1), row = rows.find((r) => r.objectId === id);
- if (method === 'GET') { assert.ok(row); return structuredClone(row); }
- if (method === 'POST') { const created = { objectId: 'sync-id', ...body }; rows.push(created); return { objectId: created.objectId }; }
- if (method === 'PUT') { assert.ok(row); Object.assign(row, body); return {}; }
- if (method === 'DELETE') { rows.splice(rows.indexOf(row), 1); return {}; }
- throw Error('unexpected request');
- };
- const run = (args) => manage({ args, request, releaseDir: dir });
- return { dir, base, built, rows, calls, run };
- }
- test('production overlay builds exact executable entries while rejecting unknown base shapes', () => {
- assert.throws(() => buildIdentityFunctions({ name: 'other', code: '' }), /UNEXPECTED_PUBLIC_FUNCTION/);
- });
- test('upload hooks overlay each current production function without replacing its original business implementation', async (t) => {
- const f = setup(t), uploads = ['ksfStartWorkshopSession', 'ksfSaveWorkshopTask'].map((name) => ({ ...f.base, name, objectId: name + '-id', path: '/' + name, code: f.base.code.replace('"ksfGetPublicBoard"', JSON.stringify(name)) }));
- const result = buildIdentityFunctions(f.base, { uploadBases: uploads });
- assert.equal(result.length, 4); assert.deepEqual(result.map((r) => r.name).sort(), ['ksfGetPublicBoard', 'ksfSyncWecomEmployee', 'ksfStartWorkshopSession', 'ksfSaveWorkshopTask'].sort());
- for (const source of uploads) { const built = result.find((r) => r.name === source.name); assert.equal(built.priorSha256, sha(source.code)); assert.ok(built.code.includes('runDatabaseFunction(' + JSON.stringify(source.name))); new Function(built.code); }
- assert.throws(() => buildIdentityFunctions(f.base, { uploadBases: [uploads[0]] }), /UNEXPECTED_UPLOAD_BASES/);
- for (const entry of result) fs.writeFileSync(path.join(f.dir, entry.name + '.js'), entry.code);
- fs.writeFileSync(path.join(f.dir, 'manifest.json'), JSON.stringify({ version: 'ksf.wecom-identity.20261103.v1', functions: result.map(({ code, ...r }) => r) }));
- f.rows.push(...structuredClone(uploads));
- const deployed = await f.run(['deploy', '--apply', '--receipt-dir', f.dir]); assert.equal(deployed.functions, 4); assert.equal(f.rows.length, 4);
- await f.run(['rollback', '--receipt', deployed.receipt, '--apply']); assert.equal(f.rows.length, 3);
- for (const source of [f.base, ...uploads]) assert.equal(f.rows.find((r) => r.name === source.name).code, source.code);
- });
- test('function deployment is read-only by default, checks exact baseline, repeats without writes and restores old public code', async (t) => {
- const f = setup(t), plan = await f.run(['deploy']); assert.equal(plan.readOnly, true); assert.ok(f.calls.every((c) => c.method === 'GET'));
- const applied = await f.run(['deploy', '--apply', '--receipt-dir', f.dir]); assert.equal(applied.applied, true); assert.equal(f.rows.length, 2);
- const before = f.calls.length; assert.equal((await f.run(['deploy', '--apply', '--receipt-dir', f.dir])).unchanged, true); assert.ok(f.calls.slice(before).every((c) => c.method === 'GET'));
- await f.run(['rollback', '--receipt', applied.receipt]); assert.notEqual(sha(f.rows[0].code), sha(f.base.code));
- await f.run(['rollback', '--receipt', applied.receipt, '--apply']); assert.equal(f.rows.length, 1); assert.equal(f.rows[0].code, f.base.code);
- });
- test('unknown function changes and malformed flags stop before any writes', async (t) => {
- const f = setup(t); f.rows[0].code += '/*newer production revision*/';
- await assert.rejects(() => f.run(['deploy', '--apply', '--receipt-dir', f.dir]), { code: 'FUNCTION_BASELINE_MISMATCH' });
- await assert.rejects(() => f.run(['deploy', '--apply', '--unexpected']), { code: 'USAGE' });
- assert.ok(f.calls.every((c) => c.method === 'GET'));
- });
- test('rollback refuses concurrent edits before changing either function', async (t) => {
- const f = setup(t), applied = await f.run(['deploy', '--apply', '--receipt-dir', f.dir]); f.rows[0].code += '/*concurrent*/'; const before = f.calls.length;
- await assert.rejects(() => f.run(['rollback', '--receipt', applied.receipt, '--apply']), { code: 'CONCURRENT_FUNCTION_CHANGE' });
- assert.ok(f.calls.slice(before).every((c) => c.method === 'GET')); assert.equal(f.rows.length, 2);
- });
|