diff options
Diffstat (limited to 'packages/core/tests')
| -rw-r--r-- | packages/core/tests/notifications/ntfy.test.ts | 45 |
1 files changed, 44 insertions, 1 deletions
diff --git a/packages/core/tests/notifications/ntfy.test.ts b/packages/core/tests/notifications/ntfy.test.ts index 3fb1d51..c183847 100644 --- a/packages/core/tests/notifications/ntfy.test.ts +++ b/packages/core/tests/notifications/ntfy.test.ts @@ -61,6 +61,27 @@ describe("validateTopicUrl", () => { expect(validateTopicUrl("https://ntfy.sh")).toMatch(/topic/); expect(validateTopicUrl("https://ntfy.sh/")).toMatch(/topic/); }); + + it("rejects multi-segment paths (topic must be a single segment)", () => { + expect(validateTopicUrl("https://ntfy.sh/team/sub")).toMatch(/single topic/); + }); + + it("rejects topic names with disallowed characters", () => { + expect(validateTopicUrl("https://ntfy.sh/has.dot")).toMatch(/letters\/numbers/); + expect(validateTopicUrl("https://ntfy.sh/has space")).toMatch(/letters\/numbers/); + expect(validateTopicUrl("https://ntfy.sh/has%20enc")).toMatch(/letters\/numbers/); + }); + + it("rejects topic names longer than 64 chars", () => { + const long = "a".repeat(65); + expect(validateTopicUrl(`https://ntfy.sh/${long}`)).toMatch(/64/); + }); + + it("accepts 64-char topic names and underscore/hyphen mixes", () => { + const maxlen = "a".repeat(64); + expect(validateTopicUrl(`https://ntfy.sh/${maxlen}`)).toBeNull(); + expect(validateTopicUrl("https://ntfy.sh/My_Topic-123")).toBeNull(); + }); }); describe("sendNtfy", () => { @@ -91,13 +112,23 @@ describe("sendNtfy", () => { expect(init.headers.Tags).toBe("rotating_light"); }); - it("attaches Authorization header when authToken is set", async () => { + it("attaches Authorization header with Bearer prefix when authToken is a bare token", async () => { const fetchImpl = makeFetch(); await sendNtfy(makeConfig({ authToken: "tk_secret " }), makeEvent(), fetchImpl); const init = fetchImpl.mock.calls[0][1]; expect(init.headers.Authorization).toBe("Bearer tk_secret"); }); + it("passes a pre-prefixed Authorization value (Basic, custom schemes) through verbatim", async () => { + const fetchImpl = makeFetch(); + await sendNtfy(makeConfig({ authToken: "Basic dXNlcjpwYXNz" }), makeEvent(), fetchImpl); + expect(fetchImpl.mock.calls[0][1].headers.Authorization).toBe("Basic dXNlcjpwYXNz"); + + const fetchImpl2 = makeFetch(); + await sendNtfy(makeConfig({ authToken: "Bearer already_prefixed" }), makeEvent(), fetchImpl2); + expect(fetchImpl2.mock.calls[0][1].headers.Authorization).toBe("Bearer already_prefixed"); + }); + it("omits Authorization when authToken is blank", async () => { const fetchImpl = makeFetch(); await sendNtfy(makeConfig({ authToken: " " }), makeEvent(), fetchImpl); @@ -112,6 +143,18 @@ describe("sendNtfy", () => { expect(init.headers.Click).toBe("https://example.com/tab/abc"); }); + it("sanitizes Click header (CR/LF injection guard)", async () => { + const fetchImpl = makeFetch(); + await sendNtfy( + makeConfig(), + makeEvent({ clickUrl: "https://example.com/\r\nInjected: yes" }), + fetchImpl, + ); + const v = fetchImpl.mock.calls[0][1].headers.Click; + expect(v).not.toContain("\n"); + expect(v).not.toContain("\r"); + }); + it("appends short tab tag when tabId is set", async () => { const fetchImpl = makeFetch(); await sendNtfy( |
