Files
renovate/lib/modules/datasource/apk/index.spec.ts
Punsoong Khut 795d083264 feat(datasource): add APK datasource support (#40250)
* feat(datasource): add APK datasource support

* remove missing manager link for now

* lint docs

* update tests

* replace binary fixtures

* fix coverage

* Update lib/modules/datasource/apk/readme.md

Co-authored-by: Jamie Tanna <github@jamietanna.co.uk>

* Apply suggestion from @viceice

Co-authored-by: Michael Kriese <michael.kriese@gmx.de>

* use tar instead of tar-stream

* use codeBlock

* add ignore back

* make method private

* use an async extract instead of list

* add usage example

* stream apkindex download and parse line-by-line

* refactor(datasource): address review comments on APK datasource

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: sync pnpm-lock.yaml after merge

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: remove ignored lines

As they seem to be covered, anyway.

* fixup! Merge remote-tracking branch 'origin/HEAD' into apk-datasource

* fixup! fixup! Merge remote-tracking branch 'origin/HEAD' into apk-datasource

* lint: use `node:` prefix

* Update lib/modules/datasource/apk/index.ts

Co-authored-by: Michael Kriese <michael.kriese@gmx.de>

* refactor(datasource/apk): use asTimestamp millis instead of new Date

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(datasource/apk): rename uncached method to _getPackages

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(datasource/apk): encode registry URL parts as query parameters

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(datasource/apk): reject invalid registry URLs, skip caching empty indexes

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(datasource/apk): throw on unparseable registry URL and drop unset sourceUrl support

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(datasource/apk): show how registry URL parameters build the index URLs

Co-authored-by: Cursor <cursoragent@cursor.com>

* Apply suggestions from code review

Co-authored-by: RahulGautamSingh <rahultesnik@gmail.com>

* fix(datasource/apk): keep credentials and port in component URLs

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(datasource/apk): extract APKINDEX by basename and key packages by name

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(datasource/apk): report a version served by two components only once

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Jamie Tanna <github@jamietanna.co.uk>
Co-authored-by: Michael Kriese <michael.kriese@gmx.de>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
Co-authored-by: RahulGautamSingh <rahultesnik@gmail.com>
2026-09-04 12:25:21 +00:00

784 lines
23 KiB
TypeScript

import { promisify } from 'node:util';
import * as zlib from 'node:zlib';
import { codeBlock } from 'common-tags';
import { Header, Pack, ReadEntry } from 'tar';
import type { DirectoryResult } from 'tmp-promise';
import { dir } from 'tmp-promise';
import { vi } from 'vitest';
import * as httpMock from '~test/http-mock.ts';
import { GlobalConfig } from '../../../config/global.ts';
import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages.ts';
import { getPkgReleases } from '../index.ts';
import type { GetPkgReleasesConfig } from '../types.ts';
import { ApkDatasource } from './index.ts';
import * as parser from './parser.ts';
const gzip = promisify(zlib.gzip);
async function createTarGz(
entries: { name: string; content: string }[],
): Promise<Buffer> {
const pack = new Pack({ gzip: true });
for (const entry of entries) {
const data = Buffer.from(entry.content, 'utf8');
const header = new Header({
path: entry.name,
size: data.length,
type: 'File',
});
const readEntry = new ReadEntry(header);
readEntry.end(data);
pack.add(readEntry);
}
pack.end();
const chunks: Buffer[] = [];
for await (const chunk of pack) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
const nginxApkIndex = [
'P:nginx',
'V:1.24.0-r16',
'U:https://www.nginx.org/',
't:1714170434',
].join('\n');
describe('modules/datasource/apk/index', () => {
let apkIndexArchive: Buffer;
let cacheDir: DirectoryResult | null;
beforeAll(async () => {
apkIndexArchive = await createTarGz([
{ name: 'APKINDEX', content: nginxApkIndex },
]);
});
beforeEach(async () => {
cacheDir = await dir({ unsafeCleanup: true });
GlobalConfig.set({ cacheDir: cacheDir.path });
});
afterEach(async () => {
await cacheDir?.cleanup();
cacheDir = null;
});
const apkDatasource = new ApkDatasource();
it('should export ApkDatasource', () => {
expect(ApkDatasource).toBeDefined();
});
it('should have correct id', () => {
expect(ApkDatasource.id).toBe('apk');
});
it('should have default registry URLs', () => {
expect(apkDatasource.defaultRegistryUrls).toEqual([
'https://dl-cdn.alpinelinux.org/alpine?branch=latest-stable&components=main&arch=x86_64',
]);
});
it('should support custom registries', () => {
expect(apkDatasource.customRegistrySupport).toBe(true);
});
describe('getReleases', () => {
it('should return null for unknown package', async () => {
const config: GetPkgReleasesConfig = {
datasource: 'apk',
packageName: 'unknown-package',
};
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz')
.reply(200, apkIndexArchive);
const res = await getPkgReleases(config);
expect(res).toBeNull();
});
it('should use default registry URLs', async () => {
const config: GetPkgReleasesConfig = {
datasource: 'apk',
packageName: 'nginx',
};
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz')
.reply(200, apkIndexArchive);
const res = await getPkgReleases(config);
expect(res).toEqual({
homepage: 'https://www.nginx.org/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=latest-stable&components=main&arch=x86_64',
releases: [
{
version: '1.24.0-r16',
releaseTimestamp: '2024-04-26T22:27:14.000Z',
},
],
});
});
it('should use custom registry URLs', async () => {
const config: GetPkgReleasesConfig = {
datasource: 'apk',
packageName: 'nginx',
registryUrls: [
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main&arch=x86_64',
],
};
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/v3.19/main/x86_64/APKINDEX.tar.gz')
.reply(200, apkIndexArchive);
const res = await getPkgReleases(config);
expect(res).toEqual({
homepage: 'https://www.nginx.org/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main&arch=x86_64',
releases: [
{
version: '1.24.0-r16',
releaseTimestamp: '2024-04-26T22:27:14.000Z',
},
],
});
});
it('should aggregate releases across components', async () => {
const communityArchive = await createTarGz([
{
name: 'APKINDEX',
content: ['P:nginx', 'V:1.26.2-r0', 't:1747894670'].join('\n'),
},
]);
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/v3.19/main/x86_64/APKINDEX.tar.gz')
.reply(200, apkIndexArchive)
.get('/alpine/v3.19/community/x86_64/APKINDEX.tar.gz')
.reply(200, communityArchive);
const res = await apkDatasource.getReleases({
packageName: 'nginx',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
});
expect(res).toEqual({
homepage: 'https://www.nginx.org/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
releases: [
{
version: '1.24.0-r16',
releaseTimestamp: '2024-04-26T22:27:14.000Z',
},
{
version: '1.26.2-r0',
releaseTimestamp: '2025-05-22T06:17:50.000Z',
},
],
});
});
it('should report a version served by two components only once', async () => {
const communityArchive = await createTarGz([
{
name: 'APKINDEX',
content: ['P:nginx', 'V:1.24.0-r16', 't:1714170434'].join('\n'),
},
]);
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/v3.19/main/x86_64/APKINDEX.tar.gz')
.reply(200, apkIndexArchive)
.get('/alpine/v3.19/community/x86_64/APKINDEX.tar.gz')
.reply(200, communityArchive);
const res = await apkDatasource.getReleases({
packageName: 'nginx',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
});
expect(res).toEqual({
homepage: 'https://www.nginx.org/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
releases: [
{
version: '1.24.0-r16',
releaseTimestamp: '2024-04-26T22:27:14.000Z',
},
],
});
});
it('should skip components which do not contain the package', async () => {
const communityArchive = await createTarGz([
{
name: 'APKINDEX',
content: ['P:docker', 'V:27.3.1-r0', 't:1747894670'].join('\n'),
},
]);
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/v3.19/main/x86_64/APKINDEX.tar.gz')
.reply(200, apkIndexArchive)
.get('/alpine/v3.19/community/x86_64/APKINDEX.tar.gz')
.reply(200, communityArchive);
const res = await apkDatasource.getReleases({
packageName: 'docker',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
});
expect(res).toEqual({
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
releases: [
{
version: '27.3.1-r0',
releaseTimestamp: '2025-05-22T06:17:50.000Z',
},
],
});
});
it('should skip components which cannot be fetched', async () => {
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/v3.19/testing/x86_64/APKINDEX.tar.gz')
.reply(404)
.get('/alpine/v3.19/main/x86_64/APKINDEX.tar.gz')
.reply(200, apkIndexArchive);
const res = await apkDatasource.getReleases({
packageName: 'nginx',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=testing,main&arch=x86_64',
});
expect(res).toEqual({
homepage: 'https://www.nginx.org/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=testing,main&arch=x86_64',
releases: [
{
version: '1.24.0-r16',
releaseTimestamp: '2024-04-26T22:27:14.000Z',
},
],
});
});
it('should use the homepage of the first version which has one', async () => {
const archive = await createTarGz([
{
name: 'APKINDEX',
content: codeBlock`
P:bash
V:5.3-r1
t:1752770415
P:bash
V:5.3-r2
U:https://www.gnu.org/software/bash/
t:1752888034
`,
},
]);
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, archive);
const res = await apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(res).toEqual({
homepage: 'https://www.gnu.org/software/bash/',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
releases: [
{ version: '5.3-r1', releaseTimestamp: '2025-07-17T16:40:15.000Z' },
{ version: '5.3-r2', releaseTimestamp: '2025-07-19T01:20:34.000Z' },
],
});
});
it('should use the homepage of a later component when the first has none', async () => {
const mainArchive = await createTarGz([
{
name: 'APKINDEX',
content: ['P:bash', 'V:5.3-r1', 't:1752770415'].join('\n'),
},
]);
const communityArchive = await createTarGz([
{
name: 'APKINDEX',
content: [
'P:bash',
'V:5.3-r2',
'U:https://www.gnu.org/software/bash/',
't:1752888034',
].join('\n'),
},
]);
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/v3.19/main/x86_64/APKINDEX.tar.gz')
.reply(200, mainArchive)
.get('/alpine/v3.19/community/x86_64/APKINDEX.tar.gz')
.reply(200, communityArchive);
const res = await apkDatasource.getReleases({
packageName: 'bash',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
});
expect(res).toEqual({
homepage: 'https://www.gnu.org/software/bash/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64',
releases: [
{ version: '5.3-r1', releaseTimestamp: '2025-07-17T16:40:15.000Z' },
{ version: '5.3-r2', releaseTimestamp: '2025-07-19T01:20:34.000Z' },
],
});
});
it('should throw for a registry URL without arch', async () => {
await expect(
apkDatasource.getReleases({
packageName: 'nginx',
registryUrl: 'https://dl-cdn.alpinelinux.org/alpine?components=main',
}),
).rejects.toThrow('Invalid apk repo URL');
});
});
describe('getReleases with real APK data', () => {
it('should parse multiple versions of bash package', async () => {
const indexContent = codeBlock`
C:Q1vtlRfMK8sXD8JDnaOMI5kCrJG9A=
P:bash
V:5.3-r1
A:x86_64
S:756553
I:1585105
T:GNU bourne again shell
L:GPL-3.0-or-later
o:bash
m:wolfi
t:1752770415
c:e71f2e8133acebc89937730af9d9ec874acacc00
D:merged-bin so:ld-linux-x86-64.so.2 so:libc.so.6 so:libtinfo.so.6 wolfi-baselayout
p:cmd:bash=5.3-r1
C:Q1jngl4lAcPFLwCTlbCwNp+10Usd8=
P:bash
V:5.3-r2
A:x86_64
S:756557
I:1585129
T:GNU bourne again shell
L:GPL-3.0-or-later
o:bash
m:wolfi
t:1752888034
c:b96768fe1c8d6dbd5bcf00b324fa94288a2b8eff
D:merged-bin so:ld-linux-x86-64.so.2 so:libc.so.6 so:libtinfo.so.6 wolfi-baselayout
p:cmd:bash=5.3-r2
C:Q1iOqqGFee8KnAMAiigIso6EN+u7o=
P:bash
V:5.3-r3
A:x86_64
S:757735
I:1585231
T:GNU bourne again shell
L:GPL-3.0-or-later
o:bash
m:wolfi
t:1754953964
c:57df7e0f0fda63de7a35cf4da489dd46a2d4bcb3
D:merged-bin so:ld-linux-x86-64.so.2 so:libc.so.6 so:libtinfo.so.6 wolfi-baselayout
p:cmd:bash=5.3-r3
`;
const apkDatasource = new ApkDatasource();
const tarGzBuffer = await createTarGz([
{ name: 'APKINDEX', content: indexContent },
]);
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, tarGzBuffer);
const result = await apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toEqual({
homepage: undefined, // No URL field in the test data
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
releases: [
{
version: '5.3-r1',
releaseTimestamp: '2025-07-17T16:40:15.000Z',
},
{
version: '5.3-r2',
releaseTimestamp: '2025-07-19T01:20:34.000Z',
},
{
version: '5.3-r3',
releaseTimestamp: '2025-08-11T23:12:44.000Z',
},
],
});
});
it('should handle packages with URL field', async () => {
const indexContent = codeBlock`
C:Q1sUvhopwmAmHD4IGg4MsoPPo2veQ=
P:nginx
V:1.28.0-r3
A:x86_64
S:520401
I:1183347
T:HTTP and reverse proxy server (stable version)
U:https://www.nginx.org/
L:BSD-2-Clause
o:nginx
m:Jakub Jirutka <jakub@jirutka.cz>
t:1747894670
c:7094d681a90006e7599ce94b8b2aed5c27b53c1d
D:/bin/sh so:libc.musl-x86_64.so.1 so:libcrypto.so.3 so:libpcre2-8.so.0 so:libssl.so.3 so:libz.so.1
p:cmd:nginx=1.28.0-r3
`;
const apkDatasource = new ApkDatasource();
const tarGzBuffer = await createTarGz([
{ name: 'APKINDEX', content: indexContent },
]);
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz')
.reply(200, tarGzBuffer);
const result = await apkDatasource.getReleases({
packageName: 'nginx',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=latest-stable&components=main&arch=x86_64',
});
expect(result).toEqual({
homepage: 'https://www.nginx.org/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=latest-stable&components=main&arch=x86_64',
releases: [
{
version: '1.28.0-r3',
releaseTimestamp: '2025-05-22T06:17:50.000Z',
},
],
});
});
it('should return null for unknown package', async () => {
const indexContent = codeBlock`
C:Q1vtlRfMK8sXD8JDnaOMI5kCrJG9A=
P:bash
V:5.3-r1
A:x86_64
S:756553
I:1585105
T:GNU bourne again shell
L:GPL-3.0-or-later
o:bash
m:wolfi
t:1752770415
c:e71f2e8133acebc89937730af9d9ec874acacc00
D:merged-bin so:ld-linux-x86-64.so.2 so:libc.so.6 so:libtinfo.so.6 wolfi-baselayout
p:cmd:bash=5.3-r1
`;
const apkDatasource = new ApkDatasource();
const tarGzBuffer = await createTarGz([
{ name: 'APKINDEX', content: indexContent },
]);
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, tarGzBuffer);
const result = await apkDatasource.getReleases({
packageName: 'unknown-package',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toBeNull();
});
it('should extract the index from an archive laid out like a real one', async () => {
const tarGzBuffer = await createTarGz([
{
name: '.SIGN.RSA.alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub',
content: 'signature',
},
{ name: 'DESCRIPTION', content: 'alpine v3.19 main' },
{ name: './APKINDEX', content: nginxApkIndex },
]);
httpMock
.scope('https://dl-cdn.alpinelinux.org')
.get('/alpine/v3.19/main/x86_64/APKINDEX.tar.gz')
.reply(200, tarGzBuffer);
const result = await apkDatasource.getReleases({
packageName: 'nginx',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main&arch=x86_64',
});
expect(result).toEqual({
homepage: 'https://www.nginx.org/',
registryUrl:
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main&arch=x86_64',
releases: [
{
version: '1.24.0-r16',
releaseTimestamp: '2024-04-26T22:27:14.000Z',
},
],
});
});
it('should handle packages without buildDate', async () => {
const indexContent = codeBlock`
C:Q1vtlRfMK8sXD8JDnaOMI5kCrJG9A=
P:minimal-package
V:1.0.0
A:x86_64
S:1000
I:2000
T:Minimal package without build date
L:MIT
o:minimal-package
m:test@example.com
c:abc123def456
`;
const apkDatasource = new ApkDatasource();
const tarGzBuffer = await createTarGz([
{ name: 'APKINDEX', content: indexContent },
]);
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, tarGzBuffer);
const result = await apkDatasource.getReleases({
packageName: 'minimal-package',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toEqual({
homepage: undefined,
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
releases: [
{
version: '1.0.0',
releaseTimestamp: undefined,
},
],
});
});
});
describe('error handling', () => {
it('should throw ExternalHostError for 429', async () => {
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(429);
await expect(
apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
}),
).rejects.toThrow(EXTERNAL_HOST_ERROR);
});
it('should throw ExternalHostError for 5xx', async () => {
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(502);
await expect(
apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
}),
).rejects.toThrow(EXTERNAL_HOST_ERROR);
});
it('should return null for 404', async () => {
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(404);
const result = await apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toBeNull();
});
it('should return null for 401', async () => {
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(401);
const result = await apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toBeNull();
});
it('should throw ExternalHostError for 503', async () => {
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(503);
await expect(
apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
}),
).rejects.toThrow(EXTERNAL_HOST_ERROR);
});
it('should throw ExternalHostError for 500', async () => {
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(500);
await expect(
apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
}),
).rejects.toThrow(EXTERNAL_HOST_ERROR);
});
it('should handle extraction errors gracefully', async () => {
const apkDatasource = new ApkDatasource();
const tarGzBuffer = await createTarGz([
{ name: 'APKINDEX', content: 'P:bash\nV:5.0.0\n' },
]);
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, tarGzBuffer);
const parseSpy = vi
.spyOn(parser, 'parseApkIndexFile')
.mockRejectedValue(new Error('Parse failed'));
const result = await apkDatasource.getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toBeNull();
parseSpy.mockRestore();
});
it('should handle invalid gzip data', async () => {
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, Buffer.from('not-gzip-data'));
const result = await new ApkDatasource().getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toBeNull();
});
it('should handle tar archive without APKINDEX file', async () => {
const tarGzBuffer = await createTarGz([
{ name: 'DESCRIPTION', content: 'not the index file' },
]);
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, tarGzBuffer);
const result = await new ApkDatasource().getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toBeNull();
});
it('should handle corrupt tar data', async () => {
const corruptTarGz = await gzip(Buffer.alloc(512, 0xff));
httpMock
.scope('https://packages.wolfi.dev')
.get('/os/x86_64/APKINDEX.tar.gz')
.reply(200, corruptTarGz);
const result = await new ApkDatasource().getReleases({
packageName: 'bash',
registryUrl: 'https://packages.wolfi.dev/os?arch=x86_64',
});
expect(result).toBeNull();
});
});
});