Initial Commit
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
/**
|
||||
* 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("");
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user