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"