← Back to team overview

sts-sponsors team mailing list archive

[Merge] ~ack/maas-site-manager:api-tests-grouping into maas-site-manager:main

 

Alberto Donato has proposed merging ~ack/maas-site-manager:api-tests-grouping into maas-site-manager:main.

Commit message:
group API tests by handler, add more tests



Requested reviews:
  MAAS Lander (maas-lander): unittests
  MAAS Committers (maas-committers)

For more details, see:
https://code.launchpad.net/~ack/maas-site-manager/+git/site-manager/+merge/442485
-- 
Your team MAAS Committers is requested to review the proposed merge of ~ack/maas-site-manager:api-tests-grouping into maas-site-manager:main.
diff --git a/backend/tests/user_api/test_handlers.py b/backend/tests/user_api/test_handlers.py
index bf62306..79314da 100644
--- a/backend/tests/user_api/test_handlers.py
+++ b/backend/tests/user_api/test_handlers.py
@@ -32,414 +32,220 @@ def duration_format(time: timedelta, time_format: str) -> str:
 
 
 @pytest.mark.asyncio
-async def test_root(user_app_client: AsyncClient) -> None:
-    response = await user_app_client.get("/")
-    assert response.status_code == 200
-    assert response.json() == {"version": "0.0.1"}
+class TestRootHandler:
+    async def test_get(self, user_app_client: AsyncClient) -> None:
+        response = await user_app_client.get("/")
+        assert response.status_code == 200
+        assert response.json() == {"version": "0.0.1"}
 
-
-@pytest.mark.asyncio
-async def test_root_as_auth(
-    authenticated_user_app_client: AuthAsyncClient,
-) -> None:
-    response = await authenticated_user_app_client.get("/")
-    assert response.status_code == 200
-    assert response.json() == {"version": "0.0.1"}
+    async def test_get_authenticated(
+        self,
+        authenticated_user_app_client: AuthAsyncClient,
+    ) -> None:
+        response = await authenticated_user_app_client.get("/")
+        assert response.status_code == 200
+        assert response.json() == {"version": "0.0.1"}
 
 
 @pytest.mark.asyncio
-async def test_sites_get(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    site1 = {
-        "name": "LondonHQ",
-        "city": "London",
-        "country": "gb",
-        "latitude": "51.509865",
-        "longitude": "-0.118092",
-        "note": "the first site",
-        "region": "Blue Fin Bldg",
-        "street": "110 Southwark St",
-        "timezone": "Europe/London",
-        "url": "https://londoncalling.example.com";,
-        "accepted": True,
-    }
-    site2 = site1.copy()
-    site2.update(
-        {
-            "name": "BerlinHQ",
-            "timezone": "Europe/Berlin",
-            "city": "Berlin",
-            "country": "de",
+class TestSitesHandler:
+    async def test_get(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        site1 = {
+            "name": "LondonHQ",
+            "city": "London",
+            "country": "gb",
+            "latitude": "51.509865",
+            "longitude": "-0.118092",
+            "note": "the first site",
+            "region": "Blue Fin Bldg",
+            "street": "110 Southwark St",
+            "timezone": "Europe/London",
+            "url": "https://londoncalling.example.com";,
+            "accepted": True,
         }
-    )
-    sites = await fixture.create("site", [site1, site2])
-    for site in sites:
-        site["stats"] = None
-        del site["created"]
-        del site["accepted"]
-
-    page1 = await authenticated_user_app_client.get("/sites")
-    assert page1.status_code == 200
-    assert page1.json() == {
-        "page": 1,
-        "size": 20,
-        "total": 2,
-        "items": sites,
-    }
-    filtered = await authenticated_user_app_client.get(
-        "/sites?city=onDo"
-    )  # vs London
-    assert filtered.status_code == 200
-    assert filtered.json() == {
-        "page": 1,
-        "size": 20,
-        "total": 1,
-        "items": [sites[0]],
-    }
-    paginated = await authenticated_user_app_client.get("/sites?page=2&size=1")
-    assert paginated.status_code == 200
-    assert paginated.json() == {
-        "page": 2,
-        "size": 1,
-        "total": 2,
-        "items": [sites[1]],
-    }
-
-
-@pytest.mark.asyncio
-async def test_sites_get_only_accepted(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    site1 = {
-        "name": "LondonHQ",
-        "city": "London",
-        "country": "gb",
-        "latitude": "51.509865",
-        "longitude": "-0.118092",
-        "note": "the first site",
-        "region": "Blue Fin Bldg",
-        "street": "110 Southwark St",
-        "timezone": "Europe/London",
-        "url": "https://londoncalling.example.com";,
-        "accepted": True,
-    }
-    site2 = site1.copy()
-    site2.update(
-        {
-            "name": "BerlinHQ",
-            "timezone": "Europe/Berlin",
-            "city": "Berlin",
-            "country": "de",
-            "accepted": False,
+        site2 = site1.copy()
+        site2.update(
+            {
+                "name": "BerlinHQ",
+                "timezone": "Europe/Berlin",
+                "city": "Berlin",
+                "country": "de",
+            }
+        )
+        sites = await fixture.create("site", [site1, site2])
+        for site in sites:
+            site["stats"] = None
+            del site["created"]
+            del site["accepted"]
+
+        page1 = await authenticated_user_app_client.get("/sites")
+        assert page1.status_code == 200
+        assert page1.json() == {
+            "page": 1,
+            "size": 20,
+            "total": 2,
+            "items": sites,
         }
-    )
-    created_site, _ = await fixture.create("site", [site1, site2])
-    created_site["stats"] = None
-    del created_site["created"]
-    del created_site["accepted"]
-
-    page1 = await authenticated_user_app_client.get("/sites")
-    assert page1.status_code == 200
-    assert page1.json() == {
-        "page": 1,
-        "size": 20,
-        "total": 1,
-        "items": [created_site],
-    }
-
-
-@pytest.mark.asyncio
-async def test_sites_get_filter_timezone(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    site1 = {
-        "name": "LondonHQ",
-        "city": "London",
-        "country": "gb",
-        "latitude": "51.509865",
-        "longitude": "-0.118092",
-        "note": "the first site",
-        "region": "Blue Fin Bldg",
-        "street": "110 Southwark St",
-        "timezone": "Europe/London",
-        "url": "https://londoncalling.example.com";,
-        "accepted": True,
-    }
-    site2 = site1.copy()
-    site2.update(
-        {
-            "name": "BerlinHQ",
-            "timezone": "Europe/Berlin",
-            "city": "Berlin",
-            "country": "de",
+        filtered = await authenticated_user_app_client.get(
+            "/sites?city=onDo"
+        )  # vs London
+        assert filtered.status_code == 200
+        assert filtered.json() == {
+            "page": 1,
+            "size": 20,
+            "total": 1,
+            "items": [sites[0]],
+        }
+        paginated = await authenticated_user_app_client.get(
+            "/sites?page=2&size=1"
+        )
+        assert paginated.status_code == 200
+        assert paginated.json() == {
+            "page": 2,
+            "size": 1,
+            "total": 2,
+            "items": [sites[1]],
         }
-    )
-    [created_site, _] = await fixture.create("site", [site1, site2])
-    created_site["stats"] = None
-    del created_site["created"]
-    del created_site["accepted"]
-    page1 = await authenticated_user_app_client.get(
-        "/sites?timezone=Europe/London"
-    )
-    assert page1.status_code == 200
-    assert page1.json() == {
-        "page": 1,
-        "size": 20,
-        "total": 1,
-        "items": [created_site],
-    }
-
 
-@pytest.mark.asyncio
-async def test_sites_get_with_stats(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    [site] = await fixture.create(
-        "site",
-        [
-            {
-                "name": "LondonHQ",
-                "city": "London",
-                "country": "gb",
-                "latitude": "51.509865",
-                "longitude": "-0.118092",
-                "note": "the first site",
-                "region": "Blue Fin Bldg",
-                "street": "110 Southwark St",
-                "timezone": "Europe/London",
-                "url": "https://londoncalling.example.com";,
-                "accepted": True,
-            }
-        ],
-    )
-    [site_data] = await fixture.create(
-        "site_data",
-        [
+    async def test_get_only_accepted(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        site1 = {
+            "name": "LondonHQ",
+            "city": "London",
+            "country": "gb",
+            "latitude": "51.509865",
+            "longitude": "-0.118092",
+            "note": "the first site",
+            "region": "Blue Fin Bldg",
+            "street": "110 Southwark St",
+            "timezone": "Europe/London",
+            "url": "https://londoncalling.example.com";,
+            "accepted": True,
+        }
+        site2 = site1.copy()
+        site2.update(
             {
-                "site_id": site["id"],
-                "allocated_machines": 10,
-                "deployed_machines": 20,
-                "ready_machines": 30,
-                "error_machines": 40,
-                "last_seen": datetime.utcnow(),
+                "name": "BerlinHQ",
+                "timezone": "Europe/Berlin",
+                "city": "Berlin",
+                "country": "de",
+                "accepted": False,
             }
-        ],
-    )
-    del site_data["id"]
-    del site_data["site_id"]
-    site_data["last_seen"] = site_data["last_seen"].isoformat()
-    site["stats"] = site_data
-    del site["created"]
-    del site["accepted"]
-
-    page = await authenticated_user_app_client.get("/sites")
-    assert page.status_code == 200
-    assert page.json() == {
-        "page": 1,
-        "size": 20,
-        "total": 1,
-        "items": [site],
-    }
-
-
-@pytest.mark.asyncio
-async def test_pending_sites_get(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    site1 = {
-        "name": "LondonHQ",
-        "city": "London",
-        "country": "gb",
-        "latitude": "51.509865",
-        "longitude": "-0.118092",
-        "note": "the first site",
-        "region": "Blue Fin Bldg",
-        "street": "110 Southwark St",
-        "timezone": "Europe/London",
-        "url": "https://londoncalling.example.com";,
-        "accepted": True,
-    }
-    site2 = site1.copy()
-    site2.update(
-        {
-            "name": "BerlinHQ",
-            "timezone": "Europe/Berlin",
-            "city": "Berlin",
-            "country": "de",
-            "accepted": False,
+        )
+        created_site, _ = await fixture.create("site", [site1, site2])
+        created_site["stats"] = None
+        del created_site["created"]
+        del created_site["accepted"]
+
+        page1 = await authenticated_user_app_client.get("/sites")
+        assert page1.status_code == 200
+        assert page1.json() == {
+            "page": 1,
+            "size": 20,
+            "total": 1,
+            "items": [created_site],
         }
-    )
-    _, pending_site = await fixture.create("site", [site1, site2])
-
-    response = await authenticated_user_app_client.get("/requests")
-    assert response.status_code == 200
-    assert response.json() == {
-        "page": 1,
-        "size": 20,
-        "total": 1,
-        "items": [
-            {
-                "id": pending_site["id"],
-                "name": pending_site["name"],
-                "url": pending_site["url"],
-                "created": pending_site["created"].isoformat(),
-            },
-        ],
-    }
-
-
-@pytest.mark.asyncio
-async def test_pending_sites_post_accept(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    site = {
-        "name": "LondonHQ",
-        "url": "https://londoncalling.example.com";,
-        "accepted": False,
-    }
-    [pending_site] = await fixture.create("site", [site])
-
-    response = await authenticated_user_app_client.post(
-        "/requests",
-        json={"ids": [pending_site["id"]], "accept": True},
-    )
-    assert response.status_code == 204
-    [created_site] = await fixture.get("site")
-    assert created_site["accepted"]
-
-
-@pytest.mark.asyncio
-async def test_pending_sites_post_reject(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    site = {
-        "name": "LondonHQ",
-        "url": "https://londoncalling.example.com";,
-        "accepted": False,
-    }
-    [pending_site] = await fixture.create("site", [site])
-
-    response = await authenticated_user_app_client.post(
-        "/requests",
-        json={"ids": [pending_site["id"]], "accept": False},
-    )
-    assert response.status_code == 204
-    assert await fixture.get("site") == []
 
-
-@pytest.mark.asyncio
-async def test_pending_sites_post_invalid_ids(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    site = {
-        "name": "LondonHQ",
-        "url": "https://londoncalling.example.com";,
-        "accepted": True,
-    }
-    [site] = await fixture.create("site", [site])
-    # unknown IDs and IDs for non-pending sites are invalid
-    ids = [site["id"], 10000]
-    response = await authenticated_user_app_client.post(
-        "/requests",
-        json={"ids": ids, "accept": True},
-    )
-    assert response.status_code == 400
-    assert response.json() == {
-        "detail": {"message": "Unknown pending sites", "ids": ids}
-    }
-
-
-@pytest.mark.asyncio
-@pytest.mark.parametrize("time_format", ["ISO 8601", "Float"])
-async def test_token_time_format(
-    time_format: str, authenticated_user_app_client: AuthAsyncClient
-) -> None:
-    seconds = 100
-    expiry = timedelta(seconds=seconds)
-    formatted_expiry = duration_format(expiry, time_format)
-
-    response = await authenticated_user_app_client.post(
-        "/tokens", json={"count": 5, "duration": formatted_expiry}
-    )
-    assert response.status_code == 200
-    result = response.json()
-    assert datetime.fromisoformat(result["expired"]) < (
-        datetime.utcnow() + timedelta(seconds=seconds)
-    )
-    assert len(result["tokens"]) == 5
-
-
-@pytest.mark.asyncio
-async def test_tokens_get(
-    authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
-) -> None:
-    tokens = await fixture.create(
-        "token",
-        [
-            {
-                "site_id": None,
-                "value": "c54e5ba6-d214-40dd-b601-01ebb1019c07",
-                "expired": datetime.fromisoformat(
-                    "2023-02-23T09:09:51.103703"
-                ),
-                "created": datetime.fromisoformat(
-                    "2023-02-22T03:14:15.926535"
-                ),
-            },
+    async def test_get_filter_timezone(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        site1 = {
+            "name": "LondonHQ",
+            "city": "London",
+            "country": "gb",
+            "latitude": "51.509865",
+            "longitude": "-0.118092",
+            "note": "the first site",
+            "region": "Blue Fin Bldg",
+            "street": "110 Southwark St",
+            "timezone": "Europe/London",
+            "url": "https://londoncalling.example.com";,
+            "accepted": True,
+        }
+        site2 = site1.copy()
+        site2.update(
             {
-                "site_id": None,
-                "value": "b67c449e-fcf6-4014-887d-909859f9fb70",
-                "expired": datetime.fromisoformat(
-                    "2023-02-23T11:28:54.382456"
-                ),
-                "created": datetime.fromisoformat(
-                    "2023-02-22T03:14:15.926535"
-                ),
-            },
-        ],
-    )
-    for token in tokens:
-        token["expired"] = token["expired"].isoformat()
-        token["created"] = token["created"].isoformat()
-        token["value"] = str(token["value"])
-    response = await authenticated_user_app_client.get("/tokens")
-    assert response.status_code == 200
-    assert response.json()["total"] == 2
-    assert response.json()["items"] == tokens
+                "name": "BerlinHQ",
+                "timezone": "Europe/Berlin",
+                "city": "Berlin",
+                "country": "de",
+            }
+        )
+        [created_site, _] = await fixture.create("site", [site1, site2])
+        created_site["stats"] = None
+        del created_site["created"]
+        del created_site["accepted"]
+        page1 = await authenticated_user_app_client.get(
+            "/sites?timezone=Europe/London"
+        )
+        assert page1.status_code == 200
+        assert page1.json() == {
+            "page": 1,
+            "size": 20,
+            "total": 1,
+            "items": [created_site],
+        }
 
+    async def test_get_with_stats(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        [site] = await fixture.create(
+            "site",
+            [
+                {
+                    "name": "LondonHQ",
+                    "city": "London",
+                    "country": "gb",
+                    "latitude": "51.509865",
+                    "longitude": "-0.118092",
+                    "note": "the first site",
+                    "region": "Blue Fin Bldg",
+                    "street": "110 Southwark St",
+                    "timezone": "Europe/London",
+                    "url": "https://londoncalling.example.com";,
+                    "accepted": True,
+                }
+            ],
+        )
+        [site_data] = await fixture.create(
+            "site_data",
+            [
+                {
+                    "site_id": site["id"],
+                    "allocated_machines": 10,
+                    "deployed_machines": 20,
+                    "ready_machines": 30,
+                    "error_machines": 40,
+                    "last_seen": datetime.utcnow(),
+                }
+            ],
+        )
+        del site_data["id"]
+        del site_data["site_id"]
+        site_data["last_seen"] = site_data["last_seen"].isoformat()
+        site["stats"] = site_data
+        del site["created"]
+        del site["accepted"]
 
-@pytest.mark.asyncio
-async def test_login_post_fails_with_wrong_password(
-    user_app_client: AsyncClient, fixture: Fixture
-) -> None:
-    phash = "$2b$12$F5sgrhRNtWAOehcoVO.XK.oSvupmcg8.0T2jCHOTg15M8N8LrpRwS"
-    userdata = {
-        "id": 1,
-        "email": "admin@xxxxxxxxxxx",
-        "full_name": "Admin",
-        "password": phash,
-    }
-    await fixture.create("user", userdata)
-
-    fail_response = await user_app_client.post(
-        "/login",
-        json={"username": userdata["email"], "password": "incorrect_pass"},
-    )
-    assert fail_response.status_code == 401, "Expected authentication error."
-
-    fail_response = await user_app_client.post(
-        "/login", json={"username": userdata["email"], "password": "admin"}
-    )
-    assert fail_response.status_code == 200, "Expected user login."
+        page = await authenticated_user_app_client.get("/sites")
+        assert page.status_code == 200
+        assert page.json() == {
+            "page": 1,
+            "size": 20,
+            "total": 1,
+            "items": [site],
+        }
 
 
 @pytest.mark.asyncio
-async def test_sites_get_unauthenticated(
-    user_app_client: AsyncClient, fixture: Fixture
-) -> None:
-    site = [
-        {
+class TestPendingSitesHandler:
+    async def test_get(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        site1 = {
             "name": "LondonHQ",
             "city": "London",
             "country": "gb",
@@ -452,17 +258,210 @@ async def test_sites_get_unauthenticated(
             "url": "https://londoncalling.example.com";,
             "accepted": True,
         }
-    ]
-    await fixture.create("site", site)
-    page1 = await user_app_client.get("/sites")
-    assert page1.status_code == 401, "Expected authentication error."
+        site2 = site1.copy()
+        site2.update(
+            {
+                "name": "BerlinHQ",
+                "timezone": "Europe/Berlin",
+                "city": "Berlin",
+                "country": "de",
+                "accepted": False,
+            }
+        )
+        _, pending_site = await fixture.create("site", [site1, site2])
+
+        response = await authenticated_user_app_client.get("/requests")
+        assert response.status_code == 200
+        assert response.json() == {
+            "page": 1,
+            "size": 20,
+            "total": 1,
+            "items": [
+                {
+                    "id": pending_site["id"],
+                    "name": pending_site["name"],
+                    "url": pending_site["url"],
+                    "created": pending_site["created"].isoformat(),
+                },
+            ],
+        }
+
+    async def test_post_accept(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        site = {
+            "name": "LondonHQ",
+            "url": "https://londoncalling.example.com";,
+            "accepted": False,
+        }
+        [pending_site] = await fixture.create("site", [site])
+
+        response = await authenticated_user_app_client.post(
+            "/requests",
+            json={"ids": [pending_site["id"]], "accept": True},
+        )
+        assert response.status_code == 204
+        [created_site] = await fixture.get("site")
+        assert created_site["accepted"]
+
+    async def test_post_reject(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        site = {
+            "name": "LondonHQ",
+            "url": "https://londoncalling.example.com";,
+            "accepted": False,
+        }
+        [pending_site] = await fixture.create("site", [site])
+
+        response = await authenticated_user_app_client.post(
+            "/requests",
+            json={"ids": [pending_site["id"]], "accept": False},
+        )
+        assert response.status_code == 204
+        assert await fixture.get("site") == []
+
+    async def test_post_invalid_ids(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        site = {
+            "name": "LondonHQ",
+            "url": "https://londoncalling.example.com";,
+            "accepted": True,
+        }
+        [site] = await fixture.create("site", [site])
+        # unknown IDs and IDs for non-pending sites are invalid
+        ids = [site["id"], 10000]
+        response = await authenticated_user_app_client.post(
+            "/requests",
+            json={"ids": ids, "accept": True},
+        )
+        assert response.status_code == 400
+        assert response.json() == {
+            "detail": {"message": "Unknown pending sites", "ids": ids}
+        }
 
 
 @pytest.mark.asyncio
-async def test_token_get_unauthenticated(user_app_client: AsyncClient) -> None:
-    seconds = 100
+class TestTokensHandler:
+    @pytest.mark.parametrize("time_format", ["ISO 8601", "Float"])
+    async def test_token_time_format(
+        self, time_format: str, authenticated_user_app_client: AuthAsyncClient
+    ) -> None:
+        seconds = 100
+        expiry = timedelta(seconds=seconds)
+        formatted_expiry = duration_format(expiry, time_format)
+
+        response = await authenticated_user_app_client.post(
+            "/tokens", json={"count": 5, "duration": formatted_expiry}
+        )
+        assert response.status_code == 200
+        result = response.json()
+        assert datetime.fromisoformat(result["expired"]) < (
+            datetime.utcnow() + timedelta(seconds=seconds)
+        )
+        assert len(result["tokens"]) == 5
+
+    async def test_tokens_get(
+        self, authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
+    ) -> None:
+        tokens = await fixture.create(
+            "token",
+            [
+                {
+                    "site_id": None,
+                    "value": "c54e5ba6-d214-40dd-b601-01ebb1019c07",
+                    "expired": datetime.fromisoformat(
+                        "2023-02-23T09:09:51.103703"
+                    ),
+                    "created": datetime.fromisoformat(
+                        "2023-02-22T03:14:15.926535"
+                    ),
+                },
+                {
+                    "site_id": None,
+                    "value": "b67c449e-fcf6-4014-887d-909859f9fb70",
+                    "expired": datetime.fromisoformat(
+                        "2023-02-23T11:28:54.382456"
+                    ),
+                    "created": datetime.fromisoformat(
+                        "2023-02-22T03:14:15.926535"
+                    ),
+                },
+            ],
+        )
+        for token in tokens:
+            token["expired"] = token["expired"].isoformat()
+            token["created"] = token["created"].isoformat()
+            token["value"] = str(token["value"])
+        response = await authenticated_user_app_client.get("/tokens")
+        assert response.status_code == 200
+        assert response.json()["total"] == 2
+        assert response.json()["items"] == tokens
 
-    response = await user_app_client.post(
-        "/tokens", json={"count": 5, "duration": seconds}
-    )
-    assert response.status_code == 401, "Expected authentication error."
+
+@pytest.mark.asyncio
+class TestLoginHandler:
+    async def test_post(
+        self, user_app_client: AsyncClient, fixture: Fixture
+    ) -> None:
+        phash = "$2b$12$F5sgrhRNtWAOehcoVO.XK.oSvupmcg8.0T2jCHOTg15M8N8LrpRwS"
+        userdata = {
+            "id": 1,
+            "email": "admin@xxxxxxxxxxx",
+            "full_name": "Admin",
+            "password": phash,
+        }
+        await fixture.create("user", userdata)
+        response = await user_app_client.post(
+            "/login",
+            json={"username": userdata["email"], "password": "admin"},
+        )
+        assert response.status_code == 200
+        assert response.json()["token_type"] == "bearer"
+
+    async def test_post_fails_with_wrong_password(
+        self, user_app_client: AsyncClient, fixture: Fixture
+    ) -> None:
+        phash = "$2b$12$F5sgrhRNtWAOehcoVO.XK.oSvupmcg8.0T2jCHOTg15M8N8LrpRwS"
+        userdata = {
+            "id": 1,
+            "email": "admin@xxxxxxxxxxx",
+            "full_name": "Admin",
+            "password": phash,
+        }
+        await fixture.create("user", userdata)
+
+        fail_response = await user_app_client.post(
+            "/login",
+            json={"username": userdata["email"], "password": "incorrect_pass"},
+        )
+        assert (
+            fail_response.status_code == 401
+        ), "Expected authentication error."
+
+        fail_response = await user_app_client.post(
+            "/login", json={"username": userdata["email"], "password": "admin"}
+        )
+        assert fail_response.status_code == 200, "Expected user login."
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+    "method,url",
+    [
+        ("GET", "/sites"),
+        ("GET", "/requests"),
+        ("POST", "/requests"),
+        ("GET", "/tokens"),
+        ("POST", "/tokens"),
+        ("GET", "/users/me"),
+    ],
+)
+async def test_handler_auth_required(
+    user_app_client: AsyncClient, method: str, url: str
+) -> None:
+    response = await user_app_client.request(method, url)
+    assert (
+        response.status_code == 401
+    ), f"Auth should be required for {method} {url}"