From fd8e2f34898a52eb14aff0ae0f9cd3bf638b6a0b Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:03:11 -0500 Subject: [PATCH 01/10] Add security tests --- users/tests/test_security.py | 141 ++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/users/tests/test_security.py b/users/tests/test_security.py index 90c6b3d..f5023f6 100644 --- a/users/tests/test_security.py +++ b/users/tests/test_security.py @@ -1,5 +1,11 @@ from django.test import Client, TestCase from django.urls import reverse +from django.contrib.auth import get_user_model +from users.models import HashedEmail, RecoveryKey +from django.contrib.auth.hashers import check_password +import hashlib + +User = get_user_model() class TestRegistrationSecurity(TestCase): def setUp(self): @@ -48,4 +54,137 @@ def test_xss_username(self): 'username', 'Enter a valid username. This value may contain only letters, ' 'numbers, and @/./+/-/_ characters.' - ) \ No newline at end of file + ) + +class TestSecurityMeasures(TestCase): + def setUp(self): + self.client = Client() + self.signup_url = reverse('users:signup') + self.login_url = reverse('users:login') + self.email = "test@example.com" + self.password = "SecurePass123!" + self.username = "testuser" + + # Create a test user + self.user = User.objects.create_user( + username=self.username, + email=self.email, + password=self.password + ) + self.hashed_email = HashedEmail.get_or_create_hash(self.email) + self.user.hashed_email = self.hashed_email + self.user.save() + + def test_email_purging(self): + """Test that email is properly purged after recovery key is viewed""" + # Start with a fresh user that hasn't viewed their recovery key + self.user.recovery_key_viewed = False + self.user.email_purged = False + self.user.save() + + # Log in first + self.client.force_login(self.user) + + # Simulate first login behavior by generating recovery key + recovery_key = RecoveryKey.generate_recovery_key() + recovery_key_obj = RecoveryKey(user=self.user) + recovery_key_obj.encrypt_recovery_key(recovery_key) + recovery_key_obj.save() + + # Set up session with recovery key + session = self.client.session + session['recovery_key'] = recovery_key + session.save() + + # Now get the recovery key page + response = self.client.get(reverse('users:show_recovery_key')) + self.assertEqual(response.status_code, 302) + + def test_purged_email_recovery(self): + """Test that purged emails cannot be recovered""" + # Purge the email + self.user.email_purged = True + self.user.email = '' + self.user.save() + + # Try to find user by original email through various means + self.assertFalse(User.objects.filter(email=self.email).exists()) + + # Verify we can still find user by hashed email + email_hash = HashedEmail.hash_email(self.email) + self.assertTrue(User.objects.filter(hashed_email__email_hash=email_hash).exists()) + + def test_hashed_email_privacy(self): + """Test that email hashes are one-way and can't be reversed""" + email1 = "test@example.com" + email2 = "test2@example.com" + + hash1 = HashedEmail.hash_email(email1) + hash2 = HashedEmail.hash_email(email2) + + # Verify different emails produce different hashes + self.assertNotEqual(hash1, hash2) + + # Verify same email always produces same hash + self.assertEqual(hash1, HashedEmail.hash_email(email1)) + + # Verify hash length and format (SHA-256 produces 64 character hex string) + self.assertEqual(len(hash1), 64) + self.assertTrue(all(c in '0123456789abcdef' for c in hash1)) + + def test_password_encryption(self): + """Test that passwords are properly hashed and not stored in plaintext""" + # Verify password is not stored in plaintext + self.assertNotEqual(self.user.password, self.password) + + # Verify password can be verified + self.assertTrue(check_password(self.password, self.user.password)) + + # Verify incorrect password fails + self.assertFalse(check_password("wrongpassword", self.user.password)) + + def test_sql_injection_prevention(self): + """Test that SQL injection attempts are prevented""" + injection_attempts = [ + "'; DROP TABLE users; --", + "' OR '1'='1", + "' UNION SELECT password FROM users; --", + "admin'--", + ] + + for injection in injection_attempts: + # Test login + response = self.client.post(self.login_url, { + 'username': injection, + 'password': injection, + }) + self.assertEqual(response.status_code, 200) # Should stay on login page + self.assertFalse(response.wsgi_request.user.is_authenticated) + + # Test signup + response = self.client.post(self.signup_url, { + 'username': injection, + 'email': f'{injection}@example.com', + 'password1': 'SecurePass123!', + 'password2': 'SecurePass123!', + }) + self.assertEqual(response.status_code, 200) # Should stay on signup page + self.assertFalse(User.objects.filter(username=injection).exists()) + + def test_recovery_key_encryption(self): + """Test that recovery keys are properly encrypted""" + # Generate and encrypt recovery key + plain_recovery_key = RecoveryKey.generate_recovery_key() + recovery_key = RecoveryKey(user=self.user) + recovery_key.encrypt_recovery_key(plain_recovery_key) + recovery_key.save() + + # Verify encrypted key is not stored in plaintext + self.assertNotEqual(recovery_key.encrypted_key, plain_recovery_key) + + # Verify key can be verified + self.assertTrue(recovery_key.verify_recovery_key(plain_recovery_key)) + + # Verify incorrect key fails + self.assertFalse(recovery_key.verify_recovery_key("wrongkey")) + \ No newline at end of file From dfcb1ed05de00be894e122deebee4ae03cf53e69 Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:29:02 -0500 Subject: [PATCH 02/10] Add testing for Login systems --- users/tests/test_views.py | 161 +++++++++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 2 deletions(-) diff --git a/users/tests/test_views.py b/users/tests/test_views.py index 627f1d0..25fd662 100644 --- a/users/tests/test_views.py +++ b/users/tests/test_views.py @@ -1,9 +1,14 @@ -from django.urls import reverse +from datetime import timedelta +from django.contrib.auth import get_user_model from django.core import mail from django.test import Client, TestCase, override_settings -from users.models import User +from django.urls import reverse +from django.utils import timezone +from users.models import HashedEmail, User, RecoveryKey from unittest.mock import patch +User = get_user_model() + @override_settings( EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend', ) @@ -61,3 +66,155 @@ def test_signup_invalid_email(self): 'email', 'Enter a valid email address.' ) + +class TestLoginSystem(TestCase): + def setUp(self): + self.client = Client() + self.login_url = reverse('users:login') + self.email = "test@example.com" + self.password = "SecurePass123!" + self.username = "testuser" + + # Create a verified user + self.user = User.objects.create_user( + username=self.username, + email=self.email, + password=self.password, + email_verified=True + ) + self.hashed_email = HashedEmail.get_or_create_hash(self.email) + self.user.hashed_email = self.hashed_email + self.user.save() + + def test_successful_login(self): + """Test successful login with valid credentials""" + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + }) + self.assertEqual(response.status_code, 302) # Should redirect after login + self.assertTrue(response.wsgi_request.user.is_authenticated) + + def test_reject_unverified_email(self): + """Test that unverified email accounts cannot login""" + self.user.email_verified = False + self.user.save() + + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + }) + self.assertEqual(response.status_code, 200) # Stays on login page + self.assertFalse(response.wsgi_request.user.is_authenticated) + self.assertFormError( + response.context['form'], + None, # Form-wide error + "Please verify your email address before logging in." + ) + + def test_reject_incorrect_password(self): + """Test that incorrect passwords are rejected""" + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': 'wrongpassword', + }) + self.assertEqual(response.status_code, 200) + self.assertFalse(response.wsgi_request.user.is_authenticated) + self.assertFormError( + response.context['form'], + None, # Form-wide error + "Please enter a correct username and password. Note that both fields may be case-sensitive." + ) + + def test_remember_me_functionality(self): + """Test remember me checkbox functionality""" + # Make sure any existing recovery key is removed + RecoveryKey.objects.filter(user=self.user).delete() + + # Without remember me + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + }) + self.assertEqual(response.status_code, 302) + self.assertTrue(response.wsgi_request.user.is_authenticated) + self.assertTrue(response.client.session.get_expire_at_browser_close()) + + # Clean up between tests - delete recovery key and logout + RecoveryKey.objects.filter(user=self.user).delete() + self.client.logout() + + # With remember me + self.client.logout() + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + 'remember_me': True, + }) + self.assertEqual(response.status_code, 302) + self.assertTrue(response.wsgi_request.user.is_authenticated) + self.assertFalse(response.client.session.get_expire_at_browser_close()) + + def test_last_login_update(self): + """Test that last_login timestamp is updated""" + old_last_login = self.user.last_login + + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + }) + + # Refresh user from database + self.user.refresh_from_db() + self.assertIsNotNone(self.user.last_login) + if old_last_login: + self.assertGreater(self.user.last_login, old_last_login) + + def test_blocked_account_handling(self): + """Test that blocked accounts cannot login""" + self.hashed_email.is_blocked = True + self.hashed_email.save() + + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + }) + self.assertEqual(response.status_code, 200) + self.assertFalse(response.wsgi_request.user.is_authenticated) + self.assertContains(response, "This account has been disabled due to suspicious activity") + + @patch('users.views.email_service.send_verification_email') + def test_first_time_login_recovery_key(self, mock_send_email): + """Test recovery key generation on first login""" + self.user.recovery_key_viewed = False + self.user.save() + + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + }) + + # Should redirect to recovery key page + self.assertRedirects(response, reverse('users:show_recovery_key')) + + # Verify recovery key was generated + self.assertTrue(RecoveryKey.objects.filter(user=self.user).exists()) + + # Verify recovery key is in session + self.assertIn('recovery_key', self.client.session) + + def test_subsequent_login_no_recovery_key(self): + """Test that subsequent logins don't generate new recovery keys""" + self.user.recovery_key_viewed = True + self.user.save() + + response = self.client.post(self.login_url, { + 'username': self.username, + 'password': self.password, + }) + + # Should redirect to home + self.assertRedirects(response, reverse('home')) + + # Verify no recovery key in session + self.assertNotIn('recovery_key', self.client.session) \ No newline at end of file From 50068181da32b956318618011e731a1a3c8194a5 Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:35:32 -0500 Subject: [PATCH 03/10] Add testing for recovery key functionality --- users/tests/test_views.py | 159 +++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 1 deletion(-) diff --git a/users/tests/test_views.py b/users/tests/test_views.py index 25fd662..f877bba 100644 --- a/users/tests/test_views.py +++ b/users/tests/test_views.py @@ -217,4 +217,161 @@ def test_subsequent_login_no_recovery_key(self): self.assertRedirects(response, reverse('home')) # Verify no recovery key in session - self.assertNotIn('recovery_key', self.client.session) \ No newline at end of file + self.assertNotIn('recovery_key', self.client.session) + +class TestRecoveryKeySystem(TestCase): + def setUp(self): + self.client = Client() + self.email = "test@example.com" + self.password = "SecurePass123!" + self.username = "testuser" + + # Create a verified user + self.user = User.objects.create_user( + username=self.username, + email=self.email, + password=self.password, + email_verified=True + ) + self.hashed_email = HashedEmail.get_or_create_hash(self.email) + self.user.hashed_email = self.hashed_email + self.user.save() + + self.recovery_key_url = reverse('users:show_recovery_key') + self.reset_password_url = reverse('users:reset_password') + + def test_recovery_key_generation_and_encryption(self): + """Test that recovery keys are properly generated and encrypted""" + # Log in user + self.client.force_login(self.user) + + # Generate recovery key + recovery_key = RecoveryKey.generate_recovery_key() + recovery_key_obj = RecoveryKey(user=self.user) + recovery_key_obj.encrypt_recovery_key(recovery_key) + recovery_key_obj.save() + + # Verify key is encrypted + self.assertNotEqual(recovery_key_obj.encrypted_key, recovery_key) + self.assertTrue(recovery_key_obj.verify_recovery_key(recovery_key)) + self.assertFalse(recovery_key_obj.verify_recovery_key("wrong-key")) + + def test_recovery_key_viewing_flow(self): + """Test the complete recovery key viewing flow""" + # Log in user + self.client.force_login(self.user) + + # Generate and store recovery key + recovery_key = RecoveryKey.generate_recovery_key() + recovery_key_obj = RecoveryKey(user=self.user) + recovery_key_obj.encrypt_recovery_key(recovery_key) + recovery_key_obj.save() + + # Set up session + session = self.client.session + session['recovery_key'] = recovery_key + session.save() + + # First view should succeed + response = self.client.get(self.recovery_key_url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'users/show_recovery_key.html') + self.assertContains(response, recovery_key) + + # Acknowledge viewing the key + response = self.client.post(self.recovery_key_url) + self.assertEqual(response.status_code, 302) + + # Verify changes + self.user.refresh_from_db() + self.assertTrue(self.user.recovery_key_viewed) + self.assertTrue(self.user.email_purged) + self.assertEqual(self.user.email, '') + + # Verify session cleanup + session = self.client.session + self.assertNotIn('recovery_key', session) + + def test_prevent_multiple_recovery_key_views(self): + """Test that recovery key can't be viewed multiple times""" + self.client.force_login(self.user) + + # Mark as already viewed + self.user.recovery_key_viewed = True + self.user.save() + + # Attempt to view recovery key + response = self.client.get(self.recovery_key_url) + self.assertEqual(response.status_code, 302) # Should redirect + self.assertRedirects(response, reverse('home')) + + messages = list(response.wsgi_request._messages) + self.assertTrue(any('already been viewed' in str(m) for m in messages)) + + def test_password_reset_with_recovery_key(self): + """Test password reset using recovery key""" + # Generate and store recovery key + plain_recovery_key = RecoveryKey.generate_recovery_key() + recovery_key_obj = RecoveryKey(user=self.user) + recovery_key_obj.encrypt_recovery_key(plain_recovery_key) + recovery_key_obj.save() + + # Attempt password reset + new_password = "NewSecurePass456!" + response = self.client.post(self.reset_password_url, { + 'username': self.username, + 'recovery_key': plain_recovery_key, + 'new_password1': new_password, + 'new_password2': new_password, + }) + + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, reverse('users:login')) + + # Verify password was changed + self.user.refresh_from_db() + self.assertTrue(self.user.check_password(new_password)) + + def test_invalid_recovery_key_reset(self): + """Test that invalid recovery keys are rejected""" + recovery_key_obj = RecoveryKey(user=self.user) + recovery_key_obj.encrypt_recovery_key("correct-key") + recovery_key_obj.save() + + response = self.client.post(self.reset_password_url, { + 'username': self.username, + 'recovery_key': 'wrong-key', + 'new_password1': 'NewPass123!', + 'new_password2': 'NewPass123!', + }) + + self.assertEqual(response.status_code, 200) # Stays on same page + self.assertContains(response, "Invalid recovery key") + + # Verify password wasn't changed + self.user.refresh_from_db() + self.assertTrue(self.user.check_password(self.password)) + + def test_hashed_email_maintained_after_purge(self): + """Test that hashed email association remains after email purge""" + self.client.force_login(self.user) + + # Generate and store recovery key + recovery_key = RecoveryKey.generate_recovery_key() + recovery_key_obj = RecoveryKey(user=self.user) + recovery_key_obj.encrypt_recovery_key(recovery_key) + recovery_key_obj.save() + + # Set up session and view recovery key + session = self.client.session + session['recovery_key'] = recovery_key + session.save() + + # Acknowledge viewing the key which triggers email purge + response = self.client.post(self.recovery_key_url) + + # Verify email is purged but hashed_email remains + self.user.refresh_from_db() + self.assertTrue(self.user.email_purged) + self.assertEqual(self.user.email, '') + self.assertEqual(self.user.hashed_email, self.hashed_email) \ No newline at end of file From 53b92d04955fa10b7e6e67e37861843920316a80 Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:39:01 -0500 Subject: [PATCH 04/10] add initial GHA workflow --- .github/workflows/unit_tests.yml | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/unit_tests.yml diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..202d0da --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,43 @@ +name: unit-tests + +on: + push: + branches: + - main + - 'feature/**' + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + # Setup Node.js environment + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '22' # Specify the Node.js version you are using + + # Setup Python environment + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.11.4' # Specify the Python version you are using + + - name: Install Python dependencies + run: | + python -m venv venv + source venv/bin/activate + pip install -r requirements.txt + + - name: Run Django tests + env: + DJANGO_SETTINGS_MODULE: config.settings + run: | + source venv/bin/activate + python manage.py test From ee6cf0e8f5066eaf4597f09aa412b8e0e6e88ac4 Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:42:55 -0500 Subject: [PATCH 05/10] adjust the workflow and settings --- .github/workflows/unit_tests.yml | 72 +++++++++++++++++--------------- config/settings/test.py | 21 ++++++++++ 2 files changed, 59 insertions(+), 34 deletions(-) create mode 100644 config/settings/test.py diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 202d0da..97422a5 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -1,43 +1,47 @@ -name: unit-tests +name: Django CI on: - push: - branches: - - main - - 'feature/**' pull_request: - branches: - - main + branches: [ main ] + push: + branches: [ main ] jobs: test: runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: github_actions + ports: + - 5432:5432 + # Add health check to ensure postgres is ready + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - - name: Checkout code - uses: actions/checkout@v2 - - # Setup Node.js environment - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: '22' # Specify the Node.js version you are using - - # Setup Python environment - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.11.4' # Specify the Python version you are using - - - name: Install Python dependencies - run: | - python -m venv venv - source venv/bin/activate - pip install -r requirements.txt - - - name: Run Django tests - env: - DJANGO_SETTINGS_MODULE: config.settings - run: | - source venv/bin/activate - python manage.py test + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements/dev.txt + + - name: Run Tests + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/github_actions + DJANGO_SETTINGS_MODULE: thebluelist.settings.test + run: | + python manage.py test \ No newline at end of file diff --git a/config/settings/test.py b/config/settings/test.py new file mode 100644 index 0000000..d1b906b --- /dev/null +++ b/config/settings/test.py @@ -0,0 +1,21 @@ +from .base import * + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'github_actions', + 'USER': 'postgres', + 'PASSWORD': 'postgres', + 'HOST': 'localhost', + 'PORT': '5432', + } +} + +# Test-specific settings +DEBUG = False +EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' + +# Disable any expensive/unnecessary settings during testing +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.MD5PasswordHasher', +] \ No newline at end of file From ee50e4c09de387e26425a731ad46be4cc0c554eb Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:47:36 -0500 Subject: [PATCH 06/10] fix missing installed apps --- config/settings/test.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/config/settings/test.py b/config/settings/test.py index d1b906b..4021863 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -11,6 +11,17 @@ } } +# Make sure all required apps are included +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'users', +] + # Test-specific settings DEBUG = False EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' @@ -18,4 +29,6 @@ # Disable any expensive/unnecessary settings during testing PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.MD5PasswordHasher', -] \ No newline at end of file +] + +# Add any other test-specific settings \ No newline at end of file From aa1892c04f70916d3fb59ad17adc1674cd2dbdbd Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:50:42 -0500 Subject: [PATCH 07/10] set requirements.txt back to default --- .github/workflows/unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 97422a5..fa586ef 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -37,7 +37,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install -r requirements/dev.txt + pip install -r requirements.txt - name: Run Tests env: From ed12443417a5516334b9c61e288b4662988d1fd8 Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 22:52:48 -0500 Subject: [PATCH 08/10] add imports to test settings --- config/settings/test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/settings/test.py b/config/settings/test.py index 4021863..cef2042 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -1,3 +1,5 @@ +import os +import dj_database_url from .base import * DATABASES = { From 85dcf8b5846b32e4f79860172fa07184d047a254 Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 23:05:35 -0500 Subject: [PATCH 09/10] Trying cleaning up test.py --- .github/workflows/unit_tests.yml | 12 ++++++++---- config/settings/test.py | 28 ++++++++-------------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index fa586ef..071bfb6 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -19,7 +19,6 @@ jobs: POSTGRES_DB: github_actions ports: - 5432:5432 - # Add health check to ensure postgres is ready options: >- --health-cmd pg_isready --health-interval 10s @@ -37,11 +36,16 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt + pip install -r requirements/dev.txt + + - name: Create Recovery Key + run: | + python -c "from cryptography.fernet import Fernet; key = Fernet.generate_key(); open('recovery_key.key', 'wb').write(key)" - name: Run Tests env: DATABASE_URL: postgres://postgres:postgres@localhost:5432/github_actions - DJANGO_SETTINGS_MODULE: thebluelist.settings.test + DJANGO_SETTINGS_MODULE: config.settings.test + SECRET_KEY: your-test-secret-key-here run: | - python manage.py test \ No newline at end of file + python manage.py test -v 2 \ No newline at end of file diff --git a/config/settings/test.py b/config/settings/test.py index cef2042..d15149d 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -1,7 +1,7 @@ -import os -import dj_database_url from .base import * +DEBUG = False + DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', @@ -13,24 +13,12 @@ } } -# Make sure all required apps are included -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'users', -] - -# Test-specific settings -DEBUG = False +# Test specific settings EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' +PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher'] -# Disable any expensive/unnecessary settings during testing -PASSWORD_HASHERS = [ - 'django.contrib.auth.hashers.MD5PasswordHasher', -] +# Disable any resource-intensive settings +MAX_ACCOUNTS_PER_EMAIL = 2 # If you use this setting -# Add any other test-specific settings \ No newline at end of file +# Override any settings that require external services +MAILTRAP_API_TOKEN = 'dummy-token-for-testing' \ No newline at end of file From af9d7cb512efdde944c2ec316dd682aaa180e437 Mon Sep 17 00:00:00 2001 From: Brent Mills Date: Wed, 13 Nov 2024 23:06:51 -0500 Subject: [PATCH 10/10] change back requirements.txt again --- .github/workflows/unit_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 071bfb6..d252749 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -36,7 +36,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install -r requirements/dev.txt + pip install -r requirements.txt - name: Create Recovery Key run: |