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

1from unittest.mock import AsyncMock 

2 

3import pytest 

4from botocore.exceptions import ClientError 

5from fastapi.testclient import TestClient 

6 

7from app.utils.aws_storage import AWSStorageError 

8 

9STS_CREDENTIALS = { 

10 "AccessKeyId": "ASIAIOSFODNN7EXAMPLE", 

11 "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", 

12 "SessionToken": "FwoGZXIvYXdzEBY...", 

13} 

14 

15 

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 

20 

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 

26 

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} 

34 

35 resp = client.post( 

36 "/coverage/create-site/", headers={"token": api_key} 

37 ) 

38 

39 assert resp.status_code == 200 

40 

41 

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} 

51 

52 resp = client.post( 

53 "/coverage/create-site/", headers={"token": api_key} 

54 ) 

55 

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"] 

65 

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" 

71 

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} 

85 

86 resp = client.post( 

87 "/coverage/create-site/", headers={"token": api_key} 

88 ) 

89 

90 assert resp.status_code == 200 

91 assert mock_s3_client.put_object.await_count == 3 

92 

93 

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} 

107 

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 ) 

112 

113 assert mock_s3_client.put_object.await_count == 3 

114 

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 ) 

125 

126 with pytest.raises(AWSStorageError, match="Failed to create site directory"): 

127 client.post("/coverage/create-site/", headers={"token": api_key})