1import * as AWS from "@/AWS";
2import { Capability, Partnership, Profile, Transformer } from "@/AWS/B2BI";
3import type { PolicyStatement } from "@/AWS/IAM";
4import { Bucket } from "@/AWS/S3";
5import * as Test from "@/Test/Alchemy";
6import * as b2bi from "@distilled.cloud/aws/b2bi";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import * as Schedule from "effect/Schedule";
10
11const { test } = Test.make({ providers: AWS.providers() });
12
13const BUCKET = "alchemy-test-b2bi-partnership";
14const BUCKET_ARN = `arn:aws:s3:::${BUCKET}`;
15
16const b2biBucketPolicy: PolicyStatement[] = [
17 {
18 Effect: "Allow",
19 Principal: { Service: "b2bi.amazonaws.com" },
20 Action: [
21 "s3:GetObject",
22 "s3:PutObject",
23 "s3:DeleteObject",
24 "s3:ListBucket",
25 "s3:GetBucketLocation",
26 "s3:AbortMultipartUpload",
27 ],
28 Resource: [BUCKET_ARN, `${BUCKET_ARN}/*`],
29 },
30];
31
32const partnershipProgram = (partnershipName: string) =>
33 Effect.gen(function* () {
34 const bucket = yield* Bucket("PartnershipBucket", {
35 bucketName: BUCKET,
36 forceDestroy: true,
37 policy: b2biBucketPolicy,
38 });
39 const profile = yield* Profile("TradingProfile", {
40 name: "alchemy-b2bi-pt-profile",
41 businessName: "Alchemy Trading Corp",
42 phone: "+15555550100",
43 email: "edi@alchemy.example",
44 });
45 const transformer = yield* Transformer("PtTransformer", {
46 name: "alchemy-b2bi-pt-transformer",
47 status: "active",
48 inputConversion: {
49 fromFormat: "X12",
50 formatOptions: {
51 x12: { transactionSet: "X12_850", version: "VERSION_4010" },
52 },
53 },
54 mapping: {
55 templateLanguage: "JSONATA",
56 template: '{ "orderId": "test" }',
57 },
58 });
59 const capability = yield* Capability("PtCapability", {
60 name: "alchemy-b2bi-pt-capability",
61 configuration: {
62 edi: {
63 capabilityDirection: "INBOUND",
64 type: {
65 x12Details: {
66 transactionSet: "X12_850",
67 version: "VERSION_4010",
68 },
69 },
70 inputLocation: { bucketName: bucket.bucketName, key: "inbound/" },
71 outputLocation: {
72 bucketName: bucket.bucketName,
73 key: "processed/",
74 },
75 transformerId: transformer.transformerId,
76 },
77 },
78 });
79 return yield* Partnership("TradingPartner", {
80 profileId: profile.profileId,
81 name: partnershipName,
82 email: "partner@alchemy.example",
83 phone: "+15555550101",
84 capabilities: [capability.capabilityId],
85 });
86 });
87
88const assertPartnershipGone = (partnershipId: string) =>
89 Effect.gen(function* () {
90 const result = yield* b2bi.getPartnership({ partnershipId }).pipe(
91 Effect.map(() => "present" as const),
92 Effect.catchTag("ResourceNotFoundException", () =>
93 Effect.succeed("gone" as const),
94 ),
95 );
96 if (result === "present") {
97 return yield* Effect.fail(
98 new Error(`Partnership '${partnershipId}' still exists`),
99 );
100 }
101 }).pipe(
102 Effect.retry({
103 schedule: Schedule.max([Schedule.fixed("2 seconds"), Schedule.recurs(8)]),
104 }),
105 );
106
107// The full chain (profile → transformer → capability → partnership) is
108// credential-free, so the lifecycle runs ungated.
109test.provider(
110 "create, update, and destroy a B2BI partnership",
111 (stack) =>
112 Effect.gen(function* () {
113 yield* stack.destroy();
114
115 // Create.
116 const created = yield* stack.deploy(
117 partnershipProgram("alchemy-b2bi-partnership"),
118 );
119 expect(created.partnershipId).toMatch(/^ps-/);
120 expect(created.profileId).toMatch(/^p-/);
121 expect(created.partnershipArn).toContain(":b2bi:");
122
123 // Out-of-band verification.
124 const described = yield* b2bi.getPartnership({
125 partnershipId: created.partnershipId,
126 });
127 expect(described.name).toBe("alchemy-b2bi-partnership");
128 expect(described.capabilities ?? []).toHaveLength(1);
129
130 // Update the partnership name in place.
131 const updated = yield* stack.deploy(
132 partnershipProgram("alchemy-b2bi-partnership-renamed"),
133 );
134 expect(updated.partnershipId).toBe(created.partnershipId);
135 const reDescribed = yield* b2bi.getPartnership({
136 partnershipId: created.partnershipId,
137 });
138 expect(reDescribed.name).toBe("alchemy-b2bi-partnership-renamed");
139
140 // Destroy the whole chain and verify.
141 yield* stack.destroy();
142 yield* assertPartnershipGone(created.partnershipId);
143 }),
144 {
145 tags: ["provider:aws", "provider:aws:b2bi", "provider:aws:s3", "live"],
146 timeout: 150_000,
147 },
148);