import pytest from datetime import datetime, timezone from flask import Flask from api.user_api import user_api from api.auth_api import auth_api from db.db import users_db from tinydb import Query import jwt # Test user credentials TEST_EMAIL = "usertest@example.com" TEST_PASSWORD = "testpass123" MARKED_EMAIL = "marked@example.com" MARKED_PASSWORD = "markedpass" def add_test_users(): """Add test users to the database.""" # Remove if exists users_db.remove(Query().email == TEST_EMAIL) users_db.remove(Query().email == MARKED_EMAIL) # Add regular test user users_db.insert({ "id": "test_user_id", "first_name": "Test", "last_name": "User", "email": TEST_EMAIL, "password": TEST_PASSWORD, "verified": True, "image_id": "boy01", "marked_for_deletion": False, "marked_for_deletion_at": None }) # Add user already marked for deletion users_db.insert({ "id": "marked_user_id", "first_name": "Marked", "last_name": "User", "email": MARKED_EMAIL, "password": MARKED_PASSWORD, "verified": True, "image_id": "girl01", "marked_for_deletion": True, "marked_for_deletion_at": "2024-01-15T10:30:00+00:00" }) def login_and_get_token(client, email, password): """Login and extract JWT token from response.""" resp = client.post('/login', json={"email": email, "password": password}) assert resp.status_code == 200 # Extract token from Set-Cookie header set_cookie = resp.headers.get("Set-Cookie") assert set_cookie and "token=" in set_cookie # Flask test client automatically handles cookies return resp @pytest.fixture def client(): """Setup Flask test client with registered blueprints.""" app = Flask(__name__) app.register_blueprint(user_api) app.register_blueprint(auth_api) app.config['TESTING'] = True app.config['SECRET_KEY'] = 'supersecretkey' app.config['FRONTEND_URL'] = 'http://localhost:5173' # Needed for email_sender with app.test_client() as client: add_test_users() yield client @pytest.fixture def authenticated_client(client): """Setup client with authenticated user session.""" login_and_get_token(client, TEST_EMAIL, TEST_PASSWORD) return client @pytest.fixture def marked_client(client): """Setup client with marked-for-deletion user session.""" login_and_get_token(client, MARKED_EMAIL, MARKED_PASSWORD) return client def test_mark_user_for_deletion_success(authenticated_client): """Test successfully marking a user account for deletion.""" response = authenticated_client.post('/user/mark-for-deletion', json={"email": TEST_EMAIL}) assert response.status_code == 200 data = response.get_json() assert data['success'] is True # Verify database was updated UserQuery = Query() user = users_db.search(UserQuery.email == TEST_EMAIL)[0] assert user['marked_for_deletion'] is True assert user['marked_for_deletion_at'] is not None # Verify timestamp is valid ISO format marked_at = datetime.fromisoformat(user['marked_for_deletion_at']) assert marked_at.tzinfo is not None def test_login_for_marked_user_returns_403(client): """Test that login for a marked-for-deletion user returns 403 Forbidden.""" response = client.post('/login', json={ "email": MARKED_EMAIL, "password": MARKED_PASSWORD }) assert response.status_code == 403 data = response.get_json() assert 'error' in data assert data['code'] == 'ACCOUNT_MARKED_FOR_DELETION' def test_mark_for_deletion_requires_auth(client): """Test that marking for deletion requires authentication.""" response = client.post('/user/mark-for-deletion', json={"email": TEST_EMAIL}) assert response.status_code == 401 data = response.get_json() assert 'error' in data def test_login_blocked_for_marked_user(client): """Test that login is blocked for users marked for deletion.""" response = client.post('/login', json={ "email": MARKED_EMAIL, "password": MARKED_PASSWORD }) assert response.status_code == 403 data = response.get_json() assert 'error' in data assert data['code'] == 'ACCOUNT_MARKED_FOR_DELETION' def test_login_succeeds_for_unmarked_user(client): """Test that login works normally for users not marked for deletion.""" response = client.post('/login', json={ "email": TEST_EMAIL, "password": TEST_PASSWORD }) assert response.status_code == 200 data = response.get_json() assert 'message' in data def test_password_reset_ignored_for_marked_user(client): """Test that password reset requests are silently ignored for marked users.""" response = client.post('/request-password-reset', json={"email": MARKED_EMAIL}) assert response.status_code == 200 data = response.get_json() assert 'message' in data def test_password_reset_works_for_unmarked_user(client): """Test that password reset works normally for unmarked users.""" response = client.post('/request-password-reset', json={"email": TEST_EMAIL}) assert response.status_code == 200 data = response.get_json() assert 'message' in data def test_mark_for_deletion_updates_timestamp(authenticated_client): """Test that marking for deletion sets a proper timestamp.""" before_time = datetime.now(timezone.utc) response = authenticated_client.post('/user/mark-for-deletion', json={"email": TEST_EMAIL}) assert response.status_code == 200 after_time = datetime.now(timezone.utc) # Verify timestamp is between before and after UserQuery = Query() user = users_db.search(UserQuery.email == TEST_EMAIL)[0] marked_at = datetime.fromisoformat(user['marked_for_deletion_at']) assert before_time <= marked_at <= after_time def test_mark_for_deletion_with_invalid_jwt(client): """Test marking for deletion with invalid JWT token.""" # Set invalid cookie manually client.set_cookie('token', 'invalid.jwt.token') response = client.post('/user/mark-for-deletion', json={}) assert response.status_code == 401 data = response.get_json() assert 'error' in data