diff --git a/netbox/core/jobs.py b/netbox/core/jobs.py index 46dc10d1c..e55973c0f 100644 --- a/netbox/core/jobs.py +++ b/netbox/core/jobs.py @@ -222,8 +222,11 @@ class SystemHousekeepingJob(JobRunner): if 'tag_name' not in release or release.get('devrelease') or release.get('prerelease'): continue releases.append((version.parse(release['tag_name']), release.get('html_url'))) - latest_release = max(releases) self.logger.debug(f"Found {len(response.json())} releases; {len(releases)} usable") + if not releases: + self.logger.info("No usable releases found; skipping") + return + latest_release = max(releases) self.logger.info(f"Latest release: {latest_release[0]}") # Cache the most recent release diff --git a/netbox/core/tests/test_jobs.py b/netbox/core/tests/test_jobs.py index 859b743bc..650d843a9 100644 --- a/netbox/core/tests/test_jobs.py +++ b/netbox/core/tests/test_jobs.py @@ -298,6 +298,26 @@ class CheckForNewReleasesTestCase(HousekeepingRunnerMixin, TestCase): cache.set.assert_not_called() + @override_settings(ISOLATED_DEPLOYMENT=False, RELEASE_CHECK_URL='https://api.example/') + def test_check_for_new_releases_handles_no_stable_releases(self): + # All entries are filtered out (prereleases, devreleases, or missing tag_name); + # check_for_new_releases() must not crash on the resulting empty list. + response = MagicMock() + response.json.return_value = [ + {'tag_name': 'v4.7.0-rc1', 'html_url': 'https://example/rc', 'prerelease': True}, + {'tag_name': 'v4.7.0-dev', 'html_url': 'https://example/dev', 'devrelease': True}, + {'html_url': 'https://example/no-tag'}, + ] + + with ( + patch('core.jobs.requests.get', return_value=response), + patch('core.jobs.cache.set') as cache_set, + patch('core.jobs.resolve_proxies', return_value={}), + ): + self._runner().check_for_new_releases() + + cache_set.assert_not_called() + @override_settings(ISOLATED_DEPLOYMENT=False, RELEASE_CHECK_URL='https://api.example/') def test_check_for_new_releases_caches_latest_stable_release(self): response = MagicMock()