Files
renovate/lib/modules/manager/docker-compose/extract.spec.ts
Michael Kriese 55a7341be2 test(manager): close long-tail branch coverage gaps (#45954)
* test(manager): close azure-pipelines, bundler and cocoapods gaps

Adds a pipeline whose step task is not versioned, so nothing is
extracted, and a gem lock line carrying no version, which is skipped.

Marks four guards that cannot be false: `extractContainer()` always
returns a dep, `getLockFilePath()` always returns a path, and the
cocoapods dep name, spec name and git account all derive from values
their enclosing check already proved present.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* test(manager): close cargo, composer and cpanfile gaps

Adds a composer repository map that disables something other than
packagist, which is dropped rather than recorded.

Marks five guards: the cargo warning only fires for a crate dep and its
retry only runs when a dep was already current, a gitlab rule without a
token never passes the auth check, and both cpanfile handlers run only
once their match produced a value.

Two implicit elses stay counted, in the cargo version resolver and the
composer repository mapper, since a coverage-ignore hint does not attach
to an `else if`.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* test(manager): close deps-edn, docker-compose and dockerfile gaps

Adds three extractions: a deps.edn alias whose value is not a map, a
compose extension carrying no image, and a dockerfile escape directive
naming a character other than a backtick, which leaves the default in
place.

Marks the deps-edn repository name check, since object keys are always
strings, and notes the repository spec chain, where a coverage-ignore
hint does not attach to an `else if`.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* test(manager/flux): close the flux coverage gaps

Adds two GitRepository resources whose url is ssh rather than http, one
pinned to a tag and one to a commit. Neither yields a source url, since
only http urls can be turned into one.

Marks the Kustomization image guard: the schema rejects an image without
a name, so `extractImage()` cannot return null there.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* test(manager): mark github-actions, gitlabci and glasskube guards

None of these six needs a test. A parsed action ref is either a docker
or a repository ref, `getDep()` always returns a dep for a job container
and a service, and the remaining three need a listed file that cannot be
read, a listed file that yields no deps, or a manifest whose
repositories match no name.

The glasskube resource kind chain stays counted, since a coverage-ignore
hint does not attach to an `else if`.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* test(manager): mark gomod and gradle-wrapper guards

Four guards that need setups no test provides: a removed dep that is not
added back, a go.mod the replace massaging leaves unchanged, and a
daemon-jvm file carrying no toolchain version. A parsed go directive
always yields a version.

The additional-dependency count chain stays counted, since a
coverage-ignore hint does not attach to an `else if`.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

---------

Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
2026-09-15 07:10:10 +00:00

295 lines
9.0 KiB
TypeScript

import { codeBlock } from 'common-tags';
import { Fixtures } from '~test/fixtures.ts';
import { extractPackageFile } from './index.ts';
const yamlFile1 = Fixtures.get('docker-compose.1.yml');
const yamlFile3 = Fixtures.get('docker-compose.3.yml');
const yamlFile3NoVersion = Fixtures.get('docker-compose.3-no-version.yml');
const yamlFile3DefaultValue = Fixtures.get('docker-compose.3-default-val.yml');
describe('modules/manager/docker-compose/extract', () => {
describe('extractPackageFile()', () => {
it('returns null for empty', () => {
expect(extractPackageFile('', '', {})).toBeNull();
});
it('ignores an x- extension that carries no image', () => {
expect(
extractPackageFile(
codeBlock`
x-no-image:
command: echo hello
`,
'docker-compose.yml',
{},
)?.deps,
).toBeEmpty();
});
it('returns null for non-object YAML', () => {
expect(extractPackageFile('nothing here', '', {})).toBeNull();
});
it('returns null for malformed YAML', () => {
expect(extractPackageFile('nothing here\n:::::::', '', {})).toBeNull();
});
const expectedDeps = [
{ depName: 'quay.io/something/redis', currentValue: 'alpine' },
{ depName: 'node', currentValue: '10.0.0' },
{ depName: 'postgres', currentValue: '9.4.0' },
{
depName: 'dockersamples/examplevotingapp_vote',
currentValue: 'before',
},
{
depName: 'dockersamples/examplevotingapp_result',
currentValue: 'before',
},
{ depName: 'dockersamples/examplevotingapp_worker' },
{ depName: 'dockersamples/visualizer', currentValue: 'stable' },
{
replaceString: '${IMAGE:-synkodevelopers/edplugins}:${TAG:-latest}',
skipReason: 'contains-variable',
},
];
it('extracts multiple image lines for version 1', () => {
const res = extractPackageFile(yamlFile1, '', {});
expect(res?.deps).toMatchObject(expectedDeps);
expect(res?.deps).toHaveLength(8);
});
it('extracts multiple image lines for version 3', () => {
const res = extractPackageFile(yamlFile3, '', {});
expect(res?.deps).toMatchObject(expectedDeps);
expect(res?.deps).toHaveLength(8);
});
it('extracts multiple image lines for version 3 without set version key', () => {
const res = extractPackageFile(yamlFile3NoVersion, '', {});
expect(res?.deps).toMatchObject(expectedDeps);
expect(res?.deps).toHaveLength(8);
});
it('extracts default variable values for version 3', () => {
const res = extractPackageFile(yamlFile3DefaultValue, '', {});
expect(res?.deps).toEqual([
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: 'sha256:abcd',
currentValue: '5.0.0',
datasource: 'docker',
depName: 'redis',
packageName: 'redis',
replaceString: 'redis:5.0.0@sha256:abcd',
},
]);
expect(res?.deps).toHaveLength(1);
});
it('extracts can parse yaml tags for version 3', () => {
const compose = codeBlock`
web:
image: node:20.0.0
ports:
- "80:8000"
worker:
extends:
service: web
ports: !reset null
`;
const res = extractPackageFile(compose, '', {});
expect(res).toEqual({
deps: [
{
depName: 'node',
packageName: 'node',
currentValue: '20.0.0',
currentDigest: undefined,
replaceString: 'node:20.0.0',
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
datasource: 'docker',
},
],
});
});
it('extracts image and replaces registry', () => {
const compose = codeBlock`
version: "3"
services:
nginx:
image: quay.io/nginx:0.0.1
`;
const res = extractPackageFile(compose, '', {
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'quay.io/nginx',
packageName: 'my-quay-mirror.registry.com/nginx',
replaceString: 'quay.io/nginx:0.0.1',
},
],
});
});
it('extracts image but no replacement', () => {
const compose = codeBlock`
version: "3"
services:
nginx:
image: quay.io/nginx:0.0.1
`;
const res = extractPackageFile(compose, '', {
registryAliases: {
'index.docker.io': 'my-docker-mirror.registry.com',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'quay.io/nginx',
packageName: 'quay.io/nginx',
replaceString: 'quay.io/nginx:0.0.1',
},
],
});
});
it('extracts image and no double replacement', () => {
const compose = codeBlock`
version: "3"
services:
nginx:
image: quay.io/nginx:0.0.1
`;
const res = extractPackageFile(compose, '', {
registryAliases: {
'quay.io': 'my-quay-mirror.registry.com',
'my-quay-mirror.registry.com': 'quay.io',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'quay.io/nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'quay.io/nginx',
packageName: 'my-quay-mirror.registry.com/nginx',
replaceString: 'quay.io/nginx:0.0.1',
},
],
});
});
it('extracts image with variable default registry alias', () => {
const compose = codeBlock`
version: "3"
services:
nginx:
image: \${CI_REGISTRY:-}nginx:0.0.1
`;
const res = extractPackageFile(compose, '', {
registryAliases: {
'${CI_REGISTRY:-}': 'my-registry.io',
},
});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'${CI_REGISTRY:-}nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: '${CI_REGISTRY:-}nginx',
packageName: 'my-registry.io/nginx',
replaceString: '${CI_REGISTRY:-}nginx:0.0.1',
},
],
});
});
it('extracts image of templated compose file', () => {
const compose = codeBlock`
version: "3"
services:
nginx:
image: quay.io/nginx:0.0.1
envrionment:
{{ services['nginx']['env'] }}
`;
const res = extractPackageFile(compose, '', {});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '0.0.1',
datasource: 'docker',
depName: 'quay.io/nginx',
packageName: 'quay.io/nginx',
replaceString: 'quay.io/nginx:0.0.1',
},
],
});
});
it('extract images from fragments', () => {
const compose = codeBlock`
---
x-shared_setting: &shared_settings
image: debian:11
# Other shared properties here
services:
service-a:
<<: *shared_settings
environment:
- SERVICE=a
service-b:
<<: *shared_settings
environment:
- SERVICE=b
`;
const res = extractPackageFile(compose, '', {});
expect(res).toEqual({
deps: [
{
autoReplaceStringTemplate:
'{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}',
currentDigest: undefined,
currentValue: '11',
datasource: 'docker',
depName: 'debian',
packageName: 'debian',
replaceString: 'debian:11',
versioning: 'debian',
},
],
});
});
});
});