Files
Michael Kriese 4dc30ea9bc fix(http): share concurrent identical cache provider requests (#46627)
* fix(http): share concurrent identical cache provider requests

Identical requests that use a cache provider and run at the same time now
share one in-flight request instead of each fetching from the server.
Callers that join an in-flight request get a deep copy of the response.

This replaces the per-URL mutex, which only covered the cache check and
was released before the fetch finished.

Co-Authored-By: Claude Opus 5.5 <michael.kriese+claude-code@mend.io>

* fix(http): reuse cache providers for npm and typst lookups

The npm and typst datasources now use one module-level cache provider, so
concurrent lookups of the same package share one request. The request key
is only hashed for GET and HEAD requests and includes `throwHttpErrors`.

Co-Authored-By: Claude Opus 5.5 <michael.kriese+claude-code@mend.io>
Co-Authored-By: Claude Sonnet 5.5 <michael.kriese+claude-code@mend.io>

* fix(http): don't share requests that differ in response type

Add `responseType` and `responseBecomesConfig` to the key of shared
requests, and test that requests with different headers aren't shared
and that a shared response is copied for every caller.

Co-Authored-By: Claude Opus 5.5 <michael.kriese+claude-code@mend.io>
Co-Authored-By: Claude Sonnet 5.5 <michael.kriese+claude-code@mend.io>

* fix(http): keep in-flight requests per Http class and copy binary bodies

Co-Authored-By: Claude Opus 5.5 <michael.kriese+claude-code@mend.io>

---------

Co-authored-by: Claude Opus 5.5 <michael.kriese+claude-code@mend.io>
2026-10-13 15:38:10 +00:00

39 lines
1.1 KiB
TypeScript

import { HOST_BLOCKED, HOST_DISABLED } from '../../constants/error-messages.ts';
import { copyResponse, refusedHostMessage } from './util.ts';
describe('util/http/util', () => {
describe('copyResponse', () => {
it('deep copies a Buffer body', () => {
const body = Buffer.from('abc');
const res = copyResponse({ statusCode: 200, headers: {}, body }, true);
body[0] = 0x78;
expect(Buffer.isBuffer(res.body)).toBeTrue();
expect(res.body).toEqual(Buffer.from('abc'));
});
it('deep copies a Uint8Array body', () => {
const body = new Uint8Array([1, 2, 3]);
const res = copyResponse(
{ statusCode: 200, headers: {}, body, cached: true },
true,
);
body[0] = 9;
expect(res.body).toEqual(new Uint8Array([1, 2, 3]));
expect(res.cached).toBeTrue();
});
});
describe('refusedHostMessage', () => {
it('distinguishes a blocked host from a disabled one', () => {
expect(refusedHostMessage(new Error(HOST_BLOCKED))).toBe('Host blocked');
expect(refusedHostMessage(new Error(HOST_DISABLED))).toBe(
'Host disabled',
);
});
});
});