4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
675 lines
24 KiB
Python
675 lines
24 KiB
Python
"""Tests for the Argo CD API client."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
import httpx
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from integrations.argocd.client import ArgoCDClient, ArgoCDConfig, make_argocd_client
|
|
|
|
|
|
def _application_payload(name: str = "payments-api") -> dict[str, Any]:
|
|
return {
|
|
"metadata": {"name": name, "namespace": "argocd"},
|
|
"spec": {
|
|
"project": "default",
|
|
"source": {"repoURL": "https://github.com/example/payments", "targetRevision": "main"},
|
|
"destination": {"server": "https://kubernetes.default.svc", "namespace": "payments"},
|
|
},
|
|
"status": {
|
|
"sync": {"status": "OutOfSync", "revision": "abc123"},
|
|
"health": {"status": "Degraded", "message": "Deployment unavailable"},
|
|
"history": [
|
|
{"revision": "old111", "deployedAt": "2026-04-01T00:00:00Z", "id": 1},
|
|
{"revision": "abc123", "deployedAt": "2026-04-02T00:00:00Z", "id": 2},
|
|
],
|
|
"operationState": {"phase": "Succeeded", "syncResult": {"revision": "abc123"}},
|
|
"summary": {"images": ["registry.example.com/payments:abc123"]},
|
|
},
|
|
}
|
|
|
|
|
|
def test_config_normalizes_base_url_bearer_prefix_and_verify_ssl() -> None:
|
|
config = ArgoCDConfig(base_url="https://argocd.example.com/", bearer_token="Bearer tok_test")
|
|
insecure_config = ArgoCDConfig(
|
|
base_url="https://argocd.example.com/", bearer_token="tok_test", verify_ssl="false"
|
|
)
|
|
|
|
assert config.base_url == "https://argocd.example.com"
|
|
assert config.bearer_token == "tok_test"
|
|
assert config.verify_ssl is True
|
|
assert insecure_config.verify_ssl is False
|
|
|
|
|
|
def test_make_argocd_client_requires_base_url_and_auth() -> None:
|
|
assert make_argocd_client("", bearer_token="tok") is None
|
|
assert make_argocd_client("https://argocd.example.com") is None
|
|
assert make_argocd_client("https://argocd.example.com", bearer_token="tok") is not None
|
|
assert (
|
|
make_argocd_client("https://argocd.example.com", username="admin", password="pw")
|
|
is not None
|
|
)
|
|
|
|
|
|
def test_probe_access_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(
|
|
ArgoCDClient,
|
|
"list_applications",
|
|
lambda _self, **_kwargs: {
|
|
"success": True,
|
|
"applications": [{"name": "payments"}],
|
|
"total": 1,
|
|
},
|
|
)
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test")
|
|
)
|
|
|
|
result = client.probe_access()
|
|
|
|
assert result.status == "passed"
|
|
assert "1 application" in result.detail
|
|
|
|
|
|
def test_config_rejects_plain_http_except_loopback() -> None:
|
|
with pytest.raises(ValidationError):
|
|
ArgoCDConfig(base_url="http://argocd.example.com", bearer_token="tok")
|
|
|
|
local = ArgoCDConfig(base_url="http://127.0.0.1:8080", bearer_token="tok")
|
|
assert local.base_url == "http://127.0.0.1:8080"
|
|
|
|
ipv6_local = ArgoCDConfig(base_url="http://[::1]:8080", bearer_token="tok")
|
|
assert ipv6_local.base_url == "http://[::1]:8080"
|
|
assert make_argocd_client("http://argocd.example.com", bearer_token="tok") is None
|
|
|
|
|
|
def test_config_rejects_ambiguous_auth_methods() -> None:
|
|
with pytest.raises(ValidationError, match="one auth method"):
|
|
ArgoCDConfig(
|
|
base_url="https://argocd.example.com",
|
|
bearer_token="tok_test",
|
|
username="admin",
|
|
password="pw",
|
|
)
|
|
|
|
assert (
|
|
make_argocd_client(
|
|
"https://argocd.example.com",
|
|
bearer_token="tok_test",
|
|
username="admin",
|
|
password="pw",
|
|
)
|
|
is None
|
|
)
|
|
|
|
|
|
def test_config_only_strips_bearer_prefix_from_bearer_token() -> None:
|
|
token_config = ArgoCDConfig(
|
|
base_url="https://argocd.example.com",
|
|
bearer_token="Bearer tok_prefixed",
|
|
project="bearer platform",
|
|
app_namespace="bearer namespace",
|
|
integration_id="bearer integration",
|
|
)
|
|
assert token_config.bearer_token == "tok_prefixed"
|
|
assert token_config.project == "bearer platform"
|
|
assert token_config.app_namespace == "bearer namespace"
|
|
assert token_config.integration_id == "bearer integration"
|
|
|
|
login_config = ArgoCDConfig(
|
|
base_url="https://argocd.example.com",
|
|
username="bearer admin",
|
|
password="bearer password",
|
|
)
|
|
assert login_config.username == "bearer admin"
|
|
assert login_config.password == "bearer password"
|
|
|
|
|
|
def test_list_applications_uses_bearer_auth_and_normalizes_status() -> None:
|
|
seen_requests: list[httpx.Request] = []
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
seen_requests.append(request)
|
|
assert request.headers["authorization"] == "Bearer tok_test"
|
|
assert request.url.path == "/api/v1/applications"
|
|
return httpx.Response(200, json={"items": [_application_payload()]}, request=request)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.list_applications(projects=["default"], selector="app=payments")
|
|
|
|
assert result["success"] is True
|
|
assert result["total"] == 1
|
|
app = result["applications"][0]
|
|
assert app["name"] == "payments-api"
|
|
assert app["sync_status"] == "OutOfSync"
|
|
assert app["health_status"] == "Degraded"
|
|
assert app["revision"] == "abc123"
|
|
assert app["history_count"] == 2
|
|
assert "projects=default" in str(seen_requests[0].url)
|
|
assert "selector=app%3Dpayments" in str(seen_requests[0].url)
|
|
|
|
|
|
def test_username_password_login_fetches_session_token_lazily() -> None:
|
|
seen: list[tuple[str, str, str]] = []
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
auth = request.headers.get("authorization", "")
|
|
seen.append((request.method, request.url.path, auth))
|
|
if request.method == "POST" and request.url.path == "/api/v1/session":
|
|
assert json.loads(request.content.decode()) == {"username": "admin", "password": "pw"}
|
|
return httpx.Response(200, json={"token": "session-token"}, request=request)
|
|
assert auth == "Bearer session-token"
|
|
return httpx.Response(200, json={"items": [_application_payload()]}, request=request)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", username="admin", password="pw"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
assert client.list_applications()["success"] is True
|
|
assert seen[0] == ("POST", "/api/v1/session", "")
|
|
assert seen[1][0:2] == ("GET", "/api/v1/applications")
|
|
assert seen[1][2] == "Bearer session-token"
|
|
|
|
|
|
def test_expired_session_token_is_cleared_and_retried_once() -> None:
|
|
seen: list[tuple[str, str, str]] = []
|
|
issued_tokens = iter(["expired-session-token", "fresh-session-token"])
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
auth = request.headers.get("authorization", "")
|
|
seen.append((request.method, request.url.path, auth))
|
|
if request.method == "POST" and request.url.path == "/api/v1/session":
|
|
return httpx.Response(200, json={"token": next(issued_tokens)}, request=request)
|
|
if auth == "Bearer expired-session-token":
|
|
return httpx.Response(401, text="session expired", request=request)
|
|
assert auth == "Bearer fresh-session-token"
|
|
return httpx.Response(200, json=_application_payload(), request=request)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", username="admin", password="pw"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_summary("payments-api")
|
|
|
|
assert result["success"] is True
|
|
assert result["application"]["name"] == "payments-api"
|
|
assert seen == [
|
|
("POST", "/api/v1/session", ""),
|
|
("GET", "/api/v1/applications/payments-api", "Bearer expired-session-token"),
|
|
("POST", "/api/v1/session", ""),
|
|
("GET", "/api/v1/applications/payments-api", "Bearer fresh-session-token"),
|
|
]
|
|
|
|
|
|
def test_expired_session_token_final_401_redacts_retired_tokens() -> None:
|
|
seen: list[tuple[str, str, str]] = []
|
|
issued_tokens = iter(["expired-session-token", "second-expired-session-token"])
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
auth = request.headers.get("authorization", "")
|
|
seen.append((request.method, request.url.path, auth))
|
|
if request.method == "POST" and request.url.path == "/api/v1/session":
|
|
return httpx.Response(200, json={"token": next(issued_tokens)}, request=request)
|
|
return httpx.Response(
|
|
401,
|
|
text=f"session expired for {auth} password leaked-password",
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", username="admin", password="pw"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_summary("payments-api")
|
|
|
|
assert result["success"] is False
|
|
assert "401" in result["error"]
|
|
assert "expired-session-token" not in result["error"]
|
|
assert "second-expired-session-token" not in result["error"]
|
|
assert "leaked-password" not in result["error"]
|
|
assert "[REDACTED]" in result["error"]
|
|
assert [call[0] for call in seen] == ["POST", "GET", "POST", "GET"]
|
|
|
|
|
|
def test_get_application_summary_includes_revision_history_and_operation_state() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
assert request.url.path == "/api/v1/applications/payments-api"
|
|
return httpx.Response(200, json=_application_payload(), request=request)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_summary(
|
|
"payments-api", project="default", app_namespace="argocd"
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["application"]["name"] == "payments-api"
|
|
assert result["application"]["project"] == "default"
|
|
assert result["application"]["operation_phase"] == "Succeeded"
|
|
assert [h["revision"] for h in result["recent_history"]] == ["abc123", "old111"]
|
|
|
|
|
|
def test_get_application_diff_reports_drift_from_server_side_diff() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
assert request.url.path == "/api/v1/applications/payments-api/server-side-diff"
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"diffs": [
|
|
{
|
|
"group": "apps",
|
|
"kind": "Deployment",
|
|
"name": "payments-api",
|
|
"namespace": "payments",
|
|
"diff": "- replicas: 3\n+ replicas: 2",
|
|
}
|
|
]
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-api", project="default", app_namespace="argocd")
|
|
|
|
assert result["success"] is True
|
|
assert result["drift_detected"] is True
|
|
assert result["diff_count"] == 1
|
|
assert result["diffs"][0]["kind"] == "Deployment"
|
|
|
|
|
|
def test_get_application_diff_accepts_argocd_items_response() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
assert request.url.path == "/api/v1/applications/payments-api/server-side-diff"
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"modified": True,
|
|
"items": [
|
|
{
|
|
"group": "apps",
|
|
"kind": "Deployment",
|
|
"name": "payments-api",
|
|
"namespace": "payments",
|
|
"modified": True,
|
|
"diff": "- replicas: 3\n+ replicas: 2",
|
|
}
|
|
],
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-api")
|
|
|
|
assert result["success"] is True
|
|
assert result["drift_detected"] is True
|
|
assert result["diff_count"] == 1
|
|
assert result["diffs"][0]["kind"] == "Deployment"
|
|
|
|
|
|
def test_get_application_diff_falls_back_to_managed_resources_when_server_side_diff_empty() -> None:
|
|
seen_paths: list[str] = []
|
|
target_state = json.dumps(
|
|
{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": {"name": "payments-api", "namespace": "payments"},
|
|
"spec": {"replicas": 1},
|
|
},
|
|
sort_keys=True,
|
|
)
|
|
live_state = json.dumps(
|
|
{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": {"name": "payments-api", "namespace": "payments"},
|
|
"spec": {"replicas": 2},
|
|
},
|
|
sort_keys=True,
|
|
)
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
seen_paths.append(request.url.path)
|
|
if request.url.path.endswith("/server-side-diff"):
|
|
return httpx.Response(200, json={"modified": False}, request=request)
|
|
assert request.url.path.endswith("/managed-resources")
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"items": [
|
|
{
|
|
"group": "apps",
|
|
"kind": "Deployment",
|
|
"name": "payments-api",
|
|
"namespace": "payments",
|
|
"targetState": target_state,
|
|
"normalizedLiveState": live_state,
|
|
}
|
|
]
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-api")
|
|
|
|
assert result["success"] is True
|
|
assert result["drift_detected"] is True
|
|
assert result["diff_count"] == 1
|
|
assert result["diffs"][0]["kind"] == "Deployment"
|
|
assert "replicas" in result["diffs"][0]["diff"]
|
|
assert seen_paths == [
|
|
"/api/v1/applications/payments-api/server-side-diff",
|
|
"/api/v1/applications/payments-api/managed-resources",
|
|
]
|
|
|
|
|
|
def test_get_application_diff_fallback_ignores_equal_managed_resource_states() -> None:
|
|
state = json.dumps(
|
|
{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": {"name": "payments-api", "namespace": "payments"},
|
|
"spec": {"replicas": 1},
|
|
},
|
|
sort_keys=True,
|
|
)
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
if request.url.path.endswith("/server-side-diff"):
|
|
return httpx.Response(200, json={"modified": False}, request=request)
|
|
assert request.url.path.endswith("/managed-resources")
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"items": [
|
|
{
|
|
"group": "apps",
|
|
"kind": "Deployment",
|
|
"name": "payments-api",
|
|
"namespace": "payments",
|
|
"targetState": state,
|
|
"normalizedLiveState": state,
|
|
}
|
|
]
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-api")
|
|
|
|
assert result["success"] is True
|
|
assert result["drift_detected"] is False
|
|
assert result["diff_count"] == 0
|
|
assert result["diffs"] == []
|
|
|
|
|
|
def test_get_application_diff_fallback_skips_non_json_managed_resource_states() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
if request.url.path.endswith("/server-side-diff"):
|
|
return httpx.Response(200, json={"modified": False}, request=request)
|
|
assert request.url.path.endswith("/managed-resources")
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"items": [
|
|
{
|
|
"group": "",
|
|
"kind": "ConfigMap",
|
|
"name": "payments-config",
|
|
"namespace": "payments",
|
|
"targetState": "kind: ConfigMap\nmetadata:\n name: payments-config\n",
|
|
"normalizedLiveState": "kind: ConfigMap\nmetadata: {name: payments-config}\n",
|
|
}
|
|
]
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-config")
|
|
|
|
assert result["success"] is True
|
|
assert result["drift_detected"] is False
|
|
assert result["diff_count"] == 0
|
|
|
|
|
|
def test_get_application_diff_fallback_redacts_sensitive_non_secret_json_state() -> None:
|
|
target_state = json.dumps(
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": {"name": "payments-config", "namespace": "payments"},
|
|
"data": {"clientSecret": "old-super-secret-value"},
|
|
},
|
|
sort_keys=True,
|
|
)
|
|
live_state = json.dumps(
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": {"name": "payments-config", "namespace": "payments"},
|
|
"data": {"clientSecret": "new-super-secret-value"},
|
|
},
|
|
sort_keys=True,
|
|
)
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
if request.url.path.endswith("/server-side-diff"):
|
|
return httpx.Response(200, json={"modified": False}, request=request)
|
|
assert request.url.path.endswith("/managed-resources")
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"items": [
|
|
{
|
|
"group": "",
|
|
"kind": "ConfigMap",
|
|
"name": "payments-config",
|
|
"namespace": "payments",
|
|
"targetState": target_state,
|
|
"normalizedLiveState": live_state,
|
|
}
|
|
]
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-config")
|
|
|
|
assert result["success"] is True
|
|
assert result["drift_detected"] is True
|
|
assert result["diff_count"] == 1
|
|
rendered = result["diffs"][0]["diff"]
|
|
assert "old-super-secret-value" not in rendered
|
|
assert "new-super-secret-value" not in rendered
|
|
assert rendered == "[REDACTED secret-bearing resource diff]"
|
|
|
|
|
|
def test_get_application_diff_redacts_secret_resources_and_token_like_values() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"diffs": [
|
|
{
|
|
"group": "",
|
|
"kind": "Secret",
|
|
"name": "payments-secret",
|
|
"namespace": "payments",
|
|
"diff": "+ opaque: dummy-sensitive-value",
|
|
},
|
|
{
|
|
"group": "",
|
|
"kind": "ConfigMap",
|
|
"name": "payments-config",
|
|
"namespace": "payments",
|
|
"diff": "+ auth: Bearer dummy-sensitive-value",
|
|
},
|
|
]
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-api")
|
|
|
|
assert result["success"] is True
|
|
rendered = "\n".join(diff["diff"] for diff in result["diffs"])
|
|
assert "dummy-sensitive-value" not in rendered
|
|
assert "[REDACTED" in rendered
|
|
|
|
|
|
def test_http_errors_are_returned_without_leaking_tokens() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(403, text="forbidden for token tok_secret", request=request)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_secret"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_summary("payments-api")
|
|
|
|
assert result["success"] is False
|
|
assert "403" in result["error"]
|
|
assert "tok_secret" not in result["error"]
|
|
assert "[REDACTED]" in result["error"]
|
|
|
|
|
|
def test_unauthorized_errors_are_returned_without_leaking_tokens() -> None:
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(
|
|
401,
|
|
text="unauthorized for bearer tok_secret password leaked-password",
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_secret"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_summary("payments-api")
|
|
|
|
assert result["success"] is False
|
|
assert "401" in result["error"]
|
|
assert "tok_secret" not in result["error"]
|
|
assert "leaked-password" not in result["error"]
|
|
assert "[REDACTED]" in result["error"]
|
|
|
|
|
|
def test_large_diffs_are_truncated_after_redaction() -> None:
|
|
large_diff = "+ replicas: 3\n" + ("+ safe-config: value\n" * 700)
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(
|
|
200,
|
|
json={
|
|
"diffs": [
|
|
{
|
|
"group": "apps",
|
|
"kind": "Deployment",
|
|
"name": "payments-api",
|
|
"namespace": "payments",
|
|
"diff": large_diff,
|
|
}
|
|
]
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.get_application_diff("payments-api")
|
|
|
|
assert result["success"] is True
|
|
rendered = result["diffs"][0]["diff"]
|
|
assert len(rendered) < len(large_diff)
|
|
assert "[truncated after 10000 chars]" in rendered
|
|
|
|
|
|
def test_list_applications_omits_empty_project_and_selector_params() -> None:
|
|
seen_requests: list[httpx.Request] = []
|
|
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
seen_requests.append(request)
|
|
return httpx.Response(200, json={"items": []}, request=request)
|
|
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(base_url="https://argocd.example.com", bearer_token="tok_test"),
|
|
transport=httpx.MockTransport(handler),
|
|
)
|
|
|
|
result = client.list_applications(projects=["", " "], selector=" ")
|
|
|
|
assert result["success"] is True
|
|
assert seen_requests[0].url.query == b""
|
|
|
|
|
|
def test_verify_ssl_false_is_passed_to_http_client(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
class FakeClient:
|
|
def __init__(self, **kwargs: Any) -> None:
|
|
captured.update(kwargs)
|
|
|
|
monkeypatch.setattr("integrations.argocd.client.httpx.Client", FakeClient)
|
|
client = ArgoCDClient(
|
|
ArgoCDConfig(
|
|
base_url="https://argocd.example.com",
|
|
bearer_token="tok_test",
|
|
verify_ssl=False,
|
|
)
|
|
)
|
|
|
|
assert client._get_client() is not None
|
|
assert captured["verify"] is False
|