test_routes_renew_expiration.py
1 # Copyright 2025 Alibaba Group Holding Ltd. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 15 from datetime import datetime, timedelta, timezone 16 17 from fastapi.exceptions import HTTPException 18 from fastapi.testclient import TestClient 19 20 from opensandbox_server.api import lifecycle 21 from opensandbox_server.api.schema import RenewSandboxExpirationResponse 22 23 24 def test_renew_expiration_returns_updated_timestamp( 25 client: TestClient, 26 auth_headers: dict, 27 monkeypatch, 28 ) -> None: 29 target = datetime.now(timezone.utc) + timedelta(hours=2) 30 calls: list[tuple[str, datetime]] = [] 31 32 class StubService: 33 @staticmethod 34 def renew_expiration(sandbox_id: str, request) -> RenewSandboxExpirationResponse: 35 calls.append((sandbox_id, request.expires_at)) 36 return RenewSandboxExpirationResponse(expiresAt=target) 37 38 monkeypatch.setattr(lifecycle, "sandbox_service", StubService()) 39 40 response = client.post( 41 "/v1/sandboxes/sbx-001/renew-expiration", 42 headers=auth_headers, 43 json={"expiresAt": target.isoformat()}, 44 ) 45 46 assert response.status_code == 200 47 expires_at = datetime.fromisoformat(response.json()["expiresAt"].replace("Z", "+00:00")) 48 assert expires_at == target 49 assert calls == [("sbx-001", target)] 50 51 52 def test_renew_expiration_rejects_invalid_payload( 53 client: TestClient, 54 auth_headers: dict, 55 ) -> None: 56 response = client.post( 57 "/v1/sandboxes/sbx-001/renew-expiration", 58 headers=auth_headers, 59 json={"expiresAt": "not-a-datetime"}, 60 ) 61 62 assert response.status_code == 422 63 64 65 def test_renew_expiration_propagates_service_http_error( 66 client: TestClient, 67 auth_headers: dict, 68 monkeypatch, 69 ) -> None: 70 class StubService: 71 @staticmethod 72 def renew_expiration(sandbox_id: str, request) -> RenewSandboxExpirationResponse: 73 raise HTTPException( 74 status_code=409, 75 detail={ 76 "code": "INVALID_EXPIRES_AT", 77 "message": f"Requested expiresAt is not valid for sandbox {sandbox_id}", 78 }, 79 ) 80 81 monkeypatch.setattr(lifecycle, "sandbox_service", StubService()) 82 83 response = client.post( 84 "/v1/sandboxes/sbx-001/renew-expiration", 85 headers=auth_headers, 86 json={"expiresAt": "2030-01-01T00:00:00Z"}, 87 ) 88 89 assert response.status_code == 409 90 assert response.json() == { 91 "code": "INVALID_EXPIRES_AT", 92 "message": "Requested expiresAt is not valid for sandbox sbx-001", 93 } 94 95 96 def test_renew_expiration_returns_409_for_manual_cleanup_sandbox( 97 client: TestClient, 98 auth_headers: dict, 99 monkeypatch, 100 ) -> None: 101 class StubService: 102 @staticmethod 103 def renew_expiration(sandbox_id: str, request) -> RenewSandboxExpirationResponse: 104 raise HTTPException( 105 status_code=409, 106 detail={ 107 "code": "DOCKER::INVALID_EXPIRATION", 108 "message": f"Sandbox {sandbox_id} does not have automatic expiration enabled.", 109 }, 110 ) 111 112 monkeypatch.setattr(lifecycle, "sandbox_service", StubService()) 113 114 response = client.post( 115 "/v1/sandboxes/sbx-manual/renew-expiration", 116 headers=auth_headers, 117 json={"expiresAt": "2030-01-01T00:00:00Z"}, 118 ) 119 120 assert response.status_code == 409 121 assert response.json() == { 122 "code": "DOCKER::INVALID_EXPIRATION", 123 "message": "Sandbox sbx-manual does not have automatic expiration enabled.", 124 } 125 126 127 def test_renew_expiration_requires_api_key(client: TestClient) -> None: 128 response = client.post( 129 "/v1/sandboxes/sbx-001/renew-expiration", 130 json={"expiresAt": "2030-01-01T00:00:00Z"}, 131 ) 132 133 assert response.status_code == 401 134 assert response.json()["code"] == "MISSING_API_KEY"