From 89e3fd96e8bc1afeb4672525033ab6147255f773 Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 01:43:32 +0500 Subject: [PATCH 1/9] fix(ci): resolve webhook IP cache invalidation bug --- utility.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utility.py b/utility.py index 96308e41..c775b74b 100644 --- a/utility.py +++ b/utility.py @@ -78,6 +78,7 @@ def decorated_function(*args, **kwargs): cached_web_hook_blocks: List[str] = [] +cached_load_time: datetime = datetime(1970, 1, 1) def cache_has_expired() -> bool: @@ -87,7 +88,7 @@ def cache_has_expired() -> bool: :return: True if the cache was last updated more than one hour ago. :rtype: bool """ - cached_load_time = datetime(1970, 1, 1) + global cached_load_time return cached_load_time + timedelta(hours=1) < datetime.now() @@ -115,6 +116,7 @@ def get_cached_web_hook_blocks() -> List[str]: :rtype: List[str] """ global cached_web_hook_blocks + global cached_load_time from run import config if len(cached_web_hook_blocks) == 0 or cache_has_expired(): @@ -124,6 +126,8 @@ def get_cached_web_hook_blocks() -> List[str]: 'https://api.github.com/meta', auth=(client_id, client_secret)).json() try: cached_web_hook_blocks = meta_json['hooks'] + # We successfully fetched the IPs so we reset the clock + cached_load_time = datetime.now() except KeyError: g.log.critical(f"Failed to retrieve hook IP's from GitHub! API returned {meta_json}") From faa1cb922b0e316f878e3491ba9cf2470c3a29e1 Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 02:00:08 +0500 Subject: [PATCH 2/9] test: bypass webhook IP cache during tests to prevent state pollution --- utility.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utility.py b/utility.py index c775b74b..526763fd 100644 --- a/utility.py +++ b/utility.py @@ -89,6 +89,12 @@ def cache_has_expired() -> bool: :rtype: bool """ global cached_load_time + from flask import current_app + + # Always bypass cache during unit tests to prevent test pollution + if current_app.config.get('TESTING'): + return True + return cached_load_time + timedelta(hours=1) < datetime.now() From 0d45d148586e7738591a4da632c001c74aab3287 Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 02:09:15 +0500 Subject: [PATCH 3/9] fix(ci): remove fragile current_app context in favor of global config --- utility.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utility.py b/utility.py index 526763fd..7f34d94b 100644 --- a/utility.py +++ b/utility.py @@ -89,10 +89,10 @@ def cache_has_expired() -> bool: :rtype: bool """ global cached_load_time - from flask import current_app + from run import config # Always bypass cache during unit tests to prevent test pollution - if current_app.config.get('TESTING'): + if config.get('TESTING'): return True return cached_load_time + timedelta(hours=1) < datetime.now() From 2c19b3cf90ec620599fcecf03282f6d57a393578 Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 02:24:28 +0500 Subject: [PATCH 4/9] test: implement foolproof sys modules check to isolate webhook cache --- utility.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/utility.py b/utility.py index 7f34d94b..690816ec 100644 --- a/utility.py +++ b/utility.py @@ -89,12 +89,13 @@ def cache_has_expired() -> bool: :rtype: bool """ global cached_load_time - from run import config + import sys - # Always bypass cache during unit tests to prevent test pollution - if config.get('TESTING'): + # Foolproof bypass: if a test framework is running in the Python interpreter, bypass the cache to prevent mock pollution + if 'nose' in sys.modules or 'unittest' in sys.modules: return True + from datetime import datetime, timedelta return cached_load_time + timedelta(hours=1) < datetime.now() From ba335111538078f126cce1ed8779317c05628abd Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 02:32:47 +0500 Subject: [PATCH 5/9] style: wrap comment to pass 120 character limit --- utility.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utility.py b/utility.py index 690816ec..43c38afa 100644 --- a/utility.py +++ b/utility.py @@ -91,7 +91,8 @@ def cache_has_expired() -> bool: global cached_load_time import sys - # Foolproof bypass: if a test framework is running in the Python interpreter, bypass the cache to prevent mock pollution + # Foolproof bypass: if a test framework is running in the Python + # interpreter, bypass the cache to prevent mock pollution. if 'nose' in sys.modules or 'unittest' in sys.modules: return True From 6701643883a7b621670c65b03dfe47d329bce27e Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 19:12:57 +0500 Subject: [PATCH 6/9] refactor(ci): isolate test cache using mock patch and addCleanup --- tests/base.py | 13 +++++++++++++ utility.py | 7 ------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/base.py b/tests/base.py index f15c81ab..88f28c20 100644 --- a/tests/base.py +++ b/tests/base.py @@ -2,6 +2,7 @@ import os import warnings +import utility from collections import namedtuple from contextlib import contextmanager from unittest import mock @@ -278,6 +279,14 @@ def create_app(self, mock_config, mock_storage_client): def setUp(self): """Set up all entities.""" + # Patch the cache before parent setup runs to prevent HTTP calls + self.cache_patcher = mock.patch.object( + utility, 'cache_has_expired', return_value=False + ) + self.cache_patcher.start() + self.addCleanup(self.cache_patcher.stop) + + super().setUp() self.app.preprocess_request() g.db = create_session( self.app.config['DATABASE_URI'], drop_tables=True) @@ -396,6 +405,10 @@ def setUp(self): g.db.add_all(forbidden_ext) g.db.commit() + def tearDown(self): + """Clean up after every test.""" + super().tearDown() + @staticmethod def create_login_form_data(email, password) -> dict: """ diff --git a/utility.py b/utility.py index 43c38afa..a12429c5 100644 --- a/utility.py +++ b/utility.py @@ -89,13 +89,6 @@ def cache_has_expired() -> bool: :rtype: bool """ global cached_load_time - import sys - - # Foolproof bypass: if a test framework is running in the Python - # interpreter, bypass the cache to prevent mock pollution. - if 'nose' in sys.modules or 'unittest' in sys.modules: - return True - from datetime import datetime, timedelta return cached_load_time + timedelta(hours=1) < datetime.now() From a11763c31879a4b78219d57abf54cdb8c7d5105a Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 19:25:34 +0500 Subject: [PATCH 7/9] style(ci): sort imports in base test class to satisfy isort --- tests/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/base.py b/tests/base.py index 88f28c20..021f720a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -2,7 +2,6 @@ import os import warnings -import utility from collections import namedtuple from contextlib import contextmanager from unittest import mock @@ -12,6 +11,7 @@ from sqlalchemy import text from werkzeug.datastructures import Headers +import utility from database import create_session from mod_auth.models import Role, User from mod_customized.models import CustomizedTest, TestFork From 2f8ffc423a48dbb3f4a222c05ed16c9617c1ac9a Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 19:48:38 +0500 Subject: [PATCH 8/9] refactor(ci): convert cache expiration to pure function per review --- tests/base.py | 14 +++++++------- utility.py | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/base.py b/tests/base.py index 021f720a..10cca106 100644 --- a/tests/base.py +++ b/tests/base.py @@ -4,7 +4,6 @@ import warnings from collections import namedtuple from contextlib import contextmanager -from unittest import mock from flask import g from flask_testing import TestCase @@ -279,12 +278,13 @@ def create_app(self, mock_config, mock_storage_client): def setUp(self): """Set up all entities.""" - # Patch the cache before parent setup runs to prevent HTTP calls - self.cache_patcher = mock.patch.object( - utility, 'cache_has_expired', return_value=False - ) - self.cache_patcher.start() - self.addCleanup(self.cache_patcher.stop) + from datetime import datetime + + import utility + + # Reset the state directly for test isolation. No mocks needed! + utility.cached_load_time = datetime(1970, 1, 1) + utility.cached_web_hook_blocks = [] super().setUp() self.app.preprocess_request() diff --git a/utility.py b/utility.py index a12429c5..bd341307 100644 --- a/utility.py +++ b/utility.py @@ -81,16 +81,15 @@ def decorated_function(*args, **kwargs): cached_load_time: datetime = datetime(1970, 1, 1) -def cache_has_expired() -> bool: +def cache_has_expired(load_time: datetime) -> bool: """ Check if the cache expired. :return: True if the cache was last updated more than one hour ago. :rtype: bool """ - global cached_load_time from datetime import datetime, timedelta - return cached_load_time + timedelta(hours=1) < datetime.now() + return load_time + timedelta(hours=1) < datetime.now() def is_github_web_hook_ip(request_ip: Union[IPv4Address, IPv6Address]) -> bool: @@ -120,7 +119,7 @@ def get_cached_web_hook_blocks() -> List[str]: global cached_load_time from run import config - if len(cached_web_hook_blocks) == 0 or cache_has_expired(): + if len(cached_web_hook_blocks) == 0 or cache_has_expired(cached_load_time): client_id = config.get('GITHUB_CLIENT_ID', '') client_secret = config.get('GITHUB_CLIENT_KEY', '') meta_json = requests.get( From aab3b5ac94fac4967d651f46047eb9c6c3cbebda Mon Sep 17 00:00:00 2001 From: MrButtCode Date: Sun, 15 Mar 2026 19:52:11 +0500 Subject: [PATCH 9/9] fix(ci): restore unittest mock import for existing cloud storage tests --- tests/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/base.py b/tests/base.py index 10cca106..7f6e0d19 100644 --- a/tests/base.py +++ b/tests/base.py @@ -4,6 +4,7 @@ import warnings from collections import namedtuple from contextlib import contextmanager +from unittest import mock from flask import g from flask_testing import TestCase