from __future__ import annotations import importlib from fastapi.testclient import TestClient from app.minio_provisioning import default_namespace, policy_json_for_backend def _client(tmp_path, monkeypatch) -> TestClient: monkeypatch.setenv("AUTHZ_DATA_DIR", str(tmp_path)) monkeypatch.setenv("AUTHZ_PRIVATE_KEY_PATH", str(tmp_path / "signing_key.pem")) monkeypatch.setenv("AUTHZ_INTERNAL_TOKEN", "test-internal-token") import app.main as main main = importlib.reload(main) return TestClient(main.app) def _register_backend(client: TestClient) -> None: response = client.post( "/backends/register", json={"backend_id": "alice", "name": "Alice", "base_url": "http://alice.local"}, ) assert response.status_code == 200 def test_minio_settings_round_trip_with_masked_public_read(tmp_path, monkeypatch) -> None: with _client(tmp_path, monkeypatch) as client: _register_backend(client) saved = client.post( "/backends/alice/settings/minio", json={ "endpoint": "minio.local:9000", "access_key": "alice-access", "secret_key": "alice-secret", "bucket": "beaver-user-files", "namespace": "users/alice", "secure": True, "region": "us-east-1", }, ) public = client.get("/backends/alice/settings/minio") internal = client.get( "/internal/backends/alice/settings/minio", headers={"Authorization": "Bearer test-internal-token"}, ) assert saved.status_code == 200 assert saved.json()["configured"] is True assert saved.json()["endpoint"] == "minio.local:9000" assert saved.json()["secret_key_masked"] is True assert "secret_key" not in saved.json() assert public.status_code == 200 assert public.json()["access_key"] == "alice-access" assert public.json()["bucket"] == "beaver-user-files" assert public.json()["namespace"] == "users/alice" assert public.json()["secret_key_masked"] is True assert "secret_key" not in public.json() assert internal.status_code == 200 assert internal.json()["secret_key"] == "alice-secret" assert internal.json()["bucket"] == "beaver-user-files" assert internal.json()["namespace"] == "users/alice" def test_minio_settings_preserve_secret_on_masked_update(tmp_path, monkeypatch) -> None: with _client(tmp_path, monkeypatch) as client: _register_backend(client) first = client.post( "/backends/alice/settings/minio", json={ "endpoint": "minio.local:9000", "access_key": "alice-access", "secret_key": "alice-secret", "bucket": "beaver-user-files", "namespace": "users/alice", }, ) updated = client.post( "/backends/alice/settings/minio", json={ "endpoint": "minio2.local:9000", "access_key": "alice-access-2", "secret_key": "", "bucket": "beaver-user-files", "namespace": "users/alice-v2", }, ) internal = client.get( "/internal/backends/alice/settings/minio", headers={"Authorization": "Bearer test-internal-token"}, ) assert first.status_code == 200 assert updated.status_code == 200 assert updated.json()["endpoint"] == "minio2.local:9000" assert updated.json()["namespace"] == "users/alice-v2" assert internal.status_code == 200 assert internal.json()["secret_key"] == "alice-secret" assert internal.json()["bucket"] == "beaver-user-files" assert internal.json()["namespace"] == "users/alice-v2" def test_minio_settings_delete_and_missing_behavior(tmp_path, monkeypatch) -> None: with _client(tmp_path, monkeypatch) as client: _register_backend(client) missing_public = client.get("/backends/alice/settings/minio") missing_internal = client.get( "/internal/backends/alice/settings/minio", headers={"Authorization": "Bearer test-internal-token"}, ) client.post( "/backends/alice/settings/minio", json={ "endpoint": "minio.local:9000", "access_key": "alice-access", "secret_key": "alice-secret", "bucket": "beaver-user-files", "namespace": "users/alice", }, ) deleted = client.delete("/backends/alice/settings/minio") after_delete = client.get("/backends/alice/settings/minio") assert missing_public.status_code == 200 assert missing_public.json() == {"configured": False} assert missing_internal.status_code == 404 assert deleted.status_code == 200 assert deleted.json() == {"ok": True} assert after_delete.status_code == 200 assert after_delete.json() == {"configured": False} def test_minio_namespace_policy_is_scoped_to_backend_prefix() -> None: policy = policy_json_for_backend(backend_id="alice", bucket="beaver-user-files") assert default_namespace("alice") == "users/alice" assert "arn:aws:s3:::beaver-user-files/users/alice/*" in policy assert "users/bob" not in policy