Files
renovate/lib/modules/datasource/apk/url.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

148 lines
5.1 KiB
TypeScript

import { constructComponentUrls } from './url.ts';
describe('modules/datasource/apk/url', () => {
describe('constructComponentUrls', () => {
it('constructs one URL per component', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,community&arch=x86_64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64',
'https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64',
]);
});
it('ignores surrounding whitespace and empty components', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=edge&components=main, testing,&arch=aarch64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://dl-cdn.alpinelinux.org/alpine/edge/main/aarch64',
'https://dl-cdn.alpinelinux.org/alpine/edge/testing/aarch64',
]);
});
it('omits the component segment when components are absent', () => {
const registryUrl = 'https://packages.wolfi.dev/os?arch=x86_64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://packages.wolfi.dev/os/x86_64',
]);
});
it('omits the component segment when components are empty', () => {
const registryUrl =
'https://packages.wolfi.dev/os?components=&arch=x86_64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://packages.wolfi.dev/os/x86_64',
]);
});
it('omits the branch segment when the branch is absent', () => {
const registryUrl =
'https://packages.cgr.dev/extras?components=main&arch=x86_64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://packages.cgr.dev/extras/main/x86_64',
]);
});
it('keeps unrelated path segments of the base URL', () => {
const registryUrl =
'https://artifactory.example.com/artifactory/alpine-remote?branch=v3.19&components=main&arch=x86_64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://artifactory.example.com/artifactory/alpine-remote/v3.19/main/x86_64',
]);
});
it('keeps credentials in the base URL', () => {
const registryUrl =
'https://user:token@mirror.example.com/alpine?branch=v3.19&components=main&arch=x86_64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://user:token@mirror.example.com/alpine/v3.19/main/x86_64',
]);
});
it('keeps a non-default port in the base URL', () => {
const registryUrl =
'https://mirror.example.com:8443/alpine?branch=v3.19&components=main&arch=x86_64';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://mirror.example.com:8443/alpine/v3.19/main/x86_64',
]);
});
it('drops anything else in the base URL', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main&arch=x86_64#fragment';
expect(constructComponentUrls(registryUrl)).toEqual([
'https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64',
]);
});
it('throws when arch is missing', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main';
expect(() => constructComponentUrls(registryUrl)).toThrow(
`Missing required query parameter 'arch'`,
);
});
it('throws for an unknown parameter', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main&arch=x86_64&token=secret';
expect(() => constructComponentUrls(registryUrl)).toThrow(
`Unknown query parameter 'token'`,
);
});
it('throws for a misspelt parameter instead of ignoring it', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&component=main&arch=x86_64';
expect(() => constructComponentUrls(registryUrl)).toThrow(
`Unknown query parameter 'component'`,
);
});
it('throws for an arch which is not a usable path segment', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main&arch=../..';
expect(() => constructComponentUrls(registryUrl)).toThrow(
`Invalid 'arch' query parameter: '../..'`,
);
});
it('throws for a branch which is not a usable path segment', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=../../etc&components=main&arch=x86_64';
expect(() => constructComponentUrls(registryUrl)).toThrow(
`Invalid 'branch' query parameter: '../../etc'`,
);
});
it('throws for a component which is not a usable path segment', () => {
const registryUrl =
'https://dl-cdn.alpinelinux.org/alpine?branch=v3.19&components=main,../..&arch=x86_64';
expect(() => constructComponentUrls(registryUrl)).toThrow(
`Invalid 'components' query parameter: '../..'`,
);
});
it('throws for an unparseable registry URL', () => {
expect(() => constructComponentUrls('not-a-valid-url')).toThrow(
'Cannot parse URL',
);
});
});
});