414 lines · 12.1 KB · TypeScript
RawDownload
import * as NodeCryptoLayer from "@effect/platform-node/NodeCrypto";import { describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer"; import * as RelayDb from "../db.ts";import { relayDeliveryAttempts } from "../persistence/schema.ts";import * as DeliveryAttempts from "./DeliveryAttempts.ts"; describe("DeliveryAttempts", () => {  it.effect("records the signed queue source job id for APNs delivery auditability", () => {    const insertedValues: Array<Record<string, unknown>> = [];    const fakeDb = {      insert: (table: unknown) => {        expect(table).toBe(relayDeliveryAttempts);        return {          values: (values: Record<string, unknown>) => {            insertedValues.push(values);            return Effect.void;          },        };      },    } as unknown as RelayDb.RelayDb["Service"];     return Effect.gen(function* () {      const attempts = yield* DeliveryAttempts.DeliveryAttempts;      yield* attempts.record({        userId: "user-1",        environmentId: "env-1",        threadId: "thread-1",        deviceId: "device-1",        kind: "live_activity_update",        sourceJobId: "job-1",        token: "apns-token",        apnsStatus: 200,        apnsId: "apns-id",      });       expect(insertedValues).toHaveLength(1);      expect(insertedValues[0]).toMatchObject({        userId: "user-1",        environmentId: "env-1",        threadId: "thread-1",        deviceId: "device-1",        kind: "live_activity_update",        sourceJobId: "job-1",        tokenSuffix: "ns-token",        apnsStatus: 200,        apnsId: "apns-id",      });    }).pipe(      Effect.provide(        DeliveryAttempts.layer.pipe(          Layer.provide(NodeCryptoLayer.layer),          Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),        ),      ),    );  });   it.effect("claims signed queue source jobs before APNs delivery", () => {    const insertedValues: Array<Record<string, unknown>> = [];    const conflictTargets: Array<unknown> = [];    const fakeDb = {      insert: (table: unknown) => {        expect(table).toBe(relayDeliveryAttempts);        return {          values: (values: Record<string, unknown>) => {            insertedValues.push(values);            return {              onConflictDoNothing: (config: { readonly target: unknown }) => {                conflictTargets.push(config.target);                return {                  returning: () => Effect.succeed([{ id: values.id }]),                };              },            };          },        };      },    } as unknown as RelayDb.RelayDb["Service"];     return Effect.gen(function* () {      const attempts = yield* DeliveryAttempts.DeliveryAttempts;      const claimed = yield* attempts.claimSourceJob({        userId: "user-1",        environmentId: "env-1",        threadId: "thread-1",        deviceId: "device-1",        kind: "push_notification",        sourceJobId: "job-1",        token: "apns-token",      });       expect(claimed).toBe("claimed");      expect(conflictTargets).toEqual([relayDeliveryAttempts.sourceJobId]);      expect(insertedValues[0]).toMatchObject({        kind: "push_notification",        sourceJobId: "job-1",        tokenSuffix: "ns-token",        apnsStatus: null,      });    }).pipe(      Effect.provide(        DeliveryAttempts.layer.pipe(          Layer.provide(NodeCryptoLayer.layer),          Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),        ),      ),    );  });   it.effect("reports completed source jobs when the durable claim already exists", () => {    const fakeDb = {      insert: () => ({        values: () => ({          onConflictDoNothing: () => ({            returning: () => Effect.succeed([]),          }),        }),      }),      select: () => ({        from: () => ({          where: () => ({            limit: () =>              Effect.succeed([                {                  createdAt: "2026-05-26T00:00:00.000Z",                  apnsStatus: 200,                  apnsReason: null,                  apnsId: null,                  transportError: null,                },              ]),          }),        }),      }),    } as unknown as RelayDb.RelayDb["Service"];     return Effect.gen(function* () {      const attempts = yield* DeliveryAttempts.DeliveryAttempts;      const claimed = yield* attempts.claimSourceJob({        userId: "user-1",        environmentId: null,        threadId: null,        deviceId: "device-1",        kind: "live_activity_update",        sourceJobId: "job-1",        token: "apns-token",      });       expect(claimed).toBe("completed");    }).pipe(      Effect.provide(        DeliveryAttempts.layer.pipe(          Layer.provide(NodeCryptoLayer.layer),          Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),        ),      ),    );  });   it.effect("reports in-flight source jobs while an active claim lease exists", () => {    const fakeDb = {      insert: () => ({        values: () => ({          onConflictDoNothing: () => ({            returning: () => Effect.succeed([]),          }),        }),      }),      select: () => ({        from: () => ({          where: () => ({            limit: () =>              Effect.succeed([                {                  createdAt: "2999-01-01T00:00:00.000Z",                  apnsStatus: null,                  apnsReason: null,                  apnsId: null,                  transportError: null,                },              ]),          }),        }),      }),    } as unknown as RelayDb.RelayDb["Service"];     return Effect.gen(function* () {      const attempts = yield* DeliveryAttempts.DeliveryAttempts;      const claimed = yield* attempts.claimSourceJob({        userId: "user-1",        environmentId: null,        threadId: null,        deviceId: "device-1",        kind: "live_activity_update",        sourceJobId: "job-1",        token: "apns-token",      });       expect(claimed).toBe("in_flight");    }).pipe(      Effect.provide(        DeliveryAttempts.layer.pipe(          Layer.provide(NodeCryptoLayer.layer),          Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),        ),      ),    );  });   it.effect("reclaims source jobs after the claim lease expires", () => {    const updatedValues: Array<Record<string, unknown>> = [];    const fakeDb = {      insert: () => ({        values: () => ({          onConflictDoNothing: () => ({            returning: () => Effect.succeed([]),          }),        }),      }),      select: () => ({        from: () => ({          where: () => ({            limit: () =>              Effect.succeed([                {                  createdAt: "1969-12-31T23:00:00.000Z",                  apnsStatus: null,                  apnsReason: null,                  apnsId: null,                  transportError: null,                },              ]),          }),        }),      }),      update: () => ({        set: (values: Record<string, unknown>) => {          updatedValues.push(values);          return {            where: () => ({              returning: () => Effect.succeed([{ id: "attempt-1" }]),            }),          };        },      }),    } as unknown as RelayDb.RelayDb["Service"];     return Effect.gen(function* () {      const attempts = yield* DeliveryAttempts.DeliveryAttempts;      const claimed = yield* attempts.claimSourceJob({        userId: "user-1",        environmentId: null,        threadId: null,        deviceId: "device-1",        kind: "live_activity_update",        sourceJobId: "job-1",        token: "apns-token",      });       expect(claimed).toBe("claimed");      expect(updatedValues[0]?.createdAt).toEqual(expect.any(String));    }).pipe(      Effect.provide(        DeliveryAttempts.layer.pipe(          Layer.provide(NodeCryptoLayer.layer),          Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),        ),      ),    );  });   it.effect("completes a claimed source job with the APNs outcome", () => {    const updatedValues: Array<Record<string, unknown>> = [];    const whereClauses: Array<unknown> = [];    const fakeDb = {      update: (table: unknown) => {        expect(table).toBe(relayDeliveryAttempts);        return {          set: (values: Record<string, unknown>) => {            updatedValues.push(values);            return {              where: (clause: unknown) => {                whereClauses.push(clause);                return Effect.void;              },            };          },        };      },    } as unknown as RelayDb.RelayDb["Service"];     return Effect.gen(function* () {      const attempts = yield* DeliveryAttempts.DeliveryAttempts;      yield* attempts.completeSourceJob({        sourceJobId: "job-1",        apnsStatus: 410,        apnsReason: "Unregistered",        apnsId: "apns-id",      });       expect(whereClauses).toHaveLength(1);      expect(updatedValues).toEqual([        {          createdAt: expect.any(String),          apnsStatus: 410,          apnsReason: "Unregistered",          apnsId: "apns-id",          transportError: null,        },      ]);    }).pipe(      Effect.provide(        DeliveryAttempts.layer.pipe(          Layer.provide(NodeCryptoLayer.layer),          Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),        ),      ),    );  });   it.effect("preserves operation context and causes for persistence failures", () => {    const cause = new Error("database unavailable");    const fakeDb = {      insert: () => ({        values: (values: Record<string, unknown>) =>          values.kind === "record"            ? Effect.fail(cause)            : {                onConflictDoNothing: () => ({                  returning: () => Effect.fail(cause),                }),              },      }),      update: () => ({        set: () => ({          where: () => Effect.fail(cause),        }),      }),    } as unknown as RelayDb.RelayDb["Service"];     return Effect.gen(function* () {      const attempts = yield* DeliveryAttempts.DeliveryAttempts;      const recordError = yield* Effect.flip(        attempts.record({          userId: "user-1",          environmentId: "env-1",          threadId: "thread-1",          deviceId: "device-1",          kind: "record",          sourceJobId: "job-1",          token: "apns-token",        }),      );      const claimError = yield* Effect.flip(        attempts.claimSourceJob({          userId: "user-2",          environmentId: "env-2",          threadId: "thread-2",          deviceId: "device-2",          kind: "claim",          sourceJobId: "job-2",          token: "apns-token",        }),      );      const completionError = yield* Effect.flip(        attempts.completeSourceJob({ sourceJobId: "job-3", apnsStatus: 500 }),      );       expect(recordError).toMatchObject({        operation: "record",        sourceJobId: "job-1",        userId: "user-1",        environmentId: "env-1",        threadId: "thread-1",        deviceId: "device-1",        kind: "record",        cause,        message: "Failed to persist APNs delivery attempt during record.",      });      expect(claimError).toMatchObject({        operation: "claim-source-job",        sourceJobId: "job-2",        userId: "user-2",        environmentId: "env-2",        threadId: "thread-2",        deviceId: "device-2",        kind: "claim",        cause,        message: "Failed to persist APNs delivery attempt during claim-source-job.",      });      expect(completionError).toMatchObject({        operation: "complete-source-job",        sourceJobId: "job-3",        userId: null,        environmentId: null,        threadId: null,        deviceId: null,        kind: null,        cause,        message: "Failed to persist APNs delivery attempt during complete-source-job.",      });    }).pipe(      Effect.provide(        DeliveryAttempts.layer.pipe(          Layer.provide(NodeCryptoLayer.layer),          Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),        ),      ),    );  });});