Files
2026-05-27 23:29:53 +01:00

511 lines
18 KiB
TypeScript

/**
* Second targeted branch-coverage suite — closes the remaining
* istanbul branch gaps (default-arms of `??` chains, "fetch undefined"
* arms of per-provider `common()`, parseInbound default-fall-through
* shapes, http content-type-absent path, default baseUrl path for
* AWS SNS, conditional-spread absent-arms for delivery receipts, and
* the non-object `safeJsonObject` fallback).
*/
import { afterEach, describe, expect, it } from "@suaveplan/testing/runner";
import { AwsSnsProvider } from "../src/aws-sns/aws-sns.js";
import { httpRequest } from "../src/http/http.js";
import { MessageBirdProvider } from "../src/messagebird/messagebird.js";
import { PlivoProvider } from "../src/plivo/plivo.js";
import { TwilioProvider } from "../src/twilio/twilio.js";
import type { FetchLike } from "../src/types/provider.js";
import { VonageProvider } from "../src/vonage/vonage.js";
import {
jsonResponder,
type RecordingServer,
startRecordingServer,
} from "./setup/http-server.js";
let server: RecordingServer | undefined;
afterEach(async () => {
if (server) {
await server.close();
server = undefined;
}
});
/**
* Temporarily replace `globalThis.fetch` with a path-rewriting fetch that
* routes any URL to the recording server's origin via the captured
* platform fetch. Restores the original on exit. Used so providers
* constructed without an explicit `fetch` config option exercise the
* "this.fetch !== undefined === false" arm of their `common()` helper.
*/
async function withGlobalFetch<T>(
origin: string,
body: () => Promise<T>
): Promise<T> {
const original = globalThis.fetch;
const rewriter = async (
input: RequestInfo | URL,
init?: RequestInit
): Promise<Response> => {
const inputUrl =
typeof input === "string" || input instanceof URL
? new URL(input.toString())
: new URL(input.url);
const rerouted = `${origin}${inputUrl.pathname}${inputUrl.search}`;
return original.call(globalThis, rerouted, init);
};
Object.defineProperty(globalThis, "fetch", {
configurable: true,
writable: true,
value: rewriter as unknown as typeof fetch,
});
try {
return await body();
} finally {
Object.defineProperty(globalThis, "fetch", {
configurable: true,
writable: true,
value: original,
});
}
}
describe("http.ts — headers.get('content-type') returns null", () => {
it("treats a missing content-type header as empty and falls back to start-byte JSON sniffing", {
timeout: 5000,
}, async () => {
const fetchImpl: FetchLike = async () =>
({
ok: true,
status: 200,
headers: { get: () => null },
text: async () => '{"k":1}',
}) as unknown as Response;
const { data } = await httpRequest<{ k: number }>({
method: "GET",
url: "https://x/",
fetch: fetchImpl,
});
expect(data).toEqual({ k: 1 });
});
});
describe("Twilio — getStatus without date_created falls back to a fresh Date", () => {
it("returns a Date timestamp when date_created is omitted", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(
jsonResponder({ sid: "S", status: "sent", to: "+1" })
);
const t = new TwilioProvider({
accountSid: "AC",
authToken: "T",
baseUrl: server.origin,
fetch: server.fetch,
});
const r = await t.getStatus("S");
expect(r.timestamp).toBeInstanceOf(Date);
});
});
describe("common() — this.fetch === undefined arm (default global fetch)", () => {
it("Twilio.send works when no explicit fetch is configured", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(
jsonResponder({ sid: "S", status: "queued", to: "+1" })
);
const localServer = server;
const t = new TwilioProvider({
accountSid: "AC",
authToken: "T",
baseUrl: server.origin,
});
const r = await withGlobalFetch(localServer.origin, () =>
t.send({ to: "+1", body: "x" })
);
expect(r.messageId).toBe("S");
});
it("Vonage.send works when no explicit fetch is configured", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(
jsonResponder({
"message-count": "1",
messages: [{ to: "1", "message-id": "x", status: "0" }],
})
);
const localServer = server;
const v = new VonageProvider({
apiKey: "k",
apiSecret: "s",
baseUrl: server.origin,
});
const r = await withGlobalFetch(localServer.origin, () =>
v.send({ to: "+1", body: "hi" })
);
expect(r.messageId).toBe("x");
});
it("MessageBird.send works when no explicit fetch is configured", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(
jsonResponder({
id: "mb",
originator: "S",
body: "x",
type: "sms",
recipients: {
totalCount: 1,
items: [{ recipient: 1, status: "sent" }],
},
})
);
const localServer = server;
const m = new MessageBirdProvider({
accessKey: "K",
baseUrl: server.origin,
});
const r = await withGlobalFetch(localServer.origin, () =>
m.send({ to: "+1", body: "x" })
);
expect(r.messageId).toBe("mb");
});
it("Plivo.send works when no explicit fetch is configured", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(
jsonResponder(
{
api_id: "x",
message: "ok",
message_uuid: ["u1"],
},
202
)
);
const localServer = server;
const p = new PlivoProvider({
authId: "A",
authToken: "T",
baseUrl: server.origin,
});
const r = await withGlobalFetch(localServer.origin, () =>
p.send({ to: "+1", body: "x" })
);
expect(r.messageId).toBe("u1");
});
it("AwsSns.send works when no explicit fetch is configured", {
timeout: 5000,
}, async () => {
const PUBLISH_XML =
'<?xml version="1.0"?><PublishResponse><PublishResult><MessageId>x</MessageId></PublishResult></PublishResponse>';
server = await startRecordingServer((_req, res) => {
res.writeHead(200, { "content-type": "text/xml" });
res.end(PUBLISH_XML);
});
const localServer = server;
const a = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "K",
secretAccessKey: "S",
baseUrl: server.origin,
now: () => new Date("2026-04-24T10:00:00Z"),
});
const r = await withGlobalFetch(localServer.origin, () =>
a.send({ to: "+1", body: "hi" })
);
expect(r.messageId).toBe("x");
});
});
describe("AWS SNS — default baseUrl path (config.baseUrl omitted)", () => {
it("constructs the default https://sns.<region>.amazonaws.com base URL when baseUrl is omitted", {
timeout: 5000,
}, async () => {
// We exercise the `?? \`https://${host}\`` default arm of the
// constructor; the resulting fetch URL hits the AWS host, but
// the swapped global fetch routes it to our recording server.
const PUBLISH_XML =
'<?xml version="1.0"?><PublishResponse><PublishResult><MessageId>m1</MessageId></PublishResult></PublishResponse>';
server = await startRecordingServer((_req, res) => {
res.writeHead(200, { "content-type": "text/xml" });
res.end(PUBLISH_XML);
});
const localServer = server;
const a = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "K",
secretAccessKey: "S",
now: () => new Date("2026-04-24T10:00:00Z"),
});
const r = await withGlobalFetch(localServer.origin, () =>
a.send({ to: "+1", body: "hi" })
);
expect(r.messageId).toBe("m1");
});
});
describe("AWS SNS — parseInbound delivery receipt without errorCode", () => {
it("omits errorCode when the SNS Message payload has no string errorCode field", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const a = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "K",
secretAccessKey: "S",
baseUrl: server.origin,
fetch: server.fetch,
now: () => new Date("2026-04-24T10:00:00Z"),
});
const evt = a.parseInbound({
Type: "Notification",
MessageId: "m1",
Subject: "AWS SMS Delivery Receipt",
Message: JSON.stringify({ status: "DELIVERED" }),
Timestamp: "2026-04-24T10:00:00Z",
TopicArn: "arn:aws:sns:us-east-1:000000000000:topic",
Signature: "",
SigningCertURL: "",
SignatureVersion: "1",
});
expect(evt.type).toBe("delivery_receipt");
if (evt.type === "delivery_receipt") {
expect(evt.errorCode).toBeUndefined();
}
});
it("falls back to '' status when the SNS Message payload lacks a status field (delivery receipt path)", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const a = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "K",
secretAccessKey: "S",
baseUrl: server.origin,
fetch: server.fetch,
now: () => new Date("2026-04-24T10:00:00Z"),
});
const evt = a.parseInbound({
Type: "Notification",
MessageId: "m2",
Subject: "AWS SMS Delivery Receipt",
Message: JSON.stringify({}),
Timestamp: "2026-04-24T10:00:00Z",
TopicArn: "arn:aws:sns:us-east-1:000000000000:topic",
Signature: "",
SigningCertURL: "",
SignatureVersion: "1",
});
expect(evt.type).toBe("delivery_receipt");
if (evt.type === "delivery_receipt") {
// `mapStatus("")` defaults to "failed" per the SNS table.
expect(evt.status).toBe("failed");
}
});
});
describe("AWS SNS — safeJsonObject non-object fallback", () => {
it("parseInbound treats a non-object JSON payload as an empty object (covers the `typeof === 'object' && !== null` else arm)", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const a = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "K",
secretAccessKey: "S",
baseUrl: server.origin,
fetch: server.fetch,
now: () => new Date("2026-04-24T10:00:00Z"),
});
// `42` parses to a number — `typeof parsed === "object"` is false,
// so safeJsonObject returns `{}`. The non-delivery-receipt branch
// then produces a message_received event with empty fields.
const evt = a.parseInbound({
Type: "Notification",
MessageId: "mX",
Message: "42",
Timestamp: "2026-04-24T10:00:00Z",
TopicArn: "arn:aws:sns:us-east-1:000000000000:topic",
Signature: "",
SigningCertURL: "",
SignatureVersion: "1",
});
expect(evt.type).toBe("message_received");
if (evt.type === "message_received") {
expect(evt.from).toBe("");
expect(evt.to).toBe("");
expect(evt.body).toBe("");
}
});
});
describe("AWS SNS — fetchPem default-fetch arm (no provider fetch configured)", () => {
it("verifyNotification fetches the SNS signing cert via globalThis.fetch when no fetch is configured", {
timeout: 5000,
}, async () => {
const PEM =
"-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----\n";
server = await startRecordingServer((_req, res) => {
res.writeHead(200, {
"content-type": "application/x-pem-file",
});
res.end(PEM);
});
const localServer = server;
const a = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "K",
secretAccessKey: "S",
now: () => new Date("2026-04-24T10:00:00Z"),
});
const ok = await withGlobalFetch(localServer.origin, () =>
a.verifyNotification(
{
Type: "Notification",
MessageId: "m",
Message: "{}",
Timestamp: "2026-04-24T10:00:00Z",
TopicArn: "arn:aws:sns:us-east-1:0:t",
Signature: "x",
SigningCertURL: `${localServer.origin}/cert.pem`,
SignatureVersion: "1",
},
async () => true
)
);
expect(ok).toBe(true);
});
});
describe("MessageBird — verifyWebhookSignature without nowMs (default arm)", () => {
it("accepts a JWT issued at the real wall-clock when nowMs is omitted", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const { createHmac } = await import("node:crypto");
const b64url = (input: Buffer | string): string => {
const buf =
typeof input === "string" ? Buffer.from(input, "utf8") : input;
return buf
.toString("base64")
.replace(/=+$/, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
};
const nowSec = Math.floor(Date.now() / 1000);
const header = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
const payload = b64url(
JSON.stringify({ iat: nowSec - 1, exp: nowSec + 60 })
);
const sig = createHmac("sha256", "secret")
.update(`${header}.${payload}`)
.digest();
const token = `${header}.${payload}.${b64url(sig)}`;
const m = new MessageBirdProvider({
accessKey: "K",
baseUrl: server.origin,
fetch: server.fetch,
});
const ok = await m.verifyWebhookSignature(token, "secret", {});
expect(ok).toBe(true);
});
});
describe("MessageBird — parseInbound fallthrough defaults", () => {
it("returns message_received with from='' when originator is absent", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const m = new MessageBirdProvider({
accessKey: "K",
baseUrl: server.origin,
fetch: server.fetch,
});
const evt = m.parseInbound({ recipient: "+2", payload: "hello" });
expect(evt.type).toBe("message_received");
if (evt.type === "message_received") {
expect(evt.from).toBe("");
expect(evt.body).toBe("hello");
}
});
it("returns message_received with body='' when neither payload nor body is present", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const m = new MessageBirdProvider({
accessKey: "K",
baseUrl: server.origin,
fetch: server.fetch,
});
const evt = m.parseInbound({
originator: "+1",
recipient: "+2",
});
expect(evt.type).toBe("message_received");
if (evt.type === "message_received") {
expect(evt.body).toBe("");
}
});
});
describe("Plivo — parseInbound fallthrough defaults", () => {
it("returns message_received with from='' when From is absent", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const p = new PlivoProvider({
authId: "A",
authToken: "T",
baseUrl: server.origin,
fetch: server.fetch,
});
const evt = p.parseInbound({ To: "+2", Text: "hello" });
expect(evt.type).toBe("message_received");
if (evt.type === "message_received") {
expect(evt.from).toBe("");
}
});
it("returns message_received with body='' when neither Text nor text is present", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const p = new PlivoProvider({
authId: "A",
authToken: "T",
baseUrl: server.origin,
fetch: server.fetch,
});
const evt = p.parseInbound({ From: "+1", To: "+2" });
expect(evt.type).toBe("message_received");
if (evt.type === "message_received") {
expect(evt.body).toBe("");
}
});
});
describe("Vonage — parseInbound fallthrough default", () => {
it("returns message_received with body='' when text is absent", {
timeout: 5000,
}, async () => {
server = await startRecordingServer(jsonResponder({}));
const v = new VonageProvider({
apiKey: "k",
apiSecret: "s",
baseUrl: server.origin,
fetch: server.fetch,
});
const evt = v.parseInbound({ msisdn: "+1", to: "+2" });
expect(evt.type).toBe("message_received");
if (evt.type === "message_received") {
expect(evt.body).toBe("");
}
});
});