diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e9e23fa395aaaa154366e5646233f133397865ae..f3eaa245343ebbb5a3a0c0dc89c2ad44f0cd17ab 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -404,7 +404,7 @@ jobs:
       - name: Check coverage threshold
         run: |
           pnpm nyc check-coverage -t ./coverage/nyc \
-            --branches 99.57 \
+            --branches 99.61 \
             --functions 100 \
             --lines 100 \
             --statements 100
diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts
index 81beb430f6695066b59a49e20bb2563d1d3765d8..8f3a907702fa2373803a50a1e838ee26ebffd84d 100644
--- a/lib/config/migration.spec.ts
+++ b/lib/config/migration.spec.ts
@@ -669,9 +669,6 @@ describe('config/migration', () => {
 
   it('it migrates gradle-lite', () => {
     const config: RenovateConfig = {
-      gradle: {
-        enabled: false,
-      },
       'gradle-lite': {
         enabled: true,
         fileMatch: ['foo'],
diff --git a/lib/config/migrations/custom/package-rules-migration.ts b/lib/config/migrations/custom/package-rules-migration.ts
index a1e0712ea470dc8ffc39de3c8bef257e8394d4c3..3e6450956a9b7018ed0a3a9b86f929138bfa270e 100644
--- a/lib/config/migrations/custom/package-rules-migration.ts
+++ b/lib/config/migrations/custom/package-rules-migration.ts
@@ -1,3 +1,4 @@
+import is from '@sindresorhus/is';
 import type { PackageRule } from '../../types';
 import { AbstractMigration } from '../base/abstract-migration';
 
@@ -30,11 +31,11 @@ export class PackageRulesMigration extends AbstractMigration {
   override readonly propertyName = 'packageRules';
 
   override run(value: unknown): void {
-    let packageRules = (this.get('packageRules') as PackageRule[]) ?? [];
-    packageRules = Array.isArray(packageRules) ? [...packageRules] : [];
+    let packageRules = this.get('packageRules') as PackageRule[];
+    if (is.nonEmptyArray(packageRules)) {
+      packageRules = packageRules.map(renameKeys);
 
-    packageRules = packageRules.map(renameKeys);
-
-    this.rewrite(packageRules);
+      this.rewrite(packageRules);
+    }
   }
 }
diff --git a/lib/modules/datasource/datasource.spec.ts b/lib/modules/datasource/datasource.spec.ts
index bf74641b1c7b8f423c58df4048e9fc3726db8011..91b7b0d9d711622d54f3283fed8be979413ca84f 100644
--- a/lib/modules/datasource/datasource.spec.ts
+++ b/lib/modules/datasource/datasource.spec.ts
@@ -33,4 +33,14 @@ describe('modules/datasource/datasource', () => {
       testDatasource.getReleases(partial<GetReleasesConfig>())
     ).rejects.toThrow(EXTERNAL_HOST_ERROR);
   });
+
+  it('should throw on statusCode >=500 && <600', async () => {
+    const testDatasource = new TestDatasource();
+
+    httpMock.scope(exampleUrl).get('/').reply(504);
+
+    await expect(
+      testDatasource.getReleases(partial<GetReleasesConfig>())
+    ).rejects.toThrow(EXTERNAL_HOST_ERROR);
+  });
 });
diff --git a/lib/modules/datasource/datasource.ts b/lib/modules/datasource/datasource.ts
index 0a427cefc6ce6e9bb0b4cb4507f487dd1f542223..343ca34ddcbe59d898e030f2b86dd60189dc8dfb 100644
--- a/lib/modules/datasource/datasource.ts
+++ b/lib/modules/datasource/datasource.ts
@@ -46,11 +46,7 @@ export abstract class Datasource implements DatasourceApi {
 
       const statusCode = err.response?.statusCode;
       if (statusCode) {
-        if (statusCode === 429) {
-          throw new ExternalHostError(err);
-        }
-
-        if (statusCode >= 500 && statusCode < 600) {
+        if (statusCode === 429 || (statusCode >= 500 && statusCode < 600)) {
           throw new ExternalHostError(err);
         }
       }
diff --git a/lib/modules/datasource/index.spec.ts b/lib/modules/datasource/index.spec.ts
index 4646a4e53f8c74c2816928632fdb2417919416d5..28c673dc5d0a9d98b71ab8bbbe050810141ce70e 100644
--- a/lib/modules/datasource/index.spec.ts
+++ b/lib/modules/datasource/index.spec.ts
@@ -94,6 +94,28 @@ class DummyDatasource3 extends Datasource {
   }
 }
 
+class DummyDatasource4 extends DummyDatasource3 {
+  override defaultRegistryUrls = undefined as never;
+}
+
+class DummyDatasource5 extends Datasource {
+  override registryStrategy = undefined as never;
+
+  constructor(private registriesMock: RegistriesMock = defaultRegistriesMock) {
+    super(datasource);
+  }
+
+  override getReleases({
+    registryUrl,
+  }: GetReleasesConfig): Promise<ReleaseResult | null> {
+    const fn = this.registriesMock[registryUrl!];
+    if (typeof fn === 'function') {
+      return Promise.resolve(fn());
+    }
+    return Promise.resolve(fn ?? null);
+  }
+}
+
 jest.mock('./metadata-manual', () => ({
   manualChangelogUrls: {
     dummy: {
@@ -313,6 +335,16 @@ describe('modules/datasource/index', () => {
       });
     });
 
+    // for coverage
+    it('undefined defaultRegistryUrls with customRegistrySupport works', async () => {
+      datasources.set(datasource, new DummyDatasource4());
+      const res = await getPkgReleases({
+        datasource,
+        packageName,
+      });
+      expect(res).toBeNull();
+    });
+
     it('applies extractVersion', async () => {
       const registries: RegistriesMock = {
         'https://reg1.com': {
@@ -454,6 +486,7 @@ describe('modules/datasource/index', () => {
       describe('merge', () => {
         class MergeRegistriesDatasource extends DummyDatasource {
           override readonly registryStrategy = 'merge';
+          override caching = true;
           override readonly defaultRegistryUrls = [
             'https://reg1.com',
             'https://reg2.com',
@@ -472,6 +505,10 @@ describe('modules/datasource/index', () => {
           'https://reg5.com': () => {
             throw new Error('b');
           },
+          // for coverage
+          'https://reg6.com': null,
+          // has the same result as reg1 url, to test de-deplication of releases
+          'https://reg7.com': () => ({ releases: [{ version: '1.0.0' }] }),
         };
 
         beforeEach(() => {
@@ -492,7 +529,7 @@ describe('modules/datasource/index', () => {
           });
         });
 
-        it('ignores custom defaultRegistryUrls if registrUrls are set', async () => {
+        it('ignores custom defaultRegistryUrls if registryUrls are set', async () => {
           const res = await getPkgReleases({
             datasource,
             packageName,
@@ -522,6 +559,20 @@ describe('modules/datasource/index', () => {
           });
         });
 
+        it('filters out duplicate releases', async () => {
+          const res = await getPkgReleases({
+            datasource,
+            packageName,
+            registryUrls: ['https://reg1.com', 'https://reg7.com'],
+          });
+          expect(res).toMatchObject({
+            releases: [
+              { registryUrl: 'https://reg1.com', version: '1.0.0' },
+              // { registryUrl: 'https://reg2.com', version: '1.0.0' },
+            ],
+          });
+        });
+
         it('merges registries and aborts on ExternalHostError', async () => {
           await expect(
             getPkgReleases({
@@ -614,11 +665,20 @@ describe('modules/datasource/index', () => {
         it('returns null if no releases are found', async () => {
           const registries: RegistriesMock = {
             'https://reg1.com': () => {
-              throw new Error('a');
+              throw { statusCode: '404' };
             },
             'https://reg2.com': () => {
+              throw { statusCode: '401' };
+            },
+            'https://reg3.com': () => {
+              throw { statusCode: '403' };
+            },
+            'https://reg4.com': () => {
               throw new Error('b');
             },
+            'https://reg5.com': () => {
+              throw { code: '403' };
+            },
           };
           const registryUrls = Object.keys(registries);
           datasources.set(datasource, new HuntRegistriyDatasource(registries));
@@ -631,6 +691,31 @@ describe('modules/datasource/index', () => {
 
           expect(res).toBeNull();
         });
+
+        it('defaults to hunt strategy', async () => {
+          const registries: RegistriesMock = {
+            'https://reg1.com': null,
+            'https://reg2.com': () => {
+              throw new Error('unknown');
+            },
+            'https://reg3.com': { releases: [{ version: '1.0.0' }] },
+            'https://reg4.com': { releases: [{ version: '2.0.0' }] },
+            'https://reg5.com': { releases: [{ version: '3.0.0' }] },
+          };
+          const registryUrls = Object.keys(registries);
+          datasources.set(datasource, new DummyDatasource5(registries));
+
+          const res = await getPkgReleases({
+            datasource,
+            packageName,
+            registryUrls,
+          });
+
+          expect(res).toMatchObject({
+            registryUrl: 'https://reg3.com',
+            releases: [{ version: '1.0.0' }],
+          });
+        });
       });
 
       describe('relaseConstraintFiltering', () => {
diff --git a/lib/modules/datasource/metadata.spec.ts b/lib/modules/datasource/metadata.spec.ts
index da55b5bd5f1ddd0954959c46cd814a54c115ac36..36661f2490078e4012cb8ffcc8f87cafd5bcc43e 100644
--- a/lib/modules/datasource/metadata.spec.ts
+++ b/lib/modules/datasource/metadata.spec.ts
@@ -1,9 +1,11 @@
+import { partial } from '../../../test/util';
 import { HelmDatasource } from './helm';
 import { MavenDatasource } from './maven';
 import {
   addMetaData,
   massageGithubUrl,
   massageUrl,
+  normalizeDate,
   shouldDeleteHomepage,
 } from './metadata';
 import { NpmDatasource } from './npm';
@@ -502,4 +504,38 @@ describe('modules/datasource/metadata', () => {
       expect(shouldDeleteHomepage(sourceUrl, homepage)).toBe(expected);
     }
   );
+
+  // for coverage
+  it('should handle dep with no releases', () => {
+    const dep = partial<ReleaseResult>({});
+
+    const datasource = PypiDatasource.id;
+    const packageName = 'django';
+
+    addMetaData(dep, datasource, packageName);
+    expect(dep).toEqual({
+      changelogUrl:
+        'https://github.com/django/django/tree/master/docs/releases',
+      sourceUrl: 'https://github.com/django/django',
+    });
+  });
+
+  describe('normalizeDate()', () => {
+    it('works for number input', () => {
+      const now = Date.now();
+      expect(normalizeDate(now)).toBe(new Date(now).toISOString());
+    });
+
+    it('works for string input', () => {
+      expect(normalizeDate('2021-01-01')).toBe(
+        new Date('2021-01-01').toISOString()
+      );
+    });
+
+    it('works for Date instance', () => {
+      expect(normalizeDate(new Date('2021-01-01'))).toBe(
+        new Date('2021-01-01').toISOString()
+      );
+    });
+  });
 });