Files
qinglong/test/back/ql3PanelRunControlLiveClient.test.cjs
T

108 lines
3.6 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
preparePanelClient,
loadPanelClient,
} = require('../../scripts/lib/ql3-panel-run-control-live-client.cjs');
function fixture(t, transport) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-panel-live-client-'));
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const directory = path.join(root, 'client');
const manifest = preparePanelClient(
path.resolve(__dirname, '../..'),
directory,
);
const loaded = loadPanelClient(directory, 12345, transport);
assert.equal(
loaded.auth.setQingLong3Credential(`ql3c_test_${'A'.repeat(43)}`),
true,
);
return { ...loaded, directory, manifest };
}
test('live adapter compiles actual sources and rejects modified generated modules', (t) => {
const f = fixture(t, () => assert.fail('no HTTP expected'));
assert.equal(
f.manifest.control.source,
'src/components/qinglong3/runControl.ts',
);
assert.match(f.manifest.control.sourceSha256, /^[a-f0-9]{64}$/);
fs.appendFileSync(path.join(f.directory, 'control.cjs'), '\n// modified');
assert.throws(
() => loadPanelClient(f.directory, 12345),
/Expected values to be strictly equal/,
);
});
test('live adapter constrains capability discovery to the isolated loopback server', async (t) => {
let calls = 0;
const f = fixture(t, async (url, options) => {
calls++;
assert.equal(url, 'http://127.0.0.1:12345/api/v3/capabilities');
assert.equal(options.credentials, 'omit');
assert.equal(options.redirect, 'error');
return new Response('{}', { status: 404 });
});
for (const url of [
'https://example.com/',
'//example.com/',
'/\\example.com/',
]) {
assert.equal(await f.auth.discoverQingLong3(url), null);
}
assert.equal(calls, 0);
assert.equal(await f.auth.discoverQingLong3('/api/v3/capabilities'), null);
assert.equal(calls, 1);
});
test('response loss consumes an accepted receipt before actual client retries the same cancellation', async (t) => {
let cancelled = 0;
const f = fixture(t, async (url, options) => {
assert.ok(
url.startsWith('http://127.0.0.1:12345/api/v3/projects/default/'),
);
if (options.method === 'GET')
return Response.json({
run: {
id: 'run:a',
projectId: 'default',
taskId: 'task:a',
taskRevision: '1',
status: 'running',
createdAtMs: 1,
},
});
cancelled++;
return Response.json(
{
schema: 'qinglong/run-cancellation@v1',
projectId: 'default',
runId: 'run:a',
status: cancelled === 1 ? 'accepted' : 'already_requested',
},
{ status: cancelled === 1 ? 202 : 200 },
);
});
const client = f.control.createPanelRunControl(
{ ql3: { projectId: 'default', taskId: 'task:a' } },
{
panel: { runControl: 'task_run_v1' },
limits: { logChunkBytes: 16384 },
},
);
const action = client.prepareCancel(await client.readRun('run:a'));
f.loseNextCancellationResponse();
await assert.rejects(action.execute(), (error) => error.uncertain === true);
assert.equal(cancelled, 1);
assert.equal((await action.execute()).status, 'already_requested');
const writes = f.requests.filter((r) => r.method === 'POST');
assert.equal(writes.length, 2);
assert.equal(writes[0].body, writes[1].body);
assert.equal(writes[0].path, writes[1].path);
assert.ok(!JSON.stringify(f.requests).includes('ql3c_'));
});