from datetime import datetime, timedelta from sqlalchemy import select from app.auth.services import create_user from app.tasks.models import Task from app.tasks.services import ( add_subtask, create_task, list_today_tasks, toggle_subtask, ) def create_account_and_login(client, suffix: str = "") -> None: email = f"person{suffix}@example.com" client.post( "/auth/register", data={ "username": f"steady_user{suffix}", "email": email, "password": "a calm secure password", "confirm_password": "a calm secure password", }, ) client.post( "/auth/login", data={"email": email, "password": "a calm secure password"}, ) def task_form_data(**overrides): data = { "title": "Write a gentle plan", "description": "Start with one paragraph.", "status": "not_started", "priority": "normal", "due_date": "", } data.update(overrides) return data def test_task_pages_require_login(client): response = client.get("/tasks/") assert response.status_code == 302 assert "/auth/login" in response.headers["Location"] def test_create_edit_and_delete_task(client, db): create_account_and_login(client) create_response = client.post( "/tasks/new", data=task_form_data(due_date="2026-08-09T14:30"), follow_redirects=True, ) task = db.session.scalar( select(Task).where(Task.title == "Write a gentle plan") ) assert create_response.status_code == 200 assert b"Task captured" in create_response.data assert task is not None assert task.due_date == datetime(2026, 8, 9, 14, 30) edit_response = client.post( f"/tasks/{task.id}/edit", data=task_form_data( title="Write the first paragraph", status="done", priority="important", ), follow_redirects=True, ) db.session.refresh(task) assert b"Task updated" in edit_response.data assert task.title == "Write the first paragraph" assert task.completed_at is not None delete_response = client.post( f"/tasks/{task.id}/delete", follow_redirects=True, ) assert b"Task deleted" in delete_response.data assert db.session.get(Task, task.id) is None def test_status_filter_only_shows_selected_tasks(client, db): create_account_and_login(client) client.post("/tasks/new", data=task_form_data(title="Visible active task")) client.post( "/tasks/new", data=task_form_data(title="Hidden finished task", status="done"), ) response = client.get("/tasks/?status=not_started") assert response.status_code == 200 assert b"Visible active task" in response.data assert b"Hidden finished task" not in response.data def test_invalid_status_filter_is_rejected(client): create_account_and_login(client) response = client.get("/tasks/?status=unexpected") assert response.status_code == 400 def test_today_is_limited_and_hides_future_and_completed_tasks(db): user = create_user("today_user", "today@example.com", "secure password") now = datetime(2026, 8, 8, 12, 0) for number in range(8): create_task( user.id, title=f"Inbox task {number}", description=None, status="not_started", priority="normal", due_date=None, ) future = create_task( user.id, title="Future task", description=None, status="not_started", priority="urgent", due_date=now + timedelta(days=2), ) completed = create_task( user.id, title="Completed task", description=None, status="done", priority="urgent", due_date=now, ) tasks = list_today_tasks(user.id, now=now) assert len(tasks) == 7 assert future not in tasks assert completed not in tasks def test_users_cannot_access_each_others_tasks(client, db): owner = create_user("owner", "owner@example.com", "secure password") intruder = create_user("intruder", "intruder@example.com", "secure password") task = create_task( owner.id, title="Owner-only task", description=None, status="not_started", priority="normal", due_date=None, ) client.post( "/auth/login", data={"email": intruder.email, "password": "secure password"}, ) assert client.get(f"/tasks/{task.id}").status_code == 404 assert client.post(f"/tasks/{task.id}/delete").status_code == 404 assert db.session.get(Task, task.id) is not None def test_subtasks_update_derived_progress(client, db): user = create_user("steps_user", "steps@example.com", "secure password") task = create_task( user.id, title="Two-step task", description=None, status="in_progress", priority="important", due_date=None, ) first = add_subtask(task, "First small step") add_subtask(task, "Second small step") assert task.progress_percentage == 0 toggle_subtask(task, first.id) assert task.progress_percentage == 50 client.post( "/auth/login", data={"email": user.email, "password": "secure password"}, ) response = client.get(f"/tasks/{task.id}") assert b"First small step" in response.data assert b'value="50"' in response.data def test_blank_task_title_is_rejected(client, db): create_account_and_login(client) response = client.post( "/tasks/new", data=task_form_data(title=" "), ) assert response.status_code == 200 assert b"This field cannot be blank" in response.data assert db.session.scalar(select(Task)) is None