From 69939a097c0a34631644033baa9c27c57fe3e913 Mon Sep 17 00:00:00 2001 From: Aspirant200715 Date: Fri, 13 Nov 2026 21:48:41 +0530 Subject: [PATCH] fix(controllers): guard .first() database queries against None to prevent AttributeError Guard against AttributeError on empty database or unseeded GeneralData and CCExtractorVersion records. - mod_home/controllers.py: guard last_commit query on index route - mod_sample/controllers.py: guard last_commit and last_release queries; avoid querying Test when commit hash is None - mod_test/controllers.py: raise TestNotFoundException in by_platform if fetch_commit entry is missing - tests/: add unit tests for unseeded database scenarios in test_home, test_sample, and test_test --- mod_home/controllers.py | 3 ++- mod_sample/controllers.py | 10 ++++++---- mod_test/controllers.py | 6 +++++- tests/test_home/test_controllers.py | 16 ++++++++++++++++ tests/test_sample/test_controllers.py | 21 +++++++++++++++++++++ tests/test_test/test_controllers.py | 14 ++++++++++++++ 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/mod_home/controllers.py b/mod_home/controllers.py index f44a4e59d..5cc711382 100755 --- a/mod_home/controllers.py +++ b/mod_home/controllers.py @@ -22,7 +22,8 @@ def before_app_request() -> None: @template_renderer() def index(): """Render index home page.""" - last_commit = GeneralData.query.filter(GeneralData.key == 'last_commit').first().value + last_commit_entry = GeneralData.query.filter(GeneralData.key == 'last_commit').first() + last_commit = last_commit_entry.value if last_commit_entry is not None else None last_release = CCExtractorVersion.query.order_by(CCExtractorVersion.released.desc()).first() test_access = False if g.user is not None and g.user.role in [Role.tester, Role.contributor, Role.admin]: diff --git a/mod_sample/controllers.py b/mod_sample/controllers.py index dc3ef161f..992a421e6 100755 --- a/mod_sample/controllers.py +++ b/mod_sample/controllers.py @@ -56,11 +56,13 @@ def display_sample_info(sample) -> Dict[str, Any]: # in case no media info present in the sample media_info = None - latest_commit = GeneralData.query.filter(GeneralData.key == 'last_commit').first().value - last_release = CCExtractorVersion.query.order_by(CCExtractorVersion.released.desc()).first().commit + latest_commit_entry = GeneralData.query.filter(GeneralData.key == 'last_commit').first() + latest_commit = latest_commit_entry.value if latest_commit_entry is not None else None + last_release_entry = CCExtractorVersion.query.order_by(CCExtractorVersion.released.desc()).first() + last_release = last_release_entry.commit if last_release_entry is not None else None - test_commit = Test.query.filter(Test.commit == latest_commit).first() - test_release = Test.query.filter(Test.commit == last_release).first() + test_commit = Test.query.filter(Test.commit == latest_commit).first() if latest_commit else None + test_release = Test.query.filter(Test.commit == last_release).first() if last_release else None regression_tests = RegressionTest.query.filter(RegressionTest.sample_id == sample.id).all() status = 'Unknown' status_release = 'Unknown' diff --git a/mod_test/controllers.py b/mod_test/controllers.py index 691ab698f..17f758b51 100644 --- a/mod_test/controllers.py +++ b/mod_test/controllers.py @@ -315,7 +315,11 @@ def latest_commit_info(platform): g.log.critical(f"platform {platform} is not supported!") abort(404) # Look up the hash of the latest commit - commit_hash = GeneralData.query.filter(GeneralData.key == 'fetch_commit_' + platform.value).first().value + commit_entry = GeneralData.query.filter(GeneralData.key == 'fetch_commit_' + platform.value).first() + if commit_entry is None: + g.log.error(f"no commit hash found for platform {str(platform)}") + raise TestNotFoundException(f"There is no commit hash available for {str(platform)}") + commit_hash = commit_entry.value test = Test.query.filter(Test.commit == commit_hash, Test.platform == platform).first() if test is None: diff --git a/tests/test_home/test_controllers.py b/tests/test_home/test_controllers.py index 875a9b0aa..ee46283d5 100644 --- a/tests/test_home/test_controllers.py +++ b/tests/test_home/test_controllers.py @@ -30,3 +30,19 @@ def test_if_user_has_test_access_rights(self): self.assertEqual(response.status_code, 200) self.assert_context('test_access', True) self.assert_template_used('home/index.html') + + def test_root_without_general_data(self): + """Test the access of the home page when GeneralData and CCExtractorVersion are empty.""" + from flask import g + + from mod_home.models import CCExtractorVersion, GeneralData + + GeneralData.query.delete() + CCExtractorVersion.query.delete() + g.db.commit() + + response = self.app.test_client().get('/') + self.assertEqual(response.status_code, 200) + self.assert_context('ccx_latest_commit', None) + self.assert_context('ccx_last_release', None) + self.assert_template_used('home/index.html') diff --git a/tests/test_sample/test_controllers.py b/tests/test_sample/test_controllers.py index 70e86d293..bb0f79ef3 100644 --- a/tests/test_sample/test_controllers.py +++ b/tests/test_sample/test_controllers.py @@ -39,6 +39,27 @@ def test_sample_pass(self, mock_media): self.assertIn('Repository: Pass', str(response.data)) self.assertIn('Last release: Pass', str(response.data)) + @mock.patch('mod_sample.media_info_parser.MediaInfoFetcher') + def test_sample_info_without_commit_or_release(self, mock_media): + """Test sample info page when GeneralData and CCExtractorVersion are empty.""" + from flask import g + + import mod_sample.controllers + from mod_home.models import CCExtractorVersion, GeneralData + + GeneralData.query.delete() + CCExtractorVersion.query.delete() + g.db.commit() + + reload(mod_sample.controllers) + response = self.app.test_client().get('/sample/sample1') + self.assertEqual(response.status_code, 200) + self.assert_template_used('sample/sample_info.html') + self.assert_context('latest_commit', 'Unknown') + self.assert_context('latest_commit_test', None) + self.assert_context('latest_release', 'Unknown') + self.assert_context('latest_release_test', None) + @mock.patch('mod_sample.media_info_parser.MediaInfoFetcher') def test_sample_fail(self, mock_media): """Test the access and data of the failed sample info page.""" diff --git a/tests/test_test/test_controllers.py b/tests/test_test/test_controllers.py index eeec69b22..60d9ca43a 100644 --- a/tests/test_test/test_controllers.py +++ b/tests/test_test/test_controllers.py @@ -315,3 +315,17 @@ def test_download_build_log_file(self, mock_g, mock_serve, mock_test, mock_os): self.assertEqual(response, mock_serve()) mock_test.query.filter.assert_called_once() mock_os.path.isfile.assert_called_once() + + def test_by_platform_no_commit_hash(self): + """Test by_platform raises TestNotFoundException when no commit hash in GeneralData.""" + from flask import g + + from mod_home.models import GeneralData + from mod_test.controllers import TestNotFoundException, by_platform + + GeneralData.query.delete() + g.db.commit() + + with self.app.test_request_context(): + with self.assertRaises(TestNotFoundException): + by_platform('linux')