1import * as Prisma from "@/Prisma";
2import * as Test from "@/Test/Alchemy";
3import {
4 getBranch,
5 getDatabase,
6 getProject,
7 getProjectBranches,
8 updateDatabase,
9} from "@distilled.cloud/prisma/management";
10import { expect } from "alchemy-test";
11import * as Effect from "effect/Effect";
12import * as Schedule from "effect/Schedule";
13
14const { test } = Test.make({ providers: Prisma.providers() });
15
16const expectDatabase = Effect.fn(function* (
17 database: Prisma.Database["Attributes"],
18 branchId: string,
19) {
20 const observed = yield* getDatabase({ databaseId: database.databaseId });
21 expect(observed.data.id).toBe(database.databaseId);
22 expect(observed.data.project.id).toBe(database.projectId);
23 expect(observed.data.name).toBe(database.databaseName);
24 expect(observed.data.branchId).toBe(branchId);
25 expect(database.branchId).toBe(branchId);
26});
27
28const expectGone = <E, R>(read: Effect.Effect<boolean, E, R>) =>
29 read.pipe(
30 Effect.repeat({
31 schedule: Schedule.spaced("1 second"),
32 times: 8,
33 until: (gone) => gone,
34 }),
35 Effect.tap((gone) => Effect.sync(() => expect(gone).toBe(true))),
36 );
37
38const expectDatabaseGone = (databaseId: string) =>
39 expectGone(
40 getDatabase({ databaseId }).pipe(
41 Effect.as(false),
42 Effect.catchTag("NotFound", () => Effect.succeed(true)),
43 ),
44 );
45
46const expectBranchGone = (branchId: string) =>
47 expectGone(
48 getBranch({ branchId }).pipe(
49 Effect.as(false),
50 Effect.catchTag("NotFound", () => Effect.succeed(true)),
51 ),
52 );
53
54const expectProjectGone = (id: string) =>
55 expectGone(
56 getProject({ id }).pipe(
57 Effect.as(false),
58 Effect.catchTag("NotFound", () => Effect.succeed(true)),
59 ),
60 );
61
62test.provider(
63 "attaches named and generated databases to the default branch and preserves it on updates",
64 Effect.fn(function* (stack: Test.ScratchStack) {
65 yield* stack.destroy();
66
67 const resources = (updated = false) =>
68 Effect.gen(function* () {
69 const project = yield* Prisma.Project("Project", {
70 createDatabase: false,
71 });
72 const generated = yield* Prisma.Database("Generated", {
73 project,
74 name: updated ? "generated-updated" : undefined,
75 });
76 const named = yield* Prisma.Database("Named", {
77 project,
78 name: updated ? "named-updated" : "named-database",
79 });
80 return { project, generated, named };
81 });
82
83 const initial = yield* stack.deploy(resources());
84 const branches = yield* getProjectBranches({
85 projectId: initial.project.projectId,
86 });
87 const defaults = branches.data.filter((branch) => branch.isDefault);
88 expect(defaults).toHaveLength(1);
89 const defaultBranch = defaults[0]!;
90 yield* expectDatabase(initial.generated, defaultBranch.id);
91 yield* expectDatabase(initial.named, defaultBranch.id);
92
93 const updated = yield* stack.deploy(resources(true));
94 expect(updated.generated.databaseId).toBe(initial.generated.databaseId);
95 expect(updated.named.databaseId).toBe(initial.named.databaseId);
96 expect(updated.generated.databaseName).toBe("generated-updated");
97 expect(updated.named.databaseName).toBe("named-updated");
98 yield* expectDatabase(updated.generated, defaultBranch.id);
99 yield* expectDatabase(updated.named, defaultBranch.id);
100
101 const repeated = yield* stack.deploy(resources(true));
102 expect(repeated.generated.databaseId).toBe(initial.generated.databaseId);
103 expect(repeated.named.databaseId).toBe(initial.named.databaseId);
104 yield* expectDatabase(repeated.generated, defaultBranch.id);
105 yield* expectDatabase(repeated.named, defaultBranch.id);
106
107 yield* stack.destroy();
108 yield* expectDatabaseGone(initial.generated.databaseId);
109 yield* expectDatabaseGone(initial.named.databaseId);
110 yield* expectProjectGone(initial.project.projectId);
111 }),
112 {
113 tags: [
114 "provider:prisma",
115 "provider:prisma:database",
116 "provider:prisma:project",
117 "live",
118 ],
119 timeout: 120_000,
120 },
121);
122
123test.provider(
124 "preserves a non-default branch when explicit branch props are removed or the attachment changes out of band",
125 Effect.fn(function* (stack: Test.ScratchStack) {
126 yield* stack.destroy();
127
128 const resources = (
129 attachment: "id" | "gitName" | "omitted",
130 name?: string,
131 ) =>
132 Effect.gen(function* () {
133 const project = yield* Prisma.Project("Project", {
134 createDatabase: false,
135 });
136 const first = yield* Prisma.Branch("First", {
137 project,
138 gitName: "feature/first",
139 });
140 const second = yield* Prisma.Branch("Second", {
141 project,
142 gitName: "feature/second",
143 });
144 const database = yield* Prisma.Database("Database", {
145 project,
146 name,
147 ...(attachment === "id"
148 ? { branchId: first.branchId }
149 : attachment === "gitName"
150 ? { branchGitName: second.gitName }
151 : {}),
152 });
153 return { project, first, second, database };
154 });
155
156 const initial = yield* stack.deploy(resources("id"));
157 expect(initial.first.isDefault).toBe(false);
158 expect(initial.second.isDefault).toBe(false);
159 yield* expectDatabase(initial.database, initial.first.branchId);
160
161 const byName = yield* stack.deploy(resources("gitName"));
162 expect(byName.database.databaseId).toBe(initial.database.databaseId);
163 yield* expectDatabase(byName.database, initial.second.branchId);
164
165 const omitted = yield* stack.deploy(resources("omitted", "renamed"));
166 expect(omitted.database.databaseId).toBe(initial.database.databaseId);
167 expect(omitted.database.databaseName).toBe("renamed");
168 yield* expectDatabase(omitted.database, initial.second.branchId);
169
170 yield* updateDatabase({
171 databaseId: initial.database.databaseId,
172 branchId: initial.first.branchId,
173 });
174 const drifted = yield* stack.deploy(resources("omitted", "renamed-again"));
175 expect(drifted.database.databaseId).toBe(initial.database.databaseId);
176 expect(drifted.database.databaseName).toBe("renamed-again");
177 yield* expectDatabase(drifted.database, initial.first.branchId);
178
179 const explicit = yield* stack.deploy(resources("gitName", "renamed-again"));
180 expect(explicit.database.databaseId).toBe(initial.database.databaseId);
181 yield* expectDatabase(explicit.database, initial.second.branchId);
182
183 yield* stack.destroy();
184 yield* expectDatabaseGone(initial.database.databaseId);
185 yield* expectBranchGone(initial.first.branchId);
186 yield* expectBranchGone(initial.second.branchId);
187 yield* expectProjectGone(initial.project.projectId);
188 }),
189 {
190 tags: [
191 "provider:prisma",
192 "provider:prisma:branch",
193 "provider:prisma:database",
194 "provider:prisma:project",
195 "live",
196 ],
197 timeout: 120_000,
198 },
199);