mirror of
https://github.com/multipleof4/ntfy.git
synced 2026-10-11 06:15:21 +00:00
128 lines
4.6 KiB
JavaScript
128 lines
4.6 KiB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import userManager from "./UserManager";
|
|
import api from "./Api";
|
|
|
|
// Api.js talks to the server through the global fetch and looks up credentials via userManager.
|
|
// (vi.mock is hoisted above the imports by vitest, so the import above receives the mock.)
|
|
vi.mock("./UserManager", () => ({ default: { get: vi.fn() } }));
|
|
|
|
let fetchMock;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
fetchMock = vi.fn();
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
userManager.get.mockResolvedValue(undefined); // anonymous by default
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
describe("Api.poll", () => {
|
|
it("parses newline-delimited JSON and skips lines without an id", async () => {
|
|
const body = [
|
|
JSON.stringify({ id: "1", message: "a" }),
|
|
JSON.stringify({ event: "keepalive" }), // no id -> skipped
|
|
JSON.stringify({ id: "2", message: "b" }),
|
|
].join("\n");
|
|
fetchMock.mockResolvedValue(new Response(body));
|
|
|
|
const messages = await api.poll("https://ntfy.sh", "mytopic");
|
|
|
|
expect(messages.map((m) => m.id)).toEqual(["1", "2"]);
|
|
});
|
|
|
|
it("uses the plain poll URL without a since cursor", async () => {
|
|
fetchMock.mockResolvedValue(new Response(""));
|
|
await api.poll("https://ntfy.sh", "mytopic");
|
|
expect(fetchMock).toHaveBeenCalledWith("https://ntfy.sh/mytopic/json?poll=1", expect.anything());
|
|
});
|
|
|
|
it("uses the since URL when a cursor is given", async () => {
|
|
fetchMock.mockResolvedValue(new Response(""));
|
|
await api.poll("https://ntfy.sh", "mytopic", 12345);
|
|
expect(fetchMock).toHaveBeenCalledWith("https://ntfy.sh/mytopic/json?poll=1&since=12345", expect.anything());
|
|
});
|
|
});
|
|
|
|
describe("Api.publish", () => {
|
|
it("PUTs the message body to the base URL", async () => {
|
|
fetchMock.mockResolvedValue({ status: 200 });
|
|
await api.publish("https://ntfy.sh", "mytopic", "Hello", { priority: 5, tags: ["warning"] });
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://ntfy.sh",
|
|
expect.objectContaining({
|
|
method: "PUT",
|
|
body: JSON.stringify({ topic: "mytopic", message: "Hello", priority: 5, tags: ["warning"] }),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("attaches basic auth when the user has a password", async () => {
|
|
userManager.get.mockResolvedValue({ username: "phil", password: "secret" });
|
|
fetchMock.mockResolvedValue({ status: 200 });
|
|
await api.publish("https://ntfy.sh", "mytopic", "Hi");
|
|
const [, options] = fetchMock.mock.calls[0];
|
|
expect(options.headers.Authorization).toBe(`Basic ${btoa("phil:secret")}`);
|
|
});
|
|
|
|
it("attaches bearer auth when the user has a token", async () => {
|
|
userManager.get.mockResolvedValue({ token: "tk_abc" });
|
|
fetchMock.mockResolvedValue({ status: 200 });
|
|
await api.publish("https://ntfy.sh", "mytopic", "Hi");
|
|
const [, options] = fetchMock.mock.calls[0];
|
|
expect(options.headers.Authorization).toBe("Bearer tk_abc");
|
|
});
|
|
});
|
|
|
|
describe("Api.topicAuth", () => {
|
|
it("returns true for a 2xx response", async () => {
|
|
fetchMock.mockResolvedValue({ status: 200 });
|
|
expect(await api.topicAuth("https://ntfy.sh", "mytopic")).toBe(true);
|
|
});
|
|
|
|
it("returns false for 401/403", async () => {
|
|
fetchMock.mockResolvedValue({ status: 403 });
|
|
expect(await api.topicAuth("https://ntfy.sh", "mytopic")).toBe(false);
|
|
});
|
|
|
|
it("throws for any other status", async () => {
|
|
fetchMock.mockResolvedValue({ status: 500 });
|
|
await expect(api.topicAuth("https://ntfy.sh", "mytopic")).rejects.toThrow("Unexpected server response 500");
|
|
});
|
|
});
|
|
|
|
describe("Api web push", () => {
|
|
const pushSubscription = {
|
|
endpoint: "https://push.example/abc",
|
|
keys: { auth: "AUTH", p256dh: "P256" },
|
|
};
|
|
|
|
it("updateWebPush POSTs endpoint, keys and topics to the web push URL", async () => {
|
|
fetchMock.mockResolvedValue({ status: 200 });
|
|
await api.updateWebPush(pushSubscription, ["topicA", "topicB"]);
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://ntfy.sh/v1/webpush",
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: JSON.stringify({ endpoint: "https://push.example/abc", auth: "AUTH", p256dh: "P256", topics: ["topicA", "topicB"] }),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("deleteWebPush DELETEs just the endpoint", async () => {
|
|
fetchMock.mockResolvedValue({ status: 200 });
|
|
await api.deleteWebPush(pushSubscription);
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
"https://ntfy.sh/v1/webpush",
|
|
expect.objectContaining({
|
|
method: "DELETE",
|
|
body: JSON.stringify({ endpoint: "https://push.example/abc" }),
|
|
}),
|
|
);
|
|
});
|
|
});
|