import json import httpx import pytest from langgraph_cli.host_backend import HostBackendClient, HostBackendError @pytest.fixture def mock_transport(): return httpx.MockTransport(lambda req: httpx.Response(200, json={"ok": True})) @pytest.fixture def client(mock_transport): c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=mock_transport, headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) return c def test_constructor_strips_trailing_slash(): c = HostBackendClient("https://api.example.com/", "key") assert str(c._client.base_url) == "https://api.example.com" def test_constructor_empty_url_raises(): with pytest.raises(Exception, match="Host backend URL is required"): HostBackendClient("", "key") def test_request_sends_headers(): def handler(req: httpx.Request) -> httpx.Response: assert req.headers["x-api-key"] == "test-key" assert req.headers["accept"] == "application/json" return httpx.Response(200, json={"ok": True}) c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=httpx.MockTransport(handler), headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) result = c._request("GET", "/test") assert result == {"ok": True} def test_request_sends_json_payload(): def handler(req: httpx.Request) -> httpx.Response: assert req.headers["content-type"] == "application/json" assert req.content == b'{"key":"value"}' return httpx.Response(200, json={"created": True}) c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=httpx.MockTransport(handler), headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) result = c._request("POST", "/test", {"key": "value"}) assert result == {"created": True} def test_request_empty_body_returns_none(): transport = httpx.MockTransport(lambda req: httpx.Response(200, content=b"")) c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=transport, headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) assert c._request("DELETE", "/test") is None def test_request_http_error_raises(): transport = httpx.MockTransport(lambda req: httpx.Response(404, text="not found")) c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=transport, headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) with pytest.raises(HostBackendError, match="404"): c._request("GET", "/missing") def test_request_invalid_json_raises(): transport = httpx.MockTransport( lambda req: httpx.Response(200, content=b"not json") ) c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=transport, headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) with pytest.raises(HostBackendError, match="Failed to decode"): c._request("GET", "/bad-json") def test_request_transport_error_raises(): def handler(req: httpx.Request) -> httpx.Response: raise httpx.ConnectError("connection refused") c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=httpx.MockTransport(handler), headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) with pytest.raises(HostBackendError, match="connection refused"): c._request("GET", "/test") def test_create_deployment(client): result = client.create_deployment( name="my-deploy", deployment_type="dev", source="internal_docker" ) assert result == {"ok": True} def test_get_deployment(client): result = client.get_deployment("dep-123") assert result == {"ok": True} def test_list_deployments(client): result = client.list_deployments("my-app") assert result == {"ok": True} def test_list_deployments_sends_query_params(): def handler(req: httpx.Request) -> httpx.Response: assert req.url.path == "/v2/deployments" assert req.url.params["name_contains"] == "my app" return httpx.Response(200, json={"ok": True}) c = HostBackendClient("https://api.example.com", "test-key") c._client = httpx.Client( base_url="https://api.example.com", transport=httpx.MockTransport(handler), headers={"X-Api-Key": "test-key", "Accept": "application/json"}, timeout=30, ) result = c.list_deployments("my app") assert result == {"ok": True} def test_delete_deployment(client): result = client.delete_deployment("dep-123") assert result == {"ok": True} def test_request_push_token(client): result = client.request_push_token("dep-123") assert result == {"ok": True} def test_update_deployment(client): result = client.update_deployment( "dep-123", "image:latest", secrets=[{"name": "KEY", "value": "val"}] ) assert result == {"ok": True} def test_update_deployment_no_secrets(client): result = client.update_deployment("dep-123", "image:latest") assert result == {"ok": True} def _capturing_client(captured: dict) -> HostBackendClient: def handler(req: httpx.Request) -> httpx.Response: captured["body"] = req.read() return httpx.Response(200, json={"ok": True}) c = HostBackendClient("https://api.example.com", "key") c._client = httpx.Client( base_url="https://api.example.com", transport=httpx.MockTransport(handler), headers={"X-Api-Key": "key", "Accept": "application/json"}, timeout=30, ) return c def test_update_deployment_forwards_tracked_packages(): captured: dict = {} c = _capturing_client(captured) c.update_deployment( "dep-123", "image:latest", tracked_packages=["google-adk:1.0.0"], ) body = json.loads(captured["body"]) assert body["tracked_packages"] == ["google-adk:1.0.0"] assert "tracked_packages" not in body["source_revision_config"] def test_update_deployment_omits_tracked_packages_when_absent(): captured: dict = {} c = _capturing_client(captured) c.update_deployment("dep-123", "image:latest") body = json.loads(captured["body"]) assert "tracked_packages" not in body def test_update_deployment_internal_source_forwards_tracked_packages(): captured: dict = {} c = _capturing_client(captured) c.update_deployment_internal_source( "dep-123", source_tarball_path="path/to/tarball", config_path="langgraph.json", tracked_packages=["google-adk:>=0.5"], ) body = json.loads(captured["body"]) assert body["tracked_packages"] == ["google-adk:>=0.5"] assert body["source_revision_config"]["source_tarball_path"] == "path/to/tarball" assert "tracked_packages" not in body["source_revision_config"] def test_update_deployment_internal_source_omits_tracked_packages_when_absent(): captured: dict = {} c = _capturing_client(captured) c.update_deployment_internal_source( "dep-123", source_tarball_path="path/to/tarball", config_path="langgraph.json", ) body = json.loads(captured["body"]) assert "tracked_packages" not in body def test_list_revisions(client): result = client.list_revisions("dep-123", limit=5) assert result == {"ok": True} def test_get_revision(client): result = client.get_revision("dep-123", "rev-456") assert result == {"ok": True} def test_get_build_logs(client): result = client.get_build_logs("proj-1", "rev-1", {"limit": 10}) assert result == {"ok": True} def test_get_deploy_logs_all_revisions(): def handler(req: httpx.Request) -> httpx.Response: assert "/v1/projects/proj-1/deploy_logs" in str(req.url) assert "/revisions/" not in str(req.url) return httpx.Response(200, json={"logs": [{"message": "running"}]}) c = HostBackendClient("https://api.example.com", "key") c._client = httpx.Client( base_url="https://api.example.com", transport=httpx.MockTransport(handler), headers={"X-Api-Key": "key", "Accept": "application/json"}, timeout=30, ) result = c.get_deploy_logs("proj-1", {"limit": 10}) assert result == {"logs": [{"message": "running"}]} def test_get_deploy_logs_specific_revision(): def handler(req: httpx.Request) -> httpx.Response: assert "/v1/projects/proj-1/revisions/rev-2/deploy_logs" in str(req.url) return httpx.Response(200, json={"logs": []}) c = HostBackendClient("https://api.example.com", "key") c._client = httpx.Client( base_url="https://api.example.com", transport=httpx.MockTransport(handler), headers={"X-Api-Key": "key", "Accept": "application/json"}, timeout=30, ) result = c.get_deploy_logs("proj-1", {"limit": 10}, revision_id="rev-2") assert result == {"logs": []}