531 lines
17 KiB
Python
531 lines
17 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from collections.abc import Generator
|
||
|
|
from datetime import UTC, datetime, timedelta
|
||
|
|
from decimal import Decimal
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from auth_helpers import install_legacy_header_auth_override
|
||
|
|
from fastapi import FastAPI, HTTPException
|
||
|
|
from fastapi.testclient import TestClient
|
||
|
|
from sqlalchemy import create_engine, select
|
||
|
|
from sqlalchemy.orm import Session, sessionmaker
|
||
|
|
from sqlalchemy.pool import StaticPool
|
||
|
|
|
||
|
|
from app.api.deps import CurrentUserContext, get_db
|
||
|
|
from app.api.v1.endpoints.agent_runs import router as agent_runs_router
|
||
|
|
from app.api.v1.endpoints.analytics import router as analytics_router
|
||
|
|
from app.db.base import Base
|
||
|
|
from app.models.agent_run import AgentRun, AgentToolCall
|
||
|
|
from app.models.budget import BudgetAllocation
|
||
|
|
from app.models.expense_case import ExpenseCase, ExpenseCaseLink
|
||
|
|
from app.models.financial_record import ExpenseClaim
|
||
|
|
from app.services.finance_dashboard import FinanceDashboardService
|
||
|
|
from app.services.finance_dashboard_access_policy import FinanceDashboardAccessPolicy
|
||
|
|
from app.services.finance_dashboard_scope import resolve_finance_dashboard_data_scope
|
||
|
|
from app.services.finance_dashboard_snapshot import (
|
||
|
|
FINANCE_DASHBOARD_TASK_TYPE,
|
||
|
|
FinanceDashboardSnapshotService,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _session_factory() -> sessionmaker[Session]:
|
||
|
|
engine = create_engine(
|
||
|
|
"sqlite+pysqlite:///:memory:",
|
||
|
|
connect_args={"check_same_thread": False},
|
||
|
|
poolclass=StaticPool,
|
||
|
|
)
|
||
|
|
Base.metadata.create_all(bind=engine)
|
||
|
|
return sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
||
|
|
|
||
|
|
|
||
|
|
def _claim(
|
||
|
|
*,
|
||
|
|
claim_id: str,
|
||
|
|
claim_no: str,
|
||
|
|
amount: str,
|
||
|
|
tenant_id: str = "default",
|
||
|
|
) -> ExpenseClaim:
|
||
|
|
now = datetime.now(UTC)
|
||
|
|
return ExpenseClaim(
|
||
|
|
id=claim_id,
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
claim_no=claim_no,
|
||
|
|
employee_name=f"employee-{claim_id}",
|
||
|
|
department_name="财务部",
|
||
|
|
expense_type="travel",
|
||
|
|
reason="tenant scope test",
|
||
|
|
location="上海",
|
||
|
|
amount=Decimal(amount),
|
||
|
|
invoice_count=1,
|
||
|
|
occurred_at=now - timedelta(hours=2),
|
||
|
|
submitted_at=now - timedelta(hours=1),
|
||
|
|
status="paid",
|
||
|
|
approval_stage="payment",
|
||
|
|
risk_flags_json=[],
|
||
|
|
hermes_risk_flag=False,
|
||
|
|
created_at=now - timedelta(hours=2),
|
||
|
|
updated_at=now - timedelta(minutes=30),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _link_claim(db: Session, claim: ExpenseClaim, *, tenant_id: str) -> None:
|
||
|
|
expense_case = ExpenseCase(
|
||
|
|
id=f"case-{claim.id}",
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
case_no=f"CASE-{claim.claim_no}",
|
||
|
|
scene_code="travel",
|
||
|
|
title=f"{claim.claim_no} case",
|
||
|
|
current_stage="completed",
|
||
|
|
status="completed",
|
||
|
|
)
|
||
|
|
db.add(expense_case)
|
||
|
|
db.flush()
|
||
|
|
db.add(
|
||
|
|
ExpenseCaseLink(
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
expense_case_id=expense_case.id,
|
||
|
|
resource_type="expense_claim",
|
||
|
|
resource_id=claim.id,
|
||
|
|
relation_type="reimbursement",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _seed_tenant_claim(
|
||
|
|
db: Session,
|
||
|
|
*,
|
||
|
|
tenant_id: str,
|
||
|
|
claim_id: str,
|
||
|
|
claim_no: str,
|
||
|
|
amount: str,
|
||
|
|
) -> ExpenseClaim:
|
||
|
|
claim = _claim(
|
||
|
|
claim_id=claim_id,
|
||
|
|
claim_no=claim_no,
|
||
|
|
amount=amount,
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
)
|
||
|
|
db.add(claim)
|
||
|
|
db.flush()
|
||
|
|
_link_claim(db, claim, tenant_id=tenant_id)
|
||
|
|
return claim
|
||
|
|
|
||
|
|
|
||
|
|
def _seed_finance_snapshot_run(
|
||
|
|
db: Session,
|
||
|
|
*,
|
||
|
|
run_id: str,
|
||
|
|
tenant_id: str | None,
|
||
|
|
amount: str,
|
||
|
|
route_data_scope: str | None = None,
|
||
|
|
) -> None:
|
||
|
|
route_json = {
|
||
|
|
"task_type": FINANCE_DASHBOARD_TASK_TYPE,
|
||
|
|
"snapshot_key": f"snapshot-{run_id}",
|
||
|
|
"snapshot_payload": {
|
||
|
|
"tenant_marker": tenant_id or "legacy-unscoped",
|
||
|
|
"amount": amount,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
ontology_json = {"scenario": "finance_dashboard"}
|
||
|
|
if tenant_id is not None:
|
||
|
|
expected_scope = resolve_finance_dashboard_data_scope(tenant_id)
|
||
|
|
route_json["tenant_id"] = tenant_id
|
||
|
|
route_json["data_scope"] = route_data_scope or expected_scope
|
||
|
|
ontology_json["tenant_id"] = tenant_id
|
||
|
|
ontology_json["data_scope"] = expected_scope
|
||
|
|
|
||
|
|
db.add(
|
||
|
|
AgentRun(
|
||
|
|
run_id=run_id,
|
||
|
|
agent="hermes",
|
||
|
|
source="system_event",
|
||
|
|
user_id="digital_employee",
|
||
|
|
ontology_json=ontology_json,
|
||
|
|
route_json=route_json,
|
||
|
|
permission_level="read",
|
||
|
|
status="succeeded",
|
||
|
|
result_summary=f"sensitive amount {amount}",
|
||
|
|
started_at=datetime.now(UTC),
|
||
|
|
finished_at=datetime.now(UTC),
|
||
|
|
)
|
||
|
|
)
|
||
|
|
db.flush()
|
||
|
|
db.add(
|
||
|
|
AgentToolCall(
|
||
|
|
run_id=run_id,
|
||
|
|
tool_type="database",
|
||
|
|
tool_name="digital_employee.finance_dashboard.snapshot",
|
||
|
|
request_json={"tenant_id": tenant_id, "amount": amount},
|
||
|
|
response_json={"secret_amount": amount},
|
||
|
|
status="succeeded",
|
||
|
|
duration_ms=1,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_finance_dashboard_isolates_claims_and_hides_legacy_budget_outside_default() -> None:
|
||
|
|
now = datetime.now(UTC)
|
||
|
|
session_factory = _session_factory()
|
||
|
|
|
||
|
|
with session_factory() as db:
|
||
|
|
db.add(_claim(claim_id="legacy-default", claim_no="CLM-LEGACY-001", amount="100.00"))
|
||
|
|
_seed_tenant_claim(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
claim_id="claim-a",
|
||
|
|
claim_no="CLM-TENANT-A-001",
|
||
|
|
amount="200.00",
|
||
|
|
)
|
||
|
|
_seed_tenant_claim(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-b",
|
||
|
|
claim_id="claim-b",
|
||
|
|
claim_no="CLM-TENANT-B-001",
|
||
|
|
amount="300.00",
|
||
|
|
)
|
||
|
|
db.add(
|
||
|
|
BudgetAllocation(
|
||
|
|
budget_no="BUD-LEGACY-DEFAULT-001",
|
||
|
|
fiscal_year=now.year,
|
||
|
|
period_type="year",
|
||
|
|
period_key=str(now.year),
|
||
|
|
department_name="财务部",
|
||
|
|
subject_code="travel",
|
||
|
|
subject_name="差旅费",
|
||
|
|
original_amount=Decimal("10000.00"),
|
||
|
|
adjusted_amount=Decimal("0.00"),
|
||
|
|
status="active",
|
||
|
|
warning_threshold=Decimal("80.00"),
|
||
|
|
control_action="warn",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
db.commit()
|
||
|
|
|
||
|
|
default_dashboard = FinanceDashboardService(db).build_dashboard()
|
||
|
|
tenant_a_dashboard = FinanceDashboardService(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
).build_dashboard()
|
||
|
|
tenant_b_dashboard = FinanceDashboardService(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-b",
|
||
|
|
).build_dashboard()
|
||
|
|
|
||
|
|
assert default_dashboard.totals["reimbursementCount"] == 1
|
||
|
|
assert default_dashboard.totals["reimbursementAmount"] == 100.0
|
||
|
|
assert default_dashboard.budget_summary["included"] is True
|
||
|
|
assert default_dashboard.budget_summary["total"] == "¥10,000"
|
||
|
|
|
||
|
|
assert tenant_a_dashboard.totals["reimbursementCount"] == 1
|
||
|
|
assert tenant_a_dashboard.totals["reimbursementAmount"] == 200.0
|
||
|
|
assert "CLM-TENANT-B-001" not in str(tenant_a_dashboard.top_claims)
|
||
|
|
assert tenant_a_dashboard.budget_summary == {
|
||
|
|
"ratio": 0.0,
|
||
|
|
"total": "¥0",
|
||
|
|
"used": "¥0",
|
||
|
|
"left": "¥0",
|
||
|
|
"included": False,
|
||
|
|
"scope": "unavailable",
|
||
|
|
"reason": "当前租户尚未接入独立预算池,预算指标未纳入统计。",
|
||
|
|
}
|
||
|
|
assert tenant_a_dashboard.totals["budgetUsageRate"] == 0.0
|
||
|
|
assert all(
|
||
|
|
metric["detail"] == "当前租户未接入独立预算池" and metric["tone"] == "neutral"
|
||
|
|
for metric in tenant_a_dashboard.budget_metrics
|
||
|
|
)
|
||
|
|
assert "预算超支" not in {item["name"] for item in tenant_a_dashboard.bottlenecks}
|
||
|
|
|
||
|
|
assert tenant_b_dashboard.totals["reimbursementCount"] == 1
|
||
|
|
assert tenant_b_dashboard.totals["reimbursementAmount"] == 300.0
|
||
|
|
assert tenant_b_dashboard.budget_summary["included"] is False
|
||
|
|
|
||
|
|
|
||
|
|
def test_finance_dashboard_uses_structured_claim_tenant_when_legacy_link_disagrees() -> None:
|
||
|
|
session_factory = _session_factory()
|
||
|
|
|
||
|
|
with session_factory() as db:
|
||
|
|
claim = _claim(
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
claim_id="structured-tenant-claim",
|
||
|
|
claim_no="CLM-STRUCTURED-TENANT-001",
|
||
|
|
amount="456.00",
|
||
|
|
)
|
||
|
|
db.add(claim)
|
||
|
|
db.flush()
|
||
|
|
# Case Link 是历史关联索引,不能覆盖 Claim 自身的结构化租户归属。
|
||
|
|
_link_claim(db, claim, tenant_id="tenant-b")
|
||
|
|
db.commit()
|
||
|
|
|
||
|
|
tenant_a_dashboard = FinanceDashboardService(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
).build_dashboard()
|
||
|
|
tenant_b_dashboard = FinanceDashboardService(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-b",
|
||
|
|
).build_dashboard()
|
||
|
|
|
||
|
|
assert tenant_a_dashboard.totals["reimbursementCount"] == 1
|
||
|
|
assert tenant_a_dashboard.totals["reimbursementAmount"] == 456.0
|
||
|
|
assert tenant_b_dashboard.totals["reimbursementCount"] == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_finance_dashboard_snapshot_cache_is_partitioned_by_tenant_and_data_scope() -> None:
|
||
|
|
session_factory = _session_factory()
|
||
|
|
|
||
|
|
with session_factory() as db:
|
||
|
|
_seed_tenant_claim(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
claim_id="snapshot-claim-a",
|
||
|
|
claim_no="CLM-SNAPSHOT-A-001",
|
||
|
|
amount="880.00",
|
||
|
|
)
|
||
|
|
_seed_tenant_claim(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-b",
|
||
|
|
claim_id="snapshot-claim-b",
|
||
|
|
claim_no="CLM-SNAPSHOT-B-001",
|
||
|
|
amount="990.00",
|
||
|
|
)
|
||
|
|
db.commit()
|
||
|
|
|
||
|
|
tenant_a_service = FinanceDashboardSnapshotService(db, tenant_id="tenant-a")
|
||
|
|
tenant_b_service = FinanceDashboardSnapshotService(db, tenant_id="tenant-b")
|
||
|
|
first_a = tenant_a_service.build_dashboard()
|
||
|
|
first_b = tenant_b_service.build_dashboard()
|
||
|
|
second_a = tenant_a_service.build_dashboard()
|
||
|
|
|
||
|
|
runs = list(
|
||
|
|
db.scalars(
|
||
|
|
select(AgentRun).where(
|
||
|
|
AgentRun.route_json["task_type"].as_string() == FINANCE_DASHBOARD_TASK_TYPE
|
||
|
|
)
|
||
|
|
).all()
|
||
|
|
)
|
||
|
|
routes = [run.route_json or {} for run in runs]
|
||
|
|
|
||
|
|
assert first_a.totals["reimbursementAmount"] == 880.0
|
||
|
|
assert first_b.totals["reimbursementAmount"] == 990.0
|
||
|
|
assert second_a.generated_at == first_a.generated_at
|
||
|
|
assert len(runs) == 2
|
||
|
|
assert {route["tenant_id"] for route in routes} == {"tenant-a", "tenant-b"}
|
||
|
|
assert all(route["data_scope"] for route in routes)
|
||
|
|
assert len({route["snapshot_key"] for route in routes}) == 2
|
||
|
|
assert all(route["tenant_id"] in route["snapshot_key"] for route in routes)
|
||
|
|
|
||
|
|
|
||
|
|
def test_default_scheduler_snapshot_rejects_non_default_tenant_scope() -> None:
|
||
|
|
session_factory = _session_factory()
|
||
|
|
|
||
|
|
with session_factory() as db:
|
||
|
|
service = FinanceDashboardSnapshotService(db, tenant_id="tenant-a")
|
||
|
|
with pytest.raises(ValueError, match="default 系统租户"):
|
||
|
|
service.refresh_default_snapshot()
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("tenant_id", ["", " "])
|
||
|
|
def test_finance_dashboard_access_policy_rejects_missing_tenant_scope(
|
||
|
|
tenant_id: str,
|
||
|
|
) -> None:
|
||
|
|
current_user = CurrentUserContext(
|
||
|
|
username="finance-without-tenant",
|
||
|
|
name="Finance Without Tenant",
|
||
|
|
role_codes=["finance"],
|
||
|
|
is_admin=False,
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
)
|
||
|
|
|
||
|
|
assert FinanceDashboardAccessPolicy.can_read(current_user) is False
|
||
|
|
with pytest.raises(HTTPException) as exc_info:
|
||
|
|
FinanceDashboardAccessPolicy.require_read(current_user)
|
||
|
|
assert exc_info.value.status_code == 403
|
||
|
|
|
||
|
|
|
||
|
|
def test_finance_dashboard_endpoint_requires_finance_read_role_and_passes_tenant() -> None:
|
||
|
|
session_factory = _session_factory()
|
||
|
|
with session_factory() as db:
|
||
|
|
_seed_tenant_claim(
|
||
|
|
db,
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
claim_id="endpoint-claim-a",
|
||
|
|
claim_no="CLM-ENDPOINT-A-001",
|
||
|
|
amount="1234.00",
|
||
|
|
)
|
||
|
|
db.commit()
|
||
|
|
|
||
|
|
app = FastAPI()
|
||
|
|
app.include_router(analytics_router)
|
||
|
|
install_legacy_header_auth_override(app)
|
||
|
|
|
||
|
|
def override_db() -> Generator[Session, None, None]:
|
||
|
|
with session_factory() as db:
|
||
|
|
yield db
|
||
|
|
|
||
|
|
app.dependency_overrides[get_db] = override_db
|
||
|
|
client = TestClient(app)
|
||
|
|
|
||
|
|
ordinary_response = client.get(
|
||
|
|
"/analytics/finance-dashboard",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "ordinary",
|
||
|
|
"X-Auth-Role-Codes": "user",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-a",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
budget_monitor_response = client.get(
|
||
|
|
"/analytics/finance-dashboard",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "budget-monitor",
|
||
|
|
"X-Auth-Role-Codes": "budget_monitor",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-a",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
finance_response = client.get(
|
||
|
|
"/analytics/finance-dashboard",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "finance",
|
||
|
|
"X-Auth-Role-Codes": "finance",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-a",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
executive_other_tenant_response = client.get(
|
||
|
|
"/analytics/finance-dashboard",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "executive",
|
||
|
|
"X-Auth-Role-Codes": "executive",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-b",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
admin_response = client.get(
|
||
|
|
"/analytics/finance-dashboard",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "admin-reader",
|
||
|
|
"X-Auth-Is-Admin": "true",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-b",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
assert ordinary_response.status_code == 403
|
||
|
|
assert budget_monitor_response.status_code == 403
|
||
|
|
assert finance_response.status_code == 200
|
||
|
|
assert finance_response.json()["totals"]["reimbursementAmount"] == 1234.0
|
||
|
|
assert executive_other_tenant_response.status_code == 200
|
||
|
|
assert executive_other_tenant_response.json()["totals"]["reimbursementCount"] == 0
|
||
|
|
assert admin_response.status_code == 200
|
||
|
|
|
||
|
|
|
||
|
|
def test_agent_run_endpoint_cannot_bypass_finance_snapshot_tenant_and_role_scope() -> None:
|
||
|
|
session_factory = _session_factory()
|
||
|
|
with session_factory() as db:
|
||
|
|
_seed_finance_snapshot_run(
|
||
|
|
db,
|
||
|
|
run_id="run-finance-tenant-a",
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
amount="111.00",
|
||
|
|
)
|
||
|
|
_seed_finance_snapshot_run(
|
||
|
|
db,
|
||
|
|
run_id="run-finance-tenant-b",
|
||
|
|
tenant_id="tenant-b",
|
||
|
|
amount="222.00",
|
||
|
|
)
|
||
|
|
_seed_finance_snapshot_run(
|
||
|
|
db,
|
||
|
|
run_id="run-finance-legacy-unscoped",
|
||
|
|
tenant_id=None,
|
||
|
|
amount="999.00",
|
||
|
|
)
|
||
|
|
_seed_finance_snapshot_run(
|
||
|
|
db,
|
||
|
|
run_id="run-finance-corrupt-scope",
|
||
|
|
tenant_id="tenant-a",
|
||
|
|
amount="777.00",
|
||
|
|
route_data_scope="claims:corrupt;budget:corrupt",
|
||
|
|
)
|
||
|
|
db.commit()
|
||
|
|
|
||
|
|
app = FastAPI()
|
||
|
|
app.include_router(agent_runs_router)
|
||
|
|
install_legacy_header_auth_override(app)
|
||
|
|
|
||
|
|
def override_db() -> Generator[Session, None, None]:
|
||
|
|
with session_factory() as db:
|
||
|
|
yield db
|
||
|
|
|
||
|
|
app.dependency_overrides[get_db] = override_db
|
||
|
|
client = TestClient(app)
|
||
|
|
finance_a_headers = {
|
||
|
|
"X-Auth-Username": "finance-a",
|
||
|
|
"X-Auth-Role-Codes": "finance",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-a",
|
||
|
|
}
|
||
|
|
finance_b_headers = {
|
||
|
|
"X-Auth-Username": "finance-b",
|
||
|
|
"X-Auth-Role-Codes": "finance",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-b",
|
||
|
|
}
|
||
|
|
|
||
|
|
finance_a_list = client.get("/agent-runs", headers=finance_a_headers)
|
||
|
|
ordinary_a_list = client.get(
|
||
|
|
"/agent-runs",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "ordinary-a",
|
||
|
|
"X-Auth-Role-Codes": "user",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-a",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
assert finance_a_list.status_code == 200
|
||
|
|
assert [item["run_id"] for item in finance_a_list.json()] == ["run-finance-tenant-a"]
|
||
|
|
assert ordinary_a_list.status_code == 200
|
||
|
|
assert ordinary_a_list.json() == []
|
||
|
|
|
||
|
|
allowed_detail = client.get(
|
||
|
|
"/agent-runs/run-finance-tenant-a",
|
||
|
|
headers=finance_a_headers,
|
||
|
|
)
|
||
|
|
assert allowed_detail.status_code == 200
|
||
|
|
assert allowed_detail.json()["route_json"]["snapshot_payload"]["amount"] == "111.00"
|
||
|
|
assert allowed_detail.json()["tool_calls"][0]["response_json"] == {"secret_amount": "111.00"}
|
||
|
|
|
||
|
|
cross_tenant_detail = client.get(
|
||
|
|
"/agent-runs/run-finance-tenant-a",
|
||
|
|
headers=finance_b_headers,
|
||
|
|
)
|
||
|
|
cross_tenant_admin_detail = client.get(
|
||
|
|
"/agent-runs/run-finance-tenant-a",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "admin-b",
|
||
|
|
"X-Auth-Is-Admin": "true",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-b",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
same_tenant_ordinary_detail = client.get(
|
||
|
|
"/agent-runs/run-finance-tenant-a",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "ordinary-a",
|
||
|
|
"X-Auth-Role-Codes": "user",
|
||
|
|
"X-Auth-Tenant-Id": "tenant-a",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
legacy_unscoped_detail = client.get(
|
||
|
|
"/agent-runs/run-finance-legacy-unscoped",
|
||
|
|
headers={
|
||
|
|
"X-Auth-Username": "finance-default",
|
||
|
|
"X-Auth-Role-Codes": "finance",
|
||
|
|
"X-Auth-Tenant-Id": "default",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
corrupt_scope_detail = client.get(
|
||
|
|
"/agent-runs/run-finance-corrupt-scope",
|
||
|
|
headers=finance_a_headers,
|
||
|
|
)
|
||
|
|
|
||
|
|
assert cross_tenant_detail.status_code == 404
|
||
|
|
assert cross_tenant_admin_detail.status_code == 404
|
||
|
|
assert same_tenant_ordinary_detail.status_code == 403
|
||
|
|
assert legacy_unscoped_detail.status_code == 404
|
||
|
|
assert corrupt_scope_detail.status_code == 404
|