/ server / tests / test_routes_renew_expiration.py
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"