diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0651f0a..13002bf6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,4 +115,5 @@ jobs: MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_header_syntax_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_syntax_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_schema_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 - MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_status_combine --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 \ No newline at end of file + MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_status_combine --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 + MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_management_commands --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 \ No newline at end of file diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index f2467583..287b993b 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -116,6 +116,7 @@ jobs: MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_header_syntax_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_syntax_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_schema_validation_task --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 + MEDIA_ROOT=./apps/ifc_validation/fixtures python3 manage.py test apps.ifc_validation.tests.tests_management_commands --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 deploy: diff --git a/backend/Makefile b/backend/Makefile index c44117f3..e7d49d12 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -66,7 +66,7 @@ stop-worker: -$(PYTHON) -m celery -A core control shutdown \ --destination=worker@$(shell hostname) || true -test: test-models test-header-validation-task test-syntax-task test-syntax-header-validation-task test-schema-task test-magic-and-av-task test-utils test-file-retention-task test-status-combine +test: test-models test-header-validation-task test-syntax-task test-syntax-header-validation-task test-schema-task test-magic-and-av-task test-utils test-file-retention-task test-status-combine test-management-commands test-models: MEDIA_ROOT=./apps/ifc_validation/fixtures $(PYTHON) manage.py test apps/ifc_validation_models --settings apps.ifc_validation_models.test_settings --debug-mode --verbosity 3 @@ -92,6 +92,9 @@ test-file-retention-task: test-status-combine: MEDIA_ROOT=./apps/ifc_validation/fixtures $(PYTHON) manage.py test apps.ifc_validation.tests.tests_status_combine --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 +test-management-commands: + MEDIA_ROOT=./apps/ifc_validation/fixtures $(PYTHON) manage.py test apps.ifc_validation.tests.tests_management_commands --settings apps.ifc_validation.test_settings --debug-mode --verbosity 3 + test-utils: $(PYTHON) manage.py test core.tests.test_utils --debug-mode --verbosity 3 diff --git a/backend/apps/ifc_validation/management/commands/display_user_locks.py b/backend/apps/ifc_validation/management/commands/display_user_locks.py new file mode 100644 index 00000000..59e12e6e --- /dev/null +++ b/backend/apps/ifc_validation/management/commands/display_user_locks.py @@ -0,0 +1,36 @@ +from redis import Redis +from redis.lock import Lock +import logging + +from django.core.management.base import BaseCommand + +from core.settings import CELERY_BROKER_URL + +logger = logging.getLogger(__name__) +redis_client: Redis = Redis.from_url(CELERY_BROKER_URL, decode_responses=True) + +class Command(BaseCommand): + + help = ( + 'Scans and displays all current user locks (user ID, task name, TTL)' + ) + + def handle(self, *args, **options): + + # Scan for keys matching the lock pattern + lock_pattern = "lock:celery:user:*:task:*" + lock_keys = redis_client.keys(lock_pattern) + + if not lock_keys: + logger.info("No active user locks found.") + return + + logger.info(f"Found {len(lock_keys)} active user lock(s):") + for key in lock_keys: + # Extract user_id and task_name from the key + parts = key.split(":") + if len(parts) >= 6: + user_id = parts[3] + task_name = parts[5] + ttl = redis_client.ttl(key) + logger.info(f"- User ID: {user_id}, Task: {task_name}, TTL: {ttl:,} seconds") diff --git a/backend/apps/ifc_validation/tests/tests_management_commands.py b/backend/apps/ifc_validation/tests/tests_management_commands.py new file mode 100644 index 00000000..4a6b3eed --- /dev/null +++ b/backend/apps/ifc_validation/tests/tests_management_commands.py @@ -0,0 +1,34 @@ +from django.core.management import call_command +from django.contrib.auth.models import User +from django.test import TransactionTestCase + +from core.redis_lock import acquire_user_lock + + +class DisplayUserLocksManagementCommandTestCase(TransactionTestCase): + + def test_display_user_locks_no_active_locks(self): + + # arrange + test_user = User.objects.create_user(username='testuser', password='testpass') + + # act + with self.assertLogs(level='INFO') as cm: + call_command('display_user_locks') + + # assert + self.assertTrue(any("No active user locks found." in message for message in cm.output)) + + def test_display_user_locks_active_user_lock(self): + + # arrange + test_user = User.objects.create_user(username='testuser', password='testpass') + with acquire_user_lock(user_id=test_user.id, task_name='test_task') as lock: + + # act + with self.assertLogs(level='INFO') as cm: + call_command('display_user_locks') + + # assert + print(cm.output) # for debugging if test fails + self.assertTrue(any(f"User ID: {test_user.id}, Task: test_task" in message for message in cm.output)) \ No newline at end of file