- Added new modules for advisor compliance, KYC sessions, and script templates, enhancing the advisor agent's capabilities. - Implemented a comprehensive API structure under the `/api/advisor-agent` prefix, ensuring clear organization and access to new features. - Established database models and repositories for compliance rules and KYC sessions, facilitating robust data management. - Integrated exception handling and response models to improve error management and user feedback. - Updated settings to include new configurations for compliance and KYC features, ensuring flexibility and adaptability. This update significantly expands the advisor agent's functionality, providing essential tools for compliance and customer interaction while maintaining a structured API design.
146 lines
5.0 KiB
Python
146 lines
5.0 KiB
Python
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
from fastapi.testclient import TestClient
|
|
from app.main import app
|
|
|
|
client = TestClient(app)
|
|
|
|
from tests.advisor_test_utils import login_staff_token, STAFF_ADVISOR, STAFF_COMPLIANCE
|
|
|
|
def login_token(username: str, password: str) -> str:
|
|
_ = password
|
|
actor = STAFF_COMPLIANCE if username == "compliance_test" else STAFF_ADVISOR
|
|
return login_staff_token(client, actor_id=actor)
|
|
|
|
def test_compliance_rule_migration_sql_defines_core_tables():
|
|
migration = (
|
|
Path(__file__).resolve().parents[1]
|
|
/ "scripts"
|
|
/ "agent"
|
|
/ "migrate-advisor-agent-sprint1-3.sql"
|
|
)
|
|
text = migration.read_text(encoding="utf-8")
|
|
for table in ("compliance_rule", "compliance_check_log"):
|
|
assert f"CREATE TABLE IF NOT EXISTS {table}" in text
|
|
for column in (
|
|
"rule_code",
|
|
"rule_type",
|
|
"pattern",
|
|
"severity",
|
|
"category",
|
|
"suggestion",
|
|
"is_active",
|
|
"priority",
|
|
"created_by",
|
|
"updated_by",
|
|
"check_id",
|
|
"trace_id",
|
|
"advisor_id",
|
|
"input_text",
|
|
"risk_level",
|
|
"hit_details",
|
|
"ai_analysis",
|
|
"ai_degraded",
|
|
):
|
|
assert column in text
|
|
|
|
def test_compliance_rule_service_creates_updates_lists_and_soft_deletes_rule():
|
|
from app.model.advisor_schemas import ComplianceRuleCreate, ComplianceRuleUpdate
|
|
from app.repository.compliance_rule_repository import ComplianceRuleRepository
|
|
from app.service.compliance_rule_service import ComplianceRuleService
|
|
|
|
pattern = f"guaranteed-return-{uuid4().hex}"
|
|
service = ComplianceRuleService(ComplianceRuleRepository())
|
|
|
|
created = service.create_rule(
|
|
ComplianceRuleCreate(
|
|
rule_type="keyword",
|
|
pattern=pattern,
|
|
severity="block",
|
|
category="return_promise",
|
|
suggestion="Do not promise returns; use risk disclosure instead.",
|
|
priority=10,
|
|
),
|
|
actor_id="compliance_test",
|
|
)
|
|
|
|
assert created.id > 0
|
|
assert created.rule_code.startswith("CR-")
|
|
assert created.is_active is True
|
|
|
|
listed = service.list_rules(keyword=pattern)
|
|
assert listed.total == 1
|
|
assert listed.items[0].pattern == pattern
|
|
|
|
updated = service.update_rule(
|
|
created.id,
|
|
ComplianceRuleUpdate(severity="warn", suggestion="Use risk disclosure wording."),
|
|
actor_id="admin_test",
|
|
)
|
|
assert updated.severity == "warn"
|
|
assert updated.updated_by == "admin_test"
|
|
|
|
deleted = service.soft_delete_rule(created.id, actor_id="admin_test")
|
|
assert deleted.is_active is False
|
|
assert service.list_rules(keyword=pattern, is_active=True).total == 0
|
|
assert service.list_rules(keyword=pattern, is_active=False).total == 1
|
|
|
|
def test_compliance_rule_api_enforces_permission_and_exposes_crud_flow():
|
|
advisor_token = login_token("advisor_test", "advisor_test")
|
|
compliance_token = login_token("compliance_test", "compliance_test")
|
|
pattern = f"promise-profit-{uuid4().hex}"
|
|
|
|
denied = client.post(
|
|
"/api/advisor-agent/compliance/rules",
|
|
json={
|
|
"rule_type": "keyword",
|
|
"pattern": pattern,
|
|
"severity": "block",
|
|
"category": "return_promise",
|
|
"suggestion": "Do not promise returns.",
|
|
},
|
|
headers={"Authorization": f"Bearer {advisor_token}", "X-Trace-Id": "trace-rule-denied"},
|
|
)
|
|
assert denied.status_code == 403
|
|
assert denied.json()["error_code"] == "AUTH_403_PERMISSION"
|
|
|
|
created = client.post(
|
|
"/api/advisor-agent/compliance/rules",
|
|
json={
|
|
"rule_type": "keyword",
|
|
"pattern": pattern,
|
|
"severity": "block",
|
|
"category": "return_promise",
|
|
"suggestion": "Do not promise returns.",
|
|
"priority": 20,
|
|
},
|
|
headers={"Authorization": f"Bearer {compliance_token}", "X-Trace-Id": "trace-rule-create"},
|
|
)
|
|
assert created.status_code == 200
|
|
rule = created.json()["data"]
|
|
assert rule["pattern"] == pattern
|
|
assert rule["rule_code"].startswith("CR-")
|
|
|
|
listed = client.get(
|
|
f"/api/advisor-agent/compliance/rules?keyword={pattern}",
|
|
headers={"Authorization": f"Bearer {compliance_token}", "X-Trace-Id": "trace-rule-list"},
|
|
)
|
|
assert listed.status_code == 200
|
|
assert listed.json()["data"]["total"] == 1
|
|
|
|
updated = client.put(
|
|
f"/api/advisor-agent/compliance/rules/{rule['id']}",
|
|
json={"severity": "warn", "suggestion": "Use risk disclosure wording."},
|
|
headers={"Authorization": f"Bearer {compliance_token}", "X-Trace-Id": "trace-rule-update"},
|
|
)
|
|
assert updated.status_code == 200
|
|
assert updated.json()["data"]["severity"] == "warn"
|
|
|
|
deleted = client.delete(
|
|
f"/api/advisor-agent/compliance/rules/{rule['id']}",
|
|
headers={"Authorization": f"Bearer {compliance_token}", "X-Trace-Id": "trace-rule-delete"},
|
|
)
|
|
assert deleted.status_code == 200
|
|
assert deleted.json()["data"]["is_active"] is False
|