Coverage for backend / tests / test_create_site.py: 100%
52 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-02 15:51 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-02 15:51 +0000
1from unittest.mock import AsyncMock
3import pytest
4from botocore.exceptions import ClientError
5from fastapi.testclient import TestClient
7from app.utils.aws_storage import AWSStorageError
9STS_CREDENTIALS = {
10 "AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
11 "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
12 "SessionToken": "FwoGZXIvYXdzEBY...",
13}
16class TestCreateSiteAuth:
17 def test_missing_token_returns_401(self, client: TestClient):
18 resp = client.post("/coverage/create-site/")
19 assert resp.status_code == 401
21 def test_invalid_token_returns_403(self, client: TestClient):
22 resp = client.post(
23 "/coverage/create-site/", headers={"token": "wrong-key"}
24 )
25 assert resp.status_code == 403
27 def test_valid_token_returns_200(
28 self,
29 client: TestClient,
30 mock_sts_client: AsyncMock,
31 api_key: str,
32 ):
33 mock_sts_client.assume_role.return_value = {"Credentials": STS_CREDENTIALS}
35 resp = client.post(
36 "/coverage/create-site/", headers={"token": api_key}
37 )
39 assert resp.status_code == 200
42class TestCreateSite:
43 def test_returns_session(
44 self,
45 client: TestClient,
46 mock_s3_client: AsyncMock,
47 mock_sts_client: AsyncMock,
48 api_key: str,
49 ):
50 mock_sts_client.assume_role.return_value = {"Credentials": STS_CREDENTIALS}
52 resp = client.post(
53 "/coverage/create-site/", headers={"token": api_key}
54 )
56 assert resp.status_code == 200
57 data = resp.json()
58 assert len(data["site_id"]) == 12
59 assert all(c in "0123456789abcdef" for c in data["site_id"])
60 assert data["bucket"] == "test-bucket"
61 assert data["region"] == "us-east-1"
62 assert data["access_key_id"] == STS_CREDENTIALS["AccessKeyId"]
63 assert data["secret_access_key"] == STS_CREDENTIALS["SecretAccessKey"]
64 assert data["session_token"] == STS_CREDENTIALS["SessionToken"]
66 # S3 put_object called to reserve the site_id prefix
67 mock_s3_client.put_object.assert_awaited_once()
68 put_kwargs = mock_s3_client.put_object.call_args.kwargs
69 assert put_kwargs["Bucket"] == "test-bucket"
70 assert put_kwargs["Key"] == f"sites/{data['site_id']}/.keep"
72 def test_retries_on_site_id_collision(
73 self,
74 client: TestClient,
75 mock_s3_client: AsyncMock,
76 mock_sts_client: AsyncMock,
77 api_key: str,
78 ):
79 collision = ClientError(
80 error_response={"Error": {"Code": "PreconditionFailed"}},
81 operation_name="PutObject",
82 )
83 mock_s3_client.put_object.side_effect = [collision, collision, {}]
84 mock_sts_client.assume_role.return_value = {"Credentials": STS_CREDENTIALS}
86 resp = client.post(
87 "/coverage/create-site/", headers={"token": api_key}
88 )
90 assert resp.status_code == 200
91 assert mock_s3_client.put_object.await_count == 3
94 def test_fails_after_max_attempts_on_site_id_collision(
95 self,
96 client: TestClient,
97 mock_s3_client: AsyncMock,
98 mock_sts_client: AsyncMock,
99 api_key: str,
100 ):
101 collision = ClientError(
102 error_response={"Error": {"Code": "PreconditionFailed"}},
103 operation_name="PutObject",
104 )
105 mock_s3_client.put_object.side_effect = [collision, collision, collision]
106 mock_sts_client.assume_role.return_value = {"Credentials": STS_CREDENTIALS}
108 with pytest.raises(AWSStorageError, match="Failed to generate unique site ID after multiple attempts"):
109 client.post(
110 "/coverage/create-site/", headers={"token": api_key}
111 )
113 assert mock_s3_client.put_object.await_count == 3
115 def test_s3_error_propagates(
116 self,
117 client: TestClient,
118 mock_s3_client: AsyncMock,
119 api_key: str,
120 ):
121 mock_s3_client.put_object.side_effect = ClientError(
122 error_response={"Error": {"Code": "InternalError"}},
123 operation_name="PutObject",
124 )
126 with pytest.raises(AWSStorageError, match="Failed to create site directory"):
127 client.post("/coverage/create-site/", headers={"token": api_key})