test: cover backpressured release uploads
All checks were successful
labels / labels (pull_request) Successful in 8s
ci / test (pull_request) Successful in 14s

This commit is contained in:
codex-bot-andresmgsl 2026-08-30 10:07:32 +00:00
parent 1371ec9aa9
commit 8293c83531

View file

@ -180,6 +180,51 @@ test('release upload resolves the tag once and applies a single asset name overr
} }
}); });
test('release upload streams a large asset through receiver backpressure', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-release-large-upload-'));
const cfg = path.join(dir, 'config.json');
const asset = path.join(dir, 'large.bin');
const assetSize = 8 * 1024 * 1024;
fs.writeFileSync(asset, Buffer.alloc(assetSize, 0x61));
let uploadedBytes = 0;
let paused = false;
const server = http.createServer((req, res) => {
if (req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 7, tag_name: 'v1' }));
return;
}
req.on('data', (chunk) => {
uploadedBytes += chunk.length;
if (!paused) {
paused = true;
req.pause();
setTimeout(() => req.resume(), 100);
}
});
req.on('end', () => {
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 8, name: 'large.bin' }));
});
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
fs.writeFileSync(cfg, JSON.stringify({ url: `http://127.0.0.1:${server.address().port}`, token: 'tok' }));
try {
const result = await spawnAsync(
['release', 'upload', '-o', 'o', '-r', 'r', '--tag', 'v1', '--asset', asset],
{ STOKE_CONFIG_FILE: cfg },
);
assert.equal(result.status, 0, result.stderr);
assert.equal(paused, true);
assert.ok(uploadedBytes > assetSize, `multipart body ${uploadedBytes} did not include ${assetSize} asset bytes`);
assert.match(result.stdout, /Asset uploaded: large\.bin/);
} finally {
await new Promise((resolve) => server.close(resolve));
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('release create keeps the release and reports landed and failed assets', async () => { test('release create keeps the release and reports landed and failed assets', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-release-partial-')); const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-release-partial-'));
const cfg = path.join(dir, 'config.json'); const cfg = path.join(dir, 'config.json');
@ -211,7 +256,7 @@ test('release create keeps the release and reports landed and failed assets', as
try { try {
const result = await spawnAsync([ const result = await spawnAsync([
'release', 'create', '-o', 'o', '-r', 'r', '--tag', 'v1', 'release', 'create', '-o', 'o', '-r', 'r', '--tag', 'v1',
'--asset', good, '--asset', bad, '--asset', bad, '--asset', good,
], { STOKE_CONFIG_FILE: cfg }); ], { STOKE_CONFIG_FILE: cfg });
assert.equal(result.status, 1); assert.equal(result.status, 1);
assert.match(result.stdout, /Release id: 42/i); assert.match(result.stdout, /Release id: 42/i);
@ -220,8 +265,8 @@ test('release create keeps the release and reports landed and failed assets', as
assert.match(result.stderr, /release was kept/i); assert.match(result.stderr, /release was kept/i);
assert.deepEqual(requests.map(({ url }) => url), [ assert.deepEqual(requests.map(({ url }) => url), [
'/api/v1/repos/o/r/releases', '/api/v1/repos/o/r/releases',
'/api/v1/repos/o/r/releases/42/assets?name=good.bin',
'/api/v1/repos/o/r/releases/42/assets?name=bad.bin', '/api/v1/repos/o/r/releases/42/assets?name=bad.bin',
'/api/v1/repos/o/r/releases/42/assets?name=good.bin',
]); ]);
} finally { } finally {
await new Promise((resolve) => server.close(resolve)); await new Promise((resolve) => server.close(resolve));