Files
accounted/app/api/calendar/feed/[token]/__tests__/route.test.ts
T
MattssonandClaude Fable 5 f75ea2384d feat(calendar): enable calendar sync for Viktiga datum (#1853)
Turns on the calendar extension (built Feb 2026, stripped in the 2026-03-02 production readiness deploy, never re-enabled): ICS feed settings, calendar workspace, subscribe button on the Viktiga datum page.

Hardening before first real use: feed serve route now requires the creator to still be a company member (offboarding stops the feed); stable pagination (due_date + id, dedupe) on feed queries; fetches inside the logged try block; invoice events limited to sent/paid/partially_paid/overdue; event UIDs rebranded to accounted.se while zero feeds exist; APP_URL fallback fails closed in production; mobile stacking for the deadlines header; settings note that Google Calendar needs default notifications on subscribed calendars; calendar workspace aligned with the design system.

Skeptic reviewed (3 refutations, all fixed) plus one compliance swarm finding (fixed). No migrations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 16:32:44 +02:00

79 lines
2.6 KiB
TypeScript

/**
* Tests for /api/calendar/feed/[token] (public ICS serve route).
*
* The membership check matters most: the token is the only authentication,
* so removal from company_members must stop the feed. Without the check an
* offboarded consultant's subscribed calendar keeps receiving the company's
* deadlines and invoice details indefinitely.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createQueuedMockSupabase } from '@/tests/helpers'
const { supabase, enqueue, reset } = createQueuedMockSupabase()
vi.mock('@/lib/supabase/service-client', () => ({
createServiceRoleClient: () => supabase,
}))
import { GET } from '../route'
const FEED = {
id: 'feed-1',
company_id: 'company-1',
user_id: 'user-1',
is_active: true,
expires_at: null,
access_count: 0,
include_tax_deadlines: true,
include_invoices: false,
}
function tokenRequest(token: string) {
return [
new Request(`http://localhost/api/calendar/feed/${token}`),
{ params: Promise.resolve({ token }) },
] as const
}
beforeEach(() => {
vi.clearAllMocks()
reset()
process.env.NEXT_PUBLIC_SUPABASE_URL = 'http://localhost:54321'
process.env.SUPABASE_SERVICE_ROLE_KEY = 'service-role-key'
})
describe('GET /api/calendar/feed/[token]', () => {
it('returns 400 for a non-UUID token', async () => {
const [req, ctx] = tokenRequest('not-a-uuid')
const res = await GET(req, ctx)
expect(res.status).toBe(400)
})
it('returns 404 when no active feed matches the token', async () => {
enqueue({ data: null, error: { message: 'not found' } })
const [req, ctx] = tokenRequest('11111111-1111-1111-1111-111111111111')
const res = await GET(req, ctx)
expect(res.status).toBe(404)
})
it('returns 404 when the feed creator is no longer a company member', async () => {
enqueue({ data: FEED }) // calendar_feeds lookup
enqueue({ data: null }) // company_members lookup: offboarded
const [req, ctx] = tokenRequest('22222222-2222-2222-2222-222222222222')
const res = await GET(req, ctx)
expect(res.status).toBe(404)
})
it('serves an ICS calendar while the creator is still a member', async () => {
enqueue({ data: FEED }) // calendar_feeds lookup
enqueue({ data: { user_id: 'user-1' } }) // company_members lookup
enqueue({ data: null }) // access tracking update
enqueue({ data: [] }) // deadlines page 1 (empty: pagination stops)
const [req, ctx] = tokenRequest('33333333-3333-3333-3333-333333333333')
const res = await GET(req, ctx)
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toContain('text/calendar')
expect(await res.text()).toContain('BEGIN:VCALENDAR')
})
})