1import * as machines from "@distilled.cloud/fly-io/machines";
2import * as Fly from "@/Fly";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: Fly.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const waitUntilGone = (appName: string) =>
18 machines.getApp({ app_name: appName }).pipe(
19 Effect.as("found" as const),
20 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
21 Effect.repeat({
22 schedule: Schedule.spaced("1 second"),
23 until: (status) => status === "gone",
24 times: 10,
25 }),
26 );
27
28test.provider(
29 "create, update, and delete an app",
30 (stack) =>
31 Effect.gen(function* () {
32 yield* stack.destroy();
33
34 const created = yield* stack.deploy(
35 Effect.gen(function* () {
36 return yield* Fly.App("Site", {
37 enableSubdomains: true,
38 });
39 }),
40 );
41
42 expect(created.appId).toEqual(expect.any(String));
43 expect(created.appName).toEqual(expect.any(String));
44 expect(created.appName.length).toBeGreaterThan(0);
45 expect(created.appName.length).toBeLessThanOrEqual(30);
46 expect(created.appName).toMatch(/^[a-z][a-z0-9-]*$/);
47 expect(created.url).toEqual(`https://${created.appName}.fly.dev`);
48 expect(created.orgSlug).toEqual(expect.any(String));
49
50 const fetched = yield* machines.getApp({
51 app_name: created.appName,
52 });
53 expect(fetched.name).toEqual(created.appName);
54 expect(fetched.id).toEqual(created.appId);
55
56 const updated = yield* stack.deploy(
57 Effect.gen(function* () {
58 return yield* Fly.App("Site", {
59 enableSubdomains: false,
60 });
61 }),
62 );
63
64 expect(updated.appId).toEqual(created.appId);
65 expect(updated.appName).toEqual(created.appName);
66 expect(updated.url).toEqual(created.url);
67
68 yield* stack.destroy();
69
70 const gone = yield* waitUntilGone(created.appName);
71 expect(gone).toEqual("gone");
72 }).pipe(logLevel),
73 {
74 tags: ["provider:fly", "provider:fly:app", "provider:fly:machine", "live"],
75 timeout: 120_000,
76 },
77);
78
79test.provider(
80 "replace when name changes",
81 (stack) =>
82 Effect.gen(function* () {
83 yield* stack.destroy();
84
85 const created = yield* stack.deploy(
86 Effect.gen(function* () {
87 return yield* Fly.App("ReplaceSite");
88 }),
89 );
90
91 const nextName =
92 created.appName.slice(0, -1) +
93 (created.appName.endsWith("z") ? "y" : "z");
94
95 const replaced = yield* stack.deploy(
96 Effect.gen(function* () {
97 return yield* Fly.App("ReplaceSite", {
98 name: nextName,
99 });
100 }),
101 );
102
103 expect(replaced.appName).toEqual(nextName);
104 expect(replaced.appName).not.toEqual(created.appName);
105 expect(replaced.url).toEqual(`https://${nextName}.fly.dev`);
106
107 const fetched = yield* machines.getApp({
108 app_name: replaced.appName,
109 });
110 expect(fetched.name).toEqual(replaced.appName);
111
112 const oldGone = yield* waitUntilGone(created.appName);
113 expect(oldGone).toEqual("gone");
114
115 yield* stack.destroy();
116
117 const gone = yield* waitUntilGone(replaced.appName);
118 expect(gone).toEqual("gone");
119 }).pipe(logLevel),
120 {
121 tags: ["provider:fly", "provider:fly:app", "provider:fly:machine", "live"],
122 timeout: 120_000,
123 },
124);
125
126test.provider(
127 "list enumerates the deployed app",
128 (stack) =>
129 Effect.gen(function* () {
130 yield* stack.destroy();
131
132 const deployed = yield* stack.deploy(
133 Effect.gen(function* () {
134 return yield* Fly.App("ListSite");
135 }),
136 );
137
138 const provider = yield* Provider.findProvider(Fly.App);
139 const all = yield* provider.list();
140 const found = all.find((app) => app.appName === deployed.appName);
141 expect(found).toBeDefined();
142 expect(found?.appId).toEqual(deployed.appId);
143 expect(found?.url).toEqual(deployed.url);
144
145 yield* stack.destroy();
146
147 const gone = yield* waitUntilGone(deployed.appName);
148 expect(gone).toEqual("gone");
149 }).pipe(logLevel),
150 {
151 tags: ["provider:fly", "provider:fly:app", "provider:fly:machine", "live"],
152 timeout: 120_000,
153 },
154);