mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 18:19:51 +00:00
f4ab79f2a5
* test(datasource): replace file snapshots with explicit assertions * test(datasource): replace inline snapshots with explicit assertions * test(datasource): drop assertions implied by full equality * test(datasource): address review feedback
435 lines
13 KiB
TypeScript
435 lines
13 KiB
TypeScript
import * as httpMock from '~test/http-mock.ts';
|
|
import { GlobalConfig } from '../../../config/global.ts';
|
|
import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages.ts';
|
|
import * as hostRules from '../../../util/host-rules.ts';
|
|
import { getPkgReleases } from '../index.ts';
|
|
import { defaultRegistryUrl } from './common.ts';
|
|
import { NpmDatasource, setNpmrc } from './index.ts';
|
|
|
|
const datasource = NpmDatasource.id;
|
|
|
|
let npmResponse: any;
|
|
|
|
describe('modules/datasource/npm/index', () => {
|
|
beforeEach(() => {
|
|
GlobalConfig.reset();
|
|
hostRules.clear();
|
|
setNpmrc();
|
|
npmResponse = {
|
|
name: 'foobar',
|
|
versions: {
|
|
'0.0.1': {
|
|
foo: 1,
|
|
},
|
|
'0.0.2': {
|
|
foo: 2,
|
|
},
|
|
},
|
|
repository: {
|
|
type: 'git',
|
|
url: 'git://github.com/renovateapp/dummy.git',
|
|
directory: 'src/a',
|
|
},
|
|
homepage: 'https://github.com/renovateapp/dummy',
|
|
'dist-tags': {
|
|
latest: '0.0.1',
|
|
},
|
|
time: {
|
|
'0.0.1': '2018-05-06T07:21:53+02:00',
|
|
'0.0.2': '2018-05-07T07:21:53+02:00',
|
|
},
|
|
};
|
|
});
|
|
|
|
it('should return null for no versions', async () => {
|
|
const missingVersions = { ...npmResponse };
|
|
missingVersions.versions = {};
|
|
httpMock
|
|
.scope(defaultRegistryUrl)
|
|
.get('/foobar')
|
|
.reply(200, missingVersions);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('should fetch package info from npm', async () => {
|
|
httpMock
|
|
.scope(defaultRegistryUrl)
|
|
.get('/foobar')
|
|
.reply(200, npmResponse, { 'Cache-Control': 'public, expires=300' });
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toMatchObject({
|
|
releases: [
|
|
{ version: '0.0.1', releaseTimestamp: '2018-05-06T05:21:53.000Z' },
|
|
{ version: '0.0.2', releaseTimestamp: '2018-05-07T05:21:53.000Z' },
|
|
],
|
|
sourceDirectory: 'src/a',
|
|
sourceUrl: 'https://github.com/renovateapp/dummy',
|
|
tags: { latest: '0.0.1' },
|
|
});
|
|
expect(res?.isPrivate).toBeUndefined();
|
|
});
|
|
|
|
it('should parse repo url', async () => {
|
|
const pkg = {
|
|
name: 'foobar',
|
|
versions: {
|
|
'0.0.1': {
|
|
foo: 1,
|
|
},
|
|
},
|
|
repository: {
|
|
type: 'git',
|
|
url: 'git:github.com/renovateapp/dummy',
|
|
},
|
|
'dist-tags': {
|
|
latest: '0.0.1',
|
|
},
|
|
time: {
|
|
'0.0.1': '2018-05-06T07:21:53+02:00',
|
|
},
|
|
};
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(200, pkg);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toMatchObject({
|
|
releases: [{ version: '0.0.1' }],
|
|
sourceUrl: 'https://github.com/renovateapp/dummy',
|
|
});
|
|
});
|
|
|
|
it('should parse repo url (string)', async () => {
|
|
const pkg = {
|
|
name: 'foobar',
|
|
versions: {
|
|
'0.0.1': {
|
|
repository: 'git:github.com/renovateapp/dummy',
|
|
},
|
|
},
|
|
'dist-tags': {
|
|
latest: '0.0.1',
|
|
},
|
|
time: {
|
|
'0.0.1': '2018-05-06T07:21:53+02:00',
|
|
},
|
|
};
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(200, pkg);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toMatchObject({
|
|
releases: [{ version: '0.0.1' }],
|
|
sourceUrl: 'https://github.com/renovateapp/dummy',
|
|
});
|
|
});
|
|
|
|
it('should return deprecated', async () => {
|
|
const deprecatedPackage = {
|
|
name: 'foobar',
|
|
versions: {
|
|
'0.0.1': {
|
|
foo: 1,
|
|
},
|
|
'0.0.2': {
|
|
foo: 2,
|
|
deprecated: 'This is deprecated',
|
|
},
|
|
},
|
|
repository: {
|
|
type: 'git',
|
|
url: 'git://github.com/renovateapp/dummy.git',
|
|
},
|
|
'dist-tags': {
|
|
latest: '0.0.2',
|
|
},
|
|
time: {
|
|
'0.0.1': '2018-05-06T07:21:53+02:00',
|
|
'0.0.2': '2018-05-07T07:21:53+02:00',
|
|
},
|
|
};
|
|
httpMock
|
|
.scope(defaultRegistryUrl)
|
|
.get('/foobar')
|
|
.reply(200, deprecatedPackage);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
const deprecationMessage =
|
|
'On registry `https://registry.npmjs.org`, the "latest" version of dependency `foobar` has the following deprecation notice:\n\n`This is deprecated`\n\nMarking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake.';
|
|
expect(res).toMatchObject({
|
|
deprecationMessage,
|
|
releases: [
|
|
{ version: '0.0.1', isDeprecated: true },
|
|
{ version: '0.0.2', isDeprecated: true },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('should return attestation', async () => {
|
|
const deprecatedPackage = {
|
|
name: 'foobar',
|
|
versions: {
|
|
'0.0.1': {
|
|
foo: 1,
|
|
dist: {
|
|
attestations: {
|
|
url: `${defaultRegistryUrl}/-/npm/v1/attestations/foobar@0.0.1`,
|
|
},
|
|
},
|
|
},
|
|
'0.0.2': {
|
|
foo: 2,
|
|
dist: {
|
|
attestations: {
|
|
url: `${defaultRegistryUrl}/-/npm/v1/attestations/foobar@0.0.2`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
repository: {
|
|
type: 'git',
|
|
url: 'git://github.com/renovateapp/dummy.git',
|
|
},
|
|
'dist-tags': {
|
|
latest: '0.0.2',
|
|
},
|
|
time: {
|
|
'0.0.1': '2018-05-06T07:21:53+02:00',
|
|
'0.0.2': '2018-05-07T07:21:53+02:00',
|
|
},
|
|
};
|
|
httpMock
|
|
.scope(defaultRegistryUrl)
|
|
.get('/foobar')
|
|
.reply(200, deprecatedPackage);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toMatchObject({
|
|
releases: [
|
|
{
|
|
version: '0.0.1',
|
|
attestation: true,
|
|
},
|
|
{
|
|
version: '0.0.2',
|
|
attestation: true,
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('should handle foobar', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(200, npmResponse);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toMatchObject({
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
expect(res?.isPrivate).toBeTrue();
|
|
});
|
|
|
|
it('should handle no time', async () => {
|
|
delete npmResponse.time['0.0.2'];
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(200, npmResponse);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toMatchObject({
|
|
releases: [
|
|
{ version: '0.0.1', releaseTimestamp: '2018-05-06T05:21:53.000Z' },
|
|
{ version: '0.0.2' },
|
|
],
|
|
});
|
|
expect(res?.releases[1].releaseTimestamp).toBeUndefined();
|
|
});
|
|
|
|
it('should return null if lookup fails 401', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(401);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('should return null if lookup fails', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(404);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('should throw error for unparseable', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(200, 'oops');
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'foobar' }),
|
|
).rejects.toThrow('external-host-error');
|
|
});
|
|
|
|
it('should throw error for 429', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(429);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'foobar' }),
|
|
).rejects.toThrow('external-host-error');
|
|
});
|
|
|
|
it('should throw error for 5xx', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(503);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'foobar' }),
|
|
).rejects.toThrow(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('should throw error for 408', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(408);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'foobar' }),
|
|
).rejects.toThrow(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('should throw error for others', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(451);
|
|
await expect(
|
|
getPkgReleases({ datasource, packageName: 'foobar' }),
|
|
).rejects.toThrow('external-host-error');
|
|
});
|
|
|
|
it('should not send an authorization header if public package', async () => {
|
|
httpMock
|
|
.scope(defaultRegistryUrl, {
|
|
badheaders: ['authorization'],
|
|
})
|
|
.get('/foobar')
|
|
.reply(200, npmResponse);
|
|
const res = await getPkgReleases({ datasource, packageName: 'foobar' });
|
|
expect(res).toMatchObject({
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
});
|
|
|
|
it('should send an authorization header if provided', async () => {
|
|
httpMock
|
|
.scope(defaultRegistryUrl, {
|
|
reqheaders: { authorization: 'Basic 1234' },
|
|
})
|
|
.get('/@foobar%2Fcore')
|
|
.reply(200, { ...npmResponse, name: '@foobar/core' });
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: '@foobar/core',
|
|
npmrc: '_auth = 1234',
|
|
});
|
|
expect(res).toMatchObject({
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
});
|
|
|
|
it('should use host rules by hostName if provided', async () => {
|
|
hostRules.add({
|
|
hostType: 'npm',
|
|
matchHost: 'npm.mycustomregistry.com',
|
|
token: 'abc',
|
|
});
|
|
httpMock
|
|
.scope('https://npm.mycustomregistry.com', {
|
|
reqheaders: { authorization: 'Bearer abc' },
|
|
})
|
|
.get('/foobar')
|
|
.reply(200, npmResponse);
|
|
const npmrc = 'registry=https://npm.mycustomregistry.com/';
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'foobar',
|
|
npmrc,
|
|
});
|
|
expect(res).toMatchObject({
|
|
registryUrl: 'https://npm.mycustomregistry.com',
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
});
|
|
|
|
it('should use host rules by baseUrl if provided', async () => {
|
|
hostRules.add({
|
|
hostType: 'npm',
|
|
matchHost:
|
|
'https://npm.mycustomregistry.com/_packaging/mycustomregistry/npm/registry/',
|
|
token: 'abc',
|
|
});
|
|
httpMock
|
|
.scope(
|
|
'https://npm.mycustomregistry.com/_packaging/mycustomregistry/npm/registry',
|
|
{
|
|
reqheaders: { authorization: 'Bearer abc' },
|
|
},
|
|
)
|
|
.get('/foobar')
|
|
.reply(200, npmResponse);
|
|
const npmrc =
|
|
'registry=https://npm.mycustomregistry.com/_packaging/mycustomregistry/npm/registry/';
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'foobar',
|
|
npmrc,
|
|
});
|
|
expect(res).toMatchObject({
|
|
registryUrl:
|
|
'https://npm.mycustomregistry.com/_packaging/mycustomregistry/npm/registry',
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
});
|
|
|
|
it('resets npmrc', () => {
|
|
const npmrcContent = 'something=something';
|
|
setNpmrc(npmrcContent);
|
|
setNpmrc(npmrcContent);
|
|
expect(setNpmrc()).toBeUndefined();
|
|
});
|
|
|
|
it('should use default registry if missing from npmrc', async () => {
|
|
httpMock.scope(defaultRegistryUrl).get('/foobar').reply(200, npmResponse);
|
|
const npmrc = 'foo=bar';
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'foobar',
|
|
npmrc,
|
|
});
|
|
expect(res).toMatchObject({
|
|
registryUrl: 'https://registry.npmjs.org',
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
});
|
|
|
|
it('should fetch package info from custom registry', async () => {
|
|
httpMock
|
|
.scope('https://npm.mycustomregistry.com', {})
|
|
.get('/foobar')
|
|
.reply(200, npmResponse);
|
|
const npmrc = `registry=https://npm.mycustomregistry.com/`;
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'foobar',
|
|
npmrc,
|
|
});
|
|
expect(res).toMatchObject({
|
|
registryUrl: 'https://npm.mycustomregistry.com',
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
expect(res?.isPrivate).toBeTrue();
|
|
});
|
|
|
|
it('should replace any environment variable in npmrc', async () => {
|
|
httpMock
|
|
.scope('https://registry.from-env.com')
|
|
.get('/foobar')
|
|
.reply(200, npmResponse);
|
|
vi.stubEnv('REGISTRY', 'https://registry.from-env.com');
|
|
GlobalConfig.set({ exposeAllEnv: true });
|
|
|
|
const npmrc = 'registry=${REGISTRY}';
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'foobar',
|
|
npmrc,
|
|
});
|
|
expect(res).toMatchObject({
|
|
registryUrl: 'https://registry.from-env.com',
|
|
releases: [{ version: '0.0.1' }, { version: '0.0.2' }],
|
|
});
|
|
});
|
|
|
|
it('should throw error if necessary env var is not present', () => {
|
|
GlobalConfig.set({ exposeAllEnv: true });
|
|
|
|
expect(() => setNpmrc('registry=${REGISTRY_MISSING}')).toThrow(
|
|
Error('env-replace'),
|
|
);
|
|
});
|
|
});
|