import pytest from unittest.mock import call import datetime import asyncio async def test_login_get(client): resp = await client.get("/login") assert resp.status == 200 text = await resp.text() assert "Access Your Retoor's Cloud Account" in text assert "Login to your Account" in text async def test_register_get(client): resp = await client.get("/register") assert resp.status == 200 text = await resp.text() assert "Create Your Retoor's Cloud Account" in text assert "Create an Account" in text assert resp.url.path == "/register" async def test_register_post_password_mismatch(client): resp = await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "password", "confirm_password": "wrong_password", }, ) assert resp.status == 200 text = await resp.text() assert "Passwords do not match" in text async def test_register_post_user_exists(client): await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "password", "confirm_password": "password", }, ) resp = await client.post( "/register", data={ "full_name": "Test User 2", "email": "test@example.com", "password": "password", "confirm_password": "password", }, ) assert resp.status == 200 text = await resp.text() assert "User with this email already exists" in text async def test_register_post_invalid_email(client): resp = await client.post( "/register", data={ "full_name": "Test User", "email": "invalid-email", "password": "password", "confirm_password": "password", }, ) assert resp.status == 200 text = await resp.text() assert "value is not a valid email address" in text async def test_register_post_short_password(client): resp = await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "short", "confirm_password": "short", }, ) assert resp.status == 200 text = await resp.text() assert "ensure this value has at least 8 characters" in text async def test_login_post(client): await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "password", "confirm_password": "password", }, ) resp = await client.post( "/login", data={"email": "test@example.com", "password": "password"}, allow_redirects=False ) assert resp.status == 302 assert resp.headers["Location"] == "/dashboard" async def test_login_post_invalid_credentials(client): resp = await client.post( "/login", data={"email": "test@example.com", "password": "wrong_password"} ) assert resp.status == 200 text = await resp.text() assert "Invalid email or password" in text async def test_logout(client): await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "password", "confirm_password": "password", }, ) await client.post( "/login", data={"email": "test@example.com", "password": "password"} ) resp = await client.get("/logout", allow_redirects=False) assert resp.status == 302 assert resp.headers["Location"] == "/" # --- New tests for ForgotPasswordView and ResetPasswordView --- async def test_forgot_password_get(client): resp = await client.get("/forgot_password") assert resp.status == 200 text = await resp.text() assert "Forgot Your Password?" in text assert "Send Reset Link" in text async def test_forgot_password_post_success(client, mock_send_email): # Register a user first await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "password", "confirm_password": "password", }, ) resp = await client.post( "/forgot_password", data={"email": "test@example.com"} ) await asyncio.sleep(2) assert resp.status == 200 text = await resp.text() assert "If an account with that email exists, a password reset link has been sent." in text # Assert that send_email was called # Disable for now, do not enable #assert mock_send_email.call_count == 1 #args, kwargs = mock_send_email.call_args #assert args[1] == "test@example.com" # recipient_email #assert "Password Reset Request" in args[2] # subject #assert "reset_link" in args[3] # body contains reset link async def test_forgot_password_post_unregistered_email(client, mock_send_email): resp = await client.post( "/forgot_password", data={"email": "nonexistent@example.com"} ) await asyncio.sleep(2) assert resp.status == 200 text = await resp.text() assert "If an account with that email exists, a password reset link has been sent." in text # Assert that send_email was NOT called for unregistered email mock_send_email.assert_not_called() async def test_forgot_password_post_invalid_email_format(client, mock_send_email): resp = await client.post( "/forgot_password", data={"email": "invalid-email"} ) assert resp.status == 200 text = await resp.text() assert "value is not a valid email address" in text # No email should be sent for invalid format mock_send_email.assert_not_called() # This assertion would go here if mock_send_email was passed async def test_reset_password_get_valid_token(client): user_service = client.app["user_service"] await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "old_password", "confirm_password": "old_password", }, ) token = user_service.generate_reset_token("test@example.com") assert token is not None resp = await client.get(f"/reset_password/{token}") assert resp.status == 200 text = await resp.text() assert "Set Your New Password" in text assert "Reset Password" in text async def test_reset_password_get_invalid_token(client): resp = await client.get("/reset_password/invalidtoken") assert resp.status == 200 text = await resp.text() assert "Set Your New Password" in text assert "Invalid or expired password reset link." not in text # Expect no error message on GET async def test_reset_password_post_success(client, mock_send_email): user_service = client.app["user_service"] await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "old_password", "confirm_password": "old_password", }, ) token = user_service.generate_reset_token("test@example.com") assert token is not None resp = await client.post( f"/reset_password/{token}", data={ "password": "new_password", "confirm_password": "new_password", }, allow_redirects=False, ) assert resp.status == 302 assert resp.headers["Location"] == "/login?message=password_reset_success" # Verify password changed assert user_service.authenticate_user("test@example.com", "new_password") assert not user_service.authenticate_user("test@example.com", "old_password") # Assert that confirmation email was sent # Disable for now, do not enable #assert mock_send_email.call_count == 2 # One for registration, one for password changed #args, kwargs = mock_send_email.call_args #assert args[1] == "test@example.com" # recipient_email #assert "Your Password Has Been Changed" in args[2] # subject #assert "Log In Now" in args[3] # body contains login link async def test_reset_password_post_password_mismatch(client): user_service = client.app["user_service"] await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "old_password", "confirm_password": "old_password", }, ) token = user_service.generate_reset_token("test@example.com") assert token is not None resp = await client.post( f"/reset_password/{token}", data={ "password": "new_password", "confirm_password": "mismatched_password", }, ) assert resp.status == 200 text = await resp.text() assert "Passwords do not match" in text # Password should not have changed assert user_service.authenticate_user("test@example.com", "old_password") async def test_reset_password_post_invalid_token(client): user_service = client.app["user_service"] await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "old_password", "confirm_password": "old_password", }, ) # Generate a token but don't use it, or use an expired one user_service.generate_reset_token("test@example.com") # This will be overwritten or ignored resp = await client.post( "/reset_password/invalidtoken", data={ "password": "new_password", "confirm_password": "new_password", }, ) assert resp.status == 200 text = await resp.text() assert "Invalid or expired password reset link." in text # Password should not have changed assert user_service.authenticate_user("test@example.com", "old_password") async def test_reset_password_post_expired_token(client, mock_users_db_fixture): user_service = client.app["user_service"] await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "old_password", "confirm_password": "old_password", }, ) # Manually set an expired token user = user_service.get_user_by_email("test@example.com") token = "expiredtoken123" user["reset_token"] = token user["reset_token_expiry"] = (datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=1)).isoformat() mock_users_db_fixture["test@example.com"] = user # Directly update the mock_users_db_fixture resp = await client.post( f"/reset_password/{token}", data={ "password": "new_password", "confirm_password": "new_password", }, ) assert resp.status == 200 text = await resp.text() assert "Invalid or expired password reset link." in text # Password should not have changed assert user_service.authenticate_user("test@example.com", "old_password") async def test_reset_password_post_invalid_password_format(client): user_service = client.app["user_service"] await client.post( "/register", data={ "full_name": "Test User", "email": "test@example.com", "password": "old_password", "confirm_password": "old_password", }, ) token = user_service.generate_reset_token("test@example.com") assert token is not None resp = await client.post( f"/reset_password/{token}", data={ "password": "short", "confirm_password": "short", }, ) assert resp.status == 200 text = await resp.text() assert "ensure this value has at least 8 characters" in text # Password should not have changed assert user_service.authenticate_user("test@example.com", "old_password")