FluxIP/tests/test_integration_secret_updates.py

142 lines
4.7 KiB
Python

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)