test: migrate backend suites to pytest with module coverage gate
Split backend tests into unit and integration suites, add isolated fixtures and coverage enforcement, and update CI and test documentation. Cover current authentication, content, notification, payment, media, and admin flows; fix behaviors revealed by the new tests. Validate both suites and the per-module gate against local PostgreSQL.
This commit is contained in:
@@ -1,85 +1,72 @@
|
||||
import pytest
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
from apps.users.models import User
|
||||
from core.authentication import create_jwt_token
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.integration
|
||||
class TestUsersAPIIntegrationTests:
|
||||
|
||||
class UsersAPIIntegrationTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="mobile_user",
|
||||
mobile="09192000001",
|
||||
email="person@example.com",
|
||||
password="Sup3rSecure!123",
|
||||
is_mobile_verified=True,
|
||||
)
|
||||
def setup_method(self):
|
||||
self.user = User.objects.create_user(username='mobile_user', mobile='09192000001', email='person@example.com', password='Sup3rSecure!123', is_mobile_verified=True)
|
||||
|
||||
def _auth(self):
|
||||
return {"HTTP_AUTHORIZATION": f"Bearer {create_jwt_token(self.user)}"}
|
||||
return {'HTTP_AUTHORIZATION': f'Bearer {create_jwt_token(self.user)}'}
|
||||
|
||||
def test_password_login_accepts_mobile_identity(self):
|
||||
response = self.client.post("/api/auth/login", json.dumps({
|
||||
"identifier": self.user.mobile,
|
||||
"password": "Sup3rSecure!123",
|
||||
}), content_type="application/json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("access_token", response.json())
|
||||
response = self.client.post('/api/auth/login', json.dumps({'identifier': self.user.mobile, 'password': 'Sup3rSecure!123'}), content_type='application/json')
|
||||
assert response.status_code == 200
|
||||
assert 'access_token' in response.json()
|
||||
|
||||
def test_profile_update_treats_supplied_email_as_identity(self):
|
||||
response = self.client.put("/api/auth/profile", json.dumps({"email": "new@example.com"}),
|
||||
content_type="application/json", **self._auth())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.put('/api/auth/profile', json.dumps({'email': 'new@example.com'}), content_type='application/json', **self._auth())
|
||||
assert response.status_code == 200
|
||||
self.user.refresh_from_db()
|
||||
self.assertEqual(self.user.email, "new@example.com")
|
||||
self.assertTrue(self.user.is_email_verified)
|
||||
assert self.user.email == 'new@example.com'
|
||||
assert self.user.is_email_verified
|
||||
|
||||
def test_profile_update_clears_email_identity_flag(self):
|
||||
response = self.client.put("/api/auth/profile", json.dumps({"email": ""}),
|
||||
content_type="application/json", **self._auth())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.put('/api/auth/profile', json.dumps({'email': ''}), content_type='application/json', **self._auth())
|
||||
assert response.status_code == 200
|
||||
self.user.refresh_from_db()
|
||||
self.assertIsNone(self.user.email)
|
||||
self.assertFalse(self.user.is_email_verified)
|
||||
assert self.user.email is None
|
||||
assert not self.user.is_email_verified
|
||||
|
||||
def test_profile_requires_authentication_and_returns_account_identity(self):
|
||||
self.assertEqual(self.client.get("/api/auth/profile").status_code, 401)
|
||||
response = self.client.get("/api/auth/profile", **self._auth())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json()["mobile"], self.user.mobile)
|
||||
assert self.client.get('/api/auth/profile').status_code == 401
|
||||
response = self.client.get('/api/auth/profile', **self._auth())
|
||||
assert response.status_code == 200
|
||||
assert response.json()['mobile'] == self.user.mobile
|
||||
|
||||
def test_profile_rejects_duplicate_email(self):
|
||||
User.objects.create_user(username="second", mobile="09192000002", email="taken@example.com", password="password")
|
||||
response = self.client.put("/api/auth/profile", json.dumps({"email": "taken@example.com"}),
|
||||
content_type="application/json", **self._auth())
|
||||
self.assertEqual(response.status_code, 400)
|
||||
User.objects.create_user(username='second', mobile='09192000002', email='taken@example.com', password='password')
|
||||
response = self.client.put('/api/auth/profile', json.dumps({'email': 'taken@example.com'}), content_type='application/json', **self._auth())
|
||||
assert response.status_code == 400
|
||||
self.user.refresh_from_db()
|
||||
self.assertEqual(self.user.email, "person@example.com")
|
||||
assert self.user.email == 'person@example.com'
|
||||
|
||||
def test_profile_picture_rejects_non_image_upload(self):
|
||||
image = SimpleUploadedFile("notes.txt", b"not an image", content_type="text/plain")
|
||||
response = self.client.post("/api/auth/profile/picture", {"file": image}, **self._auth())
|
||||
self.assertEqual(response.status_code, 400)
|
||||
image = SimpleUploadedFile('notes.txt', b'not an image', content_type='text/plain')
|
||||
response = self.client.post('/api/auth/profile/picture', {'file': image}, **self._auth())
|
||||
assert response.status_code == 400
|
||||
|
||||
def test_check_username_reports_existing_user(self):
|
||||
response = self.client.get("/api/auth/check-username", {"username": self.user.username})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue(response.json()["exists"])
|
||||
response = self.client.get('/api/auth/check-username', {'username': self.user.username})
|
||||
assert response.status_code == 200
|
||||
assert response.json()['exists']
|
||||
|
||||
def test_legacy_email_auth_routes_are_gone(self):
|
||||
self.assertEqual(self.client.get("/api/auth/verify-email/token").status_code, 404)
|
||||
self.assertEqual(self.client.post("/api/auth/resend-verification").status_code, 404)
|
||||
self.assertEqual(self.client.post("/api/auth/request-password-reset").status_code, 404)
|
||||
assert self.client.get('/api/auth/verify-email/token').status_code == 404
|
||||
assert self.client.post('/api/auth/resend-verification').status_code == 404
|
||||
assert self.client.post('/api/auth/request-password-reset').status_code == 404
|
||||
|
||||
@mock.patch("apps.users.api.views.reset_password_with_otp")
|
||||
def test_mobile_otp_password_reset_route_remains_available(self, reset):
|
||||
response = self.client.post("/api/auth/reset-password", json.dumps({
|
||||
"mobile": self.user.mobile,
|
||||
"code": "123456",
|
||||
"new_password": "AnotherSecurePass!123",
|
||||
}), content_type="application/json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
def test_mobile_otp_password_reset_route_remains_available(self, mocker):
|
||||
reset = mocker.patch('apps.users.api.views.reset_password_with_otp')
|
||||
response = self.client.post('/api/auth/reset-password', json.dumps({'mobile': self.user.mobile, 'code': '123456', 'new_password': 'AnotherSecurePass!123'}), content_type='application/json')
|
||||
assert response.status_code == 200
|
||||
reset.assert_called_once()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _pytest_client(self, client):
|
||||
self.client = client
|
||||
|
||||
Reference in New Issue
Block a user