1import { afterEach, describe, expect, it, vi } from "vitest";2import type { LookupFunction } from "node:net";3 4import {5 buildPinnedRequestOptions,6 deliverPendingEventsHandler,7 MAX_FALLBACK_ADDRESSES,8 postJsonPinned,9 resolvePublicAddresses,10 type DeliveryRequest,11} from "./delivery";12import {13 DELIVERY_ID_HEADER,14 EVENT_ID_HEADER,15 SIGNATURE_HEADER,16 TIMESTAMP_HEADER,17} from "./signing";18 19const claimed = {20 deliveryId: "outboundDeliveries_1",21 leaseToken: "lease-1",22 attempts: 0,23 url: "https://hooks.example.com/iapkit?source=test",24 secret: "whsec_test",25 body: JSON.stringify({ eventType: "subscription.renewed" }),26 eventId: "commerceEvents_1",27} as const;28 29afterEach(() => {30 vi.useRealTimers();31});32 33describe("deliverPendingEventsHandler", () => {34 it("posts signed headers and records the response before claiming again", async () => {35 vi.setSystemTime(new Date("2026-08-27T00:00:00.000Z"));36 let claimCount = 0;37 const recorded: Array<Record<string, unknown>> = [];38 const runMutation = vi.fn(async (_reference, args) => {39 if (Object.keys(args).length === 0) {40 claimCount += 1;41 return claimCount === 1 ? [claimed] : [];42 }43 recorded.push(args);44 return null;45 });46 const requests: DeliveryRequest[] = [];47 48 await expect(49 deliverPendingEventsHandler({ runMutation } as never, async (request) => {50 requests.push(request);51 return 204;52 }),53 ).resolves.toEqual({ attempted: 1, delivered: 1 });54 55 expect(requests).toHaveLength(1);56 expect(requests[0].url.toString()).toBe(claimed.url);57 expect(requests[0].body).toBe(claimed.body);58 expect(requests[0].headers[EVENT_ID_HEADER]).toBe(claimed.eventId);59 expect(requests[0].headers[DELIVERY_ID_HEADER]).toBe(claimed.deliveryId);60 expect(requests[0].headers[TIMESTAMP_HEADER]).toBe("1787788800");61 expect(requests[0].headers[SIGNATURE_HEADER]).toMatch(/^v1=[0-9a-f]{64}$/);62 expect(recorded).toEqual([63 expect.objectContaining({64 deliveryId: claimed.deliveryId,65 leaseToken: claimed.leaseToken,66 ok: true,67 statusCode: 204,68 retryable: false,69 }),70 ]);71 });72 73 it("records redirects as permanent failures without following them", async () => {74 let claimCount = 0;75 const recorded: Array<Record<string, unknown>> = [];76 const runMutation = vi.fn(async (_reference, args) => {77 if (Object.keys(args).length === 0) {78 claimCount += 1;79 return claimCount === 1 ? [claimed] : [];80 }81 recorded.push(args);82 return null;83 });84 const post = vi.fn(async () => 302);85 86 await expect(87 deliverPendingEventsHandler({ runMutation } as never, post),88 ).resolves.toEqual({ attempted: 1, delivered: 0 });89 expect(post).toHaveBeenCalledTimes(1);90 expect(recorded[0]).toMatchObject({91 ok: false,92 statusCode: 302,93 retryable: false,94 });95 });96 97 it("rejects an unsafe stored URL before issuing a request", async () => {98 let claimCount = 0;99 const recorded: Array<Record<string, unknown>> = [];100 const unsafeClaim = { ...claimed, url: "https://127.0.0.1/hook" };101 const runMutation = vi.fn(async (_reference, args) => {102 if (Object.keys(args).length === 0) {103 claimCount += 1;104 return claimCount === 1 ? [unsafeClaim] : [];105 }106 recorded.push(args);107 return null;108 });109 const post = vi.fn(async () => 204);110 111 await expect(112 deliverPendingEventsHandler({ runMutation } as never, post),113 ).resolves.toEqual({ attempted: 1, delivered: 0 });114 expect(post).not.toHaveBeenCalled();115 expect(recorded[0]).toMatchObject({116 deliveryId: claimed.deliveryId,117 leaseToken: claimed.leaseToken,118 ok: false,119 error: "destination rejected: host-not-public",120 retryable: false,121 });122 });123 124 it("records transport failures as retryable", async () => {125 let claimCount = 0;126 const recorded: Array<Record<string, unknown>> = [];127 const runMutation = vi.fn(async (_reference, args) => {128 if (Object.keys(args).length === 0) {129 claimCount += 1;130 return claimCount === 1 ? [claimed] : [];131 }132 recorded.push(args);133 return null;134 });135 136 await expect(137 deliverPendingEventsHandler({ runMutation } as never, async () => {138 throw new Error("connection reset");139 }),140 ).resolves.toEqual({ attempted: 1, delivered: 0 });141 expect(recorded[0]).toMatchObject({142 deliveryId: claimed.deliveryId,143 leaseToken: claimed.leaseToken,144 ok: false,145 error: "connection reset",146 retryable: true,147 });148 });149});150 151describe("resolvePublicAddresses", () => {152 it("rejects private, shared and link-local DNS answers", async () => {153 for (const address of [154 "127.0.0.1",155 "100.64.0.1",156 "169.254.169.254",157 "::1",158 ]) {159 await expect(160 resolvePublicAddresses("hooks.example.com", async () => [161 { address, family: address.includes(":") ? 6 : 4 },162 ]),163 ).rejects.toThrow(/non-public/);164 }165 });166 167 it("rejects a mixed public/private DNS response", async () => {168 await expect(169 resolvePublicAddresses("hooks.example.com", async () => [170 { address: "93.184.216.34", family: 4 },171 { address: "10.0.0.5", family: 4 },172 ]),173 ).rejects.toThrow(/non-public/);174 });175 176 it("accepts only-public answers for a pinned connection", async () => {177 await expect(178 resolvePublicAddresses("hooks.example.com", async () => [179 { address: "93.184.216.34", family: 4 },180 { address: "2606:4700:4700::1111", family: 6 },181 ]),182 ).resolves.toHaveLength(2);183 });184 185 it("caps a tenant-controlled DNS answer set", async () => {186 const addresses = await resolvePublicAddresses(187 "hooks.example.com",188 async () =>189 Array.from({ length: 20 }, (_, index) => ({190 address: `93.184.216.${index + 1}`,191 family: 4,192 })),193 );194 expect(addresses).toHaveLength(MAX_FALLBACK_ADDRESSES);195 });196 197 it("falls back across validated addresses within one request deadline", async () => {198 const attempted: string[] = [];199 const request: DeliveryRequest = {200 url: new URL("https://hooks.example.com/iapkit"),201 headers: { "content-type": "application/json" },202 body: "{}",203 };204 const status = await postJsonPinned(205 request,206 async () => [207 { address: "93.184.216.34", family: 4 },208 { address: "2606:4700:4700::1111", family: 6 },209 ],210 async (_request, address, timeoutMs) => {211 attempted.push(address.address);212 expect(timeoutMs).toBeGreaterThan(0);213 if (attempted.length === 1) throw new Error("network unreachable");214 return 204;215 },216 );217 expect(status).toBe(204);218 expect(attempted).toEqual(["93.184.216.34", "2606:4700:4700::1111"]);219 });220 221 it("pins lookup while preserving TLS SNI and the full request path", () => {222 const request: DeliveryRequest = {223 url: new URL("https://hooks.example.com:8443/iapkit?source=test"),224 headers: { "content-type": "application/json" },225 body: "{}",226 };227 const options = buildPinnedRequestOptions(request, {228 address: "93.184.216.34",229 family: 4,230 });231 expect(options).toMatchObject({232 protocol: "https:",233 hostname: "hooks.example.com",234 servername: "hooks.example.com",235 port: "8443",236 path: "/iapkit?source=test",237 method: "POST",238 headers: request.headers,239 });240 let pinned: unknown;241 (options.lookup as LookupFunction)(242 "hooks.example.com",243 { all: false },244 (error, address, family) => {245 expect(error).toBeNull();246 pinned = { address, family };247 },248 );249 expect(pinned).toEqual({ address: "93.184.216.34", family: 4 });250 });251});252