1import * as AWS from "@/AWS";
2import { ConnectorProfile } from "@/AWS/AppFlow";
3import * as Test from "@/Test/Alchemy";
4import * as appflow from "@distilled.cloud/aws/appflow";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10// Every AppFlow connector validates the vendor connection at
11// createConnectorProfile time (verified live for Salesforce and Snowflake),
12// so a connector profile cannot be provisioned without real vendor
13// credentials. The ungated probes below pin the typed error the API returns
14// for an unreachable/unauthorized connector — the exact failure mode the
15// gated lifecycle hits without credentials.
16
17test.provider(
18 "createConnectorProfile with an unreachable connector fails with a typed error",
19 () =>
20 Effect.gen(function* () {
21 const error = yield* Effect.flip(
22 appflow.createConnectorProfile({
23 connectorProfileName: "alchemy-test-appflow-cp-probe",
24 connectorType: "Salesforce",
25 connectionMode: "Public",
26 connectorProfileConfig: {
27 connectorProfileProperties: {
28 Salesforce: {
29 instanceUrl: "https://invalid-example.my.salesforce.com",
30 },
31 },
32 connectorProfileCredentials: {
33 Salesforce: {
34 accessToken: "bogus-access-token",
35 refreshToken: "bogus-refresh-token",
36 },
37 },
38 },
39 }),
40 );
41 // ConnectorServerException (unreachable host) is the observed tag;
42 // ConnectorAuthenticationException covers a reachable-but-unauthorized
43 // vendor. Both are typed via the distilled patch for this operation.
44 expect([
45 "ConnectorServerException",
46 "ConnectorAuthenticationException",
47 ]).toContain(error._tag);
48 }),
49 { tags: ["provider:aws", "provider:aws:appflow", "live"] },
50);
51
52test.provider(
53 "describeConnectorProfiles returns an empty list for an unknown name",
54 () =>
55 Effect.gen(function* () {
56 const response = yield* appflow.describeConnectorProfiles({
57 connectorProfileNames: ["alchemy-test-appflow-cp-missing"],
58 });
59 expect(response.connectorProfileDetails ?? []).toHaveLength(0);
60 }),
61 { tags: ["provider:aws", "provider:aws:appflow", "live"] },
62);
63
64// Full lifecycle requires real vendor credentials (human-in-the-loop OAuth
65// or a live Snowflake/Redshift endpoint). Entitled runs supply them via env:
66// AWS_TEST_APPFLOW_CONNECTOR=1
67// APPFLOW_SALESFORCE_INSTANCE_URL, APPFLOW_SALESFORCE_ACCESS_TOKEN,
68// APPFLOW_SALESFORCE_REFRESH_TOKEN
69test.provider.skipIf(!process.env.AWS_TEST_APPFLOW_CONNECTOR)(
70 "create, update, and destroy a Salesforce connector profile",
71 (stack) =>
72 Effect.gen(function* () {
73 yield* stack.destroy();
74
75 const props = {
76 connectorProfileName: "alchemy-test-appflow-cp",
77 connectorType: "Salesforce" as const,
78 connectionMode: "Public" as const,
79 connectorProfileConfig: {
80 connectorProfileProperties: {
81 Salesforce: {
82 instanceUrl: process.env.APPFLOW_SALESFORCE_INSTANCE_URL!,
83 },
84 },
85 connectorProfileCredentials: {
86 Salesforce: {
87 accessToken: process.env.APPFLOW_SALESFORCE_ACCESS_TOKEN!,
88 refreshToken: process.env.APPFLOW_SALESFORCE_REFRESH_TOKEN!,
89 },
90 },
91 },
92 };
93
94 const created = yield* stack.deploy(
95 ConnectorProfile("Salesforce", props),
96 );
97 expect(created.connectorProfileName).toBe("alchemy-test-appflow-cp");
98 expect(created.connectorProfileArn).toContain(":appflow:");
99 expect(created.connectorType).toBe("Salesforce");
100
101 // Out-of-band verification.
102 const described = yield* appflow.describeConnectorProfiles({
103 connectorProfileNames: ["alchemy-test-appflow-cp"],
104 });
105 expect(described.connectorProfileDetails ?? []).toHaveLength(1);
106
107 // No-op redeploy keeps the same ARN (credentials are re-pushed since
108 // they cannot be read back).
109 const noop = yield* stack.deploy(ConnectorProfile("Salesforce", props));
110 expect(noop.connectorProfileArn).toBe(created.connectorProfileArn);
111
112 yield* stack.destroy();
113 const gone = yield* appflow.describeConnectorProfiles({
114 connectorProfileNames: ["alchemy-test-appflow-cp"],
115 });
116 expect(gone.connectorProfileDetails ?? []).toHaveLength(0);
117 }),
118 { tags: ["provider:aws", "provider:aws:appflow", "live"], timeout: 120_000 },
119);