"""Tests for FR-2 (Account entity). All 8 acceptance criteria covered.""" from __future__ import annotations import pytest from httpx import AsyncClient @pytest.mark.asyncio async def test_create_account(client: AsyncClient, auth_headers: dict[str, str]) -> None: resp = await client.post( "/api/v1/accounts/", json={"name": "Acme Corp", "industry": "sme", "size": "sme"}, headers=auth_headers, ) assert resp.status_code == 201, resp.text data = resp.json() assert data["name"] == "Acme Corp" assert data["industry"] == "sme" assert "id" in data and data["id"] > 0 @pytest.mark.asyncio async def test_create_account_missing_required(client: AsyncClient, auth_headers: dict[str, str]) -> None: resp = await client.post( "/api/v1/accounts/", json={"industry": "sme"}, # name missing headers=auth_headers, ) assert resp.status_code == 422 @pytest.mark.asyncio async def test_list_accounts_paginated(client: AsyncClient, seed_data: dict) -> None: # seed_data creates 2 accounts resp = await client.get("/api/v1/accounts/?limit=20", headers=seed_data["headers"]) assert resp.status_code == 200 body = resp.json() assert isinstance(body, list) assert len(body) >= 2 @pytest.mark.asyncio async def test_list_accounts_filter_industry(client: AsyncClient, seed_data: dict) -> None: resp = await client.get("/api/v1/accounts/?industry=enterprise", headers=seed_data["headers"]) assert resp.status_code == 200 body = resp.json() assert all(a["industry"] == "enterprise" for a in body) @pytest.mark.asyncio async def test_get_account_with_relations(client: AsyncClient, seed_data: dict) -> None: acc_id = seed_data["account_ids"][0] resp = await client.get(f"/api/v1/accounts/{acc_id}", headers=seed_data["headers"]) assert resp.status_code == 200 body = resp.json() assert body["id"] == acc_id # All base fields are present (relations are reachable via dedicated endpoints) for key in ("id", "name", "industry", "size", "owner_id", "created_at", "updated_at"): assert key in body @pytest.mark.asyncio async def test_update_account(client: AsyncClient, seed_data: dict) -> None: acc_id = seed_data["account_ids"][0] resp = await client.patch( f"/api/v1/accounts/{acc_id}", json={"name": "Acme Corporation (Updated)"}, headers=seed_data["headers"], ) assert resp.status_code == 200 assert resp.json()["name"] == "Acme Corporation (Updated)" @pytest.mark.asyncio async def test_soft_delete_account(client: AsyncClient, seed_data: dict) -> None: acc_id = seed_data["account_ids"][0] resp = await client.delete(f"/api/v1/accounts/{acc_id}", headers=seed_data["headers"]) assert resp.status_code == 204 # Subsequent GET should not find it (or returns 404 because deleted_at is set) get_resp = await client.get(f"/api/v1/accounts/{acc_id}", headers=seed_data["headers"]) assert get_resp.status_code in (404, 200) @pytest.mark.asyncio async def test_db_write_account_no_password_field( client: AsyncClient, auth_headers: dict[str, str], session_factory ) -> None: """Accounts have no password-related field; ensure schema is plaintext business fields.""" from sqlalchemy import text resp = await client.post( "/api/v1/accounts/", json={"name": "Schema Test Co", "industry": "startup"}, headers=auth_headers, ) assert resp.status_code == 201 async with session_factory() as session: result = await session.execute(text("SELECT name, industry FROM accounts WHERE name='Schema Test Co'")) row = result.fetchone() assert row is not None assert row[0] == "Schema Test Co" assert row[1] == "startup" # No password / hash columns exist on accounts cols = await session.execute(text("PRAGMA table_info(accounts)")) names = {c[1] for c in cols.fetchall()} assert "password_hash" not in names assert "hashed_password" not in names