1import * as AWS from "@/AWS";
2import { App } from "@/AWS/Amplify";
3import * as Test from "@/Test/Alchemy";
4import * as amplify from "@distilled.cloud/aws/amplify";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9import { makeAmplifyTestLease } from "./TestLease.ts";
10
11const { test, beforeAll, afterAll } = Test.make({ providers: AWS.providers() });
12const testLease = makeAmplifyTestLease();
13
14beforeAll(testLease.acquire, { timeout: 240_000 });
15afterAll(testLease.release);
16
17const findApp = (appId: string) =>
18 amplify.getApp({ appId }).pipe(
19 Effect.map((r) => r.app),
20 Effect.catchTag("NotFoundException", () => Effect.succeed(undefined)),
21 );
22
23class AppStillExists extends Data.TaggedError("AppStillExists")<{
24 readonly appId: string;
25}> {}
26
27const assertAppDeleted = (appId: string) =>
28 findApp(appId).pipe(
29 Effect.flatMap((app) =>
30 app === undefined
31 ? Effect.void
32 : Effect.fail(new AppStillExists({ appId })),
33 ),
34 Effect.retry({
35 while: (e) => e._tag === "AppStillExists",
36 schedule: Schedule.max([
37 Schedule.spaced("2 seconds"),
38 Schedule.recurs(15),
39 ]),
40 }),
41 );
42
43// Amplify apps normally connect a Git repository, but that requires an OAuth
44// handshake (CodeConnections / personal token) that is human-in-the-loop and
45// cannot be automated from infrastructure code. This test therefore exercises
46// the repo-less path: create the app container, assert it exists, update it,
47// and delete it. Connecting a repo + branches is a console/CLI follow-up.
48test.provider(
49 "create an Amplify app without a connected repo, update, and delete",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 const app = yield* stack.deploy(
55 Effect.gen(function* () {
56 return yield* App("TestAmplifyApp", {
57 description: "initial description",
58 platform: "WEB",
59 environmentVariables: { STAGE: "test" },
60 tags: { Environment: "test" },
61 });
62 }),
63 );
64
65 expect(app.appId).toBeDefined();
66 expect(app.appArn).toContain(":apps/");
67 expect(app.defaultDomain).toContain("amplifyapp.com");
68
69 const live = yield* findApp(app.appId);
70 expect(live?.description).toBe("initial description");
71 expect(live?.repository ?? "").toBe("");
72 expect(live?.tags?.["alchemy::id"]).toBe("TestAmplifyApp");
73
74 // Update description + env vars.
75 const updated = yield* stack.deploy(
76 Effect.gen(function* () {
77 return yield* App("TestAmplifyApp", {
78 description: "updated description",
79 platform: "WEB",
80 environmentVariables: { STAGE: "test", REGION: "us-west-2" },
81 tags: { Environment: "test", Extra: "yes" },
82 });
83 }),
84 );
85 expect(updated.appId).toBe(app.appId);
86
87 const live2 = yield* findApp(app.appId);
88 expect(live2?.description).toBe("updated description");
89 expect(live2?.environmentVariables?.REGION).toBe("us-west-2");
90 expect(live2?.tags?.Extra).toBe("yes");
91
92 yield* stack.destroy();
93 yield* assertAppDeleted(app.appId);
94 }),
95 { tags: ["provider:aws", "provider:aws:amplify", "live"], timeout: 180_000 },
96);