@aporthq/aport-agent-guardrails 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/LICENSE +217 -0
  2. package/README.md +481 -0
  3. package/bin/agent-guardrails +133 -0
  4. package/bin/aport-create-passport.sh +444 -0
  5. package/bin/aport-cursor-hook.sh +90 -0
  6. package/bin/aport-guardrail-api.sh +108 -0
  7. package/bin/aport-guardrail-bash.sh +394 -0
  8. package/bin/aport-guardrail-v2.sh +5 -0
  9. package/bin/aport-guardrail.sh +5 -0
  10. package/bin/aport-resolve-paths.sh +71 -0
  11. package/bin/aport-status.sh +276 -0
  12. package/bin/frameworks/crewai.sh +49 -0
  13. package/bin/frameworks/cursor.sh +95 -0
  14. package/bin/frameworks/langchain.sh +48 -0
  15. package/bin/frameworks/n8n.sh +36 -0
  16. package/bin/frameworks/openclaw.sh +19 -0
  17. package/bin/lib/allowlist.sh +18 -0
  18. package/bin/lib/common.sh +28 -0
  19. package/bin/lib/config.sh +46 -0
  20. package/bin/lib/constants.sh +232 -0
  21. package/bin/lib/detect.sh +65 -0
  22. package/bin/lib/error.sh +269 -0
  23. package/bin/lib/passport.sh +19 -0
  24. package/bin/lib/templates/.gitkeep +1 -0
  25. package/bin/lib/templates/config.yaml +6 -0
  26. package/bin/lib/validation.sh +206 -0
  27. package/bin/openclaw +660 -0
  28. package/docs/ADDING_A_FRAMEWORK.md +87 -0
  29. package/docs/AGENTS.md.example +40 -0
  30. package/docs/CODE_REVIEW.md +192 -0
  31. package/docs/DEPLOYMENT_READINESS.md +81 -0
  32. package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
  33. package/docs/FRAMEWORK_ROADMAP.md +41 -0
  34. package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
  35. package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
  36. package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
  37. package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
  38. package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
  39. package/docs/QUICKSTART.md +470 -0
  40. package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
  41. package/docs/README.md +28 -0
  42. package/docs/RELEASE.md +87 -0
  43. package/docs/REPO_LAYOUT.md +47 -0
  44. package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
  45. package/docs/TOOL_POLICY_MAPPING.md +46 -0
  46. package/docs/UPGRADE.md +46 -0
  47. package/docs/VERIFICATION_METHODS.md +97 -0
  48. package/docs/assets/README.md +8 -0
  49. package/docs/assets/porter.svg +54 -0
  50. package/docs/development/ERROR_CODES.md +616 -0
  51. package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
  52. package/docs/frameworks/crewai.md +114 -0
  53. package/docs/frameworks/cursor.md +159 -0
  54. package/docs/frameworks/langchain.md +72 -0
  55. package/docs/frameworks/n8n.md +40 -0
  56. package/docs/frameworks/openclaw.md +40 -0
  57. package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
  58. package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
  59. package/docs/launch/AWESOME_REPOS.md +53 -0
  60. package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
  61. package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
  62. package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
  63. package/docs/launch/EVIDENCE_README.md +61 -0
  64. package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
  65. package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
  66. package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
  67. package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
  68. package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
  69. package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
  70. package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
  71. package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
  72. package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
  73. package/docs/launch/READINESS_SUMMARY.md +262 -0
  74. package/docs/launch/README.md +68 -0
  75. package/docs/launch/USER_STORIES.md +327 -0
  76. package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
  77. package/docs/operations/MONITORING.md +588 -0
  78. package/docs/reviews/2026-02-18-staff-review.md +268 -0
  79. package/extensions/openclaw-aport/README.md +415 -0
  80. package/extensions/openclaw-aport/index.js +625 -0
  81. package/extensions/openclaw-aport/openclaw-aport.js +7 -0
  82. package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
  83. package/extensions/openclaw-aport/package.json +36 -0
  84. package/extensions/openclaw-aport/test.js +307 -0
  85. package/external/aport-policies/README.md +363 -0
  86. package/external/aport-policies/agent.session.create.v1/README.md +345 -0
  87. package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
  88. package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
  89. package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
  90. package/external/aport-policies/code.release.publish.v1/README.md +51 -0
  91. package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
  92. package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
  93. package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
  94. package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
  95. package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
  96. package/external/aport-policies/data.export.create.v1/README.md +226 -0
  97. package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
  98. package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
  99. package/external/aport-policies/data.export.create.v1/policy.json +133 -0
  100. package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
  101. package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
  102. package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
  103. package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
  104. package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
  105. package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
  106. package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
  107. package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
  108. package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
  109. package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
  110. package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
  111. package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
  112. package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
  113. package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
  114. package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
  115. package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
  116. package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
  117. package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
  118. package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
  119. package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
  120. package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
  121. package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
  122. package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
  123. package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
  124. package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
  125. package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
  126. package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
  127. package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
  128. package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
  129. package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
  130. package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
  131. package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
  132. package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
  133. package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
  134. package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
  135. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
  136. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
  137. package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
  138. package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
  139. package/external/aport-policies/governance.data.access.v1/README.md +292 -0
  140. package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
  141. package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
  142. package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
  143. package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
  144. package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
  145. package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
  146. package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
  147. package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
  148. package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
  149. package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
  150. package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
  151. package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
  152. package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
  153. package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
  154. package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
  155. package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
  156. package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
  157. package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
  158. package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
  159. package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
  160. package/external/aport-policies/policy-template.json +107 -0
  161. package/external/aport-policies/system.command.execute.v1/README.md +275 -0
  162. package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
  163. package/external/aport-spec/CONTRIBUTING.md +273 -0
  164. package/external/aport-spec/LICENSE +21 -0
  165. package/external/aport-spec/README.md +168 -0
  166. package/external/aport-spec/conformance/README.md +294 -0
  167. package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
  168. package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
  169. package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
  170. package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
  171. package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
  172. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
  173. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
  174. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
  175. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
  176. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
  177. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
  178. package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
  179. package/external/aport-spec/conformance/package.json +44 -0
  180. package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
  181. package/external/aport-spec/conformance/src/cases.ts +371 -0
  182. package/external/aport-spec/conformance/src/ed25519.ts +167 -0
  183. package/external/aport-spec/conformance/src/jcs.ts +85 -0
  184. package/external/aport-spec/conformance/src/runner.ts +533 -0
  185. package/external/aport-spec/conformance/src/validators.ts +185 -0
  186. package/external/aport-spec/conformance/test-runner.js +315 -0
  187. package/external/aport-spec/conformance/tsconfig.json +21 -0
  188. package/external/aport-spec/error-schema.json +192 -0
  189. package/external/aport-spec/index.json +12 -0
  190. package/external/aport-spec/integrations/clawmoat/README.md +12 -0
  191. package/external/aport-spec/integrations/shield/README.md +245 -0
  192. package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
  193. package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
  194. package/external/aport-spec/integrations/shield/test/README.md +58 -0
  195. package/external/aport-spec/integrations/shield/test/shield.md +40 -0
  196. package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
  197. package/external/aport-spec/metrics-schema.json +504 -0
  198. package/external/aport-spec/oap/CHANGELOG.md +54 -0
  199. package/external/aport-spec/oap/VERSION.md +40 -0
  200. package/external/aport-spec/oap/capability-registry.md +229 -0
  201. package/external/aport-spec/oap/conformance.md +257 -0
  202. package/external/aport-spec/oap/decision-schema.json +114 -0
  203. package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
  204. package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
  205. package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
  206. package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
  207. package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
  208. package/external/aport-spec/oap/oap-spec.md +426 -0
  209. package/external/aport-spec/oap/passport-schema.json +396 -0
  210. package/external/aport-spec/oap/security.md +213 -0
  211. package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
  212. package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
  213. package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
  214. package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
  215. package/external/aport-spec/oap/vc/tools/README.md +278 -0
  216. package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
  217. package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
  218. package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
  219. package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
  220. package/external/aport-spec/oap/vc/tools/package.json +38 -0
  221. package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
  222. package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
  223. package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
  224. package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
  225. package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
  226. package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
  227. package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
  228. package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
  229. package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
  230. package/external/aport-spec/passport-schema.json +586 -0
  231. package/external/aport-spec/rate-limiting.md +136 -0
  232. package/external/aport-spec/transport-profile.md +325 -0
  233. package/external/aport-spec/webhook-spec.md +314 -0
  234. package/package.json +70 -0
  235. package/skills/aport-agent-guardrail/SKILL.md +314 -0
  236. package/src/evaluator.js +252 -0
  237. package/src/server/index.js +72 -0
@@ -0,0 +1,778 @@
1
+ """
2
+ Comprehensive tests for finance.payment.refund.v1 policy
3
+ Tests all enforcement rules, edge cases, and fraud prevention
4
+ """
5
+
6
+ import pytest
7
+ import asyncio
8
+ from unittest.mock import AsyncMock, patch, MagicMock
9
+ import json
10
+
11
+ # Mock the policy verification
12
+ class MockPolicyResponse:
13
+ def __init__(self, allow=True, reasons=None, decision_id=None, remaining_daily_cap=None):
14
+ self.allow = allow
15
+ self.reasons = reasons or []
16
+ self.decision_id = decision_id
17
+ self.remaining_daily_cap = remaining_daily_cap or {}
18
+ self.passport = {
19
+ 'evaluation': {
20
+ 'decision_id': decision_id,
21
+ 'remaining_daily_cap': remaining_daily_cap or {}
22
+ }
23
+ }
24
+
25
+ def mock_policy_verification(response):
26
+ """Mock the policy verification endpoint"""
27
+ async def mock_verify(*args, **kwargs):
28
+ return response
29
+ return mock_verify
30
+
31
+ class TestRefundsV1Policy:
32
+ """Test suite for finance.payment.refund.v1 policy functionality"""
33
+
34
+ @pytest.fixture
35
+ def valid_context(self):
36
+ """Valid refund context for testing"""
37
+ return {
38
+ "order_id": "ORD-12345",
39
+ "customer_id": "CUST-67890",
40
+ "amount_minor": 5000,
41
+ "currency": "USD",
42
+ "region": "US",
43
+ "reason_code": "customer_request",
44
+ "idempotency_key": "idempotency_key_123",
45
+ }
46
+
47
+ @pytest.fixture
48
+ def mock_client(self):
49
+ """Mock AgentPassportClient"""
50
+ client = MagicMock()
51
+ client.verify_agent_passport = AsyncMock()
52
+ return client
53
+
54
+ def test_required_fields_validation_success(self, valid_context):
55
+ """Test that refund with all required fields is allowed"""
56
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
57
+
58
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
59
+ mock_verify.return_value = MockPolicyResponse(
60
+ allow=True,
61
+ decision_id="dec_123",
62
+ remaining_daily_cap={"USD": 25000}
63
+ )
64
+
65
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", valid_context)
66
+
67
+ assert result["allowed"] is True
68
+ assert "policy_result" in result
69
+
70
+ def test_required_fields_validation_failure(self):
71
+ """Test that refund missing required fields is denied"""
72
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
73
+
74
+ invalid_context = {
75
+ "order_id": "ORD-12345",
76
+ # Missing customer_id, amount_minor, currency, region, reason_code, idempotency_key
77
+ }
78
+
79
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
80
+ mock_verify.return_value = MockPolicyResponse(
81
+ allow=False,
82
+ reasons=[
83
+ {"code": "missing_required_field", "message": "customer_id is required"},
84
+ {"code": "missing_required_field", "message": "amount_minor is required"},
85
+ {"code": "missing_required_field", "message": "currency is required"},
86
+ {"code": "missing_required_field", "message": "region is required"},
87
+ {"code": "missing_required_field", "message": "reason_code is required"},
88
+ {"code": "missing_required_field", "message": "idempotency_key is required"},
89
+ ]
90
+ )
91
+
92
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", invalid_context)
93
+
94
+ assert result["allowed"] is False
95
+ assert len(result["violations"]) == 6
96
+
97
+ def test_currency_support(self):
98
+ """Test support for multiple currencies"""
99
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
100
+
101
+ currencies = ["USD", "EUR", "GBP", "JPY", "CAD", "AUD"]
102
+
103
+ for currency in currencies:
104
+ context = {
105
+ "order_id": "ORD-12345",
106
+ "customer_id": "CUST-67890",
107
+ "amount_minor": 1000,
108
+ "currency": currency,
109
+ "region": "US",
110
+ "reason_code": "customer_request",
111
+ "idempotency_key": f"idempotency_{currency}",
112
+ }
113
+
114
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
115
+ mock_verify.return_value = MockPolicyResponse(
116
+ allow=True,
117
+ decision_id=f"dec_{currency}",
118
+ remaining_daily_cap={currency: 25000}
119
+ )
120
+
121
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
122
+
123
+ assert result["allowed"] is True
124
+
125
+ def test_unsupported_currency(self):
126
+ """Test that unsupported currency is denied"""
127
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
128
+
129
+ context = {
130
+ "order_id": "ORD-12345",
131
+ "customer_id": "CUST-67890",
132
+ "amount_minor": 1000,
133
+ "currency": "XYZ", # Unsupported currency
134
+ "region": "US",
135
+ "reason_code": "customer_request",
136
+ "idempotency_key": "idempotency_xyz",
137
+ }
138
+
139
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
140
+ mock_verify.return_value = MockPolicyResponse(
141
+ allow=False,
142
+ reasons=[
143
+ {"code": "currency_not_supported", "message": "Currency XYZ is not supported"}
144
+ ]
145
+ )
146
+
147
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
148
+
149
+ assert result["allowed"] is False
150
+ assert result["violations"][0]["code"] == "currency_not_supported"
151
+
152
+ def test_amount_precision_validation(self):
153
+ """Test amount precision validation for different currencies"""
154
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
155
+
156
+ test_cases = [
157
+ {"currency": "USD", "amount": 1000, "valid": True}, # $10.00 - valid
158
+ {"currency": "USD", "amount": 1001, "valid": False}, # $10.01 - invalid precision
159
+ {"currency": "JPY", "amount": 1000, "valid": True}, # ¥1000 - valid
160
+ {"currency": "JPY", "amount": 1001, "valid": True}, # ¥1001 - valid (no decimals)
161
+ {"currency": "EUR", "amount": 1000, "valid": True}, # €10.00 - valid
162
+ {"currency": "EUR", "amount": 1001, "valid": False}, # €10.01 - invalid precision
163
+ ]
164
+
165
+ for test_case in test_cases:
166
+ context = {
167
+ "order_id": "ORD-12345",
168
+ "customer_id": "CUST-67890",
169
+ "amount_minor": test_case["amount"],
170
+ "currency": test_case["currency"],
171
+ "region": "US",
172
+ "reason_code": "customer_request",
173
+ "idempotency_key": f"idempotency_{test_case['currency']}_{test_case['amount']}",
174
+ }
175
+
176
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
177
+ if test_case["valid"]:
178
+ mock_verify.return_value = MockPolicyResponse(allow=True)
179
+ else:
180
+ mock_verify.return_value = MockPolicyResponse(
181
+ allow=False,
182
+ reasons=[
183
+ {"code": "invalid_amount", "message": f"Amount {test_case['amount']} has invalid precision for currency {test_case['currency']}"}
184
+ ]
185
+ )
186
+
187
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
188
+
189
+ assert result["allowed"] == test_case["valid"]
190
+
191
+ def test_amount_bounds_validation(self):
192
+ """Test amount bounds validation"""
193
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
194
+
195
+ test_cases = [
196
+ {"amount": 0, "valid": False, "reason": "Amount must be positive"},
197
+ {"amount": -100, "valid": False, "reason": "Amount must be positive"},
198
+ {"amount": 1, "valid": True, "reason": "Minimum amount"},
199
+ {"amount": 1000000000, "valid": False, "reason": "Amount exceeds maximum"},
200
+ {"amount": 999999999, "valid": True, "reason": "Maximum valid amount"},
201
+ ]
202
+
203
+ for test_case in test_cases:
204
+ context = {
205
+ "order_id": "ORD-12345",
206
+ "customer_id": "CUST-67890",
207
+ "amount_minor": test_case["amount"],
208
+ "currency": "USD",
209
+ "region": "US",
210
+ "reason_code": "customer_request",
211
+ "idempotency_key": f"idempotency_{test_case['amount']}",
212
+ }
213
+
214
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
215
+ if test_case["valid"]:
216
+ mock_verify.return_value = MockPolicyResponse(allow=True)
217
+ else:
218
+ mock_verify.return_value = MockPolicyResponse(
219
+ allow=False,
220
+ reasons=[
221
+ {"code": "invalid_amount", "message": test_case["reason"]}
222
+ ]
223
+ )
224
+
225
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
226
+
227
+ assert result["allowed"] == test_case["valid"]
228
+
229
+ def test_assurance_level_requirements_l2(self):
230
+ """Test L2 requirement for amounts <= $100"""
231
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
232
+
233
+ context = {
234
+ "order_id": "ORD-12345",
235
+ "customer_id": "CUST-67890",
236
+ "amount_minor": 10000, # $100
237
+ "currency": "USD",
238
+ "region": "US",
239
+ "reason_code": "customer_request",
240
+ "idempotency_key": "idempotency_l2",
241
+ }
242
+
243
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
244
+ mock_verify.return_value = MockPolicyResponse(
245
+ allow=True,
246
+ decision_id="dec_l2_pass",
247
+ remaining_daily_cap={"USD": 25000}
248
+ )
249
+
250
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
251
+
252
+ assert result["allowed"] is True
253
+
254
+ def test_assurance_level_requirements_l3(self):
255
+ """Test L3 requirement for amounts $100-$500"""
256
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
257
+
258
+ context = {
259
+ "order_id": "ORD-12345",
260
+ "customer_id": "CUST-67890",
261
+ "amount_minor": 25000, # $250
262
+ "currency": "USD",
263
+ "region": "US",
264
+ "reason_code": "customer_request",
265
+ "idempotency_key": "idempotency_l3",
266
+ }
267
+
268
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
269
+ mock_verify.return_value = MockPolicyResponse(
270
+ allow=False,
271
+ reasons=[
272
+ {"code": "assurance_too_low", "message": "Refund amount 25000 USD requires L3 assurance level, but agent has L2"}
273
+ ]
274
+ )
275
+
276
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
277
+
278
+ assert result["allowed"] is False
279
+ assert result["violations"][0]["code"] == "assurance_too_low"
280
+
281
+ def test_assurance_level_requirements_deny_over_500(self):
282
+ """Test that amounts > $500 are denied in v1"""
283
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
284
+
285
+ context = {
286
+ "order_id": "ORD-12345",
287
+ "customer_id": "CUST-67890",
288
+ "amount_minor": 60000, # $600
289
+ "currency": "USD",
290
+ "region": "US",
291
+ "reason_code": "customer_request",
292
+ "idempotency_key": "idempotency_deny",
293
+ }
294
+
295
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
296
+ mock_verify.return_value = MockPolicyResponse(
297
+ allow=False,
298
+ reasons=[
299
+ {"code": "assurance_too_low", "message": "Refund amount 60000 USD requires L4 assurance level, but agent has L3"}
300
+ ]
301
+ )
302
+
303
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
304
+
305
+ assert result["allowed"] is False
306
+
307
+ def test_idempotency_protection_first_request(self):
308
+ """Test that first request with idempotency key is allowed"""
309
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
310
+
311
+ context = {
312
+ "order_id": "ORD-12345",
313
+ "customer_id": "CUST-67890",
314
+ "amount_minor": 5000,
315
+ "currency": "USD",
316
+ "region": "US",
317
+ "reason_code": "customer_request",
318
+ "idempotency_key": "unique_key_123",
319
+ }
320
+
321
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
322
+ mock_verify.return_value = MockPolicyResponse(
323
+ allow=True,
324
+ decision_id="dec_first",
325
+ remaining_daily_cap={"USD": 25000}
326
+ )
327
+
328
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
329
+
330
+ assert result["allowed"] is True
331
+
332
+ def test_idempotency_protection_duplicate(self):
333
+ """Test that duplicate idempotency key is denied"""
334
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
335
+
336
+ context = {
337
+ "order_id": "ORD-12345",
338
+ "customer_id": "CUST-67890",
339
+ "amount_minor": 5000,
340
+ "currency": "USD",
341
+ "region": "US",
342
+ "reason_code": "customer_request",
343
+ "idempotency_key": "duplicate_key_123",
344
+ }
345
+
346
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
347
+ mock_verify.return_value = MockPolicyResponse(
348
+ allow=False,
349
+ reasons=[
350
+ {"code": "idempotency_replay", "message": "Duplicate idempotency key detected. Previous decision: dec_duplicate"}
351
+ ]
352
+ )
353
+
354
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
355
+
356
+ assert result["allowed"] is False
357
+ assert result["violations"][0]["code"] == "idempotency_replay"
358
+
359
+ def test_daily_cap_enforcement_within_limit(self):
360
+ """Test that refund within daily cap is allowed"""
361
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
362
+
363
+ context = {
364
+ "order_id": "ORD-12345",
365
+ "customer_id": "CUST-67890",
366
+ "amount_minor": 5000,
367
+ "currency": "USD",
368
+ "region": "US",
369
+ "reason_code": "customer_request",
370
+ "idempotency_key": "daily_cap_test",
371
+ }
372
+
373
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
374
+ mock_verify.return_value = MockPolicyResponse(
375
+ allow=True,
376
+ decision_id="dec_daily_cap",
377
+ remaining_daily_cap={"USD": 20000}
378
+ )
379
+
380
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
381
+
382
+ assert result["allowed"] is True
383
+ assert result["policy_result"].passport["evaluation"]["remaining_daily_cap"]["USD"] == 20000
384
+
385
+ def test_daily_cap_enforcement_exceeded(self):
386
+ """Test that refund exceeding daily cap is denied"""
387
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
388
+
389
+ context = {
390
+ "order_id": "ORD-12345",
391
+ "customer_id": "CUST-67890",
392
+ "amount_minor": 5000,
393
+ "currency": "USD",
394
+ "region": "US",
395
+ "reason_code": "customer_request",
396
+ "idempotency_key": "daily_cap_exceeded",
397
+ }
398
+
399
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
400
+ mock_verify.return_value = MockPolicyResponse(
401
+ allow=False,
402
+ reasons=[
403
+ {"code": "daily_cap_exceeded", "message": "Daily cap 25000 USD exceeded for USD; current 23000 + 5000 > 25000"}
404
+ ],
405
+ remaining_daily_cap={"USD": 2000}
406
+ )
407
+
408
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
409
+
410
+ assert result["allowed"] is False
411
+ assert result["violations"][0]["code"] == "daily_cap_exceeded"
412
+
413
+ def test_cross_currency_protection(self):
414
+ """Test that cross-currency refunds are denied"""
415
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
416
+
417
+ context = {
418
+ "order_id": "ORD-12345",
419
+ "customer_id": "CUST-67890",
420
+ "amount_minor": 5000,
421
+ "currency": "USD",
422
+ "order_currency": "EUR", # Different from refund currency
423
+ "region": "US",
424
+ "reason_code": "customer_request",
425
+ "idempotency_key": "cross_currency_test",
426
+ }
427
+
428
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
429
+ mock_verify.return_value = MockPolicyResponse(
430
+ allow=False,
431
+ reasons=[
432
+ {"code": "cross_currency_denied", "message": "Cross-currency refunds are not supported in v1"}
433
+ ]
434
+ )
435
+
436
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
437
+
438
+ assert result["allowed"] is False
439
+ assert result["violations"][0]["code"] == "cross_currency_denied"
440
+
441
+ def test_order_balance_validation_within_balance(self):
442
+ """Test that refund within order balance is allowed"""
443
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
444
+
445
+ context = {
446
+ "order_id": "ORD-12345",
447
+ "customer_id": "CUST-67890",
448
+ "amount_minor": 5000,
449
+ "currency": "USD",
450
+ "order_total_minor": 10000,
451
+ "already_refunded_minor": 2000,
452
+ "region": "US",
453
+ "reason_code": "customer_request",
454
+ "idempotency_key": "balance_valid",
455
+ }
456
+
457
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
458
+ mock_verify.return_value = MockPolicyResponse(
459
+ allow=True,
460
+ decision_id="dec_balance_valid",
461
+ remaining_daily_cap={"USD": 25000}
462
+ )
463
+
464
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
465
+
466
+ assert result["allowed"] is True
467
+
468
+ def test_order_balance_validation_exceeded(self):
469
+ """Test that refund exceeding order balance is denied"""
470
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
471
+
472
+ context = {
473
+ "order_id": "ORD-12345",
474
+ "customer_id": "CUST-67890",
475
+ "amount_minor": 5000,
476
+ "currency": "USD",
477
+ "order_total_minor": 10000,
478
+ "already_refunded_minor": 8000, # Only 2000 remaining
479
+ "region": "US",
480
+ "reason_code": "customer_request",
481
+ "idempotency_key": "balance_exceeded",
482
+ }
483
+
484
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
485
+ mock_verify.return_value = MockPolicyResponse(
486
+ allow=False,
487
+ reasons=[
488
+ {"code": "order_balance_exceeded", "message": "Refund amount 5000 exceeds remaining order balance 2000"}
489
+ ]
490
+ )
491
+
492
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
493
+
494
+ assert result["allowed"] is False
495
+ assert result["violations"][0]["code"] == "order_balance_exceeded"
496
+
497
+ def test_region_validation_allowed(self):
498
+ """Test that refund in allowed region is permitted"""
499
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
500
+
501
+ context = {
502
+ "order_id": "ORD-12345",
503
+ "customer_id": "CUST-67890",
504
+ "amount_minor": 5000,
505
+ "currency": "USD",
506
+ "region": "US",
507
+ "reason_code": "customer_request",
508
+ "idempotency_key": "region_valid",
509
+ }
510
+
511
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
512
+ mock_verify.return_value = MockPolicyResponse(
513
+ allow=True,
514
+ decision_id="dec_region_valid",
515
+ remaining_daily_cap={"USD": 25000}
516
+ )
517
+
518
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
519
+
520
+ assert result["allowed"] is True
521
+
522
+ def test_region_validation_denied(self):
523
+ """Test that refund in disallowed region is denied"""
524
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
525
+
526
+ context = {
527
+ "order_id": "ORD-12345",
528
+ "customer_id": "CUST-67890",
529
+ "amount_minor": 5000,
530
+ "currency": "USD",
531
+ "region": "RESTRICTED",
532
+ "reason_code": "customer_request",
533
+ "idempotency_key": "region_invalid",
534
+ }
535
+
536
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
537
+ mock_verify.return_value = MockPolicyResponse(
538
+ allow=False,
539
+ reasons=[
540
+ {"code": "region_not_allowed", "message": "Region RESTRICTED is not allowed for this agent"}
541
+ ]
542
+ )
543
+
544
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
545
+
546
+ assert result["allowed"] is False
547
+ assert result["violations"][0]["code"] == "region_not_allowed"
548
+
549
+ def test_reason_code_validation_valid(self):
550
+ """Test that valid reason codes are allowed"""
551
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
552
+
553
+ valid_reason_codes = ["customer_request", "defective", "not_as_described", "duplicate", "fraud"]
554
+
555
+ for reason_code in valid_reason_codes:
556
+ context = {
557
+ "order_id": "ORD-12345",
558
+ "customer_id": "CUST-67890",
559
+ "amount_minor": 5000,
560
+ "currency": "USD",
561
+ "region": "US",
562
+ "reason_code": reason_code,
563
+ "idempotency_key": f"reason_{reason_code}",
564
+ }
565
+
566
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
567
+ mock_verify.return_value = MockPolicyResponse(
568
+ allow=True,
569
+ decision_id=f"dec_{reason_code}",
570
+ remaining_daily_cap={"USD": 25000}
571
+ )
572
+
573
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
574
+
575
+ assert result["allowed"] is True
576
+
577
+ def test_reason_code_validation_invalid(self):
578
+ """Test that invalid reason codes are denied"""
579
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
580
+
581
+ context = {
582
+ "order_id": "ORD-12345",
583
+ "customer_id": "CUST-67890",
584
+ "amount_minor": 5000,
585
+ "currency": "USD",
586
+ "region": "US",
587
+ "reason_code": "invalid_reason",
588
+ "idempotency_key": "invalid_reason_test",
589
+ }
590
+
591
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
592
+ mock_verify.return_value = MockPolicyResponse(
593
+ allow=False,
594
+ reasons=[
595
+ {"code": "reason_code_invalid", "message": "Reason code invalid_reason is not supported"}
596
+ ]
597
+ )
598
+
599
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
600
+
601
+ assert result["allowed"] is False
602
+ assert result["violations"][0]["code"] == "reason_code_invalid"
603
+
604
+ def test_error_handling_policy_verification_failure(self):
605
+ """Test handling of policy verification failures"""
606
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
607
+
608
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
609
+ mock_verify.return_value = None # Simulate verification failure
610
+
611
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", {})
612
+
613
+ assert result["allowed"] is False
614
+ assert result["reason"] == "policy_verification_failed"
615
+
616
+ def test_error_handling_network_error(self):
617
+ """Test handling of network errors"""
618
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
619
+
620
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
621
+ mock_verify.side_effect = Exception("Network error")
622
+
623
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", {})
624
+
625
+ assert result["allowed"] is False
626
+ assert result["reason"] == "policy_check_error"
627
+
628
+ def test_edge_cases_extreme_amounts(self):
629
+ """Test prevention of extremely large amounts"""
630
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
631
+
632
+ context = {
633
+ "order_id": "ORD-12345",
634
+ "customer_id": "CUST-67890",
635
+ "amount_minor": 2**63 - 1, # Maximum safe integer
636
+ "currency": "USD",
637
+ "region": "US",
638
+ "reason_code": "customer_request",
639
+ "idempotency_key": "extreme_amount",
640
+ }
641
+
642
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
643
+ mock_verify.return_value = MockPolicyResponse(
644
+ allow=False,
645
+ reasons=[
646
+ {"code": "invalid_amount", "message": "Amount exceeds maximum allowed amount"}
647
+ ]
648
+ )
649
+
650
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
651
+
652
+ assert result["allowed"] is False
653
+
654
+ def test_edge_cases_negative_amounts(self):
655
+ """Test prevention of negative amounts"""
656
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
657
+
658
+ context = {
659
+ "order_id": "ORD-12345",
660
+ "customer_id": "CUST-67890",
661
+ "amount_minor": -1000,
662
+ "currency": "USD",
663
+ "region": "US",
664
+ "reason_code": "customer_request",
665
+ "idempotency_key": "negative_amount",
666
+ }
667
+
668
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
669
+ mock_verify.return_value = MockPolicyResponse(
670
+ allow=False,
671
+ reasons=[
672
+ {"code": "invalid_amount", "message": "Amount must be positive"}
673
+ ]
674
+ )
675
+
676
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
677
+
678
+ assert result["allowed"] is False
679
+
680
+ def test_edge_cases_zero_amounts(self):
681
+ """Test prevention of zero amounts"""
682
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
683
+
684
+ context = {
685
+ "order_id": "ORD-12345",
686
+ "customer_id": "CUST-67890",
687
+ "amount_minor": 0,
688
+ "currency": "USD",
689
+ "region": "US",
690
+ "reason_code": "customer_request",
691
+ "idempotency_key": "zero_amount",
692
+ }
693
+
694
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
695
+ mock_verify.return_value = MockPolicyResponse(
696
+ allow=False,
697
+ reasons=[
698
+ {"code": "invalid_amount", "message": "Amount must be positive"}
699
+ ]
700
+ )
701
+
702
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
703
+
704
+ assert result["allowed"] is False
705
+
706
+ def test_idempotency_key_format_validation(self):
707
+ """Test validation of idempotency key format"""
708
+ from aporthq_sdk_python.policy_enforcement import check_policy_sync
709
+
710
+ invalid_keys = ["", "a", "a" * 100, "invalid@key", "key with spaces"]
711
+
712
+ for key in invalid_keys:
713
+ context = {
714
+ "order_id": "ORD-12345",
715
+ "customer_id": "CUST-67890",
716
+ "amount_minor": 5000,
717
+ "currency": "USD",
718
+ "region": "US",
719
+ "reason_code": "customer_request",
720
+ "idempotency_key": key,
721
+ }
722
+
723
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
724
+ mock_verify.return_value = MockPolicyResponse(
725
+ allow=False,
726
+ reasons=[
727
+ {"code": "invalid_idempotency_key", "message": "Invalid idempotency key format"}
728
+ ]
729
+ )
730
+
731
+ result = check_policy_sync("agent_123", "finance.payment.refund.v1", context)
732
+
733
+ assert result["allowed"] is False
734
+
735
+
736
+ class TestRefundsV1PolicyIntegration:
737
+ """Integration tests for finance.payment.refund.v1 policy"""
738
+
739
+ def test_fastapi_middleware_integration(self):
740
+ """Test FastAPI middleware integration"""
741
+ from aporthq_middleware_fastapi.middleware_v2 import require_refunds_policy
742
+
743
+ # Test that the function exists and has the right signature
744
+ assert callable(require_refunds_policy)
745
+
746
+ dependency = require_refunds_policy("agent_123", True, True)
747
+ assert callable(dependency)
748
+
749
+ @pytest.mark.asyncio
750
+ async def test_async_policy_compliance(self):
751
+ """Test async policy compliance checking"""
752
+ from aporthq_sdk_python.policy_enforcement import check_policy_compliance
753
+
754
+ context = {
755
+ "order_id": "ORD-12345",
756
+ "customer_id": "CUST-67890",
757
+ "amount_minor": 5000,
758
+ "currency": "USD",
759
+ "region": "US",
760
+ "reason_code": "customer_request",
761
+ "idempotency_key": "async_test",
762
+ }
763
+
764
+ with patch('aporthq_sdk_python.policy_enforcement.verify_policy_compliance') as mock_verify:
765
+ mock_verify.return_value = MockPolicyResponse(
766
+ allow=True,
767
+ decision_id="dec_async",
768
+ remaining_daily_cap={"USD": 25000}
769
+ )
770
+
771
+ result = await check_policy_compliance("agent_123", "finance.payment.refund.v1", context)
772
+
773
+ assert result["allowed"] is True
774
+ assert "policy_result" in result
775
+
776
+
777
+ if __name__ == "__main__":
778
+ pytest.main([__file__])