Files
dockhand-upstream/tests/ntfy-auth.test.ts
T

90 lines
3.4 KiB
TypeScript

/**
* ntfy ?auth= query parameter resolution (#1209).
*
* Run with: bun test tests/unit/ntfy-auth.test.ts
*/
import { describe, test, expect } from 'bun:test';
import { resolveQueryAuth, resolveNtfyEndpoint } from '../src/lib/server/notifications/ntfy';
describe('ntfy resolveQueryAuth', () => {
test('raw tk_ token becomes Bearer header', () => {
expect(resolveQueryAuth('tk_fz03gou2w6vclsqnw5jt7mq9999jm')).toBe(
'Bearer tk_fz03gou2w6vclsqnw5jt7mq9999jm'
);
});
test('base64-encoded "Bearer <token>" is decoded', () => {
const encoded = Buffer.from('Bearer abc123').toString('base64');
expect(resolveQueryAuth(encoded)).toBe('Bearer abc123');
});
test('base64-encoded "Basic <creds>" is decoded', () => {
const encoded = Buffer.from('Basic dXNlcjpwYXNz').toString('base64');
expect(resolveQueryAuth(encoded)).toBe('Basic dXNlcjpwYXNz');
});
test('non-base64 raw token (no tk_ prefix) is treated as Bearer', () => {
expect(resolveQueryAuth('myrawtoken')).toBe('Bearer myrawtoken');
});
test('base64 input that decodes to non-Bearer/Basic falls back to raw', () => {
const encoded = Buffer.from('something else').toString('base64');
expect(resolveQueryAuth(encoded)).toBe(`Bearer ${encoded}`);
});
test('header value is always ASCII-safe (regression: U+FFFD)', () => {
// The original bug: raw tk_ tokens were decoded as base64, producing
// 0xFFFD chars that fetch() rejected as ByteString-incompatible.
const result = resolveQueryAuth('tk_fz03gou2w6vclsqnw5jt7mq9999jm');
for (let i = 0; i < result.length; i++) {
expect(result.charCodeAt(i)).toBeLessThanOrEqual(0xff);
}
});
});
describe('ntfy resolveNtfyEndpoint', () => {
// THE BUG (#1300): a trailing slash on the topic made ntfy 404 (POST /topic/ is a
// different route from POST /topic → {"code":40401,"http":404,...}). Confirmed
// against a live ntfy server: POST /testtopic = 200, POST /testtopic/ = 404.
test('strips a trailing slash from the topic (the #1300 404 bug)', () => {
expect(resolveNtfyEndpoint('host.de/dockhand/', true).url).toBe('https://host.de/dockhand');
});
test('strips multiple trailing slashes', () => {
expect(resolveNtfyEndpoint('host.de/dockhand//', false).url).toBe('http://host.de/dockhand');
});
test('no trailing slash is unchanged', () => {
expect(resolveNtfyEndpoint('host.de/dockhand', true).url).toBe('https://host.de/dockhand');
});
test('public ntfy.sh single topic', () => {
expect(resolveNtfyEndpoint('my-topic', false).url).toBe('https://ntfy.sh/my-topic');
});
test('public ntfy.sh topic with trailing slash is normalised', () => {
expect(resolveNtfyEndpoint('my-topic/', false).url).toBe('https://ntfy.sh/my-topic');
});
test('secure host/topic', () => {
expect(resolveNtfyEndpoint('host.de/topic', true).url).toBe('https://host.de/topic');
});
test('basic auth user:pass@host/topic with trailing slash', () => {
const r = resolveNtfyEndpoint('user:[email protected]/topic/', false);
expect(r.url).toBe('http://host.de/topic');
expect(r.authHeader).toBe(`Basic ${Buffer.from('user:pass').toString('base64')}`);
});
test('bearer token@host/topic with trailing slash', () => {
const r = resolveNtfyEndpoint('[email protected]/topic/', true);
expect(r.url).toBe('https://host.de/topic');
expect(r.authHeader).toBe('Bearer tk_abc');
});
test('nested topic path keeps inner slashes, strips only trailing', () => {
expect(resolveNtfyEndpoint('host.de/a/b/c/', true).url).toBe('https://host.de/a/b/c');
});
});