1import * as AWS from "@/AWS";
2import { SyncConfiguration } from "@/AWS/CodeConnections/SyncConfiguration.ts";
3import * as Test from "@/Test/Alchemy";
4import * as codeconnections from "@distilled.cloud/aws/codeconnections";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10// A sync configuration requires a repository link, which itself requires a
11// connection in the AVAILABLE state — and completing a connection's OAuth
12// handshake is a manual console step with no API. The ungated probe proves
13// the read wiring end-to-end (typed NotFound from the real service); the
14// full lifecycle is gated on a manually-provisioned repository link + role.
15const REPOSITORY_LINK_ID = process.env.AWS_TEST_CODECONNECTIONS_REPO_LINK_ID;
16const GIT_SYNC_ROLE_ARN = process.env.AWS_TEST_CODECONNECTIONS_SYNC_ROLE_ARN;
17
18test.provider(
19 "probe: reading a nonexistent sync configuration surfaces the typed NotFound",
20 () =>
21 Effect.gen(function* () {
22 const tag = yield* codeconnections
23 .getSyncConfiguration({
24 SyncType: "CFN_STACK_SYNC",
25 ResourceName: "alchemy-test-nonexistent-stack",
26 })
27 .pipe(
28 Effect.map(() => "Found" as const),
29 Effect.catchTag("ResourceNotFoundException", (e) =>
30 Effect.succeed(e._tag),
31 ),
32 );
33 expect(tag).toBe("ResourceNotFoundException");
34 }),
35 {
36 tags: ["provider:aws", "provider:aws:codeconnections", "live"],
37 timeout: 60_000,
38 },
39);
40
41test.provider.skipIf(!REPOSITORY_LINK_ID || !GIT_SYNC_ROLE_ARN)(
42 "lifecycle: create stack sync, update trigger, destroy (gated: AWS_TEST_CODECONNECTIONS_REPO_LINK_ID)",
43 (stack) =>
44 Effect.gen(function* () {
45 yield* stack.destroy();
46
47 const deployed = yield* stack.deploy(
48 Effect.gen(function* () {
49 return yield* SyncConfiguration("StackSync", {
50 branch: "main",
51 configFile: "deployments/alchemy-test-stack.yaml",
52 repositoryLinkId: REPOSITORY_LINK_ID!,
53 resourceName: "alchemy-test-sync-stack",
54 roleArn: GIT_SYNC_ROLE_ARN!,
55 });
56 }),
57 );
58 expect(deployed.syncType).toBe("CFN_STACK_SYNC");
59 expect(deployed.resourceName).toBe("alchemy-test-sync-stack");
60 expect(deployed.branch).toBe("main");
61
62 // Update — trigger mode is mutable via UpdateSyncConfiguration.
63 const updated = yield* stack.deploy(
64 Effect.gen(function* () {
65 return yield* SyncConfiguration("StackSync", {
66 branch: "main",
67 configFile: "deployments/alchemy-test-stack.yaml",
68 repositoryLinkId: REPOSITORY_LINK_ID!,
69 resourceName: "alchemy-test-sync-stack",
70 roleArn: GIT_SYNC_ROLE_ARN!,
71 triggerResourceUpdateOn: "FILE_CHANGE",
72 });
73 }),
74 );
75 expect(updated.resourceName).toBe(deployed.resourceName);
76 const observed = yield* codeconnections.getSyncConfiguration({
77 SyncType: "CFN_STACK_SYNC",
78 ResourceName: "alchemy-test-sync-stack",
79 });
80 expect(observed.SyncConfiguration.TriggerResourceUpdateOn).toBe(
81 "FILE_CHANGE",
82 );
83
84 // Destroy — configuration is deleted; verify out-of-band.
85 yield* stack.destroy();
86 const after = yield* codeconnections
87 .getSyncConfiguration({
88 SyncType: "CFN_STACK_SYNC",
89 ResourceName: "alchemy-test-sync-stack",
90 })
91 .pipe(
92 Effect.map((res) => res.SyncConfiguration),
93 Effect.catchTag("ResourceNotFoundException", () =>
94 Effect.succeed(undefined),
95 ),
96 );
97 expect(after).toBeUndefined();
98 }),
99 {
100 tags: ["provider:aws", "provider:aws:codeconnections", "live"],
101 timeout: 120_000,
102 },
103);