"""Tests for gp_client.py — HMAC auth and API operations.""" import base64 import hashlib import hmac import importlib from unittest.mock import MagicMock, patch import gp_client import pytest # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _expected_signature(password: str, message: str) -> str: sig = hmac.new( bytes(password, "ISO-8859-1"), msg=bytes(message, "ISO-8859-1"), digestmod=hashlib.sha1, ).digest() return base64.b64encode(sig).decode() # --------------------------------------------------------------------------- # get_headers # --------------------------------------------------------------------------- class TestGetHeaders: def test_get_request_authorization_format(self): with patch.dict("os.environ", {"GP_ADMIN_USER_ID": "user123", "GP_ADMIN_PASSWORD": "secret"}): importlib.reload(gp_client) headers = gp_client.get_headers("https://example.com/api", "GET") assert headers["Authorization"].startswith("GP-HMAC user123:") assert "GP-Date" in headers assert headers["accept"] == "application/json" assert "Content-Type" not in headers def test_put_request_includes_content_type(self): with patch.dict("os.environ", {"GP_ADMIN_USER_ID": "user123", "GP_ADMIN_PASSWORD": "secret"}): importlib.reload(gp_client) headers = gp_client.get_headers("https://example.com/api", "PUT", {"key": "val"}) assert headers["Content-Type"] == "application/json" def test_patch_request_uses_merge_patch_content_type(self): with patch.dict("os.environ", {"GP_ADMIN_USER_ID": "user123", "GP_ADMIN_PASSWORD": "secret"}): importlib.reload(gp_client) headers = gp_client.get_headers("https://example.com/api", "PATCH", {}) assert headers["Content-Type"] == "application/merge-patch+json" def test_hmac_signature_is_deterministic_for_same_inputs(self): with patch.dict("os.environ", {"GP_ADMIN_USER_ID": "user123", "GP_ADMIN_PASSWORD": "secret"}): importlib.reload(gp_client) with patch("gp_client.datetime") as mock_dt: mock_dt.now.return_value.strftime.return_value = "Thu, 26 Mar 2026 12:00:00 UTC" h1 = gp_client.get_headers("https://example.com", "GET") h2 = gp_client.get_headers("https://example.com", "GET") assert h1["Authorization"] == h2["Authorization"] # --------------------------------------------------------------------------- # list_bundles # --------------------------------------------------------------------------- class TestListBundles: def test_returns_parsed_json_on_success(self): mock_response = MagicMock() mock_response.json.return_value = {"bundleIds": ["langflow-ui"]} with ( patch.dict("os.environ", {"GP_ADMIN_USER_ID": "u", "GP_ADMIN_PASSWORD": "p"}), patch("gp_client.requests.get", return_value=mock_response) as mock_get, ): importlib.reload(gp_client) result = gp_client.list_bundles() mock_get.assert_called_once() mock_response.raise_for_status.assert_called_once() assert result == {"bundleIds": ["langflow-ui"]} def test_raises_on_http_error(self): import requests as req mock_response = MagicMock() mock_response.raise_for_status.side_effect = req.exceptions.HTTPError("401 Unauthorized") with ( patch.dict("os.environ", {"GP_ADMIN_USER_ID": "u", "GP_ADMIN_PASSWORD": "p"}), patch("gp_client.requests.get", return_value=mock_response), ): importlib.reload(gp_client) with pytest.raises(req.exceptions.HTTPError): gp_client.list_bundles() def test_raises_on_timeout(self): import requests as req with ( patch.dict("os.environ", {"GP_ADMIN_USER_ID": "u", "GP_ADMIN_PASSWORD": "p"}), patch("gp_client.requests.get", side_effect=req.exceptions.Timeout), ): importlib.reload(gp_client) with pytest.raises(req.exceptions.Timeout): gp_client.list_bundles() # --------------------------------------------------------------------------- # upload_strings # --------------------------------------------------------------------------- class TestUploadStrings: def test_successful_upload(self): mock_response = MagicMock() mock_response.json.return_value = {"status": "ok"} with ( patch.dict("os.environ", {"GP_ADMIN_USER_ID": "u", "GP_ADMIN_PASSWORD": "p"}), patch("gp_client.requests.put", return_value=mock_response) as mock_put, ): importlib.reload(gp_client) result = gp_client.upload_strings({"hello": "Hello"}) mock_put.assert_called_once() assert result == {"status": "ok"} def test_raises_on_server_error(self): import requests as req mock_response = MagicMock() mock_response.raise_for_status.side_effect = req.exceptions.HTTPError("500") with ( patch.dict("os.environ", {"GP_ADMIN_USER_ID": "u", "GP_ADMIN_PASSWORD": "p"}), patch("gp_client.requests.put", return_value=mock_response), ): importlib.reload(gp_client) with pytest.raises(req.exceptions.HTTPError): gp_client.upload_strings({"hello": "Hello"}) # --------------------------------------------------------------------------- # get_strings # --------------------------------------------------------------------------- class TestGetStrings: def test_returns_parsed_json(self): mock_response = MagicMock() mock_response.json.return_value = {"resourceStrings": {"hello": {"value": "Bonjour"}}} with ( patch.dict("os.environ", {"GP_ADMIN_USER_ID": "u", "GP_ADMIN_PASSWORD": "p"}), patch("gp_client.requests.get", return_value=mock_response), ): importlib.reload(gp_client) result = gp_client.get_strings("fr") assert result["resourceStrings"]["hello"]["value"] == "Bonjour" def test_raises_on_403(self): import requests as req mock_response = MagicMock() mock_response.raise_for_status.side_effect = req.exceptions.HTTPError("403 Forbidden") with ( patch.dict("os.environ", {"GP_ADMIN_USER_ID": "u", "GP_ADMIN_PASSWORD": "p"}), patch("gp_client.requests.get", return_value=mock_response), ): importlib.reload(gp_client) with pytest.raises(req.exceptions.HTTPError): gp_client.get_strings("fr")