Files
renovate/lib/config/presets/http/index.spec.ts
Jamie Tanna acb46bc706 fix(config): surface blocked hosts more clearly when resolving config
With the new hardening on blocking internal hosts with outbound HTTP
calls, we should build on top of this to ensure that the UX is better
when a preset is blocked.

We can do this by making sure that when `HOST_BLOCKED`, we report a more
targeted config validation error.

We can also make sure to pass around our `internalGrants`, similar to
how we currently allow an admin's `headers` are handled.

Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-10 14:52:01 +01:00

113 lines
3.5 KiB
TypeScript

import { hostRules } from '~test/host-rules.ts';
import * as httpMock from '~test/http-mock.ts';
import { HOST_BLOCKED } from '../../../constants/error-messages.ts';
import { ExternalHostError } from '../../../types/errors/external-host-error.ts';
import { GlobalConfig } from '../../global.ts';
import { PRESET_DEP_NOT_FOUND, PRESET_INVALID_JSON } from '../util.ts';
import * as http from './index.ts';
const host = 'https://my.server/';
const filePath = '/test-preset.json';
const repo = 'https://my.server/test-preset.json';
describe('config/presets/http/index', () => {
describe('getPreset()', () => {
it('should return parsed JSON', async () => {
httpMock.scope(host).get(filePath).reply(200, { foo: 'bar' });
await expect(http.getPreset({ repo })).resolves.toEqual({ foo: 'bar' });
});
it('should return parsed JSON5', async () => {
httpMock
.scope('https://my.server/')
.get('/test-preset.json5')
.reply(200, '{ foo: "bar" } // comment');
const repo = 'https://my.server/test-preset.json5';
await expect(http.getPreset({ repo })).resolves.toEqual({ foo: 'bar' });
});
it('should return parsed JSONC', async () => {
httpMock
.scope('https://my.server/')
.get('/test-preset.jsonc')
.reply(200, '{ "foo": "bar" } // comment');
const repo = 'https://my.server/test-preset.jsonc';
await expect(http.getPreset({ repo })).resolves.toEqual({ foo: 'bar' });
});
describe('internal hosts', () => {
beforeEach(() => {
GlobalConfig.set({ internalHostAccess: 'block' });
});
afterEach(() => {
GlobalConfig.reset();
});
it('throws a distinct error for a blocked host', async () => {
// no mocked response: the request is blocked before it is ever made
await expect(
http.getPreset({ repo: 'http://10.1.2.3/test-preset.json' }),
).rejects.toThrow(HOST_BLOCKED);
});
it('stays blocked when the admin only named the host', async () => {
hostRules.add({ matchHost: '10.1.2.3' }, { trusted: true });
await expect(
http.getPreset({ repo: 'http://10.1.2.3/test-preset.json' }),
).rejects.toThrow(HOST_BLOCKED);
});
it('is fetched under a scoped grant', async () => {
hostRules.add(
{ hostType: 'preset', matchHost: '10.1.2.3', allowInternal: true },
{ trusted: true },
);
httpMock
.scope('http://10.1.2.3')
.get('/test-preset.json')
.reply(200, { foo: 'bar' });
await expect(
http.getPreset({ repo: 'http://10.1.2.3/test-preset.json' }),
).resolves.toEqual({ foo: 'bar' });
});
});
it('throws if fails to parse', async () => {
httpMock.scope(host).get(filePath).reply(200, 'not json');
await expect(http.getPreset({ repo })).rejects.toThrow(
PRESET_INVALID_JSON,
);
});
it('throws if file not found', async () => {
httpMock.scope(host).get(filePath).reply(404);
await expect(http.getPreset({ repo })).rejects.toThrow(
PRESET_DEP_NOT_FOUND,
);
});
it('throws on malformed URL', async () => {
await expect(http.getPreset({ repo: 'malformed!' })).rejects.toThrow(
PRESET_DEP_NOT_FOUND,
);
});
it('throws external host error', async () => {
httpMock.scope(host).get(filePath).reply(404, {});
hostRules.add({ abortOnError: true });
await expect(http.getPreset({ repo })).rejects.toThrow(ExternalHostError);
});
});
});