/ server / tests / test_routes_get_sandbox.py
test_routes_get_sandbox.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 ImageSpec, Sandbox, SandboxStatus
 22  
 23  
 24  def test_get_sandbox_returns_service_payload(
 25      client: TestClient,
 26      auth_headers: dict,
 27      monkeypatch,
 28  ) -> None:
 29      now = datetime.now(timezone.utc)
 30  
 31      class StubService:
 32          @staticmethod
 33          def get_sandbox(sandbox_id: str) -> Sandbox:
 34              assert sandbox_id == "sbx-001"
 35              return Sandbox(
 36                  id=sandbox_id,
 37                  image=ImageSpec(uri="python:3.11"),
 38                  status=SandboxStatus(state="Running"),
 39                  metadata={"team": "infra"},
 40                  entrypoint=["python", "-V"],
 41                  expiresAt=now + timedelta(hours=1),
 42                  createdAt=now,
 43              )
 44  
 45      monkeypatch.setattr(lifecycle, "sandbox_service", StubService())
 46  
 47      response = client.get("/v1/sandboxes/sbx-001", headers=auth_headers)
 48  
 49      assert response.status_code == 200
 50      payload = response.json()
 51      assert payload["id"] == "sbx-001"
 52      assert payload["status"]["state"] == "Running"
 53      assert payload["image"]["uri"] == "python:3.11"
 54  
 55  
 56  def test_get_sandbox_propagates_not_found(
 57      client: TestClient,
 58      auth_headers: dict,
 59      monkeypatch,
 60  ) -> None:
 61      class StubService:
 62          @staticmethod
 63          def get_sandbox(sandbox_id: str) -> Sandbox:
 64              raise HTTPException(
 65                  status_code=404,
 66                  detail={
 67                      "code": "SANDBOX_NOT_FOUND",
 68                      "message": f"Sandbox {sandbox_id} not found",
 69                  },
 70              )
 71  
 72      monkeypatch.setattr(lifecycle, "sandbox_service", StubService())
 73  
 74      response = client.get("/v1/sandboxes/missing", headers=auth_headers)
 75  
 76      assert response.status_code == 404
 77      assert response.json() == {
 78          "code": "SANDBOX_NOT_FOUND",
 79          "message": "Sandbox missing not found",
 80      }
 81  
 82  
 83  def test_get_sandbox_omits_none_fields(
 84      client: TestClient,
 85      auth_headers: dict,
 86      monkeypatch,
 87  ) -> None:
 88      now = datetime.now(timezone.utc)
 89  
 90      class StubService:
 91          @staticmethod
 92          def get_sandbox(sandbox_id: str) -> Sandbox:
 93              return Sandbox(
 94                  id=sandbox_id,
 95                  image=ImageSpec(uri="python:3.11"),
 96                  status=SandboxStatus(state="Running"),
 97                  metadata=None,
 98                  entrypoint=["python", "-V"],
 99                  expiresAt=None,
100                  createdAt=now,
101              )
102  
103      monkeypatch.setattr(lifecycle, "sandbox_service", StubService())
104  
105      response = client.get("/v1/sandboxes/sbx-manual", headers=auth_headers)
106  
107      assert response.status_code == 200
108      payload = response.json()
109      assert "expiresAt" not in payload
110      assert "metadata" not in payload
111      assert "reason" not in payload["status"]
112      assert "message" not in payload["status"]
113      assert "lastTransitionAt" not in payload["status"]
114  
115  
116  def test_get_sandbox_requires_api_key(client: TestClient) -> None:
117      response = client.get("/v1/sandboxes/sbx-001")
118  
119      assert response.status_code == 401
120      assert response.json()["code"] == "MISSING_API_KEY"