Files
anonymous_github/test/zip-proxy-errors.test.js
T
Thomas Durieux a9760eab39 perf: avoid needless downloads and track request latency (#876)
* perf: avoid needless downloads and track request latency

* fix: correct request timing and monitoring revalidation
2026-10-08 10:38:33 +01:00

118 lines
5.7 KiB
JavaScript

const { expect } = require("chai");
const express = require("express");
const compression = require("compression");
const { gunzipSync } = require("node:zlib");
const http = require("node:http");
const { PassThrough } = require("node:stream");
const got = require("got");
require("ts-node/register/transpile-only");
const config = require("../src/config").default;
const utils = require("../src/server/routes/route-utils");
const router = require("../src/server/routes/repository-public").default;
describe("ZIP proxy failures", function () {
let restore, upstream, server;
beforeEach(async function () {
const previous = { download: config.ENABLE_DOWNLOAD, endpoint: config.STREAMER_ENTRYPOINT,
getRepo: utils.getRepo, getUser: utils.getUser, stream: got.stream };
restore = () => {
config.ENABLE_DOWNLOAD = previous.download;
config.STREAMER_ENTRYPOINT = previous.endpoint;
utils.getRepo = previous.getRepo; utils.getUser = previous.getUser; got.stream = previous.stream;
};
config.ENABLE_DOWNLOAD = true;
config.STREAMER_ENTRYPOINT = "http://streamer.test/";
utils.getUser = async () => { throw new Error("anonymous"); };
utils.getRepo = async () => ({ repoId: "test", options: {},
model: { source: { repositoryName: "owner/repo", commit: "abc" } },
countView: async () => {}, getToken: async () => "",
generateAnonymizeTransformer: () => ({ opt: {} }),
});
upstream = new PassThrough();
got.stream = () => upstream;
const app = express(); app.use(compression({ filter: () => true })); app.use(router);
server = await new Promise(resolve => {
const listening = app.listen(0, "127.0.0.1", () => resolve(listening));
});
});
afterEach(async function () {
upstream.destroy(); server.closeAllConnections();
await new Promise(resolve => server.close(resolve)); restore();
});
function request(onResponse, headers = {}) {
return http.get(`http://127.0.0.1:${server.address().port}/test/zip`, { headers }, onResponse);
}
it("answers HEAD without counting a view, obtaining a token or starting a ZIP stream", async function () {
utils.getRepo = async () => ({ repoId: "test", countView: () => { throw Error("view counted"); },
getToken: () => { throw Error("token requested"); }, generateAnonymizeTransformer: () => { throw Error("transform requested"); } });
got.stream = () => { throw Error("archive requested"); };
const result = await new Promise((resolve, reject) => {
http.request(`http://127.0.0.1:${server.address().port}/test/zip`, { method: "HEAD" }, res => {
const chunks = []; res.on("data", chunk => chunks.push(chunk));
res.on("end", () => resolve({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks) }));
}).on("error", reject).end();
});
expect(result.status).to.equal(200);
expect(result.headers["content-disposition"]).to.include("test.zip");
expect(result.body.length).to.equal(0);
});
it("still denies HEAD when downloads are disabled", async function () {
config.ENABLE_DOWNLOAD = false;
const status = await new Promise((resolve, reject) => {
http.request(`http://127.0.0.1:${server.address().port}/test/zip`, { method: "HEAD" }, res => {
res.resume(); res.on("end", () => resolve(res.statusCode));
}).on("error", reject).end();
});
expect(status).to.equal(403);
});
it("aborts a partial ZIP when the upstream fails after headers", async function () {
const result = new Promise((resolve, reject) => {
const client = request(response => {
expect(response.statusCode).to.equal(200);
response.once("data", () => upstream.destroy(new Error("upstream disconnected")));
response.on("aborted", resolve);
response.on("end", () => reject(new Error("truncated ZIP reported as complete")));
response.on("error", () => {});
});
client.on("error", reject);
});
upstream.write(Buffer.from("PK partial archive"));
await result;
});
it("finishes compressed JSON errors before any ZIP bytes are sent", async function () {
const error = "unavailable".repeat(300);
const result = new Promise((resolve, reject) => {
request(response => {
const chunks = [];
response.on("data", chunk => chunks.push(chunk));
response.on("error", reject);
response.on("end", () => resolve({
status: response.statusCode, encoding: response.headers["content-encoding"],
body: JSON.parse(gunzipSync(Buffer.concat(chunks)).toString()),
}));
}, { "Accept-Encoding": "gzip" }).on("error", reject);
});
upstream.once("newListener", event => {
if (event === "error") globalThis.queueMicrotask(() => upstream.destroy(Object.assign(new Error("unavailable"), {
response: { statusCode: 503, body: JSON.stringify({ error }) },
})));
});
expect(await result).to.deep.equal({ status: 502, encoding: "gzip", body: { error } });
});
it("keeps JSON errors before any ZIP bytes are sent", async function () {
const result = new Promise((resolve, reject) => {
request(response => {
let body = ""; response.on("data", chunk => { body += chunk; });
response.on("end", () => resolve({ status: response.statusCode, body: JSON.parse(body) }));
}).on("error", reject);
});
// Fail only after the route has attached its upstream listener.
upstream.once("newListener", event => {
if (event === "error") globalThis.queueMicrotask(() => upstream.destroy(Object.assign(new Error("unavailable"), {
response: { statusCode: 503, body: '{"error":"zip_not_available"}' },
})));
});
expect(await result).to.deep.equal({ status: 502, body: { error: "zip_not_available" } });
});
});