visual-journal / scripts /agent-skill-scripts.test.mjs
misonL's picture
Deploy 574ece8 to Docker Space
32151b6 verified
Raw
History Blame
7.61 kB
import assert from 'node:assert/strict';
import { spawn, spawnSync } from 'node:child_process';
import { createServer } from 'node:http';
import { fileURLToPath } from 'node:url';
import { join } from 'node:path';
import { describe, it } from 'node:test';
import { parseRetryAfterValue, resolveSameOriginUrl } from '../skills/gpt-image-playground-agent/scripts/lib/script-utils.mjs';
const repoRoot = fileURLToPath(new URL('..', import.meta.url));
const skillScriptsRoot = join(repoRoot, 'skills/gpt-image-playground-agent/scripts');
describe('Agent skill script argument validation', () => {
it('rejects invalid generate numeric options before dry-run output', () => {
const result = runSkillScript('generate-image.mjs', ['--n', 'abc', 'prompt']);
assert.equal(result.status, 2);
assert.match(result.stderr, /--n 必须是正整数/);
assert.equal(result.stdout.trim(), '');
});
it('rejects invalid edit timeout before reading the image path', () => {
const result = runSkillScript('edit-image.mjs', ['--timeout-ms', 'abc', '/tmp/missing.png', 'prompt']);
assert.equal(result.status, 2);
assert.match(result.stderr, /--timeout-ms 必须是正整数/);
assert.equal(result.stdout.trim(), '');
});
it('rejects invalid upstream probe timeout before network checks', () => {
const result = runSkillScript('probe-upstream-image.mjs', ['--timeout-ms', 'abc']);
assert.equal(result.status, 2);
assert.match(result.stderr, /--timeout-ms 必须是正整数/);
assert.equal(result.stdout.trim(), '');
});
it('rejects invalid retry attempt env values', () => {
const result = runSkillScript('generate-image.mjs', ['prompt'], {
GPT_IMAGE_AGENT_MAX_ATTEMPTS: 'abc'
});
assert.equal(result.status, 2);
assert.match(result.stderr, /GPT_IMAGE_AGENT_MAX_ATTEMPTS 必须是正整数/);
assert.equal(result.stdout.trim(), '');
});
it('rejects service base URLs with embedded credentials before dry-run output', () => {
const result = runSkillScript('generate-image.mjs', ['prompt'], {
GPT_IMAGE_PLAYGROUND_URL: 'https://user:secret@example.test'
});
assert.equal(result.status, 2);
assert.match(result.stderr, /base URL/);
assert.match(result.stderr, /不能包含凭据/);
assert.doesNotMatch(result.stderr, /secret/);
assert.equal(result.stdout.trim(), '');
});
it('rejects upstream probe base URLs with embedded credentials before network checks', () => {
const result = runSkillScript('probe-upstream-image.mjs', ['--base-url', 'https://user:secret@example.test/v1']);
assert.equal(result.status, 2);
assert.match(result.stderr, /base URL/);
assert.match(result.stderr, /不能包含凭据/);
assert.doesNotMatch(result.stderr, /secret/);
assert.equal(result.stdout.trim(), '');
});
it('does not read prompt files during default generate dry-run', () => {
const result = runSkillScript('generate-image.mjs', ['--prompt-file', '/tmp/missing-agent-prompt.txt']);
assert.equal(result.status, 0);
const body = JSON.parse(result.stdout);
assert.equal(body.dry_run, true);
assert.equal(body.billable, false);
assert.equal(result.stderr.trim(), '');
});
it('shows edit help without validating unrelated env values', () => {
const result = runSkillScript('edit-image.mjs', ['--help'], {
GPT_IMAGE_AGENT_MAX_ATTEMPTS: 'abc'
});
assert.equal(result.status, 0);
assert.match(result.stderr, /用法:edit-image\.mjs/);
assert.equal(result.stdout.trim(), '');
});
it('shows skill script help without validating service URL env values', () => {
const env = { GPT_IMAGE_PLAYGROUND_URL: 'https://user:secret@example.test' };
const generateHelp = runSkillScript('generate-image.mjs', ['--help'], env);
const editHelp = runSkillScript('edit-image.mjs', ['--help'], env);
assert.equal(generateHelp.status, 0);
assert.match(generateHelp.stderr, /用法:generate-image\.mjs/);
assert.equal(generateHelp.stdout.trim(), '');
assert.equal(editHelp.status, 0);
assert.match(editHelp.stderr, /用法:edit-image\.mjs/);
assert.equal(editHelp.stdout.trim(), '');
});
it('rejects cross-origin job result URLs before sending auth headers', () => {
assert.throws(
() => resolveSameOriginUrl('https://space.example.test', 'https://evil.example.test/result', 'job.result_url'),
/不同 origin/
);
assert.equal(
resolveSameOriginUrl('https://space.example.test', '/api/agent/jobs/abc/result', 'job.result_url'),
'https://space.example.test/api/agent/jobs/abc/result'
);
});
it('caps retry-after values before sleeping', () => {
assert.equal(parseRetryAfterValue('5'), 5);
assert.equal(parseRetryAfterValue('0'), 1);
assert.equal(parseRetryAfterValue('999999999999999999999'), 60);
assert.equal(parseRetryAfterValue('not-a-number', 7), 7);
});
it('preserves non-JSON capabilities status and body in generate failures', async () => {
await withServer(
(request, response) => {
if (request.url === '/api/agent/capabilities') {
response.writeHead(503, { 'content-type': 'text/plain' });
response.end('maintenance window');
return;
}
response.writeHead(404, { 'content-type': 'text/plain' });
response.end('missing');
},
async (baseUrl) => {
const result = await runSkillScriptAsync('generate-image.mjs', ['--allow-billable', 'prompt'], {
GPT_IMAGE_PLAYGROUND_URL: baseUrl
});
assert.equal(result.status, 1);
assert.match(result.stderr, /capabilities 请求失败,状态码 503:maintenance window/);
}
);
});
});
function runSkillScript(filename, args, env = {}) {
return spawnSync(process.execPath, [join(skillScriptsRoot, filename), ...args], {
cwd: repoRoot,
encoding: 'utf8',
env: { ...process.env, ...env }
});
}
function runSkillScriptAsync(filename, args, env = {}) {
return new Promise((resolve) => {
const child = spawn(process.execPath, [join(skillScriptsRoot, filename), ...args], {
cwd: repoRoot,
env: { ...process.env, ...env },
stdio: ['ignore', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
stdout += chunk;
});
child.stderr.on('data', (chunk) => {
stderr += chunk;
});
child.on('close', (status) => {
resolve({ status, stdout, stderr });
});
});
}
async function withServer(handler, run) {
const server = createServer(handler);
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const address = server.address();
try {
assert.equal(typeof address, 'object');
assert.ok(address);
await run(`http://127.0.0.1:${address.port}`);
} finally {
await new Promise((resolve) => server.close(resolve));
}
}