1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as storage from "@distilled.cloud/gcp/storage_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const logLevel = Effect.provideService(
12 MinimumLogLevel,
13 process.env.DEBUG ? "Debug" : "Info",
14);
15
16const waitUntilGone = (bucketName: string, notificationId: string) =>
17 storage
18 .getNotifications({
19 bucket: bucketName,
20 notification: notificationId,
21 })
22 .pipe(
23 Effect.as("found" as const),
24 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
25 Effect.repeat({
26 schedule: Schedule.spaced("1 second"),
27 until: (status) => status === "gone",
28 times: 10,
29 }),
30 );
31
32test.provider(
33 "create, replace, and delete a notification",
34 (stack) =>
35 Effect.gen(function* () {
36 yield* stack.destroy();
37
38 const created = yield* stack.deploy(
39 Effect.gen(function* () {
40 const bucket = yield* GCP.Storage.Bucket("Assets", {
41 location: "US-CENTRAL1",
42 forceDestroy: true,
43 });
44 const topic = yield* GCP.PubSub.Topic("Events", {});
45 return yield* GCP.Storage.Notification("ObjectEvents", {
46 bucketName: bucket.bucketName,
47 topic: topic.name,
48 customAttributes: { env: "test" },
49 });
50 }),
51 );
52
53 expect(created.notificationId).toEqual(expect.any(String));
54 expect(created.bucketName).toEqual(expect.any(String));
55 expect(created.payloadFormat).toEqual("JSON_API_V1");
56 expect(created.customAttributes).toMatchObject({ env: "test" });
57 expect(created.topic).toContain(created.topicId);
58
59 const fetched = yield* storage.getNotifications({
60 bucket: created.bucketName,
61 notification: created.notificationId,
62 });
63 expect(fetched.id).toEqual(created.notificationId);
64 expect(fetched.payload_format).toEqual("JSON_API_V1");
65 expect(fetched.custom_attributes?.env).toEqual("test");
66 expect(fetched.custom_attributes).toEqual({ env: "test" });
67
68 const updated = yield* stack.deploy(
69 Effect.gen(function* () {
70 const bucket = yield* GCP.Storage.Bucket("Assets", {
71 bucketName: created.bucketName,
72 location: "US-CENTRAL1",
73 forceDestroy: true,
74 });
75 const topic = yield* GCP.PubSub.Topic("Events", {
76 topicId: created.topicId,
77 });
78 return yield* GCP.Storage.Notification("ObjectEvents", {
79 bucketName: bucket.bucketName,
80 topic: topic.name,
81 eventTypes: ["OBJECT_FINALIZE"],
82 objectNamePrefix: "uploads/",
83 customAttributes: { env: "prod" },
84 });
85 }),
86 );
87
88 expect(updated.bucketName).toEqual(created.bucketName);
89 expect(updated.eventTypes).toEqual(["OBJECT_FINALIZE"]);
90 expect(updated.objectNamePrefix).toEqual("uploads/");
91 expect(updated.customAttributes).toMatchObject({ env: "prod" });
92 expect(updated.notificationId).not.toEqual(created.notificationId);
93
94 const refetched = yield* storage.getNotifications({
95 bucket: updated.bucketName,
96 notification: updated.notificationId,
97 });
98 expect(refetched.event_types).toEqual(["OBJECT_FINALIZE"]);
99 expect(refetched.object_name_prefix).toEqual("uploads/");
100 expect(refetched.custom_attributes?.env).toEqual("prod");
101
102 const previousGone = yield* waitUntilGone(
103 created.bucketName,
104 created.notificationId,
105 );
106 expect(previousGone).toEqual("gone");
107
108 yield* stack.destroy();
109
110 const gone = yield* waitUntilGone(
111 updated.bucketName,
112 updated.notificationId,
113 );
114 expect(gone).toEqual("gone");
115 }).pipe(logLevel),
116 { tags: ["provider:gcp", "provider:gcp:storage", "live"], timeout: 90_000 },
117);