Spaces:
Running
Running
File size: 7,609 Bytes
32151b6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | 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));
}
}
|