From 0838ca209c3d11d12fc65c42a46be22632be437c Mon Sep 17 00:00:00 2001
From: Michael Kriese <michael.kriese@visualon.de>
Date: Mon, 20 Jun 2022 13:15:02 +0200
Subject: [PATCH] refactor: strict datasource tests (#16150)

---
 .../datasource/golang-version/index.spec.ts   | 20 ++--
 .../datasource/gradle-version/index.spec.ts   |  9 +-
 lib/modules/datasource/helm/index.spec.ts     |  4 +-
 lib/modules/datasource/hex/index.spec.ts      | 12 +--
 lib/modules/datasource/index.spec.ts          | 19 ++--
 .../datasource/jenkins-plugins/index.spec.ts  | 18 ++--
 lib/modules/datasource/maven/index.spec.ts    | 92 +++++++++----------
 lib/modules/datasource/maven/util.spec.ts     | 23 +++--
 lib/modules/datasource/node/index.spec.ts     |  2 +-
 lib/modules/datasource/npm/get.spec.ts        | 25 ++---
 lib/modules/datasource/npm/index.spec.ts      |  6 +-
 lib/modules/datasource/repology/index.spec.ts | 40 ++++----
 lib/modules/datasource/rubygems/index.spec.ts | 38 ++++----
 .../datasource/sbt-package/index.spec.ts      |  2 +-
 .../datasource/sbt-plugin/index.spec.ts       |  8 +-
 lib/util/modules.ts                           |  2 +-
 16 files changed, 164 insertions(+), 156 deletions(-)

diff --git a/lib/modules/datasource/golang-version/index.spec.ts b/lib/modules/datasource/golang-version/index.spec.ts
index 39ea17852c..8b0ab2e03a 100644
--- a/lib/modules/datasource/golang-version/index.spec.ts
+++ b/lib/modules/datasource/golang-version/index.spec.ts
@@ -1,16 +1,16 @@
 import { getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import { loadFixture } from '../../../../test/util';
 import { ExternalHostError } from '../../../types/errors/external-host-error';
 import { GolangVersionDatasource } from '.';
 
-const golangReleasesContent = loadFixture('releases.go');
-const golangReleasesInvalidContent = loadFixture('releases-invalid.go');
-const golangReleasesInvalidContent2 = loadFixture('releases-invalid2.go');
-const golangReleasesInvalidContent3 = loadFixture('releases-invalid3.go');
-const golangReleasesInvalidContent4 = loadFixture('releases-invalid4.go');
-const golangReleasesInvalidContent5 = loadFixture('releases-invalid5.go');
-const golangReleasesInvalidContent6 = loadFixture('releases-invalid6.go');
+const golangReleasesContent = Fixtures.get('releases.go');
+const golangReleasesInvalidContent = Fixtures.get('releases-invalid.go');
+const golangReleasesInvalidContent2 = Fixtures.get('releases-invalid2.go');
+const golangReleasesInvalidContent3 = Fixtures.get('releases-invalid3.go');
+const golangReleasesInvalidContent4 = Fixtures.get('releases-invalid4.go');
+const golangReleasesInvalidContent5 = Fixtures.get('releases-invalid5.go');
+const golangReleasesInvalidContent6 = Fixtures.get('releases-invalid6.go');
 
 const datasource = GolangVersionDatasource.id;
 
@@ -25,8 +25,8 @@ describe('modules/datasource/golang-version/index', () => {
         datasource,
         depName: 'golang',
       });
-      expect(res.releases).toHaveLength(132);
-      expect(res.releases[0]).toEqual({
+      expect(res?.releases).toHaveLength(132);
+      expect(res?.releases[0]).toEqual({
         releaseTimestamp: '2012-03-28T00:00:00.000Z',
         version: '1.0.0',
       });
diff --git a/lib/modules/datasource/gradle-version/index.spec.ts b/lib/modules/datasource/gradle-version/index.spec.ts
index ce97975706..e8dbc138ea 100644
--- a/lib/modules/datasource/gradle-version/index.spec.ts
+++ b/lib/modules/datasource/gradle-version/index.spec.ts
@@ -1,11 +1,12 @@
 import { GetPkgReleasesConfig, GetReleasesConfig, getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import { loadJsonFixture, partial } from '../../../../test/util';
+import { partial } from '../../../../test/util';
 import { ExternalHostError } from '../../../types/errors/external-host-error';
 import { id as versioning } from '../../versioning/gradle';
 import { GradleVersionDatasource } from '.';
 
-const allResponse: any = loadJsonFixture('all.json');
+const allResponse = Fixtures?.get('all.json');
 
 let config: GetPkgReleasesConfig;
 
@@ -30,9 +31,9 @@ describe('modules/datasource/gradle-version/index', () => {
       const res = await getPkgReleases(config);
       expect(res).toMatchSnapshot();
       expect(res).not.toBeNull();
-      expect(res.releases).toHaveLength(300);
+      expect(res?.releases).toHaveLength(300);
       expect(
-        res.releases.filter(({ isDeprecated }) => isDeprecated)
+        res?.releases.filter(({ isDeprecated }) => isDeprecated)
       ).toHaveLength(1);
     });
 
diff --git a/lib/modules/datasource/helm/index.spec.ts b/lib/modules/datasource/helm/index.spec.ts
index 6280a5f3d3..54ea10e119 100644
--- a/lib/modules/datasource/helm/index.spec.ts
+++ b/lib/modules/datasource/helm/index.spec.ts
@@ -16,7 +16,7 @@ describe('modules/datasource/helm/index', () => {
       expect(
         await getPkgReleases({
           datasource: HelmDatasource.id,
-          depName: undefined,
+          depName: undefined as never, // #7154
           registryUrls: ['https://example-repository.com'],
         })
       ).toBeNull();
@@ -41,7 +41,7 @@ describe('modules/datasource/helm/index', () => {
       httpMock
         .scope('https://example-repository.com')
         .get('/index.yaml')
-        .reply(200, null);
+        .reply(200);
       expect(
         await getPkgReleases({
           datasource: HelmDatasource.id,
diff --git a/lib/modules/datasource/hex/index.spec.ts b/lib/modules/datasource/hex/index.spec.ts
index 669faf63f4..1e8401d8cf 100644
--- a/lib/modules/datasource/hex/index.spec.ts
+++ b/lib/modules/datasource/hex/index.spec.ts
@@ -1,11 +1,12 @@
 import { getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import { hostRules, loadJsonFixture } from '../../../../test/util';
+import { hostRules } from '../../../../test/util';
 import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages';
 import { HexDatasource } from '.';
 
-const certifiResponse = loadJsonFixture('certifi.json');
-const privatePackageResponse = loadJsonFixture('private_package.json');
+const certifiResponse = Fixtures.get('certifi.json');
+const privatePackageResponse = Fixtures.get('private_package.json');
 
 jest.mock('../../../util/host-rules');
 
@@ -24,10 +25,7 @@ describe('modules/datasource/hex/index', () => {
 
   describe('getReleases', () => {
     it('returns null for empty result', async () => {
-      httpMock
-        .scope(baseUrl)
-        .get('/packages/non_existent_package')
-        .reply(200, null);
+      httpMock.scope(baseUrl).get('/packages/non_existent_package').reply(200);
       expect(
         await getPkgReleases({
           datasource,
diff --git a/lib/modules/datasource/index.spec.ts b/lib/modules/datasource/index.spec.ts
index ace1cba910..1463e98cd0 100644
--- a/lib/modules/datasource/index.spec.ts
+++ b/lib/modules/datasource/index.spec.ts
@@ -1,3 +1,5 @@
+/* fixme #7154 */
+/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
 import fs from 'fs-extra';
 import { logger } from '../../../test/util';
 import {
@@ -20,7 +22,10 @@ import {
 const datasource = 'dummy';
 const depName = 'package';
 
-type RegistriesMock = Record<string, ReleaseResult | (() => ReleaseResult)>;
+type RegistriesMock = Record<
+  string,
+  ReleaseResult | (() => ReleaseResult) | null
+>;
 const defaultRegistriesMock: RegistriesMock = {
   'https://reg1.com': { releases: [{ version: '1.2.3' }] },
 };
@@ -35,7 +40,7 @@ class DummyDatasource extends Datasource {
   override getReleases({
     registryUrl,
   }: GetReleasesConfig): Promise<ReleaseResult | null> {
-    const fn = this.registriesMock[registryUrl];
+    const fn = this.registriesMock[registryUrl!];
     if (typeof fn === 'function') {
       return Promise.resolve(fn());
     }
@@ -55,7 +60,7 @@ class DummyDatasource2 extends Datasource {
   override getReleases({
     registryUrl,
   }: GetReleasesConfig): Promise<ReleaseResult | null> {
-    const fn = this.registriesMock[registryUrl];
+    const fn = this.registriesMock[registryUrl!];
     if (typeof fn === 'function') {
       return Promise.resolve(fn());
     }
@@ -76,7 +81,7 @@ class DummyDatasource3 extends Datasource {
   override getReleases({
     registryUrl,
   }: GetReleasesConfig): Promise<ReleaseResult | null> {
-    const fn = this.registriesMock[registryUrl];
+    const fn = this.registriesMock[registryUrl!];
     if (typeof fn === 'function') {
       return Promise.resolve(fn());
     }
@@ -151,7 +156,7 @@ describe('modules/datasource/index', () => {
       expect(Array.from(dss.keys())).toEqual(Object.keys(loadedDs));
 
       for (const dsName of dss.keys()) {
-        const ds = dss.get(dsName);
+        const ds = dss.get(dsName)!;
         expect(validateDatasource(ds, dsName)).toBeTrue();
       }
     });
@@ -159,7 +164,7 @@ describe('modules/datasource/index', () => {
     it('returns null for null datasource', async () => {
       expect(
         await getPkgReleases({
-          datasource: null,
+          datasource: null as never, // #7154
           depName: 'some/dep',
         })
       ).toBeNull();
@@ -170,7 +175,7 @@ describe('modules/datasource/index', () => {
       expect(
         await getPkgReleases({
           datasource: datasource,
-          depName: null,
+          depName: null as never, // #7154
         })
       ).toBeNull();
     });
diff --git a/lib/modules/datasource/jenkins-plugins/index.spec.ts b/lib/modules/datasource/jenkins-plugins/index.spec.ts
index be2a514f68..d00abf611f 100644
--- a/lib/modules/datasource/jenkins-plugins/index.spec.ts
+++ b/lib/modules/datasource/jenkins-plugins/index.spec.ts
@@ -1,11 +1,11 @@
 import { getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import { loadJsonFixture } from '../../../../test/util';
 import * as versioning from '../../versioning/docker';
 import { JenkinsPluginsDatasource } from '.';
 
-const jenkinsPluginsVersions = loadJsonFixture('plugin-versions.json');
-const jenkinsPluginsInfo = loadJsonFixture('update-center.actual.json');
+const jenkinsPluginsVersions = Fixtures?.getJson('plugin-versions.json');
+const jenkinsPluginsInfo = Fixtures?.getJson('update-center.actual.json');
 
 describe('modules/datasource/jenkins-plugins/index', () => {
   describe('getReleases', () => {
@@ -46,18 +46,18 @@ describe('modules/datasource/jenkins-plugins/index', () => {
         .reply(200, jenkinsPluginsVersions);
 
       const res = await getPkgReleases(params);
-      expect(res.releases).toHaveLength(75);
+      expect(res?.releases).toHaveLength(75);
       expect(res).toMatchSnapshot();
 
-      expect(res.sourceUrl).toBe(
+      expect(res?.sourceUrl).toBe(
         'https://github.com/jenkinsci/email-ext-plugin'
       );
 
       expect(
-        res.releases.find((release) => release.version === '2.69')
+        res?.releases.find((release) => release.version === '2.69')
       ).toBeDefined();
       expect(
-        res.releases.find((release) => release.version === '12.98')
+        res?.releases.find((release) => release.version === '12.98')
       ).toBeUndefined();
     });
 
@@ -73,10 +73,10 @@ describe('modules/datasource/jenkins-plugins/index', () => {
         .reply(200, '{}');
 
       const res = await getPkgReleases(params);
-      expect(res.releases).toBeEmpty();
+      expect(res?.releases).toBeEmpty();
       expect(res).toMatchSnapshot();
 
-      expect(res.sourceUrl).toBe(
+      expect(res?.sourceUrl).toBe(
         'https://github.com/jenkinsci/email-ext-plugin'
       );
     });
diff --git a/lib/modules/datasource/maven/index.spec.ts b/lib/modules/datasource/maven/index.spec.ts
index a20255759f..00b5c40321 100644
--- a/lib/modules/datasource/maven/index.spec.ts
+++ b/lib/modules/datasource/maven/index.spec.ts
@@ -1,6 +1,6 @@
 import { ReleaseResult, getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import { loadFixture } from '../../../../test/util';
 import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages';
 import * as hostRules from '../../../util/host-rules';
 import { id as versioning } from '../../versioning/maven';
@@ -25,7 +25,7 @@ interface MockOpts {
   latest?: string;
   jars?: Record<string, number> | null;
   snapshots?: SnapshotOpts[] | null;
-  html?: string;
+  html?: string | null;
 }
 
 function mockGenericPackage(opts: MockOpts = {}) {
@@ -36,8 +36,8 @@ function mockGenericPackage(opts: MockOpts = {}) {
     html,
   } = opts;
   const meta =
-    opts.meta === undefined ? loadFixture('metadata.xml') : opts.meta;
-  const pom = opts.pom === undefined ? loadFixture('pom.xml') : opts.pom;
+    opts.meta === undefined ? Fixtures.get('metadata.xml') : opts.meta;
+  const pom = opts.pom === undefined ? Fixtures.get('pom.xml') : opts.pom;
   const jars =
     opts.jars === undefined
       ? {
@@ -54,12 +54,12 @@ function mockGenericPackage(opts: MockOpts = {}) {
       ? [
           {
             version: '1.0.3-SNAPSHOT',
-            meta: loadFixture('metadata-snapshot-version.xml'),
+            meta: Fixtures.get('metadata-snapshot-version.xml'),
             jarStatus: 200,
           },
           {
             version: '1.0.4-SNAPSHOT',
-            meta: loadFixture('metadata-snapshot-version-invalid.xml'),
+            meta: Fixtures.get('metadata-snapshot-version-invalid.xml'),
           },
           {
             version: '1.0.5-SNAPSHOT',
@@ -206,9 +206,9 @@ describe('modules/datasource/maven/index', () => {
   });
 
   it('returns releases when only snapshot', async () => {
-    const meta = loadFixture('metadata-snapshot-version.xml');
+    const meta = Fixtures.get('metadata-snapshot-version.xml');
     mockGenericPackage({
-      meta: loadFixture('metadata-snapshot-only.xml'),
+      meta: Fixtures.get('metadata-snapshot-only.xml'),
       jars: null,
       html: null,
       latest: '1.0.3-SNAPSHOT',
@@ -248,8 +248,8 @@ describe('modules/datasource/maven/index', () => {
     mockGenericPackage({
       latest: '2.0.0',
       jars: null,
-      html: loadFixture('index.html'),
-      meta: loadFixture('index.xml'),
+      html: Fixtures.get('index.html'),
+      meta: Fixtures.get('index.xml'),
       snapshots: null,
     });
 
@@ -282,19 +282,15 @@ describe('modules/datasource/maven/index', () => {
     mockGenericPackage({ html: null });
     mockGenericPackage({
       base: baseUrlCustom,
-      meta: loadFixture('metadata-extra.xml'),
+      meta: Fixtures.get('metadata-extra.xml'),
       latest: '3.0.0',
       jars: { '3.0.0': 200 },
       snapshots: [],
     });
 
-    const { releases } = await get(
-      'org.example:package',
-      baseUrl,
-      baseUrlCustom
-    );
+    const res = await get('org.example:package', baseUrl, baseUrlCustom);
 
-    expect(releases).toMatchObject([
+    expect(res?.releases).toMatchObject([
       { version: '0.0.1' },
       { version: '1.0.0' },
       { version: '1.0.3-SNAPSHOT' },
@@ -308,11 +304,11 @@ describe('modules/datasource/maven/index', () => {
     httpMock
       .scope('https://failed_repo')
       .get('/org/example/package/maven-metadata.xml')
-      .reply(404, null);
+      .reply(404);
     httpMock
       .scope('https://unauthorized_repo')
       .get('/org/example/package/maven-metadata.xml')
-      .reply(403, null);
+      .reply(403);
     httpMock
       .scope('https://empty_repo')
       .get('/org/example/package/maven-metadata.xml')
@@ -347,13 +343,13 @@ describe('modules/datasource/maven/index', () => {
     const base = baseUrl.replace('https', 'http');
     mockGenericPackage({ base });
 
-    const { releases } = await get(
+    const res = await get(
       'org.example:package',
       'ftp://protocol_error_repo',
       base
     );
 
-    expect(releases).toMatchSnapshot();
+    expect(res?.releases).toMatchSnapshot();
   });
 
   it('skips registry with invalid metadata structure', async () => {
@@ -361,7 +357,7 @@ describe('modules/datasource/maven/index', () => {
     httpMock
       .scope('https://invalid_metadata_repo')
       .get('/org/example/package/maven-metadata.xml')
-      .reply(200, loadFixture('metadata-invalid.xml'));
+      .reply(200, Fixtures.get('metadata-invalid.xml'));
 
     const res = await get(
       'org.example:package',
@@ -395,7 +391,7 @@ describe('modules/datasource/maven/index', () => {
     const resB = await get('org.example:package', baseUrl.replace(/\/*$/, '/'));
     expect(resA).not.toBeNull();
     expect(resB).not.toBeNull();
-    expect(resA.releases).toEqual(resB.releases);
+    expect(resA?.releases).toEqual(resB?.releases);
   });
 
   it('returns null for invalid registryUrls', async () => {
@@ -408,12 +404,12 @@ describe('modules/datasource/maven/index', () => {
   });
 
   it('supports scm.url values prefixed with "scm:"', async () => {
-    const pom = loadFixture('pom.scm-prefix.xml');
+    const pom = Fixtures.get('pom.scm-prefix.xml');
     mockGenericPackage({ pom, html: null });
 
-    const { sourceUrl } = await get();
+    const res = await get();
 
-    expect(sourceUrl).toBe('https://github.com/example/test');
+    expect(res?.sourceUrl).toBe('https://github.com/example/test');
   });
 
   it('removes authentication header after redirect', async () => {
@@ -449,9 +445,9 @@ describe('modules/datasource/maven/index', () => {
     httpMock
       .scope(backendUrl, { badheaders: ['authorization'] })
       .get(`${metadataPath}${queryStr}`)
-      .reply(200, loadFixture('metadata.xml'))
+      .reply(200, Fixtures.get('metadata.xml'))
       .get(`${pomfilePath}${queryStr}`)
-      .reply(200, loadFixture('pom.xml'));
+      .reply(200, Fixtures.get('pom.xml'));
 
     const res = await get('org.example:package', frontendUrl);
 
@@ -462,7 +458,7 @@ describe('modules/datasource/maven/index', () => {
     const parentPackage = {
       dep: 'org.example:parent',
       meta: null,
-      pom: loadFixture('parent-scm-homepage/pom.xml'),
+      pom: Fixtures.get('parent-scm-homepage/pom.xml'),
       latest: '1.0.0',
       jars: null,
       snapshots: [],
@@ -470,8 +466,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should get source and homepage from parent', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-no-info/meta.xml'),
-        pom: loadFixture('child-no-info/pom.xml'),
+        meta: Fixtures.get('child-no-info/meta.xml'),
+        pom: Fixtures.get('child-no-info/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
@@ -489,8 +485,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should deal with missing parent fields', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-empty/meta.xml'),
-        pom: loadFixture('child-empty/pom.xml'),
+        meta: Fixtures.get('child-empty/meta.xml'),
+        pom: Fixtures.get('child-empty/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
@@ -509,7 +505,7 @@ describe('modules/datasource/maven/index', () => {
     });
 
     it('should deal with circular hierarchy', async () => {
-      const parentPom = loadFixture('child-parent-cycle/parent.pom.xml');
+      const parentPom = Fixtures.get('child-parent-cycle/parent.pom.xml');
       const parentPomMock = {
         dep: 'org.example:parent',
         meta: null,
@@ -519,8 +515,8 @@ describe('modules/datasource/maven/index', () => {
         snapshots: [],
       };
 
-      const childMeta = loadFixture('child-parent-cycle/child.meta.xml');
-      const childPom = loadFixture('child-parent-cycle/child.pom.xml');
+      const childMeta = Fixtures.get('child-parent-cycle/child.meta.xml');
+      const childPom = Fixtures.get('child-parent-cycle/child.pom.xml');
       const childPomMock = {
         dep: 'org.example:child',
         meta: null,
@@ -552,8 +548,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should get source from own pom and homepage from parent', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-scm/meta.xml'),
-        pom: loadFixture('child-scm/pom.xml'),
+        meta: Fixtures.get('child-scm/meta.xml'),
+        pom: Fixtures.get('child-scm/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
@@ -571,8 +567,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should get homepage from own pom and source from parent', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-url/meta.xml'),
-        pom: loadFixture('child-url/pom.xml'),
+        meta: Fixtures.get('child-url/meta.xml'),
+        pom: Fixtures.get('child-url/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
@@ -590,8 +586,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should get homepage and source from own pom', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-all-info/meta.xml'),
-        pom: loadFixture('child-all-info/pom.xml'),
+        meta: Fixtures.get('child-all-info/meta.xml'),
+        pom: Fixtures.get('child-all-info/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
@@ -608,8 +604,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should be able to detect git@github.com:child-scm as valid sourceUrl', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-scm-gitatcolon/meta.xml'),
-        pom: loadFixture('child-scm-gitatcolon/pom.xml'),
+        meta: Fixtures.get('child-scm-gitatcolon/meta.xml'),
+        pom: Fixtures.get('child-scm-gitatcolon/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
@@ -625,8 +621,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should be able to detect git@github.com/child-scm as valid sourceUrl', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-scm-gitatslash/meta.xml'),
-        pom: loadFixture('child-scm-gitatslash/pom.xml'),
+        meta: Fixtures.get('child-scm-gitatslash/meta.xml'),
+        pom: Fixtures.get('child-scm-gitatslash/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
@@ -642,8 +638,8 @@ describe('modules/datasource/maven/index', () => {
 
     it('should be able to detect git://@github.com/child-scm as valid sourceUrl', async () => {
       mockGenericPackage({
-        meta: loadFixture('child-scm-gitprotocol/meta.xml'),
-        pom: loadFixture('child-scm-gitprotocol/pom.xml'),
+        meta: Fixtures.get('child-scm-gitprotocol/meta.xml'),
+        pom: Fixtures.get('child-scm-gitprotocol/pom.xml'),
         latest: '2.0.0',
         jars: { '2.0.0': 200 },
         snapshots: [],
diff --git a/lib/modules/datasource/maven/util.spec.ts b/lib/modules/datasource/maven/util.spec.ts
index db90e0c215..6cd18f78b4 100644
--- a/lib/modules/datasource/maven/util.spec.ts
+++ b/lib/modules/datasource/maven/util.spec.ts
@@ -10,40 +10,51 @@ describe('modules/datasource/maven/util', () => {
   describe('downloadMavenXml', () => {
     it('returns empty object for unsupported protocols', async () => {
       const res = await downloadMavenXml(
-        null,
+        null as never, // #7154
         parseUrl('unsupported://server.com/')
       );
       expect(res).toEqual({});
     });
 
     it('returns empty object for invalid URLs', async () => {
-      const res = await downloadMavenXml(null, null);
+      const res = await downloadMavenXml(
+        null as never, // #7154
+        null
+      );
       expect(res).toEqual({});
     });
   });
 
   describe('downloadS3Protocol', () => {
     it('returns null for non-S3 URLs', async () => {
-      const res = await downloadS3Protocol(parseUrl('http://not-s3.com/'));
+      // #7154
+      const res = await downloadS3Protocol(parseUrl('http://not-s3.com/')!);
       expect(res).toBeNull();
     });
   });
 
   describe('checkResource', () => {
     it('returns not found for unsupported protocols', async () => {
-      const res = await checkResource(null, 'unsupported://server.com/');
+      const res = await checkResource(
+        null as never, // #7154
+        'unsupported://server.com/'
+      );
       expect(res).toBe('not-found');
     });
 
     it('returns error for invalid URLs', async () => {
-      const res = await checkResource(null, 'not-a-valid-url');
+      const res = await checkResource(
+        null as never, // #7154
+        'not-a-valid-url'
+      );
       expect(res).toBe('error');
     });
   });
 
   describe('checkS3Resource', () => {
     it('returns error for non-S3 URLs', async () => {
-      const res = await checkS3Resource(parseUrl('http://not-s3.com/'));
+      // #7154
+      const res = await checkS3Resource(parseUrl('http://not-s3.com/')!);
       expect(res).toBe('error');
     });
   });
diff --git a/lib/modules/datasource/node/index.spec.ts b/lib/modules/datasource/node/index.spec.ts
index 4f0d1c92ab..234b60e39a 100644
--- a/lib/modules/datasource/node/index.spec.ts
+++ b/lib/modules/datasource/node/index.spec.ts
@@ -49,7 +49,7 @@ describe('modules/datasource/node/index', () => {
         depName: 'node',
       });
       expect(res).toMatchSnapshot();
-      expect(res.releases).toHaveLength(64);
+      expect(res?.releases).toHaveLength(64);
     });
   });
 });
diff --git a/lib/modules/datasource/npm/get.spec.ts b/lib/modules/datasource/npm/get.spec.ts
index 375923b015..15c57debbd 100644
--- a/lib/modules/datasource/npm/get.spec.ts
+++ b/lib/modules/datasource/npm/get.spec.ts
@@ -219,7 +219,8 @@ describe('modules/datasource/npm/get', () => {
     registryUrl = resolveRegistryUrl('error-404');
     expect(await getDependency(http, registryUrl, 'error-404')).toBeNull();
 
-    httpMock.scope('https://test.org').get('/error4').reply(200, null);
+    // return invalid json to get coverage
+    httpMock.scope('https://test.org').get('/error4').reply(200, '{');
     registryUrl = resolveRegistryUrl('error4');
     expect(await getDependency(http, registryUrl, 'error4')).toBeNull();
 
@@ -260,8 +261,8 @@ describe('modules/datasource/npm/get', () => {
     const registryUrl = resolveRegistryUrl('@neutrinojs/react');
     const dep = await getDependency(http, registryUrl, '@neutrinojs/react');
 
-    expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino');
-    expect(dep.sourceDirectory).toBe('packages/react');
+    expect(dep?.sourceUrl).toBe('https://github.com/neutrinojs/neutrino');
+    expect(dep?.sourceDirectory).toBe('packages/react');
 
     expect(httpMock.getTrace()).toMatchInlineSnapshot(`
       Array [
@@ -297,8 +298,8 @@ describe('modules/datasource/npm/get', () => {
     const registryUrl = resolveRegistryUrl('@neutrinojs/react');
     const dep = await getDependency(http, registryUrl, '@neutrinojs/react');
 
-    expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino');
-    expect(dep.sourceDirectory).toBe('packages/react');
+    expect(dep?.sourceUrl).toBe('https://github.com/neutrinojs/neutrino');
+    expect(dep?.sourceDirectory).toBe('packages/react');
   });
 
   it('handles mixed sourceUrls in releases', async () => {
@@ -332,9 +333,9 @@ describe('modules/datasource/npm/get', () => {
     const registryUrl = resolveRegistryUrl('vue');
     const dep = await getDependency(http, registryUrl, 'vue');
 
-    expect(dep.sourceUrl).toBe('https://github.com/vuejs/vue.git');
-    expect(dep.releases[0].sourceUrl).toBeUndefined();
-    expect(dep.releases[1].sourceUrl).toBe(
+    expect(dep?.sourceUrl).toBe('https://github.com/vuejs/vue.git');
+    expect(dep?.releases[0].sourceUrl).toBeUndefined();
+    expect(dep?.releases[1].sourceUrl).toBe(
       'https://github.com/vuejs/vue-next.git'
     );
   });
@@ -358,8 +359,8 @@ describe('modules/datasource/npm/get', () => {
     const registryUrl = resolveRegistryUrl('@neutrinojs/react');
     const dep = await getDependency(http, registryUrl, '@neutrinojs/react');
 
-    expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino');
-    expect(dep.sourceDirectory).toBe('packages/foo');
+    expect(dep?.sourceUrl).toBe('https://github.com/neutrinojs/neutrino');
+    expect(dep?.sourceDirectory).toBe('packages/foo');
 
     expect(httpMock.getTrace()).toMatchInlineSnapshot(`
       Array [
@@ -396,10 +397,10 @@ describe('modules/datasource/npm/get', () => {
     const registryUrl = resolveRegistryUrl('@neutrinojs/react');
     const dep = await getDependency(http, registryUrl, '@neutrinojs/react');
 
-    expect(dep.sourceUrl).toBe(
+    expect(dep?.sourceUrl).toBe(
       'https://bitbucket.org/neutrinojs/neutrino/tree/master/packages/react'
     );
-    expect(dep.sourceDirectory).toBeUndefined();
+    expect(dep?.sourceDirectory).toBeUndefined();
 
     expect(httpMock.getTrace()).toMatchInlineSnapshot(`
       Array [
diff --git a/lib/modules/datasource/npm/index.spec.ts b/lib/modules/datasource/npm/index.spec.ts
index 5377d8d78a..45698bb03d 100644
--- a/lib/modules/datasource/npm/index.spec.ts
+++ b/lib/modules/datasource/npm/index.spec.ts
@@ -92,7 +92,7 @@ describe('modules/datasource/npm/index', () => {
     httpMock.scope('https://registry.npmjs.org').get('/foobar').reply(200, pkg);
     const res = await getPkgReleases({ datasource, depName: 'foobar' });
     expect(res).toMatchSnapshot();
-    expect(res.sourceUrl).toBeDefined();
+    expect(res?.sourceUrl).toBeDefined();
   });
 
   it('should parse repo url (string)', async () => {
@@ -113,7 +113,7 @@ describe('modules/datasource/npm/index', () => {
     httpMock.scope('https://registry.npmjs.org').get('/foobar').reply(200, pkg);
     const res = await getPkgReleases({ datasource, depName: 'foobar' });
     expect(res).toMatchSnapshot();
-    expect(res.sourceUrl).toBeDefined();
+    expect(res?.sourceUrl).toBeDefined();
   });
 
   it('should return deprecated', async () => {
@@ -146,7 +146,7 @@ describe('modules/datasource/npm/index', () => {
       .reply(200, deprecatedPackage);
     const res = await getPkgReleases({ datasource, depName: 'foobar' });
     expect(res).toMatchSnapshot();
-    expect(res.deprecationMessage).toMatchSnapshot();
+    expect(res?.deprecationMessage).toMatchSnapshot();
   });
 
   it('should handle foobar', async () => {
diff --git a/lib/modules/datasource/repology/index.spec.ts b/lib/modules/datasource/repology/index.spec.ts
index 3a216789b8..5547fa9fb3 100644
--- a/lib/modules/datasource/repology/index.spec.ts
+++ b/lib/modules/datasource/repology/index.spec.ts
@@ -1,6 +1,6 @@
 import { getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import { loadFixture } from '../../../../test/util';
 import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages';
 import { id as versioning } from '../../versioning/loose';
 import type { RepologyPackage } from './types';
@@ -48,12 +48,12 @@ const mockResolverCall = (
   }
 };
 
-const fixtureNginx = loadFixture(`nginx.json`);
-const fixtureGccDefaults = loadFixture(`gcc-defaults.json`);
-const fixtureGcc = loadFixture(`gcc.json`);
-const fixturePulseaudio = loadFixture(`pulseaudio.json`);
-const fixtureJdk = loadFixture(`openjdk.json`);
-const fixturePython = loadFixture(`python.json`);
+const fixtureNginx = Fixtures?.get(`nginx.json`);
+const fixtureGccDefaults = Fixtures?.get(`gcc-defaults.json`);
+const fixtureGcc = Fixtures?.get(`gcc.json`);
+const fixturePulseaudio = Fixtures?.get(`pulseaudio.json`);
+const fixtureJdk = Fixtures?.get(`openjdk.json`);
+const fixturePython = Fixtures?.get(`python.json`);
 
 describe('modules/datasource/repology/index', () => {
   describe('getReleases', () => {
@@ -214,8 +214,8 @@ describe('modules/datasource/repology/index', () => {
         depName: 'debian_stable/nginx',
       });
       expect(res).toMatchSnapshot();
-      expect(res.releases).toHaveLength(1);
-      expect(res.releases[0].version).toBe('1.14.2-2+deb10u1');
+      expect(res?.releases).toHaveLength(1);
+      expect(res?.releases[0].version).toBe('1.14.2-2+deb10u1');
     });
 
     it('returns correct version for source package', async () => {
@@ -233,8 +233,8 @@ describe('modules/datasource/repology/index', () => {
         depName: 'debian_stable/gcc-defaults',
       });
       expect(res).toMatchSnapshot();
-      expect(res.releases).toHaveLength(1);
-      expect(res.releases[0].version).toBe('1.181');
+      expect(res?.releases).toHaveLength(1);
+      expect(res?.releases[0].version).toBe('1.181');
     });
 
     it('returns correct version for api package', async () => {
@@ -249,8 +249,8 @@ describe('modules/datasource/repology/index', () => {
         depName: 'debian_stable/gcc-defaults',
       });
       expect(res).toMatchSnapshot();
-      expect(res.releases).toHaveLength(1);
-      expect(res.releases[0].version).toBe('1.181');
+      expect(res?.releases).toHaveLength(1);
+      expect(res?.releases[0].version).toBe('1.181');
     });
 
     it('returns correct version for multi-package project with same name', async () => {
@@ -265,8 +265,8 @@ describe('modules/datasource/repology/index', () => {
         depName: 'alpine_3_12/gcc',
       });
       expect(res).toMatchSnapshot();
-      expect(res.releases).toHaveLength(1);
-      expect(res.releases[0].version).toBe('9.3.0-r2');
+      expect(res?.releases).toHaveLength(1);
+      expect(res?.releases[0].version).toBe('9.3.0-r2');
     });
 
     it('returns correct version for multi-package project with different name', async () => {
@@ -281,8 +281,8 @@ describe('modules/datasource/repology/index', () => {
         depName: 'debian_stable/pulseaudio-utils',
       });
       expect(res).toMatchSnapshot();
-      expect(res.releases).toHaveLength(1);
-      expect(res.releases[0].version).toBe('12.2-4+deb10u1');
+      expect(res?.releases).toHaveLength(1);
+      expect(res?.releases[0].version).toBe('12.2-4+deb10u1');
     });
 
     it('returns multiple versions if they are present in repository', async () => {
@@ -300,9 +300,9 @@ describe('modules/datasource/repology/index', () => {
         depName: 'centos_8/java-11-openjdk',
       });
       expect(res).toMatchSnapshot();
-      expect(res.releases).toHaveLength(6);
-      expect(res.releases[0].version).toBe('1:11.0.7.10-1.el8_1');
-      expect(res.releases[5].version).toBe('1:11.0.9.11-3.el8_3');
+      expect(res?.releases).toHaveLength(6);
+      expect(res?.releases[0].version).toBe('1:11.0.7.10-1.el8_1');
+      expect(res?.releases[5].version).toBe('1:11.0.9.11-3.el8_3');
     });
 
     it('returns null for scenario when repo is not in package results', async () => {
diff --git a/lib/modules/datasource/rubygems/index.spec.ts b/lib/modules/datasource/rubygems/index.spec.ts
index ad41ab5580..e93b861542 100644
--- a/lib/modules/datasource/rubygems/index.spec.ts
+++ b/lib/modules/datasource/rubygems/index.spec.ts
@@ -1,18 +1,14 @@
 import { getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import {
-  loadBinaryFixture,
-  loadFixture,
-  loadJsonFixture,
-} from '../../../../test/util';
 import * as rubyVersioning from '../../versioning/ruby';
 import { resetCache } from './get-rubygems-org';
 import { RubyGemsDatasource } from '.';
 
-const rubygemsOrgVersions = loadFixture('rubygems-org.txt');
-const railsInfo = loadJsonFixture('rails/info.json');
-const railsVersions = loadJsonFixture('rails/versions.json');
-const railsDependencies = loadBinaryFixture('dependencies-rails.dat');
+const rubygemsOrgVersions = Fixtures?.get('rubygems-org.txt');
+const railsInfo = Fixtures?.getJson('rails/info.json');
+const railsVersions = Fixtures?.getJson('rails/versions.json');
+const railsDependencies = Fixtures?.getBinary('dependencies-rails.dat');
 const emptyMarshalArray = Buffer.from([4, 8, 91, 0]);
 
 describe('modules/datasource/rubygems/index', () => {
@@ -43,11 +39,11 @@ describe('modules/datasource/rubygems/index', () => {
       httpMock
         .scope('https://firstparty.com')
         .get('/basepath/api/v1/gems/rails.json')
-        .reply(200, null);
+        .reply(200);
       httpMock
         .scope('https://thirdparty.com')
         .get('/api/v1/gems/rails.json')
-        .reply(200, null);
+        .reply(200);
       expect(await getPkgReleases(params)).toBeNull();
     });
 
@@ -74,13 +70,13 @@ describe('modules/datasource/rubygems/index', () => {
         .reply(200, rubygemsOrgVersions);
       const res = await getPkgReleases(newparams);
       expect(res).not.toBeNull();
-      expect(res.releases).toHaveLength(2);
+      expect(res?.releases).toHaveLength(2);
       expect(res).toMatchSnapshot();
       expect(
-        res.releases.find((release) => release.version === '0.1.1')
+        res?.releases.find((release) => release.version === '0.1.1')
       ).toBeDefined();
       expect(
-        res.releases.find((release) => release.version === '0.1.2')
+        res?.releases.find((release) => release.version === '0.1.2')
       ).toBeUndefined();
     });
 
@@ -103,7 +99,7 @@ describe('modules/datasource/rubygems/index', () => {
         registryUrls: [],
       });
       expect(res).not.toBeNull();
-      expect(res.releases).toHaveLength(2);
+      expect(res?.releases).toHaveLength(2);
       expect(res).toMatchSnapshot();
     });
 
@@ -116,7 +112,7 @@ describe('modules/datasource/rubygems/index', () => {
         .reply(200, railsVersions);
 
       const res = await getPkgReleases(params);
-      expect(res.releases).toHaveLength(339);
+      expect(res?.releases).toHaveLength(339);
       expect(res).toMatchSnapshot();
     });
 
@@ -133,7 +129,7 @@ describe('modules/datasource/rubygems/index', () => {
         .reply(200, railsVersions);
 
       const res = await getPkgReleases(params);
-      expect(res.releases).toHaveLength(339);
+      expect(res?.releases).toHaveLength(339);
       expect(res).toMatchSnapshot();
     });
 
@@ -145,7 +141,7 @@ describe('modules/datasource/rubygems/index', () => {
       httpMock
         .scope('https://firstparty.com/')
         .get('/basepath/api/v1/gems/rails.json')
-        .reply(200, null);
+        .reply(200);
       expect(await getPkgReleases(params)).toBeNull();
     });
 
@@ -157,8 +153,8 @@ describe('modules/datasource/rubygems/index', () => {
         .get('/api/v1/versions/rails.json')
         .reply(400, {});
       const res = await getPkgReleases(params);
-      expect(res.releases).toHaveLength(1);
-      expect(res.releases[0].version).toBe(railsInfo.version);
+      expect(res?.releases).toHaveLength(1);
+      expect(res?.releases[0].version).toBe(railsInfo.version);
     });
 
     it('errors when version request fails with anything other than 400 or 404', async () => {
@@ -205,7 +201,7 @@ describe('modules/datasource/rubygems/index', () => {
         .get('/api/v1/dependencies?gems=rails')
         .reply(200, railsDependencies);
       const res = await getPkgReleases(newparams);
-      expect(res.releases).toHaveLength(339);
+      expect(res?.releases).toHaveLength(339);
       expect(res).toMatchSnapshot();
     });
   });
diff --git a/lib/modules/datasource/sbt-package/index.spec.ts b/lib/modules/datasource/sbt-package/index.spec.ts
index 271be15ee1..b85e70b1d2 100644
--- a/lib/modules/datasource/sbt-package/index.spec.ts
+++ b/lib/modules/datasource/sbt-package/index.spec.ts
@@ -22,7 +22,7 @@ describe('modules/datasource/sbt-package/index', () => {
       httpMock
         .scope('https://failed_repo')
         .get('/maven/org/scalatest/')
-        .reply(404, null);
+        .reply(404);
       httpMock
         .scope('https://repo.maven.apache.org')
         .get('/maven2/com/example/')
diff --git a/lib/modules/datasource/sbt-plugin/index.spec.ts b/lib/modules/datasource/sbt-plugin/index.spec.ts
index 1d70d672d2..9926099389 100644
--- a/lib/modules/datasource/sbt-plugin/index.spec.ts
+++ b/lib/modules/datasource/sbt-plugin/index.spec.ts
@@ -1,13 +1,13 @@
 import { getPkgReleases } from '..';
+import { Fixtures } from '../../../../test/fixtures';
 import * as httpMock from '../../../../test/http-mock';
-import { loadFixture } from '../../../../test/util';
 import * as mavenVersioning from '../../versioning/maven';
 import { MAVEN_REPO } from '../maven/common';
 import { parseIndexDir } from '../sbt-package/util';
 import { SbtPluginDatasource } from '.';
 
-const mavenIndexHtml = loadFixture(`maven-index.html`);
-const sbtPluginIndex = loadFixture(`sbt-plugins-index.html`);
+const mavenIndexHtml = Fixtures.get(`maven-index.html`);
+const sbtPluginIndex = Fixtures.get(`sbt-plugins-index.html`);
 
 describe('modules/datasource/sbt-plugin/index', () => {
   it('parses Maven index directory', () => {
@@ -23,7 +23,7 @@ describe('modules/datasource/sbt-plugin/index', () => {
       httpMock
         .scope('https://failed_repo')
         .get('/maven/org/scalatest/')
-        .reply(404, null);
+        .reply(404);
       httpMock
         .scope('https://repo.maven.apache.org')
         .get('/maven2/org/scalatest/')
diff --git a/lib/util/modules.ts b/lib/util/modules.ts
index 2f2776c1d6..e0bc3596cc 100644
--- a/lib/util/modules.ts
+++ b/lib/util/modules.ts
@@ -27,7 +27,7 @@ function relatePath(here: string, there: string): string {
 
 export function loadModules<T>(
   dirname: string,
-  validate?: (module: T, moduleName?: string) => boolean,
+  validate?: (module: T, moduleName: string) => boolean,
   filter: (moduleName: string) => boolean = () => true
 ): Record<string, T> {
   const result: Record<string, T> = {};
-- 
GitLab