import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { runCertificateMonitoring } from '../src/server/modules/monitoring/monitor.js'; import { pool } from '../src/server/db/pool.js'; import { getCertificateExpiry } from '../src/server/modules/monitoring/certificate.js'; import { deliverNotifications } from '../src/server/modules/monitoring/notifications.js'; const mocks = vi.hoisted(() => ({ client: { query: vi.fn(), release: vi.fn(), }, getCertificateExpiry: vi.fn(), deliverNotifications: vi.fn(), })); vi.mock('../src/server/db/pool.js', () => ({ pool: { connect: vi.fn(async () => mocks.client), }, })); vi.mock('../src/server/modules/monitoring/certificate.js', () => ({ getCertificateExpiry: mocks.getCertificateExpiry, })); vi.mock('../src/server/modules/monitoring/notifications.js', () => ({ deliverNotifications: mocks.deliverNotifications, })); const SITE_ID = '22222222-2222-4222-8222-222222222222'; const USER_ID = '11111111-1111-4111-8111-111111111111'; const WEBHOOK_ID = '44444444-4444-4444-8444-444444444444'; const CONDITION_ID = '55555555-5555-4555-8555-555555555555'; const NOW = new Date('2026-05-25T00:00:00.000Z'); describe('certificate monitoring', () => { beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers(); vi.setSystemTime(NOW); }); afterEach(() => { vi.useRealTimers(); }); it('stores the latest certificate expiry and creates an alert when a threshold matches', async () => { const issuer = 'C = US, O = Example CA, CN = Example Root'; const issuedAt = new Date('2026-01-01T00:00:00.000Z'); const expiresAt = new Date(Date.now() + 11.5 * 60 * 60 * 1000); getCertificateExpiry.mockResolvedValue({ issuer, issuedAt, expiresAt, hoursUntilExpiry: 12, }); deliverNotifications.mockResolvedValue({ webhook: [{ ok: true }], push: { ok: true }, }); mocks.client.query.mockImplementation(async (sql, params) => { if (sql.includes('FROM sites s') && sql.includes('LEFT JOIN site_alert_conditions')) { return { rows: [ { site_id: SITE_ID, user_id: USER_ID, url: 'https://example.com/', alias: 'Example', conditions: [ { site_alert_condition_id: CONDITION_ID, threshold_hours: 12, webhook_method_ids: [WEBHOOK_ID], push_enabled: true, }, ], }, ], }; } if (sql.includes('SET certificate_issuer')) { expect(sql).toContain('certificate_issued_at'); expect(sql).toContain('certificate_expires_at'); expect(params).toEqual([SITE_ID, issuer, issuedAt, expiresAt]); return { rows: [] }; } if (sql.includes('FROM notification_methods') && sql.includes("notification_type = 'webhook'")) { expect(params).toEqual([USER_ID, [WEBHOOK_ID]]); return { rows: [ { notification_method_id: WEBHOOK_ID, alias: 'Deploy hook', url: 'https://hooks.example.com/', }, ], }; } if (sql.includes('FROM notification_methods') && sql.includes("notification_type = 'push'")) { expect(params).toEqual([USER_ID]); return { rows: [ { notification_method_id: '66666666-6666-4666-8666-666666666666', push_endpoint: 'https://push.example.com/subscription', push_p256dh: 'p256dh', push_auth: 'auth', }, ], }; } if (sql.includes('INSERT INTO alert_history')) { expect(params[0]).toBe(USER_ID); expect(params[1]).toBe(SITE_ID); expect(params[2]).toBe('certificate_expiring'); expect(params[4]).toEqual(['app', 'webhook', 'push']); return { rows: [{ alert_id: '77777777-7777-4777-8777-777777777777' }] }; } if (sql.includes('UPDATE site_alert_conditions')) { expect(sql).toContain('last_notified_certificate_expires_at'); expect(sql).toContain('last_notified_at = now()'); expect(params).toEqual([CONDITION_ID, expiresAt]); return { rows: [] }; } throw new Error(`Unexpected query: ${sql}`); }); const result = await runCertificateMonitoring({ concurrency: 1 }); expect(pool.connect).toHaveBeenCalledOnce(); expect(getCertificateExpiry).toHaveBeenCalledWith('https://example.com/'); expect(deliverNotifications).toHaveBeenCalledOnce(); expect(mocks.client.release).toHaveBeenCalledOnce(); expect(result).toMatchObject({ checkedSites: 1, alertsCreated: 1, results: [{ siteId: SITE_ID, ok: true, alertsCreated: 1 }], }); }); it('stores certificate check errors without clearing the previous expiry', async () => { const error = new Error('openssl failed'); getCertificateExpiry.mockRejectedValue(error); mocks.client.query.mockImplementation(async (sql, params) => { if (sql.includes('FROM sites s') && sql.includes('LEFT JOIN site_alert_conditions')) { return { rows: [ { site_id: SITE_ID, user_id: USER_ID, url: 'https://example.com/', alias: 'Example', conditions: [], }, ], }; } if (sql.includes('SET certificate_checked_at') && sql.includes('certificate_check_error = $2')) { expect(sql).not.toContain('certificate_expires_at'); expect(params).toEqual([SITE_ID, error.message]); return { rows: [] }; } if (sql.includes('INSERT INTO alert_history')) { expect(params[2]).toBe('certificate_check_failed'); expect(JSON.parse(params[5])).toMatchObject({ error: error.message }); return { rows: [{ alert_id: '77777777-7777-4777-8777-777777777777' }] }; } throw new Error(`Unexpected query: ${sql}`); }); const result = await runCertificateMonitoring({ concurrency: 1 }); expect(result).toMatchObject({ checkedSites: 1, alertsCreated: 1, results: [{ siteId: SITE_ID, ok: false, error: error.message }], }); }); it('checks and stores certificate expiry for sites without alert conditions', async () => { const issuer = 'C = US, O = Example CA, CN = Example Root'; const issuedAt = new Date('2026-01-01T00:00:00.000Z'); const expiresAt = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000); getCertificateExpiry.mockResolvedValue({ issuer, issuedAt, expiresAt, hoursUntilExpiry: 90 * 24, }); mocks.client.query.mockImplementation(async (sql, params) => { if (sql.includes('FROM sites s') && sql.includes('LEFT JOIN site_alert_conditions')) { return { rows: [ { site_id: SITE_ID, user_id: USER_ID, url: 'https://example.com/', alias: 'Example', conditions: [], }, ], }; } if (sql.includes('SET certificate_issuer')) { expect(sql).toContain('certificate_issued_at'); expect(sql).toContain('certificate_expires_at'); expect(params).toEqual([SITE_ID, issuer, issuedAt, expiresAt]); return { rows: [] }; } throw new Error(`Unexpected query: ${sql}`); }); const result = await runCertificateMonitoring({ concurrency: 1 }); expect(getCertificateExpiry).toHaveBeenCalledWith('https://example.com/'); expect(deliverNotifications).not.toHaveBeenCalled(); expect(result).toMatchObject({ checkedSites: 1, alertsCreated: 0, results: [{ siteId: SITE_ID, ok: true, alertsCreated: 0 }], }); }); it('does not notify again when the same certificate expiry was already handled', async () => { const issuer = 'C = US, O = Example CA, CN = Example Root'; const issuedAt = new Date('2026-01-01T00:00:00.000Z'); const expiresAt = new Date(Date.now() + 11.5 * 60 * 60 * 1000); getCertificateExpiry.mockResolvedValue({ issuer, issuedAt, expiresAt, hoursUntilExpiry: 12, }); mocks.client.query.mockImplementation(async (sql, params) => { if (sql.includes('FROM sites s') && sql.includes('LEFT JOIN site_alert_conditions')) { return { rows: [ { site_id: SITE_ID, user_id: USER_ID, url: 'https://example.com/', alias: 'Example', conditions: [ { site_alert_condition_id: CONDITION_ID, threshold_hours: 12, webhook_method_ids: [WEBHOOK_ID], push_enabled: true, last_notified_certificate_expires_at: expiresAt, }, ], }, ], }; } if (sql.includes('SET certificate_issuer')) { expect(params).toEqual([SITE_ID, issuer, issuedAt, expiresAt]); return { rows: [] }; } throw new Error(`Unexpected query: ${sql}`); }); const result = await runCertificateMonitoring({ concurrency: 1 }); expect(deliverNotifications).not.toHaveBeenCalled(); expect(result).toMatchObject({ checkedSites: 1, alertsCreated: 0, results: [{ siteId: SITE_ID, ok: true, alertsCreated: 0 }], }); }); it('marks a condition as handled without notifying when it is more than an hour late', async () => { const issuer = 'C = US, O = Example CA, CN = Example Root'; const issuedAt = new Date('2026-01-01T00:00:00.000Z'); const expiresAt = new Date(Date.now() + 23 * 60 * 60 * 1000); getCertificateExpiry.mockResolvedValue({ issuer, issuedAt, expiresAt, hoursUntilExpiry: 23, }); mocks.client.query.mockImplementation(async (sql, params) => { if (sql.includes('FROM sites s') && sql.includes('LEFT JOIN site_alert_conditions')) { return { rows: [ { site_id: SITE_ID, user_id: USER_ID, url: 'https://example.com/', alias: 'Example', conditions: [ { site_alert_condition_id: CONDITION_ID, threshold_hours: 24, webhook_method_ids: [WEBHOOK_ID], push_enabled: true, }, ], }, ], }; } if (sql.includes('SET certificate_issuer')) { expect(params).toEqual([SITE_ID, issuer, issuedAt, expiresAt]); return { rows: [] }; } if (sql.includes('UPDATE site_alert_conditions')) { expect(sql).toContain('last_notification_skipped_at = now()'); expect(params).toEqual([CONDITION_ID, expiresAt]); return { rows: [] }; } throw new Error(`Unexpected query: ${sql}`); }); const result = await runCertificateMonitoring({ concurrency: 1 }); expect(deliverNotifications).not.toHaveBeenCalled(); expect(result).toMatchObject({ checkedSites: 1, alertsCreated: 0, results: [{ siteId: SITE_ID, ok: true, alertsCreated: 0 }], }); }); it('notifies and records handled state when a matching condition is less than an hour late', async () => { const issuer = 'C = US, O = Example CA, CN = Example Root'; const issuedAt = new Date('2026-01-01T00:00:00.000Z'); const expiresAt = new Date(Date.now() + 23.5 * 60 * 60 * 1000); getCertificateExpiry.mockResolvedValue({ issuer, issuedAt, expiresAt, hoursUntilExpiry: 23, }); deliverNotifications.mockResolvedValue({ webhook: [], push: { ok: true }, }); mocks.client.query.mockImplementation(async (sql, params) => { if (sql.includes('FROM sites s') && sql.includes('LEFT JOIN site_alert_conditions')) { return { rows: [ { site_id: SITE_ID, user_id: USER_ID, url: 'https://example.com/', alias: 'Example', conditions: [ { site_alert_condition_id: CONDITION_ID, threshold_hours: 24, webhook_method_ids: [], push_enabled: true, }, ], }, ], }; } if (sql.includes('SET certificate_issuer')) { expect(params).toEqual([SITE_ID, issuer, issuedAt, expiresAt]); return { rows: [] }; } if (sql.includes('FROM notification_methods') && sql.includes("notification_type = 'push'")) { expect(params).toEqual([USER_ID]); return { rows: [] }; } if (sql.includes('INSERT INTO alert_history')) { expect(params[2]).toBe('certificate_expiring'); expect(params[4]).toEqual(['app', 'push']); return { rows: [{ alert_id: '77777777-7777-4777-8777-777777777777' }] }; } if (sql.includes('UPDATE site_alert_conditions')) { expect(sql).toContain('last_notified_at = now()'); expect(params).toEqual([CONDITION_ID, expiresAt]); return { rows: [] }; } throw new Error(`Unexpected query: ${sql}`); }); const result = await runCertificateMonitoring({ concurrency: 1 }); expect(deliverNotifications).toHaveBeenCalledOnce(); expect(result).toMatchObject({ checkedSites: 1, alertsCreated: 1, results: [{ siteId: SITE_ID, ok: true, alertsCreated: 1 }], }); }); it('notifies again when a new certificate expiry is observed', async () => { const issuer = 'C = US, O = Example CA, CN = Example Root'; const issuedAt = new Date('2026-01-01T00:00:00.000Z'); const previousExpiresAt = new Date('2026-05-30T00:00:00.000Z'); const expiresAt = new Date(Date.now() + 11.5 * 60 * 60 * 1000); getCertificateExpiry.mockResolvedValue({ issuer, issuedAt, expiresAt, hoursUntilExpiry: 12, }); deliverNotifications.mockResolvedValue({ webhook: [], push: { ok: false, skipped: true }, }); mocks.client.query.mockImplementation(async (sql, params) => { if (sql.includes('FROM sites s') && sql.includes('LEFT JOIN site_alert_conditions')) { return { rows: [ { site_id: SITE_ID, user_id: USER_ID, url: 'https://example.com/', alias: 'Example', conditions: [ { site_alert_condition_id: CONDITION_ID, threshold_hours: 12, webhook_method_ids: [], push_enabled: false, last_notified_certificate_expires_at: previousExpiresAt, }, ], }, ], }; } if (sql.includes('SET certificate_issuer')) { expect(params).toEqual([SITE_ID, issuer, issuedAt, expiresAt]); return { rows: [] }; } if (sql.includes('INSERT INTO alert_history')) { expect(params[2]).toBe('certificate_expiring'); expect(params[4]).toEqual(['app']); return { rows: [{ alert_id: '77777777-7777-4777-8777-777777777777' }] }; } if (sql.includes('UPDATE site_alert_conditions')) { expect(params).toEqual([CONDITION_ID, expiresAt]); return { rows: [] }; } throw new Error(`Unexpected query: ${sql}`); }); const result = await runCertificateMonitoring({ concurrency: 1 }); expect(deliverNotifications).toHaveBeenCalledOnce(); expect(result).toMatchObject({ checkedSites: 1, alertsCreated: 1, results: [{ siteId: SITE_ID, ok: true, alertsCreated: 1 }], }); }); });