blob: a01dbf5c94f9a132eee403b824e5dbd258ac7b0d [file]
const assert = require('node:assert/strict');
const fs = require('node:fs');
const Module = require('node:module');
const path = require('node:path');
const test = require('node:test');
const { File } = require('node:buffer');
const typescript = require('typescript');
const previousTypeScriptLoader = require.extensions['.ts'];
require.extensions['.ts'] = (module, filename) => {
const source = fs.readFileSync(filename, 'utf8');
const output = typescript.transpileModule(source, {
compilerOptions: {
module: typescript.ModuleKind.CommonJS,
target: typescript.ScriptTarget.ES2020,
},
}).outputText;
module._compile(output, filename);
};
const apiPath = path.join(__dirname, 'profile-analysis.api.ts');
const compiledApi = typescript.transpileModule(fs.readFileSync(apiPath, 'utf8'), {
compilerOptions: {
module: typescript.ModuleKind.CommonJS,
target: typescript.ScriptTarget.ES2020,
},
}).outputText;
const apiModule = new Module(apiPath, module);
apiModule.filename = apiPath;
apiModule.paths = Module._nodeModulePaths(path.dirname(apiPath));
apiModule._compile(compiledApi, apiPath);
require.extensions['.ts'] = previousTypeScriptLoader;
const {
createAnalysisJob,
getAnalysisJob,
getAnalysisJobByClientRequestId,
MAX_FINAL_ANSWER_BYTES,
PRIVACY_NOTICE_VERSION,
ProfileAnalysisApiError,
} = apiModule.exports;
const clientRequestId = 'ca9ee8aa-3f47-4aab-a151-f3a39c5a6193';
const jobId = '550e8400-e29b-41d4-a716-446655440000';
const hcaptchaToken = 'test-hcaptcha-token';
function jsonResponse(body, init = {}) {
return new Response(JSON.stringify(body), {
status: init.status ?? 200,
headers: { 'Content-Type': 'application/json' },
});
}
test('creates an analysis job with the selected file and response language', async t => {
const originalFetch = global.fetch;
t.after(() => {
global.fetch = originalFetch;
});
const file = new File(['Query Profile text'], 'query-profile.txt', { type: 'text/plain' });
global.fetch = async (url, options) => {
assert.equal(url, 'https://agent.velodb.io/api/profile/analysis-jobs');
assert.equal(options.method, 'POST');
assert.deepEqual(options.headers, { 'Idempotency-Key': clientRequestId });
assert.equal(options.headers['Content-Type'], undefined, 'the browser must set the multipart boundary');
assert.ok(options.body instanceof FormData);
const uploadedFile = options.body.get('file');
assert.ok(uploadedFile);
assert.equal(uploadedFile.name, 'query-profile.txt');
assert.equal(await uploadedFile.text(), 'Query Profile text');
assert.equal(options.body.get('language'), 'zh-CN');
assert.equal(options.body.get('consent'), 'true');
assert.equal(options.body.get('privacyNoticeVersion'), PRIVACY_NOTICE_VERSION);
assert.equal(options.body.get('hcaptchaToken'), hcaptchaToken);
return new Response(JSON.stringify({ jobId, status: 'QUEUED' }), {
status: 202,
headers: { 'Content-Type': 'application/json', 'Retry-After': '3' },
});
};
assert.deepEqual(
await createAnalysisJob(
'https://agent.velodb.io/',
file,
'zh-CN',
clientRequestId,
hcaptchaToken,
),
{
jobId,
status: 'QUEUED',
retryAfterMs: 3000,
},
);
});
test('parses queued, running, completed, and failed job snapshots', async t => {
const originalFetch = global.fetch;
t.after(() => { global.fetch = originalFetch; });
const responses = [
{ jobId, status: 'QUEUED', jobsAhead: 3, dagStatus: 'PENDING' },
{ jobId, status: 'RUNNING', dagStatus: 'PARSING', dagError: null },
{ jobId, status: 'COMPLETED', result: { id: 'item_26', type: 'agent_message', text: 'Done' }, dagStatus: 'READY' },
{ jobId, status: 'FAILED', error: { code: 'CODEX_EXECUTION_FAILED', message: 'Failed safely.' }, dagStatus: 'UNAVAILABLE', dagError: 'DAG_UNAVAILABLE' },
];
global.fetch = async (url, options) => {
assert.equal(url, `/api/profile/analysis-jobs/${jobId}`);
assert.equal(options.method, 'GET');
return jsonResponse(responses.shift());
};
assert.deepEqual(await getAnalysisJob('', jobId), {
jobId,
status: 'QUEUED',
jobsAhead: 3,
});
assert.deepEqual(await getAnalysisJob('', jobId), {
jobId,
status: 'RUNNING',
});
assert.equal((await getAnalysisJob('', jobId)).status, 'COMPLETED');
assert.equal((await getAnalysisJob('', jobId)).status, 'FAILED');
});
test('recovers a job id from a client request id without re-uploading the profile', async t => {
const originalFetch = global.fetch;
t.after(() => { global.fetch = originalFetch; });
global.fetch = async (url, options) => {
assert.equal(url, `/api/profile/analysis-job-requests/${clientRequestId}`);
assert.equal(options.method, 'GET');
return jsonResponse({ jobId, status: 'RUNNING' });
};
assert.deepEqual(await getAnalysisJobByClientRequestId('', clientRequestId), {
jobId,
status: 'RUNNING',
});
});
test('preserves Retry-After on a temporary client-request recovery 404', async t => {
const originalFetch = global.fetch;
t.after(() => { global.fetch = originalFetch; });
global.fetch = async () =>
new Response(JSON.stringify({
code: 'ANALYSIS_JOB_NOT_FOUND',
message: 'The analysis job was not found or has expired.',
}), {
status: 404,
headers: { 'Content-Type': 'application/json', 'Retry-After': '1' },
});
await assert.rejects(
getAnalysisJobByClientRequestId('', clientRequestId),
error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.status, 404);
assert.equal(error.retryAfterMs, 1000);
return true;
},
);
});
test('accepts a terminal status when an idempotent create response is replayed', async t => {
const originalFetch = global.fetch;
t.after(() => { global.fetch = originalFetch; });
global.fetch = async () =>
new Response(JSON.stringify({ jobId, status: 'COMPLETED' }), {
status: 202,
headers: { 'Content-Type': 'application/json', 'Retry-After': '2' },
});
assert.equal(
(
await createAnalysisJob(
'',
new File(['profile'], 'profile.txt'),
'en',
clientRequestId,
hcaptchaToken,
)
).status,
'COMPLETED',
);
});
test('preserves a structured backend error on non-2xx responses', async t => {
const originalFetch = global.fetch;
t.after(() => {
global.fetch = originalFetch;
});
global.fetch = async () =>
jsonResponse({ code: 'INVALID_PROFILE', message: 'The file is not a Doris profile.' }, { status: 422 });
await assert.rejects(
createAnalysisJob(
'https://agent.velodb.io',
new File(['bad'], 'bad.txt'),
'en',
clientRequestId,
hcaptchaToken,
),
error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.status, 422);
assert.equal(error.code, 'INVALID_PROFILE');
assert.equal(error.message, 'The file is not a Doris profile.');
return true;
},
);
});
test('uses a safe fallback when an error response is not JSON', async t => {
const originalFetch = global.fetch;
t.after(() => {
global.fetch = originalFetch;
});
global.fetch = async () => new Response('<html>Bad gateway</html>', { status: 502 });
await assert.rejects(
createAnalysisJob(
'',
new File(['profile'], 'profile.txt'),
'en',
clientRequestId,
hcaptchaToken,
),
error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.status, 502);
assert.equal(error.code, 'HTTP_ERROR');
assert.doesNotMatch(error.message, /<html>/);
return true;
},
);
});
test('rejects a successful response that is not an agent message', async t => {
const originalFetch = global.fetch;
t.after(() => {
global.fetch = originalFetch;
});
global.fetch = async () => jsonResponse({ jobId, status: 'unexpected' }, { status: 202 });
await assert.rejects(
createAnalysisJob(
'',
new File(['profile'], 'profile.txt'),
'en',
clientRequestId,
hcaptchaToken,
),
error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.status, 502);
assert.equal(error.code, 'INVALID_SERVER_RESPONSE');
return true;
},
);
});
test('rejects a completed answer over the frontend UTF-8 byte limit', async t => {
const originalFetch = global.fetch;
t.after(() => {
global.fetch = originalFetch;
});
const oversized = 'a'.repeat(MAX_FINAL_ANSWER_BYTES + 1);
global.fetch = async () =>
jsonResponse({
jobId,
status: 'COMPLETED',
result: { id: 'item_26', type: 'agent_message', text: oversized },
dagStatus: 'READY',
});
await assert.rejects(getAnalysisJob('', jobId), error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.code, 'INVALID_SERVER_RESPONSE');
return true;
});
});
test('rejects an API response body over the client hard limit', async t => {
const originalFetch = global.fetch;
t.after(() => {
global.fetch = originalFetch;
});
global.fetch = async () =>
new Response(JSON.stringify({ padding: 'x'.repeat(140 * 1024) }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
await assert.rejects(getAnalysisJob('', jobId), error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.code, 'INVALID_SERVER_RESPONSE');
return true;
});
});
test('normalizes network failures into a stable API error', async t => {
const originalFetch = global.fetch;
t.after(() => {
global.fetch = originalFetch;
});
global.fetch = async () => {
throw new TypeError('fetch failed');
};
await assert.rejects(
createAnalysisJob(
'',
new File(['profile'], 'profile.txt'),
'en',
clientRequestId,
hcaptchaToken,
),
error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.status, 0);
assert.equal(error.code, 'NETWORK_ERROR');
assert.doesNotMatch(error.message, /fetch failed/);
return true;
},
);
});
test('rejects a missing hCaptcha token before sending the Profile', async t => {
const originalFetch = global.fetch;
let fetchCalls = 0;
t.after(() => {
global.fetch = originalFetch;
});
global.fetch = async () => {
fetchCalls += 1;
throw new Error('must not be called');
};
await assert.rejects(
createAnalysisJob('', new File(['profile'], 'profile.txt'), 'en', clientRequestId, ' '),
error => {
assert.ok(error instanceof ProfileAnalysisApiError);
assert.equal(error.code, 'CAPTCHA_MISSING');
return true;
},
);
assert.equal(fetchCalls, 0);
});