115 lines · 3.8 KB · TypeScript
RawDownload
import * as NodeCryptoLayer from "@effect/platform-node/NodeCrypto";import { describe, expect, it } from "@effect/vitest";import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Redacted from "effect/Redacted"; import * as RelayConfiguration from "../Config.ts";import * as ApnsDeliveryQueue from "./ApnsDeliveryQueue.ts"; const config: RelayConfiguration.RelayConfiguration["Service"] = {  relayIssuer: "https://relay.example.com",  apns: {    teamId: "team-1",    keyId: "key-1",    privateKey: Redacted.make("apns-private-key"),    bundleId: "com.t3tools.test",    environment: "sandbox",  },  clerkSecretKey: Redacted.make("clerk-secret"),  clerkPublishableKey: "pk_test_test",  clerkJwtAudience: "t3-code-relay",  apnsDeliveryJobSigningSecret: Redacted.make("apns-job-secret"),  cloudMintPrivateKey: Redacted.make("cloud-private-key"),  cloudMintPublicKey: "cloud-public-key",  managedEndpointBaseDomain: undefined,  managedEndpointNamespace: undefined,}; describe("ApnsDeliveryQueue", () => {  it.effect("does not require the deployment RuntimeContext when building the Worker layer", () => {    const sent: unknown[] = [];    const sender: Cloudflare.Queues.WriteQueueClient = {      raw: Effect.die("raw queue binding is not used"),      send: (body) =>        Effect.sync(() => {          sent.push(body);        }),      sendBatch: () => Effect.die("batch queue binding is not used"),    };    const runtimeContext = {} as Alchemy.BaseRuntimeContext;    const layer = ApnsDeliveryQueue.layerCloudflareQueues(sender, runtimeContext).pipe(      Layer.provide(NodeCryptoLayer.layer),      Layer.provide(RelayConfiguration.layer(config)),    );     return Effect.gen(function* () {      const queue = yield* ApnsDeliveryQueue.ApnsDeliveryQueue;      yield* queue.enqueuePushNotification({        userId: "user-1",        deviceId: "device-1",        token: "push-token",        notification: {          title: "Thread",          body: "Input: Project",          environmentId: "env-1",          threadId: "thread-1",          deepLink: "/threads/env-1/thread-1",        },      });       expect(sent).toHaveLength(1);    }).pipe(Effect.provide(layer));  });   it.effect("preserves job identity and the queue sender cause", () => {    const cause = new Error("queue unavailable");    const senderCause = new Cloudflare.Queues.SendError({      message: cause.message,      cause,    });    const layer = ApnsDeliveryQueue.layer.pipe(      Layer.provide(NodeCryptoLayer.layer),      Layer.provide(RelayConfiguration.layer(config)),      Layer.provide(        Layer.succeed(ApnsDeliveryQueue.ApnsDeliveryQueueSender, {          send: () => Effect.fail(senderCause),        }),      ),    );     return Effect.gen(function* () {      const queue = yield* ApnsDeliveryQueue.ApnsDeliveryQueue;      const error = yield* Effect.flip(        queue.enqueuePushNotification({          userId: "user-1",          deviceId: "device-1",          token: "push-token",          notification: {            title: "Thread",            body: "Input: Project",            environmentId: "env-1",            threadId: "thread-1",            deepLink: "/threads/env-1/thread-1",          },        }),      );       expect(error).toMatchObject({        _tag: "ApnsDeliveryQueueSendError",        operation: "send",        jobId: expect.any(String),        kind: "push_notification",        userId: "user-1",        deviceId: "device-1",        cause: senderCause,      });      expect(senderCause.cause).toBe(cause);      expect(error.message).toBe(        "Failed to enqueue APNs push notification delivery during send for device device-1.",      );    }).pipe(Effect.provide(layer));  });});