1import { Query } from "@distilled.cloud/core/query";
2import { Railway as RailwaySdk } from "@distilled.cloud/railway";
3import * as Provider from "@/Provider";
4import * as Railway from "@/Railway";
5import * as Test from "@/Test/Alchemy";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10
11const { test } = Test.make({ providers: Railway.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const readProject = Query.fn((id: string) => {
19 const project = RailwaySdk.project({ id });
20 return {
21 id: project.id,
22 name: project.name,
23 description: project.description,
24 workspaceId: project.workspaceId,
25 };
26});
27
28const readProjectDeletedAt = Query.fn((id: string) => ({
29 deletedAt: RailwaySdk.project({ id }).deletedAt,
30}));
31
32const waitUntilGone = (projectId: string) =>
33 readProjectDeletedAt(projectId).pipe(
34 Effect.map((project) =>
35 project.deletedAt != null ? ("gone" as const) : ("found" as const),
36 ),
37 Effect.catchTag("RailwayNotFound", () => Effect.succeed("gone" as const)),
38 Effect.repeat({
39 schedule: Schedule.spaced("1 second"),
40 until: (status) => status === "gone",
41 times: 10,
42 }),
43 );
44
45test.provider(
46 "create, update, list, and delete a project",
47 (stack) =>
48 Effect.gen(function* () {
49 yield* stack.destroy();
50
51 const created = yield* stack.deploy(
52 Effect.gen(function* () {
53 return yield* Railway.Project("Site", {
54 description: "v1",
55 });
56 }),
57 );
58
59 expect(created.projectId).toEqual(expect.any(String));
60 expect(created.projectId.length).toBeGreaterThan(0);
61 expect(created.name).toEqual(expect.any(String));
62 expect(created.name.length).toBeGreaterThan(0);
63 expect(created.name.length).toBeLessThanOrEqual(32);
64 expect(created.name).toMatch(/^[a-z][a-z0-9-]*$/);
65 expect(created.workspaceId).toEqual(expect.any(String));
66 expect(created.workspaceId.length).toBeGreaterThan(0);
67 expect(created.environmentId).toEqual(expect.any(String));
68 expect(created.environmentId.length).toBeGreaterThan(0);
69 expect(created.url).toEqual(
70 `https://railway.com/project/${created.projectId}`,
71 );
72
73 const fetched = yield* readProject(created.projectId);
74 expect(fetched.id).toEqual(created.projectId);
75 expect(fetched.name).toEqual(created.name);
76 expect(fetched.description).toEqual("v1");
77 expect(fetched.workspaceId).toEqual(created.workspaceId);
78
79 const provider = yield* Provider.findProvider(Railway.Project);
80 const listed = yield* provider.list();
81 const found = listed.find(
82 (project) => project.projectId === created.projectId,
83 );
84 expect(found).toBeDefined();
85 expect(found?.name).toEqual(created.name);
86 expect(found?.url).toEqual(created.url);
87 expect(found?.environmentId).toEqual(created.environmentId);
88
89 const nextName =
90 created.name.slice(0, -1) + (created.name.endsWith("z") ? "y" : "z");
91
92 const updated = yield* stack.deploy(
93 Effect.gen(function* () {
94 return yield* Railway.Project("Site", {
95 name: nextName,
96 description: "v2",
97 });
98 }),
99 );
100
101 expect(updated.projectId).toEqual(created.projectId);
102 expect(updated.name).toEqual(nextName);
103 expect(updated.workspaceId).toEqual(created.workspaceId);
104 expect(updated.environmentId).toEqual(created.environmentId);
105 expect(updated.url).toEqual(created.url);
106
107 const fetchedUpdate = yield* readProject(updated.projectId);
108 expect(fetchedUpdate.id).toEqual(updated.projectId);
109 expect(fetchedUpdate.name).toEqual(nextName);
110 expect(fetchedUpdate.description).toEqual("v2");
111
112 yield* stack.destroy();
113
114 const gone = yield* waitUntilGone(created.projectId);
115 expect(gone).toEqual("gone");
116 }).pipe(logLevel),
117 {
118 tags: ["provider:railway", "provider:railway:project", "live"],
119 timeout: 120_000,
120 },
121);