From 8ec9d1fde757dc784e240961e11344c1d7af78f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 25 May 2026 14:12:37 +0000 Subject: [PATCH] feat: pushover client --- src/lib/pushover.ts | 33 +++++++++++++++++++++++++++++++++ tests/pushover.test.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/lib/pushover.ts create mode 100644 tests/pushover.test.ts diff --git a/src/lib/pushover.ts b/src/lib/pushover.ts new file mode 100644 index 0000000..715e967 --- /dev/null +++ b/src/lib/pushover.ts @@ -0,0 +1,33 @@ +export interface PushOpts { + title: string + message: string + url?: string + urlTitle?: string + priority?: -2 | -1 | 0 | 1 | 2 +} + +export async function sendPush(opts: PushOpts): Promise { + const token = process.env.PUSHOVER_TOKEN + const user = process.env.PUSHOVER_USER + if (!token || !user) throw new Error('PUSHOVER_TOKEN/PUSHOVER_USER not set') + + const body = new URLSearchParams({ + token, + user, + title: opts.title, + message: opts.message, + priority: String(opts.priority ?? 0), + }) + if (opts.url) body.set('url', opts.url) + if (opts.urlTitle) body.set('url_title', opts.urlTitle) + + const res = await fetch('https://api.pushover.net/1/messages.json', { + method: 'POST', + body, + signal: AbortSignal.timeout(10_000), + }) + if (!res.ok) { + const txt = await res.text().catch(() => '') + throw new Error(`Pushover ${res.status}: ${txt}`) + } +} diff --git a/tests/pushover.test.ts b/tests/pushover.test.ts new file mode 100644 index 0000000..861280a --- /dev/null +++ b/tests/pushover.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { sendPush } from '@/lib/pushover' + +beforeEach(() => { + process.env.PUSHOVER_TOKEN = 'tok' + process.env.PUSHOVER_USER = 'user' + global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ status: 1 }) }) as unknown as typeof fetch +}) + +describe('sendPush', () => { + it('posts to pushover with token/user/payload', async () => { + await sendPush({ title: 'T', message: 'M', url: 'https://x' }) + const fetchMock = global.fetch as unknown as ReturnType + const [url, init] = fetchMock.mock.calls[0] + expect(url).toBe('https://api.pushover.net/1/messages.json') + const body = init.body as URLSearchParams + expect(body.get('token')).toBe('tok') + expect(body.get('user')).toBe('user') + expect(body.get('title')).toBe('T') + expect(body.get('message')).toBe('M') + expect(body.get('url')).toBe('https://x') + }) + + it('throws if env vars missing', async () => { + delete process.env.PUSHOVER_TOKEN + await expect(sendPush({ title: 'T', message: 'M' })).rejects.toThrow(/PUSHOVER/) + }) +})