fix: test suite — seed reset, FK mappings, standalone focus tests

- Change all seed inserts from ON CONFLICT DO NOTHING to DO UPDATE SET
  so seeds always reset to canonical values between test runs
- Add meeting_id, decision_id, link_id to FK_FIELD_MAP in form_factory
- Add item_ids, direction, label, content to NAME_PATTERNS
- Add TestStandaloneFocusItems (7 tests): quick-add, edit, project tab
- Add TestFocusConversion (6 tests): convert to task/note/link/list item
- Add focus_standalone seed with fixture
- Update focus_page_loads test for permanent (non-date-scoped) items
- All 398 tests passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 03:08:53 +00:00
parent a61248b67d
commit a2183af6e2
3 changed files with 386 additions and 33 deletions

View File

@@ -1165,9 +1165,9 @@ class TestFocusWorkflow:
await _delete_tasks(db_session, [tid])
@pytest.mark.asyncio
async def test_focus_page_loads_with_date(self, client: AsyncClient):
"""Focus page loads for specific date."""
r = await client.get(f"/focus/?focus_date={date.today()}")
async def test_focus_page_loads(self, client: AsyncClient):
"""Focus page loads without date filter (permanent items)."""
r = await client.get("/focus/", follow_redirects=True)
assert r.status_code == 200
@@ -2505,6 +2505,317 @@ class TestFocusFilters:
assert r.status_code == 200
# ===========================================================================
# Standalone Focus Items
# ===========================================================================
class TestStandaloneFocusItems:
"""Test quick-add standalone items, edit, and project tab."""
@pytest.mark.asyncio
async def test_quick_add_standalone_item(
self, client: AsyncClient, db_session: AsyncSession, seed_domain: dict,
):
"""Quick-add creates a standalone focus item with title."""
tag = _uid()
r = await client.post("/focus/quick-add", data={
"title": f"Standalone-{tag}",
"quick_domain_id": seed_domain["id"],
}, follow_redirects=False)
assert r.status_code == 303
result = await db_session.execute(
text("SELECT title, domain_id FROM daily_focus WHERE title = :t AND is_deleted = false"),
{"t": f"Standalone-{tag}"},
)
row = result.first()
assert row is not None
assert str(row.domain_id) == seed_domain["id"]
# Cleanup
await db_session.execute(
text("DELETE FROM daily_focus WHERE title = :t"), {"t": f"Standalone-{tag}"}
)
await db_session.commit()
@pytest.mark.asyncio
async def test_quick_add_without_domain(
self, client: AsyncClient, db_session: AsyncSession,
):
"""Quick-add works without a domain."""
tag = _uid()
r = await client.post("/focus/quick-add", data={
"title": f"NoDomain-{tag}",
}, follow_redirects=False)
assert r.status_code == 303
result = await db_session.execute(
text("SELECT title, domain_id FROM daily_focus WHERE title = :t AND is_deleted = false"),
{"t": f"NoDomain-{tag}"},
)
row = result.first()
assert row is not None
assert row.domain_id is None
await db_session.execute(
text("DELETE FROM daily_focus WHERE title = :t"), {"t": f"NoDomain-{tag}"}
)
await db_session.commit()
@pytest.mark.asyncio
async def test_edit_standalone_page_loads(
self, client: AsyncClient, seed_focus_standalone: dict,
):
"""Edit page loads for standalone focus items."""
r = await client.get(f"/focus/{seed_focus_standalone['id']}/edit")
assert r.status_code == 200
assert "Test Standalone Focus" in r.text
@pytest.mark.asyncio
async def test_edit_standalone_redirects_for_task_items(
self, client: AsyncClient, all_seeds: dict,
):
"""Edit page redirects away for task-linked focus items."""
r = await client.get(f"/focus/{all_seeds['focus']}/edit", follow_redirects=False)
assert r.status_code == 303
@pytest.mark.asyncio
async def test_update_standalone_item(
self, client: AsyncClient, db_session: AsyncSession,
seed_domain: dict, seed_project: dict,
):
"""Editing a standalone item updates title, domain, project."""
tag = _uid()
# Create a standalone item
fid = str(uuid.uuid4())
await db_session.execute(
text("INSERT INTO daily_focus (id, focus_date, completed, title, sort_order, is_deleted, created_at, updated_at) "
"VALUES (:id, CURRENT_DATE, false, :title, 0, false, now(), now())"),
{"id": fid, "title": f"EditMe-{tag}"},
)
await db_session.commit()
r = await client.post(f"/focus/{fid}/edit", data={
"title": f"Edited-{tag}",
"domain_id": seed_domain["id"],
"project_id": seed_project["id"],
}, follow_redirects=False)
assert r.status_code == 303
result = await db_session.execute(
text("SELECT title, domain_id, project_id FROM daily_focus WHERE id = :id"),
{"id": fid},
)
row = result.first()
assert row.title == f"Edited-{tag}"
assert str(row.domain_id) == seed_domain["id"]
assert str(row.project_id) == seed_project["id"]
await db_session.execute(text("DELETE FROM daily_focus WHERE id = :id"), {"id": fid})
await db_session.commit()
@pytest.mark.asyncio
async def test_focus_page_shows_standalone_items(
self, client: AsyncClient, seed_focus_standalone: dict,
):
"""Standalone items appear on the focus page."""
r = await client.get("/focus/", follow_redirects=True)
assert r.status_code == 200
assert "Test Standalone Focus" in r.text
@pytest.mark.asyncio
async def test_project_focus_tab(
self, client: AsyncClient, seed_project: dict, seed_focus_standalone: dict,
):
"""Focus tab on project detail shows standalone items assigned to that project."""
r = await client.get(f"/projects/{seed_project['id']}?tab=focus")
assert r.status_code == 200
assert "Test Standalone Focus" in r.text
# ===========================================================================
# Focus Item Conversion
# ===========================================================================
class TestFocusConversion:
"""Test converting standalone focus items to other entity types."""
@pytest.mark.asyncio
async def test_convert_to_task(
self, client: AsyncClient, db_session: AsyncSession, seed_domain: dict,
):
"""Convert standalone focus item to task."""
tag = _uid()
fid = str(uuid.uuid4())
await db_session.execute(
text("INSERT INTO daily_focus (id, focus_date, completed, title, domain_id, sort_order, is_deleted, created_at, updated_at) "
"VALUES (:id, CURRENT_DATE, false, :title, :did, 0, false, now(), now())"),
{"id": fid, "title": f"ConvTask-{tag}", "did": seed_domain["id"]},
)
await db_session.commit()
r = await client.post(f"/focus/{fid}/convert-to-task", follow_redirects=False)
assert r.status_code == 303
# Focus item should now have task_id set and title cleared
result = await db_session.execute(
text("SELECT task_id, title FROM daily_focus WHERE id = :id"), {"id": fid},
)
row = result.first()
assert row.task_id is not None
assert row.title is None
# Task should exist
result = await db_session.execute(
text("SELECT title, status FROM tasks WHERE id = :id"), {"id": str(row.task_id)},
)
task = result.first()
assert task.title == f"ConvTask-{tag}"
assert task.status == "open"
# Cleanup
await db_session.execute(text("DELETE FROM daily_focus WHERE id = :id"), {"id": fid})
await db_session.execute(text("DELETE FROM tasks WHERE id = :id"), {"id": str(row.task_id)})
await db_session.commit()
@pytest.mark.asyncio
async def test_convert_to_note(
self, client: AsyncClient, db_session: AsyncSession, seed_domain: dict,
):
"""Convert standalone focus item to note."""
tag = _uid()
fid = str(uuid.uuid4())
await db_session.execute(
text("INSERT INTO daily_focus (id, focus_date, completed, title, domain_id, sort_order, is_deleted, created_at, updated_at) "
"VALUES (:id, CURRENT_DATE, false, :title, :did, 0, false, now(), now())"),
{"id": fid, "title": f"ConvNote-{tag}", "did": seed_domain["id"]},
)
await db_session.commit()
r = await client.post(f"/focus/{fid}/convert-to-note", follow_redirects=False)
assert r.status_code == 303
# Focus item should be soft-deleted
result = await db_session.execute(
text("SELECT is_deleted FROM daily_focus WHERE id = :id"), {"id": fid},
)
assert result.first().is_deleted is True
# Note should exist
result = await db_session.execute(
text("SELECT title, domain_id FROM notes WHERE title = :t AND is_deleted = false"),
{"t": f"ConvNote-{tag}"},
)
note = result.first()
assert note is not None
assert str(note.domain_id) == seed_domain["id"]
# Cleanup
await db_session.execute(text("DELETE FROM daily_focus WHERE id = :id"), {"id": fid})
await db_session.execute(
text("DELETE FROM notes WHERE title = :t"), {"t": f"ConvNote-{tag}"}
)
await db_session.commit()
@pytest.mark.asyncio
async def test_convert_to_link(
self, client: AsyncClient, db_session: AsyncSession, seed_domain: dict,
):
"""Convert standalone focus item to link."""
tag = _uid()
fid = str(uuid.uuid4())
await db_session.execute(
text("INSERT INTO daily_focus (id, focus_date, completed, title, domain_id, sort_order, is_deleted, created_at, updated_at) "
"VALUES (:id, CURRENT_DATE, false, :title, :did, 0, false, now(), now())"),
{"id": fid, "title": f"ConvLink-{tag}", "did": seed_domain["id"]},
)
await db_session.commit()
r = await client.post(f"/focus/{fid}/convert-to-link", follow_redirects=False)
assert r.status_code == 303
# Focus item should be soft-deleted
result = await db_session.execute(
text("SELECT is_deleted FROM daily_focus WHERE id = :id"), {"id": fid},
)
assert result.first().is_deleted is True
# Link should exist with title as label
result = await db_session.execute(
text("SELECT label, domain_id FROM links WHERE label = :l AND is_deleted = false"),
{"l": f"ConvLink-{tag}"},
)
link = result.first()
assert link is not None
assert str(link.domain_id) == seed_domain["id"]
# Cleanup
await db_session.execute(text("DELETE FROM daily_focus WHERE id = :id"), {"id": fid})
await db_session.execute(
text("DELETE FROM links WHERE label = :l"), {"l": f"ConvLink-{tag}"}
)
await db_session.commit()
@pytest.mark.asyncio
async def test_convert_to_list_item(
self, client: AsyncClient, db_session: AsyncSession,
seed_domain: dict, seed_list: dict,
):
"""Convert standalone focus item to list item."""
tag = _uid()
fid = str(uuid.uuid4())
await db_session.execute(
text("INSERT INTO daily_focus (id, focus_date, completed, title, domain_id, sort_order, is_deleted, created_at, updated_at) "
"VALUES (:id, CURRENT_DATE, false, :title, :did, 0, false, now(), now())"),
{"id": fid, "title": f"ConvLI-{tag}", "did": seed_domain["id"]},
)
await db_session.commit()
r = await client.post(f"/focus/{fid}/convert-to-list-item", data={
"list_id": seed_list["id"],
}, follow_redirects=False)
assert r.status_code == 303
# Focus item should now point to a list_item_id with title cleared
result = await db_session.execute(
text("SELECT list_item_id, title FROM daily_focus WHERE id = :id"), {"id": fid},
)
row = result.first()
assert row.list_item_id is not None
assert row.title is None
# List item should exist
result = await db_session.execute(
text("SELECT content, list_id FROM list_items WHERE id = :id"),
{"id": str(row.list_item_id)},
)
li = result.first()
assert li.content == f"ConvLI-{tag}"
assert str(li.list_id) == seed_list["id"]
# Cleanup
await db_session.execute(text("DELETE FROM daily_focus WHERE id = :id"), {"id": fid})
await db_session.execute(
text("DELETE FROM list_items WHERE id = :id"), {"id": str(row.list_item_id)}
)
await db_session.commit()
@pytest.mark.asyncio
async def test_convert_nonexistent_item_redirects(
self, client: AsyncClient,
):
"""Converting a nonexistent focus item redirects to /focus."""
fake_id = str(uuid.uuid4())
r = await client.post(f"/focus/{fake_id}/convert-to-task", follow_redirects=False)
assert r.status_code == 303
@pytest.mark.asyncio
async def test_convert_task_linked_item_redirects(
self, client: AsyncClient, all_seeds: dict,
):
"""Converting a task-linked focus item (no title) redirects without action."""
r = await client.post(f"/focus/{all_seeds['focus']}/convert-to-note", follow_redirects=False)
assert r.status_code == 303
# ===========================================================================
# Entity Create with task_id/meeting_id
# ===========================================================================