import pytest from fastapi import status from fastapi.testclient import TestClient from sqlalchemy.orm import Session from datetime import date from tests.helpers import generators from modules.todo import schemas # Import schemas # Helper Function def _login_user(db: Session, client: TestClient): user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) return user, login_rsp["access_token"], login_rsp["refresh_token"] # --- Test CRUD Operations --- def test_create_todo(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) today_date = date.today() # Format the date string to match the expected response format "YYYY-MM-DDTHH:MM:SS" todo_data = { "task": "Test TODO", "date": f"{today_date.isoformat()}T00:00:00", "remind": True } response = client.post( "/api/todos/", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json=todo_data ) assert response.status_code == status.HTTP_201_CREATED data = response.json() assert data["task"] == todo_data["task"] assert data["date"] == todo_data["date"] assert data["remind"] == todo_data["remind"] assert data["complete"] is False # Default assert "id" in data assert data["owner_id"] == user.id def test_read_todos(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) # Create some todos for the user client.post("/api/todos/", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json={"task": "Todo 1"}) client.post("/api/todos/", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json={"task": "Todo 2"}) # Create a todo for another user other_user, other_password = generators.create_user(db) other_login_rsp = generators.login(db, other_user.username, other_password) other_access_token = other_login_rsp["access_token"] other_refresh_token = other_login_rsp["refresh_token"] client.post("/api/todos/", headers={"Authorization": f"Bearer {other_access_token}"}, cookies={"refresh_token": other_refresh_token}, json={"task": "Other User Todo"}) response = client.get( "/api/todos/", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token} ) assert response.status_code == status.HTTP_200_OK data = response.json() assert len(data) == 2 # Should only get todos for the logged-in user assert data[0]["task"] == "Todo 1" assert data[1]["task"] == "Todo 2" def test_read_single_todo(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) create_response = client.post( "/api/todos/", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json={"task": "Specific Todo"} ) todo_id = create_response.json()["id"] response = client.get( f"/api/todos/{todo_id}", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token} ) assert response.status_code == status.HTTP_200_OK data = response.json() assert data["id"] == todo_id assert data["task"] == "Specific Todo" assert data["owner_id"] == user.id def test_read_single_todo_not_found(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) response = client.get( "/api/todos/9999", # Non-existent ID headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token} ) assert response.status_code == status.HTTP_404_NOT_FOUND def test_read_single_todo_forbidden(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) # Create a todo for another user other_user, other_password = generators.create_user(db) other_login_rsp = generators.login(db, other_user.username, other_password) other_access_token = other_login_rsp["access_token"] other_refresh_token = other_login_rsp["refresh_token"] other_create_response = client.post("/api/todos/", headers={"Authorization": f"Bearer {other_access_token}"}, cookies={"refresh_token": other_refresh_token}, json={"task": "Other User Todo"}) other_todo_id = other_create_response.json()["id"] # Try to access the other user's todo response = client.get( f"/api/todos/{other_todo_id}", headers={"Authorization": f"Bearer {access_token}"}, # Using the first user's token cookies={"refresh_token": refresh_token} ) assert response.status_code == status.HTTP_404_NOT_FOUND # Service raises 404 if not found for *this* user def test_update_todo(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) create_response = client.post( "/api/todos/", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json={"task": "Update Me"} ) todo_id = create_response.json()["id"] update_data = {"task": "Updated Task", "complete": True} response = client.put( f"/api/todos/{todo_id}", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json=update_data ) assert response.status_code == status.HTTP_200_OK data = response.json() assert data["id"] == todo_id assert data["task"] == update_data["task"] assert data["complete"] == update_data["complete"] assert data["owner_id"] == user.id # Verify update by reading again get_response = client.get( f"/api/todos/{todo_id}", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token} ) assert get_response.json()["task"] == update_data["task"] assert get_response.json()["complete"] == update_data["complete"] def test_update_todo_not_found(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) update_data = {"task": "Updated Task", "complete": True} response = client.put( "/api/todos/9999", # Non-existent ID headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json=update_data ) assert response.status_code == status.HTTP_404_NOT_FOUND def test_delete_todo(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) create_response = client.post( "/api/todos/", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token}, json={"task": "Delete Me"} ) todo_id = create_response.json()["id"] response = client.delete( f"/api/todos/{todo_id}", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token} ) assert response.status_code == status.HTTP_200_OK # Delete returns the deleted item assert response.json()["id"] == todo_id # Verify deletion by trying to read get_response = client.get( f"/api/todos/{todo_id}", headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token} ) assert get_response.status_code == status.HTTP_404_NOT_FOUND def test_delete_todo_not_found(client: TestClient, db: Session): user, access_token, refresh_token = _login_user(db, client) response = client.delete( "/api/todos/9999", # Non-existent ID headers={"Authorization": f"Bearer {access_token}"}, cookies={"refresh_token": refresh_token} ) assert response.status_code == status.HTTP_404_NOT_FOUND # --- Test Authentication/Authorization --- def test_create_todo_unauthorized(client: TestClient): response = client.post("/api/todos/", json={"task": "No Auth"}) assert response.status_code == status.HTTP_401_UNAUTHORIZED def test_read_todos_unauthorized(client: TestClient): response = client.get("/api/todos/") assert response.status_code == status.HTTP_401_UNAUTHORIZED