from __future__ import annotations from typing import Any from fastapi.testclient import TestClient from tests.conftest import csrf_headers def put_credentials( client: TestClient, payload: dict[str, object], ) -> dict[str, Any]: response = client.put( "/api/v1/integrations", json=payload, headers=csrf_headers(client), ) assert response.status_code == 200, response.text return response.json()["data"] def create_instance( client: TestClient, payload: dict[str, object], ) -> dict[str, Any]: response = client.post( "/api/v1/instances", json=payload, headers=csrf_headers(client), ) assert response.status_code == 201, response.text return response.json()["data"] def test_credentials_api_hides_secrets_and_replacing_pair_clears_session_token( authenticated_client: TestClient, integration_payload: dict[str, object], ) -> None: client = authenticated_client assert client.put("/api/v1/integrations", json=integration_payload).status_code == 403 initial = put_credentials(client, integration_payload) assert initial["secrets_configured"] == { "aws_access_key_id": True, "aws_secret_access_key": True, "aws_session_token": True, "cloudflare_api_token": True, } assert "aws-secret-value" not in str(initial) replacement = put_credentials( client, { "use_default_aws_credentials": False, "aws_access_key_id": "AKIA_REPLACEMENT_ACCESS_KEY", "aws_secret_access_key": "replacement-secret-value", "aws_session_token": None, "cloudflare_api_token": None, }, ) assert replacement["config_version"] == initial["config_version"] + 1 assert replacement["secrets_configured"]["aws_session_token"] is False repository = client.app.state.container.integration_repository secrets = repository.get_secrets() assert secrets == { "aws_access_key_id": "AKIA_REPLACEMENT_ACCESS_KEY", "aws_secret_access_key": "replacement-secret-value", "aws_session_token": None, "cloudflare_api_token": "cloudflare-secret-value", } fetched = client.get("/api/v1/integrations") assert fetched.status_code == 200 assert fetched.json()["data"] == replacement assert not any(secret in fetched.text for secret in secrets.values() if secret) def test_active_run_blocks_credentials_but_attention_allows_secret_only_audit( authenticated_client: TestClient, integration_payload: dict[str, object], managed_instance_payload: dict[str, object], monkeypatch, ) -> None: client = authenticated_client initial = put_credentials(client, integration_payload) instance = create_instance(client, managed_instance_payload) rotation_service = client.app.state.container.rotation_service monkeypatch.setattr(rotation_service, "_spawn", lambda _run_id: None) started = client.post( f"/api/v1/instances/{instance['id']}/rotations", headers=csrf_headers(client), ) assert started.status_code == 202 run_id = started.json()["data"]["id"] blocked = client.put( "/api/v1/integrations", json={ "use_default_aws_credentials": False, "cloudflare_api_token": "blocked-secret", }, headers=csrf_headers(client), ) assert blocked.status_code == 409 assert blocked.json()["type"] == "about:blank#rotation_in_progress" repository = client.app.state.container.rotation_repository assert repository.acquire_lease(run_id, "test-worker") is True repository.transition_run(run_id, status="running", expected_owner="test-worker") item = repository.current_item(run_id) assert item is not None repository.fail_execution( run_id, item.id, error_code="CLOUDFLARE_TOKEN_EXPIRED", error_message="token expired", outcome="needs_attention", expected_owner="test-worker", ) replacement_token = "recovery-cloudflare-secret" recovered = client.put( "/api/v1/integrations", json={ "use_default_aws_credentials": False, "cloudflare_api_token": replacement_token, }, headers=csrf_headers(client), ) assert recovered.status_code == 200, recovered.text assert recovered.json()["data"]["config_version"] == initial["config_version"] assert ( client.app.state.container.integration_repository.get_secret("cloudflare_api_token") == replacement_token ) events = repository.list_events(run_id) assert events[-1]["message"] == "管理员已更新恢复凭据" assert events[-1]["details"] == {"updated_credentials": ["cloudflare_api_token"]} assert replacement_token not in str(events)