import { describe, it, expect, beforeAll } from 'vitest' import * as db from '../src/database.js' // Regression test for the "two rows created when turning email alerts back on" bug. // // Decoded production sequence (Sep 18 2026): // 13:42:05 register → row 1 (unconfirmed), confirm email #1 // 13:44:35 DELETE → row 1 tombstoned (unsubscribed_at set) // 13:44:36 register → OLD BUG: a brand-new row 2 was inserted, confirm email #2 // 14:02:32 confirm → row 2 finally confirmed // // Fix: when the same pubkey re-subscribes to the same email after being // unsubscribed, reactivate the tombstoned row instead of inserting a new one, // preserving the existing confirmed state and key. const unique = (label: string) => `${label}-${Date.now()}-${Math.random().toString(36).slice(2)}` describe('Re-subscribe after DELETE reactivates the same subscription (off/on cycle)', () => { const pubkey = unique('resub') const email = `${unique('resub')}@example.com` beforeAll(async () => { await db.migrate() }) it('creates an unconfirmed subscription', async () => { const sub = await db.insertSubscription(pubkey, email, 'daily') expect(sub).toBeTruthy() expect(sub.confirmed_at).toBeFalsy() expect(sub.unsubscribed_at).toBeFalsy() }) it('confirms it (user clicks the confirm link)', async () => { const active = await db.getSubscriptionByPubkey(pubkey) const result = await db.confirmSubscription(active!.key) expect(result).toBeTruthy() expect(result!.alreadyConfirmed).toBe(false) expect(result!.sub.confirmed_at).toBeTruthy() }) it('unsubscribes it (the flotilla DELETE path)', async () => { const active = await db.getSubscriptionByPubkey(pubkey) const gone = await db.unsubscribeSubscription(active!.key) expect(gone).toBeTruthy() expect(gone!.unsubscribed_at).toBeTruthy() expect(await db.getSubscriptionByPubkey(pubkey)).toBeFalsy() }) it('re-registers the SAME email — must reactivate row, NOT create a second row', async () => { const resigned = await db.insertSubscription(pubkey, email, 'daily') // Restores the very same row const active = await db.getSubscriptionByPubkey(pubkey) expect(active).toBeTruthy() expect(active!.key).toBe(resigned.key) expect(active!.unsubscribed_at).toBeFalsy() // Confirmed state is preserved — no second confirmation email needed expect(active!.confirmed_at).toBeTruthy() // Exhaustive: there exists exactly one row total for this pubkey const rows = await db.getAllSubscriptionsByPubkey(pubkey) expect(rows).toHaveLength(1) expect(rows[0].id).toBe(active!.id) }) }) describe('Re-subscribe after DELETE before ever confirming', () => { const pubkey = unique('resub-unconfirmed') const email = `${unique('resub-unconfirmed')}@example.com` beforeAll(async () => { await db.migrate() }) it('registers then unsubscribes without confirming', async () => { const sub = await db.insertSubscription(pubkey, email, 'daily') await db.unsubscribeSubscription(sub.key) expect(await db.getSubscriptionByPubkey(pubkey)).toBeFalsy() }) it('re-registers the same email — reactivates same row, still unconfirmed', async () => { const resigned = await db.insertSubscription(pubkey, email, 'daily') const active = await db.getSubscriptionByPubkey(pubkey) expect(active!.key).toBe(resigned.key) expect(active!.id).toBe(resigned.id) expect(active!.unsubscribed_at).toBeFalsy() // Was never confirmed, and re-subscribing does not skip confirmation expect(active!.confirmed_at).toBeFalsy() const rows = await db.getAllSubscriptionsByPubkey(pubkey) expect(rows).toHaveLength(1) }) }) describe('Re-subscribe with a DIFFERENT email after DELETE', () => { const pubkey = unique('resub-2') const email = `${unique('resub-2')}@example.com` beforeAll(async () => { await db.migrate() }) it('registers, confirms, and unsubscribes', async () => { const sub = await db.insertSubscription(pubkey, email, 'daily') await db.confirmSubscription(sub.key) await db.unsubscribeSubscription(sub.key) expect(await db.getSubscriptionByPubkey(pubkey)).toBeFalsy() }) it('a new email creates a new row, leaving the old one tombstoned', async () => { const newEmail = `${unique('resub-2-new')}@example.com` const resigned = await db.insertSubscription(pubkey, newEmail, 'daily') expect(resigned.email).toBe(newEmail) expect(resigned.confirmed_at).toBeFalsy() // new address must re-confirm const rows = await db.getAllSubscriptionsByPubkey(pubkey) expect(rows).toHaveLength(2) expect(rows[0].email).toBe(email) // old, tombstoned expect(rows[0].unsubscribed_at).toBeTruthy() expect(rows[1].email).toBe(newEmail) // new, active expect(rows[1].unsubscribed_at).toBeFalsy() }) }) describe('Re-subscribe with a changed frequency reactivates and updates cadence', () => { const pubkey = unique('resub-freq') const email = `${unique('resub-freq')}@example.com` beforeAll(async () => { await db.migrate() }) it('registers confirm-free, unsubscribes', async () => { const sub = await db.insertSubscription(pubkey, email, 'daily') await db.unsubscribeSubscription(sub.key) }) it('re-registers with weekly — reactivates the same row at the new cadence', async () => { const resigned = await db.insertSubscription(pubkey, email, 'weekly') const active = await db.getSubscriptionByPubkey(pubkey) expect(active!.key).toBe(resigned.key) expect(active!.frequency).toBe('weekly') expect(active!.unsubscribed_at).toBeFalsy() const rows = await db.getAllSubscriptionsByPubkey(pubkey) expect(rows).toHaveLength(1) }) })