teqo-backup/tests/unit/calendarFeed.unit.spec.ts
Francisco Solla 25773d0296
feat(C16): add calendar feed sync by filter with Google Calendar import link (#419)
- Add calendarFeed collection with secret slug credential
- Add iCal feed endpoint at /campanha/agenda/ical/[secret] (unauthenticated)
- Add server actions for creating and revoking calendar feeds
- Add CalendarFeedButton UI component in agenda filters bar
- Add unit tests for iCal generation
- Fail-closed access: feed re-evaluates creator's municipality scope on each read
- Leader lockdown preserved; no PII in feed output
2026-08-08 14:47:17 +00:00

120 lines
3.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { generateICalFeed } from '@/utilities/calendarFeed'
describe('generateICalFeed', () => {
const municipalityNames = new Map([
[1, 'Salvador'],
[2, 'Feira de Santana'],
])
it('generates a valid VCALENDAR with VEVENT entries', () => {
const activities = [
{
id: 1,
slug: 'comicio-salvador',
title: 'Comício em Salvador',
status: 'confirmado' as const,
startAt: '2026-08-15T19:00:00.000Z',
endAt: '2026-08-15T22:00:00.000Z',
municipality: 1,
locality: 'Centro',
tags: ['comício', 'campanha'],
deputyPresent: true,
updatedAt: '2026-08-01T10:00:00.000Z',
createdAt: '2026-08-01T10:00:00.000Z',
},
] as unknown as Parameters<typeof generateICalFeed>[0]
const result = generateICalFeed(activities, 'Agenda do Candidato', municipalityNames)
expect(result).toContain('BEGIN:VCALENDAR')
expect(result).toContain('END:VCALENDAR')
expect(result).toContain('BEGIN:VEVENT')
expect(result).toContain('END:VEVENT')
expect(result).toContain('UID:comicio-salvador@teqo.jorgesolla.com.br')
expect(result).toContain('SUMMARY:[Salvador] Comício em Salvador')
expect(result).toContain('DTSTART:20260815T190000Z')
expect(result).toContain('DTEND:20260815T220000Z')
expect(result).toContain('X-WR-CALNAME:Agenda do Candidato')
})
it('excludes cancelled activities', () => {
const activities = [
{
id: 1,
slug: 'evento-cancelado',
title: 'Evento Cancelado',
status: 'cancelado' as const,
startAt: '2026-08-15T19:00:00.000Z',
municipality: 1,
updatedAt: '2026-08-01T10:00:00.000Z',
createdAt: '2026-08-01T10:00:00.000Z',
},
] as unknown as Parameters<typeof generateICalFeed>[0]
const result = generateICalFeed(activities, 'Test', municipalityNames)
expect(result).not.toContain('BEGIN:VEVENT')
expect(result).not.toContain('evento-cancelado')
})
it('escapes special characters in text fields', () => {
const activities = [
{
id: 1,
slug: 'evento-especial',
title: 'Reunião; com vírgulas, e quebras\nde linha',
status: 'confirmado' as const,
startAt: '2026-08-15T19:00:00.000Z',
municipality: 1,
updatedAt: '2026-08-01T10:00:00.000Z',
createdAt: '2026-08-01T10:00:00.000Z',
},
] as unknown as Parameters<typeof generateICalFeed>[0]
const result = generateICalFeed(activities, 'Test', municipalityNames)
expect(result).toContain('Reunião\\; com vírgulas\\, e quebras\\nde linha')
})
it('uses title only when municipality name is not available', () => {
const activities = [
{
id: 1,
slug: 'evento-sem-municipio',
title: 'Evento Genérico',
status: 'confirmado' as const,
startAt: '2026-08-15T19:00:00.000Z',
municipality: 999,
updatedAt: '2026-08-01T10:00:00.000Z',
createdAt: '2026-08-01T10:00:00.000Z',
},
] as unknown as Parameters<typeof generateICalFeed>[0]
const result = generateICalFeed(activities, 'Test', municipalityNames)
expect(result).toContain('SUMMARY:Evento Genérico')
})
it('uses startAt as endAt when endAt is not provided', () => {
const activities = [
{
id: 1,
slug: 'evento-sem-fim',
title: 'Evento Sem Fim',
status: 'confirmado' as const,
startAt: '2026-08-15T19:00:00.000Z',
endAt: null,
municipality: 1,
updatedAt: '2026-08-01T10:00:00.000Z',
createdAt: '2026-08-01T10:00:00.000Z',
},
] as unknown as Parameters<typeof generateICalFeed>[0]
const result = generateICalFeed(activities, 'Test', municipalityNames)
expect(result).toContain('DTSTART:20260815T190000Z')
expect(result).toContain('DTEND:20260815T190000Z')
})
})