feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
@@ -26,6 +26,7 @@ from app.test_helpers.db import build_in_memory_session_factory
|
||||
def seed_employee_and_application(db: Session) -> None:
|
||||
employee = Employee(
|
||||
id="emp-linked-draft-fast",
|
||||
tenant_id="tenant-a",
|
||||
employee_no="E10001",
|
||||
name="张三",
|
||||
email="zhangsan@example.com",
|
||||
@@ -34,6 +35,7 @@ def seed_employee_and_application(db: Session) -> None:
|
||||
)
|
||||
application = ExpenseClaim(
|
||||
id="application-linked-draft-fast",
|
||||
tenant_id="tenant-a",
|
||||
claim_no="AP-202606-FAST",
|
||||
employee_id=employee.id,
|
||||
employee_name=employee.name,
|
||||
@@ -76,9 +78,11 @@ def build_client(monkeypatch) -> tuple[TestClient, object]:
|
||||
def test_linked_reimbursement_draft_job_runs_after_conversation_leaves(monkeypatch) -> None:
|
||||
clear_linked_reimbursement_draft_jobs_for_tests()
|
||||
captured_messages = []
|
||||
captured_users = []
|
||||
|
||||
def fake_run(self, payload):
|
||||
def fake_run(self, payload, *, current_user=None):
|
||||
captured_messages.append(payload.message)
|
||||
captured_users.append(current_user)
|
||||
return OrchestratorResponse(
|
||||
run_id="run-linked-draft-job",
|
||||
conversation_id=None,
|
||||
@@ -148,7 +152,13 @@ def test_linked_reimbursement_draft_job_runs_after_conversation_leaves(monkeypat
|
||||
assert payload["status"] == "succeeded"
|
||||
assert payload["draft_payload"]["claim_no"] == "RE-202606-009"
|
||||
assert payload["run_id"] == "run-linked-draft-job"
|
||||
assert captured_messages == ["我要报销\n用户选择报销场景:差旅费\n关联申请单:AP-202606-001"]
|
||||
assert captured_messages == [
|
||||
"我要报销\n用户选择报销场景:差旅费\n关联申请单:AP-202606-001"
|
||||
]
|
||||
assert len(captured_users) == 1
|
||||
assert captured_users[0] is not None
|
||||
assert captured_users[0].username == "zhangsan@example.com"
|
||||
assert captured_users[0].tenant_id == "default"
|
||||
finally:
|
||||
clear_linked_reimbursement_draft_jobs_for_tests()
|
||||
|
||||
@@ -158,9 +168,11 @@ def test_linked_job_overrides_forged_tenant_and_rejects_same_owner_cross_tenant(
|
||||
) -> None:
|
||||
clear_linked_reimbursement_draft_jobs_for_tests()
|
||||
captured_contexts = []
|
||||
captured_users = []
|
||||
|
||||
def fake_run(self, payload):
|
||||
def fake_run(self, payload, *, current_user=None):
|
||||
captured_contexts.append(dict(payload.context_json or {}))
|
||||
captured_users.append(current_user)
|
||||
return OrchestratorResponse(
|
||||
run_id="run-linked-tenant-guard",
|
||||
conversation_id=None,
|
||||
@@ -217,6 +229,10 @@ def test_linked_job_overrides_forged_tenant_and_rejects_same_owner_cross_tenant(
|
||||
"session_type": "expense",
|
||||
}
|
||||
]
|
||||
assert len(captured_users) == 1
|
||||
assert captured_users[0] is not None
|
||||
assert captured_users[0].tenant_id == "tenant-a"
|
||||
assert captured_users[0].tenant_id != "default"
|
||||
job_id = response.json()["job_id"]
|
||||
cross_tenant_response = client.get(
|
||||
f"/api/v1/reimbursements/linked-reimbursement-draft-jobs/{job_id}",
|
||||
@@ -286,6 +302,7 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path(monkeypatch) -> No
|
||||
"x-auth-name": "Zhang San",
|
||||
"x-auth-employee-no": "E10001",
|
||||
"x-auth-role-codes": "user",
|
||||
"x-auth-tenant-id": "tenant-a",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
@@ -345,11 +362,15 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path(monkeypatch) -> No
|
||||
clear_linked_reimbursement_draft_jobs_for_tests()
|
||||
|
||||
|
||||
def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_no_only(monkeypatch) -> None:
|
||||
def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_no_only(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
clear_linked_reimbursement_draft_jobs_for_tests()
|
||||
|
||||
def fail_if_orchestrator_runs(self, payload):
|
||||
raise AssertionError("linked draft job should resolve application no without full orchestrator")
|
||||
raise AssertionError(
|
||||
"linked draft job should resolve application no without full orchestrator"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(OrchestratorService, "run", fail_if_orchestrator_runs)
|
||||
try:
|
||||
@@ -362,6 +383,7 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_n
|
||||
"x-auth-name": "Zhang San",
|
||||
"x-auth-employee-no": "E10001",
|
||||
"x-auth-role-codes": "user",
|
||||
"x-auth-tenant-id": "tenant-a",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
@@ -409,9 +431,7 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_n
|
||||
draft = db.get(ExpenseClaim, payload["draft_payload"]["claim_id"])
|
||||
assert draft is not None
|
||||
link_flag = next(
|
||||
flag
|
||||
for flag in draft.risk_flags_json
|
||||
if flag.get("source") == "application_link"
|
||||
flag for flag in draft.risk_flags_json if flag.get("source") == "application_link"
|
||||
)
|
||||
assert link_flag["application_claim_no"] == "AP-202606-FAST"
|
||||
assert link_flag["application_claim_id"] == "application-linked-draft-fast"
|
||||
|
||||
Reference in New Issue
Block a user