@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.
- package/LICENSE +217 -0
- package/README.md +481 -0
- package/bin/agent-guardrails +133 -0
- package/bin/aport-create-passport.sh +444 -0
- package/bin/aport-cursor-hook.sh +90 -0
- package/bin/aport-guardrail-api.sh +108 -0
- package/bin/aport-guardrail-bash.sh +394 -0
- package/bin/aport-guardrail-v2.sh +5 -0
- package/bin/aport-guardrail.sh +5 -0
- package/bin/aport-resolve-paths.sh +71 -0
- package/bin/aport-status.sh +276 -0
- package/bin/frameworks/crewai.sh +49 -0
- package/bin/frameworks/cursor.sh +95 -0
- package/bin/frameworks/langchain.sh +48 -0
- package/bin/frameworks/n8n.sh +36 -0
- package/bin/frameworks/openclaw.sh +19 -0
- package/bin/lib/allowlist.sh +18 -0
- package/bin/lib/common.sh +28 -0
- package/bin/lib/config.sh +46 -0
- package/bin/lib/constants.sh +232 -0
- package/bin/lib/detect.sh +65 -0
- package/bin/lib/error.sh +269 -0
- package/bin/lib/passport.sh +19 -0
- package/bin/lib/templates/.gitkeep +1 -0
- package/bin/lib/templates/config.yaml +6 -0
- package/bin/lib/validation.sh +206 -0
- package/bin/openclaw +660 -0
- package/docs/ADDING_A_FRAMEWORK.md +87 -0
- package/docs/AGENTS.md.example +40 -0
- package/docs/CODE_REVIEW.md +192 -0
- package/docs/DEPLOYMENT_READINESS.md +81 -0
- package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
- package/docs/FRAMEWORK_ROADMAP.md +41 -0
- package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
- package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
- package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
- package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
- package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
- package/docs/QUICKSTART.md +470 -0
- package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE.md +87 -0
- package/docs/REPO_LAYOUT.md +47 -0
- package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
- package/docs/TOOL_POLICY_MAPPING.md +46 -0
- package/docs/UPGRADE.md +46 -0
- package/docs/VERIFICATION_METHODS.md +97 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/porter.svg +54 -0
- package/docs/development/ERROR_CODES.md +616 -0
- package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
- package/docs/frameworks/crewai.md +114 -0
- package/docs/frameworks/cursor.md +159 -0
- package/docs/frameworks/langchain.md +72 -0
- package/docs/frameworks/n8n.md +40 -0
- package/docs/frameworks/openclaw.md +40 -0
- package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
- package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
- package/docs/launch/AWESOME_REPOS.md +53 -0
- package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
- package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
- package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
- package/docs/launch/EVIDENCE_README.md +61 -0
- package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
- package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
- package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
- package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
- package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
- package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
- package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
- package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
- package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
- package/docs/launch/READINESS_SUMMARY.md +262 -0
- package/docs/launch/README.md +68 -0
- package/docs/launch/USER_STORIES.md +327 -0
- package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
- package/docs/operations/MONITORING.md +588 -0
- package/docs/reviews/2026-02-18-staff-review.md +268 -0
- package/extensions/openclaw-aport/README.md +415 -0
- package/extensions/openclaw-aport/index.js +625 -0
- package/extensions/openclaw-aport/openclaw-aport.js +7 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
- package/extensions/openclaw-aport/package.json +36 -0
- package/extensions/openclaw-aport/test.js +307 -0
- package/external/aport-policies/README.md +363 -0
- package/external/aport-policies/agent.session.create.v1/README.md +345 -0
- package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
- package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
- package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
- package/external/aport-policies/code.release.publish.v1/README.md +51 -0
- package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
- package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
- package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
- package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
- package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
- package/external/aport-policies/data.export.create.v1/README.md +226 -0
- package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
- package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
- package/external/aport-policies/data.export.create.v1/policy.json +133 -0
- package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
- package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
- package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
- package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
- package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
- package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
- package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
- package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
- package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
- package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
- package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
- package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
- package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
- package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
- package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
- package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
- package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
- package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
- package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
- package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
- package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
- package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
- package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
- package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
- package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
- package/external/aport-policies/governance.data.access.v1/README.md +292 -0
- package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
- package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
- package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
- package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
- package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
- package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
- package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
- package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
- package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
- package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
- package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
- package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
- package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
- package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
- package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
- package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
- package/external/aport-policies/policy-template.json +107 -0
- package/external/aport-policies/system.command.execute.v1/README.md +275 -0
- package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
- package/external/aport-spec/CONTRIBUTING.md +273 -0
- package/external/aport-spec/LICENSE +21 -0
- package/external/aport-spec/README.md +168 -0
- package/external/aport-spec/conformance/README.md +294 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
- package/external/aport-spec/conformance/package.json +44 -0
- package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
- package/external/aport-spec/conformance/src/cases.ts +371 -0
- package/external/aport-spec/conformance/src/ed25519.ts +167 -0
- package/external/aport-spec/conformance/src/jcs.ts +85 -0
- package/external/aport-spec/conformance/src/runner.ts +533 -0
- package/external/aport-spec/conformance/src/validators.ts +185 -0
- package/external/aport-spec/conformance/test-runner.js +315 -0
- package/external/aport-spec/conformance/tsconfig.json +21 -0
- package/external/aport-spec/error-schema.json +192 -0
- package/external/aport-spec/index.json +12 -0
- package/external/aport-spec/integrations/clawmoat/README.md +12 -0
- package/external/aport-spec/integrations/shield/README.md +245 -0
- package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
- package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
- package/external/aport-spec/integrations/shield/test/README.md +58 -0
- package/external/aport-spec/integrations/shield/test/shield.md +40 -0
- package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
- package/external/aport-spec/metrics-schema.json +504 -0
- package/external/aport-spec/oap/CHANGELOG.md +54 -0
- package/external/aport-spec/oap/VERSION.md +40 -0
- package/external/aport-spec/oap/capability-registry.md +229 -0
- package/external/aport-spec/oap/conformance.md +257 -0
- package/external/aport-spec/oap/decision-schema.json +114 -0
- package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
- package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
- package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
- package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
- package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
- package/external/aport-spec/oap/oap-spec.md +426 -0
- package/external/aport-spec/oap/passport-schema.json +396 -0
- package/external/aport-spec/oap/security.md +213 -0
- package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
- package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
- package/external/aport-spec/oap/vc/tools/README.md +278 -0
- package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
- package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
- package/external/aport-spec/oap/vc/tools/package.json +38 -0
- package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
- package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
- package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
- package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
- package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
- package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
- package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
- package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
- package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
- package/external/aport-spec/passport-schema.json +586 -0
- package/external/aport-spec/rate-limiting.md +136 -0
- package/external/aport-spec/transport-profile.md +325 -0
- package/external/aport-spec/webhook-spec.md +314 -0
- package/package.json +70 -0
- package/skills/aport-agent-guardrail/SKILL.md +314 -0
- package/src/evaluator.js +252 -0
- package/src/server/index.js +72 -0
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive tests for finance.payment.refund.v1 policy
|
|
3
|
+
* Tests all enforcement rules, edge cases, and fraud prevention
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
test,
|
|
8
|
+
expect,
|
|
9
|
+
describe,
|
|
10
|
+
beforeEach,
|
|
11
|
+
afterEach,
|
|
12
|
+
} = require("@jest/globals");
|
|
13
|
+
|
|
14
|
+
// Mock the policy verification endpoint
|
|
15
|
+
const mockPolicyVerification = (response) => {
|
|
16
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
17
|
+
ok: true,
|
|
18
|
+
json: () => Promise.resolve(response),
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const mockPolicyVerificationError = (status = 500) => {
|
|
23
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
24
|
+
ok: false,
|
|
25
|
+
status,
|
|
26
|
+
text: () => Promise.resolve("Policy verification failed"),
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("Refunds v1 Policy - Core Functionality", () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
// Reset mocks
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("Required Fields Validation", () => {
|
|
37
|
+
test("should allow refund with all required fields", async () => {
|
|
38
|
+
const validContext = {
|
|
39
|
+
order_id: "ORD-12345",
|
|
40
|
+
customer_id: "CUST-67890",
|
|
41
|
+
amount_minor: 5000,
|
|
42
|
+
currency: "USD",
|
|
43
|
+
region: "US",
|
|
44
|
+
reason_code: "customer_request",
|
|
45
|
+
idempotency_key: "idempotency_key_123",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
mockPolicyVerification({
|
|
49
|
+
allow: true,
|
|
50
|
+
decision_id: "dec_123",
|
|
51
|
+
remaining_daily_cap: { USD: 25000 },
|
|
52
|
+
expires_in: 60,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
56
|
+
const result = await verifyPolicy(
|
|
57
|
+
"agent_123",
|
|
58
|
+
"finance.payment.refund.v1",
|
|
59
|
+
validContext
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(result.allowed).toBe(true);
|
|
63
|
+
expect(result.result).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("should deny refund missing required fields", async () => {
|
|
67
|
+
const invalidContext = {
|
|
68
|
+
order_id: "ORD-12345",
|
|
69
|
+
// Missing customer_id, amount_minor, currency, region, reason_code, idempotency_key
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
mockPolicyVerification({
|
|
73
|
+
allow: false,
|
|
74
|
+
reasons: [
|
|
75
|
+
{
|
|
76
|
+
code: "missing_required_field",
|
|
77
|
+
message: "customer_id is required",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
code: "missing_required_field",
|
|
81
|
+
message: "amount_minor is required",
|
|
82
|
+
},
|
|
83
|
+
{ code: "missing_required_field", message: "currency is required" },
|
|
84
|
+
{ code: "missing_required_field", message: "region is required" },
|
|
85
|
+
{
|
|
86
|
+
code: "missing_required_field",
|
|
87
|
+
message: "reason_code is required",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
code: "missing_required_field",
|
|
91
|
+
message: "idempotency_key is required",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
97
|
+
const result = await verifyPolicy(
|
|
98
|
+
"agent_123",
|
|
99
|
+
"finance.payment.refund.v1",
|
|
100
|
+
invalidContext
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(result.allowed).toBe(false);
|
|
104
|
+
expect(result.error?.violations).toHaveLength(6);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("Currency Support", () => {
|
|
109
|
+
test("should support multiple currencies", async () => {
|
|
110
|
+
const currencies = ["USD", "EUR", "GBP", "JPY", "CAD", "AUD"];
|
|
111
|
+
|
|
112
|
+
for (const currency of currencies) {
|
|
113
|
+
const context = {
|
|
114
|
+
order_id: "ORD-12345",
|
|
115
|
+
customer_id: "CUST-67890",
|
|
116
|
+
amount_minor: 1000,
|
|
117
|
+
currency,
|
|
118
|
+
region: "US",
|
|
119
|
+
reason_code: "customer_request",
|
|
120
|
+
idempotency_key: `idempotency_${currency}`,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
mockPolicyVerification({
|
|
124
|
+
allow: true,
|
|
125
|
+
decision_id: `dec_${currency}`,
|
|
126
|
+
remaining_daily_cap: { [currency]: 25000 },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
130
|
+
const result = await verifyPolicy(
|
|
131
|
+
"agent_123",
|
|
132
|
+
"finance.payment.refund.v1",
|
|
133
|
+
context
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(result.allowed).toBe(true);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("should deny unsupported currency", async () => {
|
|
141
|
+
const context = {
|
|
142
|
+
order_id: "ORD-12345",
|
|
143
|
+
customer_id: "CUST-67890",
|
|
144
|
+
amount_minor: 1000,
|
|
145
|
+
currency: "XYZ", // Unsupported currency
|
|
146
|
+
region: "US",
|
|
147
|
+
reason_code: "customer_request",
|
|
148
|
+
idempotency_key: "idempotency_xyz",
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
mockPolicyVerification({
|
|
152
|
+
allow: false,
|
|
153
|
+
reasons: [
|
|
154
|
+
{
|
|
155
|
+
code: "currency_not_supported",
|
|
156
|
+
message: "Currency XYZ is not supported",
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
162
|
+
const result = await verifyPolicy(
|
|
163
|
+
"agent_123",
|
|
164
|
+
"finance.payment.refund.v1",
|
|
165
|
+
context
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(result.allowed).toBe(false);
|
|
169
|
+
expect(result.error?.violations[0].code).toBe("currency_not_supported");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("Amount Validation", () => {
|
|
174
|
+
test("should validate amount precision for different currencies", async () => {
|
|
175
|
+
const testCases = [
|
|
176
|
+
{ currency: "USD", amount: 1000, valid: true }, // $10.00 - valid
|
|
177
|
+
{ currency: "USD", amount: 1001, valid: false }, // $10.01 - invalid precision
|
|
178
|
+
{ currency: "JPY", amount: 1000, valid: true }, // ¥1000 - valid
|
|
179
|
+
{ currency: "JPY", amount: 1001, valid: true }, // ¥1001 - valid (no decimals)
|
|
180
|
+
{ currency: "EUR", amount: 1000, valid: true }, // €10.00 - valid
|
|
181
|
+
{ currency: "EUR", amount: 1001, valid: false }, // €10.01 - invalid precision
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
for (const testCase of testCases) {
|
|
185
|
+
const context = {
|
|
186
|
+
order_id: "ORD-12345",
|
|
187
|
+
customer_id: "CUST-67890",
|
|
188
|
+
amount_minor: testCase.amount,
|
|
189
|
+
currency: testCase.currency,
|
|
190
|
+
region: "US",
|
|
191
|
+
reason_code: "customer_request",
|
|
192
|
+
idempotency_key: `idempotency_${testCase.currency}_${testCase.amount}`,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
mockPolicyVerification({
|
|
196
|
+
allow: testCase.valid,
|
|
197
|
+
reasons: testCase.valid
|
|
198
|
+
? []
|
|
199
|
+
: [
|
|
200
|
+
{
|
|
201
|
+
code: "invalid_amount",
|
|
202
|
+
message: `Amount ${testCase.amount} has invalid precision for currency ${testCase.currency}`,
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
208
|
+
const result = await verifyPolicy(
|
|
209
|
+
"agent_123",
|
|
210
|
+
"finance.payment.refund.v1",
|
|
211
|
+
context
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(result.allowed).toBe(testCase.valid);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("should validate amount bounds", async () => {
|
|
219
|
+
const testCases = [
|
|
220
|
+
{ amount: 0, valid: false, reason: "Amount must be positive" },
|
|
221
|
+
{ amount: -100, valid: false, reason: "Amount must be positive" },
|
|
222
|
+
{ amount: 1, valid: true, reason: "Minimum amount" },
|
|
223
|
+
{ amount: 1000000000, valid: false, reason: "Amount exceeds maximum" },
|
|
224
|
+
{ amount: 999999999, valid: true, reason: "Maximum valid amount" },
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
for (const testCase of testCases) {
|
|
228
|
+
const context = {
|
|
229
|
+
order_id: "ORD-12345",
|
|
230
|
+
customer_id: "CUST-67890",
|
|
231
|
+
amount_minor: testCase.amount,
|
|
232
|
+
currency: "USD",
|
|
233
|
+
region: "US",
|
|
234
|
+
reason_code: "customer_request",
|
|
235
|
+
idempotency_key: `idempotency_${testCase.amount}`,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
mockPolicyVerification({
|
|
239
|
+
allow: testCase.valid,
|
|
240
|
+
reasons: testCase.valid
|
|
241
|
+
? []
|
|
242
|
+
: [{ code: "invalid_amount", message: testCase.reason }],
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
246
|
+
const result = await verifyPolicy(
|
|
247
|
+
"agent_123",
|
|
248
|
+
"finance.payment.refund.v1",
|
|
249
|
+
context
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
expect(result.allowed).toBe(testCase.valid);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("Assurance Level Requirements", () => {
|
|
258
|
+
test("should require L2 for amounts <= $100", async () => {
|
|
259
|
+
const context = {
|
|
260
|
+
order_id: "ORD-12345",
|
|
261
|
+
customer_id: "CUST-67890",
|
|
262
|
+
amount_minor: 10000, // $100
|
|
263
|
+
currency: "USD",
|
|
264
|
+
region: "US",
|
|
265
|
+
reason_code: "customer_request",
|
|
266
|
+
idempotency_key: "idempotency_l2",
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Test with L2 assurance (should pass)
|
|
270
|
+
mockPolicyVerification({
|
|
271
|
+
allow: true,
|
|
272
|
+
decision_id: "dec_l2_pass",
|
|
273
|
+
remaining_daily_cap: { USD: 25000 },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
277
|
+
const result = await verifyPolicy(
|
|
278
|
+
"agent_123",
|
|
279
|
+
"finance.payment.refund.v1",
|
|
280
|
+
context
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
expect(result.allowed).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("should require L3 for amounts $100-$500", async () => {
|
|
287
|
+
const context = {
|
|
288
|
+
order_id: "ORD-12345",
|
|
289
|
+
customer_id: "CUST-67890",
|
|
290
|
+
amount_minor: 25000, // $250
|
|
291
|
+
currency: "USD",
|
|
292
|
+
region: "US",
|
|
293
|
+
reason_code: "customer_request",
|
|
294
|
+
idempotency_key: "idempotency_l3",
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Test with L2 assurance (should fail)
|
|
298
|
+
mockPolicyVerification({
|
|
299
|
+
allow: false,
|
|
300
|
+
reasons: [
|
|
301
|
+
{
|
|
302
|
+
code: "assurance_too_low",
|
|
303
|
+
message:
|
|
304
|
+
"Refund amount 25000 USD requires L3 assurance level, but agent has L2",
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
310
|
+
const result = await verifyPolicy(
|
|
311
|
+
"agent_123",
|
|
312
|
+
"finance.payment.refund.v1",
|
|
313
|
+
context
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
expect(result.allowed).toBe(false);
|
|
317
|
+
expect(result.error?.violations[0].code).toBe("assurance_too_low");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("should deny amounts > $500 in v1", async () => {
|
|
321
|
+
const context = {
|
|
322
|
+
order_id: "ORD-12345",
|
|
323
|
+
customer_id: "CUST-67890",
|
|
324
|
+
amount_minor: 60000, // $600
|
|
325
|
+
currency: "USD",
|
|
326
|
+
region: "US",
|
|
327
|
+
reason_code: "customer_request",
|
|
328
|
+
idempotency_key: "idempotency_deny",
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
mockPolicyVerification({
|
|
332
|
+
allow: false,
|
|
333
|
+
reasons: [
|
|
334
|
+
{
|
|
335
|
+
code: "assurance_too_low",
|
|
336
|
+
message:
|
|
337
|
+
"Refund amount 60000 USD requires L4 assurance level, but agent has L3",
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
343
|
+
const result = await verifyPolicy(
|
|
344
|
+
"agent_123",
|
|
345
|
+
"finance.payment.refund.v1",
|
|
346
|
+
context
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect(result.allowed).toBe(false);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe("Idempotency Protection", () => {
|
|
354
|
+
test("should allow first request with idempotency key", async () => {
|
|
355
|
+
const context = {
|
|
356
|
+
order_id: "ORD-12345",
|
|
357
|
+
customer_id: "CUST-67890",
|
|
358
|
+
amount_minor: 5000,
|
|
359
|
+
currency: "USD",
|
|
360
|
+
region: "US",
|
|
361
|
+
reason_code: "customer_request",
|
|
362
|
+
idempotency_key: "unique_key_123",
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
mockPolicyVerification({
|
|
366
|
+
allow: true,
|
|
367
|
+
decision_id: "dec_first",
|
|
368
|
+
remaining_daily_cap: { USD: 25000 },
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
372
|
+
const result = await verifyPolicy(
|
|
373
|
+
"agent_123",
|
|
374
|
+
"finance.payment.refund.v1",
|
|
375
|
+
context
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
expect(result.allowed).toBe(true);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("should deny duplicate idempotency key", async () => {
|
|
382
|
+
const context = {
|
|
383
|
+
order_id: "ORD-12345",
|
|
384
|
+
customer_id: "CUST-67890",
|
|
385
|
+
amount_minor: 5000,
|
|
386
|
+
currency: "USD",
|
|
387
|
+
region: "US",
|
|
388
|
+
reason_code: "customer_request",
|
|
389
|
+
idempotency_key: "duplicate_key_123",
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
mockPolicyVerification({
|
|
393
|
+
allow: false,
|
|
394
|
+
reasons: [
|
|
395
|
+
{
|
|
396
|
+
code: "idempotency_replay",
|
|
397
|
+
message:
|
|
398
|
+
"Duplicate idempotency key detected. Previous decision: dec_duplicate",
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
404
|
+
const result = await verifyPolicy(
|
|
405
|
+
"agent_123",
|
|
406
|
+
"finance.payment.refund.v1",
|
|
407
|
+
context
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
expect(result.allowed).toBe(false);
|
|
411
|
+
expect(result.error?.violations[0].code).toBe("idempotency_replay");
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe("Daily Cap Enforcement", () => {
|
|
416
|
+
test("should allow refund within daily cap", async () => {
|
|
417
|
+
const context = {
|
|
418
|
+
order_id: "ORD-12345",
|
|
419
|
+
customer_id: "CUST-67890",
|
|
420
|
+
amount_minor: 5000,
|
|
421
|
+
currency: "USD",
|
|
422
|
+
region: "US",
|
|
423
|
+
reason_code: "customer_request",
|
|
424
|
+
idempotency_key: "daily_cap_test",
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
mockPolicyVerification({
|
|
428
|
+
allow: true,
|
|
429
|
+
decision_id: "dec_daily_cap",
|
|
430
|
+
remaining_daily_cap: { USD: 20000 },
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
434
|
+
const result = await verifyPolicy(
|
|
435
|
+
"agent_123",
|
|
436
|
+
"finance.payment.refund.v1",
|
|
437
|
+
context
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(result.allowed).toBe(true);
|
|
441
|
+
expect(result.result?.evaluation?.remaining_daily_cap?.USD).toBe(20000);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("should deny refund exceeding daily cap", async () => {
|
|
445
|
+
const context = {
|
|
446
|
+
order_id: "ORD-12345",
|
|
447
|
+
customer_id: "CUST-67890",
|
|
448
|
+
amount_minor: 5000,
|
|
449
|
+
currency: "USD",
|
|
450
|
+
region: "US",
|
|
451
|
+
reason_code: "customer_request",
|
|
452
|
+
idempotency_key: "daily_cap_exceeded",
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
mockPolicyVerification({
|
|
456
|
+
allow: false,
|
|
457
|
+
reasons: [
|
|
458
|
+
{
|
|
459
|
+
code: "daily_cap_exceeded",
|
|
460
|
+
message:
|
|
461
|
+
"Daily cap 25000 USD exceeded for USD; current 23000 + 5000 > 25000",
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
remaining_daily_cap: { USD: 2000 },
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
468
|
+
const result = await verifyPolicy(
|
|
469
|
+
"agent_123",
|
|
470
|
+
"finance.payment.refund.v1",
|
|
471
|
+
context
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
expect(result.allowed).toBe(false);
|
|
475
|
+
expect(result.error?.violations[0].code).toBe("daily_cap_exceeded");
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe("Cross-Currency Protection", () => {
|
|
480
|
+
test("should deny cross-currency refunds", async () => {
|
|
481
|
+
const context = {
|
|
482
|
+
order_id: "ORD-12345",
|
|
483
|
+
customer_id: "CUST-67890",
|
|
484
|
+
amount_minor: 5000,
|
|
485
|
+
currency: "USD",
|
|
486
|
+
order_currency: "EUR", // Different from refund currency
|
|
487
|
+
region: "US",
|
|
488
|
+
reason_code: "customer_request",
|
|
489
|
+
idempotency_key: "cross_currency_test",
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
mockPolicyVerification({
|
|
493
|
+
allow: false,
|
|
494
|
+
reasons: [
|
|
495
|
+
{
|
|
496
|
+
code: "cross_currency_denied",
|
|
497
|
+
message: "Cross-currency refunds are not supported in v1",
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
503
|
+
const result = await verifyPolicy(
|
|
504
|
+
"agent_123",
|
|
505
|
+
"finance.payment.refund.v1",
|
|
506
|
+
context
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
expect(result.allowed).toBe(false);
|
|
510
|
+
expect(result.error?.violations[0].code).toBe("cross_currency_denied");
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
describe("Order Balance Validation", () => {
|
|
515
|
+
test("should allow refund within order balance", async () => {
|
|
516
|
+
const context = {
|
|
517
|
+
order_id: "ORD-12345",
|
|
518
|
+
customer_id: "CUST-67890",
|
|
519
|
+
amount_minor: 5000,
|
|
520
|
+
currency: "USD",
|
|
521
|
+
order_total_minor: 10000,
|
|
522
|
+
already_refunded_minor: 2000,
|
|
523
|
+
region: "US",
|
|
524
|
+
reason_code: "customer_request",
|
|
525
|
+
idempotency_key: "balance_valid",
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
mockPolicyVerification({
|
|
529
|
+
allow: true,
|
|
530
|
+
decision_id: "dec_balance_valid",
|
|
531
|
+
remaining_daily_cap: { USD: 25000 },
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
535
|
+
const result = await verifyPolicy(
|
|
536
|
+
"agent_123",
|
|
537
|
+
"finance.payment.refund.v1",
|
|
538
|
+
context
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
expect(result.allowed).toBe(true);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("should deny refund exceeding order balance", async () => {
|
|
545
|
+
const context = {
|
|
546
|
+
order_id: "ORD-12345",
|
|
547
|
+
customer_id: "CUST-67890",
|
|
548
|
+
amount_minor: 5000,
|
|
549
|
+
currency: "USD",
|
|
550
|
+
order_total_minor: 10000,
|
|
551
|
+
already_refunded_minor: 8000, // Only 2000 remaining
|
|
552
|
+
region: "US",
|
|
553
|
+
reason_code: "customer_request",
|
|
554
|
+
idempotency_key: "balance_exceeded",
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
mockPolicyVerification({
|
|
558
|
+
allow: false,
|
|
559
|
+
reasons: [
|
|
560
|
+
{
|
|
561
|
+
code: "order_balance_exceeded",
|
|
562
|
+
message: "Refund amount 5000 exceeds remaining order balance 2000",
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
568
|
+
const result = await verifyPolicy(
|
|
569
|
+
"agent_123",
|
|
570
|
+
"finance.payment.refund.v1",
|
|
571
|
+
context
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
expect(result.allowed).toBe(false);
|
|
575
|
+
expect(result.error?.violations[0].code).toBe("order_balance_exceeded");
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
describe("Region Validation", () => {
|
|
580
|
+
test("should allow refund in allowed region", async () => {
|
|
581
|
+
const context = {
|
|
582
|
+
order_id: "ORD-12345",
|
|
583
|
+
customer_id: "CUST-67890",
|
|
584
|
+
amount_minor: 5000,
|
|
585
|
+
currency: "USD",
|
|
586
|
+
region: "US",
|
|
587
|
+
reason_code: "customer_request",
|
|
588
|
+
idempotency_key: "region_valid",
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
mockPolicyVerification({
|
|
592
|
+
allow: true,
|
|
593
|
+
decision_id: "dec_region_valid",
|
|
594
|
+
remaining_daily_cap: { USD: 25000 },
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
598
|
+
const result = await verifyPolicy(
|
|
599
|
+
"agent_123",
|
|
600
|
+
"finance.payment.refund.v1",
|
|
601
|
+
context
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
expect(result.allowed).toBe(true);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test("should deny refund in disallowed region", async () => {
|
|
608
|
+
const context = {
|
|
609
|
+
order_id: "ORD-12345",
|
|
610
|
+
customer_id: "CUST-67890",
|
|
611
|
+
amount_minor: 5000,
|
|
612
|
+
currency: "USD",
|
|
613
|
+
region: "RESTRICTED",
|
|
614
|
+
reason_code: "customer_request",
|
|
615
|
+
idempotency_key: "region_invalid",
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
mockPolicyVerification({
|
|
619
|
+
allow: false,
|
|
620
|
+
reasons: [
|
|
621
|
+
{
|
|
622
|
+
code: "region_not_allowed",
|
|
623
|
+
message: "Region RESTRICTED is not allowed for this agent",
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
629
|
+
const result = await verifyPolicy(
|
|
630
|
+
"agent_123",
|
|
631
|
+
"finance.payment.refund.v1",
|
|
632
|
+
context
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
expect(result.allowed).toBe(false);
|
|
636
|
+
expect(result.error?.violations[0].code).toBe("region_not_allowed");
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
describe("Reason Code Validation", () => {
|
|
641
|
+
test("should allow valid reason codes", async () => {
|
|
642
|
+
const validReasonCodes = [
|
|
643
|
+
"customer_request",
|
|
644
|
+
"defective",
|
|
645
|
+
"not_as_described",
|
|
646
|
+
"duplicate",
|
|
647
|
+
"fraud",
|
|
648
|
+
];
|
|
649
|
+
|
|
650
|
+
for (const reasonCode of validReasonCodes) {
|
|
651
|
+
const context = {
|
|
652
|
+
order_id: "ORD-12345",
|
|
653
|
+
customer_id: "CUST-67890",
|
|
654
|
+
amount_minor: 5000,
|
|
655
|
+
currency: "USD",
|
|
656
|
+
region: "US",
|
|
657
|
+
reason_code: reasonCode,
|
|
658
|
+
idempotency_key: `reason_${reasonCode}`,
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
mockPolicyVerification({
|
|
662
|
+
allow: true,
|
|
663
|
+
decision_id: `dec_${reasonCode}`,
|
|
664
|
+
remaining_daily_cap: { USD: 25000 },
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
668
|
+
const result = await verifyPolicy(
|
|
669
|
+
"agent_123",
|
|
670
|
+
"finance.payment.refund.v1",
|
|
671
|
+
context
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
expect(result.allowed).toBe(true);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
test("should deny invalid reason codes", async () => {
|
|
679
|
+
const context = {
|
|
680
|
+
order_id: "ORD-12345",
|
|
681
|
+
customer_id: "CUST-67890",
|
|
682
|
+
amount_minor: 5000,
|
|
683
|
+
currency: "USD",
|
|
684
|
+
region: "US",
|
|
685
|
+
reason_code: "invalid_reason",
|
|
686
|
+
idempotency_key: "invalid_reason_test",
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
mockPolicyVerification({
|
|
690
|
+
allow: false,
|
|
691
|
+
reasons: [
|
|
692
|
+
{
|
|
693
|
+
code: "reason_code_invalid",
|
|
694
|
+
message: "Reason code invalid_reason is not supported",
|
|
695
|
+
},
|
|
696
|
+
],
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
700
|
+
const result = await verifyPolicy(
|
|
701
|
+
"agent_123",
|
|
702
|
+
"finance.payment.refund.v1",
|
|
703
|
+
context
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
expect(result.allowed).toBe(false);
|
|
707
|
+
expect(result.error?.violations[0].code).toBe("reason_code_invalid");
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
describe("Error Handling", () => {
|
|
712
|
+
test("should handle policy verification failures gracefully", async () => {
|
|
713
|
+
mockPolicyVerificationError(500);
|
|
714
|
+
|
|
715
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
716
|
+
const result = await verifyPolicy(
|
|
717
|
+
"agent_123",
|
|
718
|
+
"finance.payment.refund.v1",
|
|
719
|
+
{}
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
expect(result.allowed).toBe(false);
|
|
723
|
+
expect(result.error?.code).toBe("policy_verification_failed");
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test("should handle network errors", async () => {
|
|
727
|
+
global.fetch = jest.fn().mockRejectedValue(new Error("Network error"));
|
|
728
|
+
|
|
729
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
730
|
+
const result = await verifyPolicy(
|
|
731
|
+
"agent_123",
|
|
732
|
+
"finance.payment.refund.v1",
|
|
733
|
+
{}
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
expect(result.allowed).toBe(false);
|
|
737
|
+
expect(result.error?.code).toBe("policy_verification_error");
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
describe("Edge Cases and Fraud Prevention", () => {
|
|
742
|
+
test("should prevent extremely large amounts", async () => {
|
|
743
|
+
const context = {
|
|
744
|
+
order_id: "ORD-12345",
|
|
745
|
+
customer_id: "CUST-67890",
|
|
746
|
+
amount_minor: Number.MAX_SAFE_INTEGER,
|
|
747
|
+
currency: "USD",
|
|
748
|
+
region: "US",
|
|
749
|
+
reason_code: "customer_request",
|
|
750
|
+
idempotency_key: "extreme_amount",
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
mockPolicyVerification({
|
|
754
|
+
allow: false,
|
|
755
|
+
reasons: [
|
|
756
|
+
{
|
|
757
|
+
code: "invalid_amount",
|
|
758
|
+
message: "Amount exceeds maximum allowed amount",
|
|
759
|
+
},
|
|
760
|
+
],
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
764
|
+
const result = await verifyPolicy(
|
|
765
|
+
"agent_123",
|
|
766
|
+
"finance.payment.refund.v1",
|
|
767
|
+
context
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
expect(result.allowed).toBe(false);
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
test("should prevent negative amounts", async () => {
|
|
774
|
+
const context = {
|
|
775
|
+
order_id: "ORD-12345",
|
|
776
|
+
customer_id: "CUST-67890",
|
|
777
|
+
amount_minor: -1000,
|
|
778
|
+
currency: "USD",
|
|
779
|
+
region: "US",
|
|
780
|
+
reason_code: "customer_request",
|
|
781
|
+
idempotency_key: "negative_amount",
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
mockPolicyVerification({
|
|
785
|
+
allow: false,
|
|
786
|
+
reasons: [
|
|
787
|
+
{ code: "invalid_amount", message: "Amount must be positive" },
|
|
788
|
+
],
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
792
|
+
const result = await verifyPolicy(
|
|
793
|
+
"agent_123",
|
|
794
|
+
"finance.payment.refund.v1",
|
|
795
|
+
context
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
expect(result.allowed).toBe(false);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test("should prevent zero amounts", async () => {
|
|
802
|
+
const context = {
|
|
803
|
+
order_id: "ORD-12345",
|
|
804
|
+
customer_id: "CUST-67890",
|
|
805
|
+
amount_minor: 0,
|
|
806
|
+
currency: "USD",
|
|
807
|
+
region: "US",
|
|
808
|
+
reason_code: "customer_request",
|
|
809
|
+
idempotency_key: "zero_amount",
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
mockPolicyVerification({
|
|
813
|
+
allow: false,
|
|
814
|
+
reasons: [
|
|
815
|
+
{ code: "invalid_amount", message: "Amount must be positive" },
|
|
816
|
+
],
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
820
|
+
const result = await verifyPolicy(
|
|
821
|
+
"agent_123",
|
|
822
|
+
"finance.payment.refund.v1",
|
|
823
|
+
context
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
expect(result.allowed).toBe(false);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("should validate idempotency key format", async () => {
|
|
830
|
+
const invalidKeys = [
|
|
831
|
+
"",
|
|
832
|
+
"a",
|
|
833
|
+
"a".repeat(100),
|
|
834
|
+
"invalid@key",
|
|
835
|
+
"key with spaces",
|
|
836
|
+
];
|
|
837
|
+
|
|
838
|
+
for (const key of invalidKeys) {
|
|
839
|
+
const context = {
|
|
840
|
+
order_id: "ORD-12345",
|
|
841
|
+
customer_id: "CUST-67890",
|
|
842
|
+
amount_minor: 5000,
|
|
843
|
+
currency: "USD",
|
|
844
|
+
region: "US",
|
|
845
|
+
reason_code: "customer_request",
|
|
846
|
+
idempotency_key: key,
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
mockPolicyVerification({
|
|
850
|
+
allow: false,
|
|
851
|
+
reasons: [
|
|
852
|
+
{
|
|
853
|
+
code: "invalid_idempotency_key",
|
|
854
|
+
message: "Invalid idempotency key format",
|
|
855
|
+
},
|
|
856
|
+
],
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
const { verifyPolicy } = require("../sdk/node/src/policy-enforcement");
|
|
860
|
+
const result = await verifyPolicy(
|
|
861
|
+
"agent_123",
|
|
862
|
+
"finance.payment.refund.v1",
|
|
863
|
+
context
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
expect(result.allowed).toBe(false);
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
describe("Refunds v1 Policy - Integration Tests", () => {
|
|
873
|
+
describe("Express Middleware Integration", () => {
|
|
874
|
+
test("should work with Express middleware", async () => {
|
|
875
|
+
const express = require("express");
|
|
876
|
+
const {
|
|
877
|
+
requireRefundsPolicy,
|
|
878
|
+
} = require("../middleware/express/src/index");
|
|
879
|
+
|
|
880
|
+
const app = express();
|
|
881
|
+
app.use(express.json());
|
|
882
|
+
|
|
883
|
+
app.post("/refund", requireRefundsPolicy("agent_123"), (req, res) => {
|
|
884
|
+
res.json({ success: true, refund_id: "ref_123" });
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// Mock the policy verification
|
|
888
|
+
mockPolicyVerification({
|
|
889
|
+
allow: true,
|
|
890
|
+
decision_id: "dec_123",
|
|
891
|
+
remaining_daily_cap: { USD: 25000 },
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
const request = require("supertest")(app);
|
|
895
|
+
const response = await request.post("/refund").send({
|
|
896
|
+
order_id: "ORD-12345",
|
|
897
|
+
customer_id: "CUST-67890",
|
|
898
|
+
amount_minor: 5000,
|
|
899
|
+
currency: "USD",
|
|
900
|
+
region: "US",
|
|
901
|
+
reason_code: "customer_request",
|
|
902
|
+
idempotency_key: "test_key_123",
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
expect(response.status).toBe(200);
|
|
906
|
+
expect(response.body.success).toBe(true);
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
describe("FastAPI Middleware Integration", () => {
|
|
911
|
+
test("should work with FastAPI middleware", async () => {
|
|
912
|
+
// This would require a more complex setup with FastAPI test client
|
|
913
|
+
// For now, we'll just verify the function exists and has the right signature
|
|
914
|
+
const {
|
|
915
|
+
require_refunds_policy,
|
|
916
|
+
} = require("../middleware/fastapi/src/aporthq_middleware_fastapi/middleware_v2");
|
|
917
|
+
|
|
918
|
+
expect(typeof require_refunds_policy).toBe("function");
|
|
919
|
+
|
|
920
|
+
const dependency = require_refunds_policy("agent_123", true, true);
|
|
921
|
+
expect(typeof dependency).toBe("function");
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
});
|