import * as marshal from '@hyrious/marshal'; import { codeBlock } from 'common-tags'; import * as httpMock from '~test/http-mock.ts'; import { ExternalHostError } from '../../../types/errors/external-host-error.ts'; import * as rubyVersioning from '../../versioning/ruby/index.ts'; import { getPkgReleases } from '../index.ts'; import { RubygemsDatasource } from './index.ts'; import { memCache } from './versions-endpoint-cache.ts'; const rubygemsOrgVersions = codeBlock` created_at: 2023-01-01T00:00:00.000Z --- foobar 1.0.0,2.0.0,3.0.0 01010101010101010101010101010101 `; function rubyMarshal(data: unknown) { return Buffer.from(marshal.dump(data)); } describe('modules/datasource/rubygems/index', () => { describe('getReleases', () => { beforeEach(() => { memCache.clear(); }); it('returns null for missing pkg', async () => { httpMock .scope('https://example.com') .get('/api/v1/versions/foobar.json') .reply(200, []) .get('/info/foobar') .reply(200, '') .get('/api/v1/dependencies?gems=foobar') .reply(200, rubyMarshal([])); await expect( getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: ['https://example.com'], }), ).resolves.toBeNull(); }); it('returns null for rubygems.org package miss', async () => { httpMock.scope('https://rubygems.org').get('/versions').reply(404); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: [], }); expect(res).toBeNull(); }); it('returns a dep for rubygems.org package hit', async () => { httpMock .scope('https://rubygems.org') .get('/versions') .reply(200, rubygemsOrgVersions) .get('/api/v1/versions/foobar.json') .reply(200, [ { number: '1.0.0', created_at: '2021-01-01' }, { number: '2.0.0', created_at: '2022-01-01' }, { number: '3.0.0', created_at: '2023-01-01' }, ]) .get('/api/v1/gems/foobar.json') .reply(200, {}); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: [], }); expect(res).toEqual({ registryUrl: 'https://rubygems.org', releases: [ { version: '1.0.0', releaseTimestamp: '2021-01-01T00:00:00.000Z' }, { version: '2.0.0', releaseTimestamp: '2022-01-01T00:00:00.000Z' }, { version: '3.0.0', releaseTimestamp: '2023-01-01T00:00:00.000Z' }, ], }); }); it('uses rubygems.org if no registry urls were provided', async () => { httpMock .scope('https://rubygems.org') .get('/versions') .reply(200, rubygemsOrgVersions) .get('/api/v1/gems/foobar.json') .reply(200, {}) .get('/api/v1/versions/foobar.json') .reply(200, [ { number: '1.0.0', created_at: '2021-01-01' }, { number: '2.0.0', created_at: '2022-01-01' }, { number: '3.0.0', created_at: '2023-01-01' }, ]); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: [], }); expect(res).toEqual({ registryUrl: 'https://rubygems.org', releases: [ { version: '1.0.0', releaseTimestamp: '2021-01-01T00:00:00.000Z' }, { version: '2.0.0', releaseTimestamp: '2022-01-01T00:00:00.000Z' }, { version: '3.0.0', releaseTimestamp: '2023-01-01T00:00:00.000Z' }, ], }); }); it('uses multiple source urls', async () => { httpMock .scope('https://registry-1.com/') .get('/api/v1/versions/foobar.json') .reply(404) .get('/info/foobar') .reply(404) .get('/api/v1/dependencies?gems=foobar') .reply(404); httpMock .scope('https://registry-2.com/nested/path') .get('/api/v1/gems/foobar.json') .reply(200, {}) .get('/api/v1/versions/foobar.json') .reply(200, [ { number: '1.0.0', created_at: '2021-01-01' }, { number: '2.0.0', created_at: '2022-01-01' }, { number: '3.0.0', created_at: '2023-01-01' }, ]); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: [ 'https://registry-1.com', 'https://registry-2.com/nested/path', ], }); expect(res).toEqual({ registryUrl: 'https://registry-2.com/nested/path', releases: [ { version: '1.0.0', releaseTimestamp: '2021-01-01T00:00:00.000Z' }, { version: '2.0.0', releaseTimestamp: '2022-01-01T00:00:00.000Z' }, { version: '3.0.0', releaseTimestamp: '2023-01-01T00:00:00.000Z' }, ], }); }); it('falls back to dependencies API', async () => { httpMock .scope('https://example.com/') .get('/api/v1/versions/foobar.json') .reply(404, {}) .get('/info/foobar') .reply(404) .get('/api/v1/dependencies?gems=foobar') .reply( 200, rubyMarshal([ { number: '1.0.0' }, { number: '2.0.0' }, { number: '3.0.0' }, ]), ); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: ['https://example.com'], }); expect(res).toEqual({ registryUrl: 'https://example.com', releases: [ { version: '1.0.0' }, { version: '2.0.0' }, { version: '3.0.0' }, ], }); }); it('supports /info endpoint', async () => { httpMock .scope('https://example.com/') .get('/api/v1/versions/foobar.json') .reply(404) .get('/info/foobar') .reply( 200, codeBlock` 1.0.0 |checksum:aaa 2.0.0 |checksum:bbb 3.0.0 |checksum:ccc `, ); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: ['https://example.com'], }); expect(res).toEqual({ registryUrl: 'https://example.com', releases: [ { version: '1.0.0' }, { version: '2.0.0' }, { version: '3.0.0' }, ], }); }); it('errors when version request fails with server error', async () => { httpMock .scope('https://example.com/') .get('/api/v1/versions/foobar.json') .reply(500); await expect( getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: ['https://example.com'], }), ).rejects.toThrow(ExternalHostError); }); it('errors when dependencies request fails server error', async () => { httpMock .scope('https://example.com/') .get('/info/foobar') .reply(404) .get('/api/v1/versions/foobar.json') .reply(404) .get('/api/v1/dependencies?gems=foobar') .reply(500); await expect( getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: ['https://example.com'], }), ).rejects.toThrow(ExternalHostError); }); it('returns null for GitHub Packages package miss', async () => { httpMock .scope('https://rubygems.pkg.github.com/example') .get('/api/v1/dependencies?gems=foobar') .reply(200, rubyMarshal([])); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: ['https://rubygems.pkg.github.com/example'], }); expect(res).toBeNull(); }); it('returns a dep for GitHub Packages package hit', async () => { httpMock .scope('https://rubygems.pkg.github.com/example') .get('/api/v1/dependencies?gems=foobar') .reply( 200, rubyMarshal([ { number: '1.0.0' }, { number: '2.0.0' }, { number: '3.0.0' }, ]), ); const res = await getPkgReleases({ versioning: rubyVersioning.id, datasource: RubygemsDatasource.id, packageName: 'foobar', registryUrls: ['https://rubygems.pkg.github.com/example'], }); expect(res).toEqual({ registryUrl: 'https://rubygems.pkg.github.com/example', releases: [ { version: '1.0.0' }, { version: '2.0.0' }, { version: '3.0.0' }, ], }); }); }); });