mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 10:09:50 +00:00
05e1271376
Enables the vitest/prefer-expect-resolves oxlint rule and applies its autofix across the spec suite.
930 lines
24 KiB
TypeScript
930 lines
24 KiB
TypeScript
import { generateKeyPair, sign as signPayload } from 'node:crypto';
|
|
import { promisify } from 'node:util';
|
|
import { gzip as _gzip } from 'node:zlib';
|
|
import protobuf from 'protobufjs';
|
|
import upath from 'upath';
|
|
import { Fixtures } from '~test/fixtures.ts';
|
|
import { hostRules } from '~test/host-rules.ts';
|
|
import * as httpMock from '~test/http-mock.ts';
|
|
import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages.ts';
|
|
import * as memCache from '../../../util/cache/memory/index.ts';
|
|
import { getPkgReleases } from '../index.ts';
|
|
import { HexDatasource } from './index.ts';
|
|
import type { Package } from './v2/package.ts';
|
|
import { Package as PackageCodec } from './v2/package.ts';
|
|
import { Signed as SignedCodec } from './v2/signed.ts';
|
|
|
|
const certifiResponse = Fixtures.get('certifi.json');
|
|
const privatePackageResponse = Fixtures.get('private_package.json');
|
|
|
|
const baseUrl = 'https://hex.pm/api';
|
|
const datasource = HexDatasource.id;
|
|
const gzip = promisify(_gzip);
|
|
|
|
const { privateKey: testPrivateKey, publicKey: testPublicKey } =
|
|
await promisify(generateKeyPair)('rsa', {
|
|
modulusLength: 2048,
|
|
privateKeyEncoding: {
|
|
format: 'pem',
|
|
type: 'pkcs8',
|
|
},
|
|
publicKeyEncoding: {
|
|
format: 'pem',
|
|
type: 'spki',
|
|
},
|
|
});
|
|
|
|
interface V2ResponseOptions {
|
|
privateKey?: string;
|
|
signature?: Buffer;
|
|
}
|
|
|
|
function protobufLoad(file: string): Promise<protobuf.Root> {
|
|
const resolvedFile = upath.join(import.meta.dirname, 'v2', file);
|
|
return new Promise((resolve, reject) => {
|
|
protobuf.load(resolvedFile, (err, root) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
if (!root) {
|
|
reject(new Error('Root is empty'));
|
|
return;
|
|
}
|
|
|
|
resolve(root);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function encodePackage(input: unknown): Promise<Buffer> {
|
|
const message = PackageCodec.fromJSON(input);
|
|
const root = await protobufLoad('package.proto');
|
|
const x = root.lookupType('Package').encode(message).finish();
|
|
return Buffer.from(x);
|
|
}
|
|
|
|
async function encodeSigned(input: unknown): Promise<Buffer> {
|
|
const message = SignedCodec.fromJSON(input);
|
|
const root = await protobufLoad('signed.proto');
|
|
const x = root.lookupType('Signed').encode(message).finish();
|
|
return Buffer.from(x);
|
|
}
|
|
|
|
async function makeV2Response(
|
|
pkg: Package,
|
|
options: V2ResponseOptions = {},
|
|
): Promise<Buffer> {
|
|
const payload = await encodePackage(pkg);
|
|
const signature =
|
|
options.signature ??
|
|
(options.privateKey
|
|
? signPayload('RSA-SHA512', payload, options.privateKey)
|
|
: Buffer.from('fake-sig'));
|
|
const signed = await encodeSigned({
|
|
payload,
|
|
signature,
|
|
});
|
|
return await gzip(signed);
|
|
}
|
|
|
|
function mockPublicKeyUnavailable(registryUrl: string): void {
|
|
httpMock.scope(registryUrl).get('/public_key').reply(404);
|
|
}
|
|
|
|
function mockPublicKey(registryUrl: string, publicKey: string): void {
|
|
httpMock.scope(registryUrl).get('/public_key').reply(200, publicKey);
|
|
}
|
|
|
|
describe('modules/datasource/hex/index', () => {
|
|
beforeEach(() => {
|
|
memCache.init();
|
|
});
|
|
|
|
afterEach(() => {
|
|
memCache.reset();
|
|
});
|
|
|
|
describe('getReleases', () => {
|
|
it('returns null for empty result', async () => {
|
|
httpMock.scope(baseUrl).get('/packages/non_existent_package').reply(200);
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'non_existent_package',
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('returns null for missing fields', async () => {
|
|
httpMock
|
|
.scope(baseUrl)
|
|
.get('/packages/non_existent_package')
|
|
.reply(200, {});
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'non_existent_package',
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('returns null for 404', async () => {
|
|
httpMock.scope(baseUrl).get('/packages/some_package').reply(404);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'some_package' }),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('returns null for 401', async () => {
|
|
httpMock.scope(baseUrl).get('/packages/some_package').reply(401);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'some_package' }),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('throws for 429', async () => {
|
|
httpMock.scope(baseUrl).get('/packages/some_crate').reply(429);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'some_crate' }),
|
|
).rejects.toThrow(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('throws for 5xx', async () => {
|
|
httpMock.scope(baseUrl).get('/packages/some_crate').reply(502);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'some_crate' }),
|
|
).rejects.toThrow(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('returns null for unknown error', async () => {
|
|
httpMock.scope(baseUrl).get('/packages/some_package').replyWithError('');
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'some_package' }),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('returns null with wrong auth token', async () => {
|
|
httpMock
|
|
.scope(baseUrl, {
|
|
reqheaders: {
|
|
authorization: 'abc',
|
|
},
|
|
})
|
|
.get('/packages/certifi')
|
|
.reply(401);
|
|
|
|
hostRules.add({
|
|
authType: 'Token-Only',
|
|
token: 'abc',
|
|
});
|
|
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'certifi',
|
|
});
|
|
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('processes real data', async () => {
|
|
httpMock
|
|
.scope(baseUrl)
|
|
.get('/packages/certifi')
|
|
.reply(200, certifiResponse);
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'certifi',
|
|
});
|
|
expect(res).toMatchObject({
|
|
releases: [
|
|
{
|
|
version: '0.1.1',
|
|
isDeprecated: true,
|
|
releaseTimestamp: '2015-09-10T13:58:55.620Z',
|
|
},
|
|
{ version: '0.2.0' },
|
|
{ version: '0.3.0' },
|
|
{ version: '0.4.0' },
|
|
{ version: '0.5.0' },
|
|
{ version: '0.6.0' },
|
|
{ version: '0.7.0' },
|
|
{ version: '1.0.0' },
|
|
{ version: '1.1.0' },
|
|
{ version: '1.2.0' },
|
|
{ version: '1.2.1' },
|
|
{ version: '2.0.0' },
|
|
{ version: '2.1.0' },
|
|
{ version: '2.2.0' },
|
|
{ version: '2.3.0' },
|
|
{ version: '2.3.1' },
|
|
{ version: '2.4.1' },
|
|
{ version: '2.4.2' },
|
|
{ version: '2.5.1' },
|
|
{
|
|
version: '2.5.2',
|
|
releaseTimestamp: '2020-03-04T14:54:16.283Z',
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('process public repo without auth', async () => {
|
|
httpMock
|
|
.scope(baseUrl)
|
|
.get('/packages/certifi')
|
|
.reply(200, certifiResponse);
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'certifi',
|
|
});
|
|
expect(res).toMatchObject({
|
|
releases: [
|
|
{ version: '0.1.1' },
|
|
{ version: '0.2.0' },
|
|
{ version: '0.3.0' },
|
|
{ version: '0.4.0' },
|
|
{ version: '0.5.0' },
|
|
{ version: '0.6.0' },
|
|
{ version: '0.7.0' },
|
|
{ version: '1.0.0' },
|
|
{ version: '1.1.0' },
|
|
{ version: '1.2.0' },
|
|
{ version: '1.2.1' },
|
|
{ version: '2.0.0' },
|
|
{ version: '2.1.0' },
|
|
{ version: '2.2.0' },
|
|
{ version: '2.3.0' },
|
|
{ version: '2.3.1' },
|
|
{ version: '2.4.1' },
|
|
{ version: '2.4.2' },
|
|
{ version: '2.5.1' },
|
|
{ version: '2.5.2' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('extracts depreceated info', async () => {
|
|
httpMock
|
|
.scope(baseUrl)
|
|
.get('/packages/certifi')
|
|
.reply(200, certifiResponse);
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'certifi',
|
|
});
|
|
expect(res?.releases.some((rel) => rel.isDeprecated)).toBeTrue();
|
|
});
|
|
|
|
it('processes a private repo with auth', async () => {
|
|
httpMock
|
|
.scope(baseUrl, {
|
|
reqheaders: {
|
|
authorization: 'abc',
|
|
},
|
|
})
|
|
.get('/repos/renovate_test/packages/private_package')
|
|
.reply(200, privatePackageResponse);
|
|
|
|
hostRules.add({
|
|
authType: 'Token-Only',
|
|
token: 'abc',
|
|
});
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'private_package:renovate_test',
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
homepage: 'https://hex.pm/packages/renovate_test/private_package',
|
|
sourceUrl: 'https://github.com/renovate_test/private_package',
|
|
registryUrl: 'https://hex.pm',
|
|
releases: [
|
|
{ releaseTimestamp: '2021-08-04T15:26:26.500Z', version: '0.1.0' },
|
|
{ releaseTimestamp: '2021-08-04T17:46:00.274Z', version: '0.1.1' },
|
|
],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getReleases (V2 protocol)', () => {
|
|
const customRegistryUrl = 'https://repo.custom-registry.example.com';
|
|
|
|
it('extracts versions from V2 response', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
{
|
|
version: '2.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
{
|
|
version: '3.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(customRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [
|
|
{ version: '1.0.0' },
|
|
{ version: '2.0.0' },
|
|
{ version: '3.0.0' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('marks retired releases as deprecated', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
retired: { reason: 3, message: 'use 2.0.0' },
|
|
},
|
|
{
|
|
version: '2.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(customRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [
|
|
{ version: '1.0.0', isDeprecated: true },
|
|
{ version: '2.0.0' },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('filters releases without versions', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
{
|
|
version: '2.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(customRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [{ version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('handles organization packages via V2', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'private_pkg',
|
|
repository: 'my_org',
|
|
releases: [
|
|
{
|
|
version: '0.1.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(customRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/repos/my_org/packages/private_pkg')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'private_pkg:my_org',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [{ version: '0.1.0' }],
|
|
});
|
|
});
|
|
|
|
it('returns null for empty releases', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'empty_package',
|
|
repository: 'custom',
|
|
releases: [],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(customRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/empty_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'empty_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('throws for 5xx errors', async () => {
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/some_package')
|
|
.reply(502);
|
|
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'some_package',
|
|
registryUrls: [customRegistryUrl],
|
|
}),
|
|
).rejects.toThrow(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('throws for 429 errors', async () => {
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/some_package')
|
|
.reply(429);
|
|
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'some_package',
|
|
registryUrls: [customRegistryUrl],
|
|
}),
|
|
).rejects.toThrow(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('returns null for 404', async () => {
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/some_package')
|
|
.reply(404);
|
|
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'some_package',
|
|
registryUrls: [customRegistryUrl],
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('returns null for network error', async () => {
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/some_package')
|
|
.replyWithError('connection refused');
|
|
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'some_package',
|
|
registryUrls: [customRegistryUrl],
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('returns null for malformed gzip', async () => {
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/bad_package')
|
|
.reply(200, Buffer.from('not-gzip-data'));
|
|
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'bad_package',
|
|
registryUrls: [customRegistryUrl],
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it('verifies signature when public key is available', async () => {
|
|
const v2Body = await makeV2Response(
|
|
{
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
},
|
|
{ privateKey: testPrivateKey },
|
|
);
|
|
|
|
mockPublicKey(customRegistryUrl, testPublicKey);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [{ version: '1.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('returns null for invalid signature when public key is available', async () => {
|
|
const v2Body = await makeV2Response(
|
|
{
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
},
|
|
{ signature: Buffer.from('invalid-signature') },
|
|
);
|
|
|
|
mockPublicKey(customRegistryUrl, testPublicKey);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null for missing signature when public key is available', async () => {
|
|
const v2Body = await makeV2Response(
|
|
{
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
},
|
|
{ signature: Buffer.alloc(0) },
|
|
);
|
|
|
|
mockPublicKey(customRegistryUrl, testPublicKey);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null for malformed public key when verification is enabled', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKey(customRegistryUrl, 'not-a-valid-public-key');
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('falls back to unsigned payload when public key response is empty', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'my_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKey(customRegistryUrl, ' ');
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [{ version: '1.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('uses pinned Hex public key for repo.hex.pm', async () => {
|
|
const defaultV2RegistryUrl = 'https://repo.hex.pm';
|
|
const v2Body = await makeV2Response({
|
|
name: 'my_package',
|
|
repository: 'hexpm',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
httpMock
|
|
.scope(defaultV2RegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [defaultV2RegistryUrl],
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('maps repo.hex.pm host aliases to hexpm repository checks', async () => {
|
|
const hostAliasRegistryUrl = 'https://repo.hex.pm:443';
|
|
const v2Body = await makeV2Response({
|
|
name: 'my_package',
|
|
repository: 'hexpm',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(hostAliasRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(hostAliasRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [hostAliasRegistryUrl],
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
registryUrl: hostAliasRegistryUrl,
|
|
releases: [{ version: '1.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('caches public key responses for subsequent package lookups', async () => {
|
|
const v2BodyOne = await makeV2Response(
|
|
{
|
|
name: 'first_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
},
|
|
{ privateKey: testPrivateKey },
|
|
);
|
|
|
|
const v2BodyTwo = await makeV2Response(
|
|
{
|
|
name: 'second_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '2.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
},
|
|
{ privateKey: testPrivateKey },
|
|
);
|
|
|
|
mockPublicKey(customRegistryUrl, testPublicKey);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/first_package')
|
|
.reply(200, v2BodyOne);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/second_package')
|
|
.reply(200, v2BodyTwo);
|
|
|
|
const firstResult = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'first_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
const secondResult = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'second_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(firstResult).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [{ version: '1.0.0' }],
|
|
});
|
|
|
|
expect(secondResult).toEqual({
|
|
registryUrl: customRegistryUrl,
|
|
releases: [{ version: '2.0.0' }],
|
|
});
|
|
});
|
|
|
|
it('returns null for package name mismatch', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'different_package',
|
|
repository: 'custom',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(customRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/packages/my_package')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'my_package',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('returns null for organization repository mismatch', async () => {
|
|
const v2Body = await makeV2Response({
|
|
name: 'private_pkg',
|
|
repository: 'wrong_org',
|
|
releases: [
|
|
{
|
|
version: '1.0.0',
|
|
innerChecksum: new Uint8Array(),
|
|
dependencies: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
mockPublicKeyUnavailable(customRegistryUrl);
|
|
|
|
httpMock
|
|
.scope(customRegistryUrl)
|
|
.get('/repos/my_org/packages/private_pkg')
|
|
.reply(200, v2Body);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'private_pkg:my_org',
|
|
registryUrls: [customRegistryUrl],
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('uses JSON API for hex.pm default registry', async () => {
|
|
httpMock
|
|
.scope(baseUrl)
|
|
.get('/packages/certifi')
|
|
.reply(200, certifiResponse);
|
|
|
|
const result = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'certifi',
|
|
});
|
|
|
|
expect(result).not.toBeNull();
|
|
expect(result?.releases.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|