mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 17:43:04 +00:00
108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
fetchCamaraActivity,
|
|
normalizeProposition,
|
|
normalizeSpeech,
|
|
propositionsUrl,
|
|
speechWindows,
|
|
} from '../../scripts/lib/dossieCamara.mjs'
|
|
|
|
const jsonResponse = (body: unknown, ok = true, status = 200) => ({
|
|
ok,
|
|
status,
|
|
json: async () => body,
|
|
text: async () => JSON.stringify(body),
|
|
})
|
|
|
|
describe('normalizeProposition / normalizeSpeech', () => {
|
|
it('builds a proposition title from sigla/numero/ano and links the tramitação', () => {
|
|
const row = normalizeProposition({
|
|
id: 123,
|
|
siglaTipo: 'PL',
|
|
numero: 456,
|
|
ano: 2023,
|
|
ementa: 'Dispõe sobre saúde.',
|
|
})
|
|
expect(row?.title).toBe('PL 456/2023')
|
|
expect(row?.detail).toBe('Dispõe sobre saúde.')
|
|
expect(row?.sourceUrl).toContain('idProposicao=123')
|
|
})
|
|
|
|
it('ignores an empty proposition row', () => {
|
|
expect(normalizeProposition({})).toBeNull()
|
|
})
|
|
|
|
it('derives the speech year and keeps the API URL as source', () => {
|
|
const row = normalizeSpeech(
|
|
{
|
|
dataHoraInicio: '2023-05-10T14:00:00',
|
|
tipoDiscurso: 'DISCURSO',
|
|
sumario: 'Fala sobre a cidade.',
|
|
},
|
|
178857,
|
|
)
|
|
expect(row?.year).toBe('2023')
|
|
expect(row?.sourceUrl).toContain('/deputados/178857/discursos')
|
|
})
|
|
})
|
|
|
|
describe('fetchCamaraActivity', () => {
|
|
it('queries proposições and discursos with an explicit window and returns sourced items', async () => {
|
|
const urls: string[] = []
|
|
const fetchImpl = async (url: string) => {
|
|
urls.push(url)
|
|
if (url.includes('/proposicoes')) {
|
|
return jsonResponse({
|
|
dados: [{ id: 1, siglaTipo: 'PL', numero: 1, ano: 2024, ementa: 'Saúde' }],
|
|
})
|
|
}
|
|
return jsonResponse({ dados: [{ dataHoraInicio: '2024-03-01T10:00:00', sumario: 'Fala' }] })
|
|
}
|
|
|
|
const result = await fetchCamaraActivity({
|
|
from: '2024-01-01',
|
|
to: '2024-12-31',
|
|
fetchImpl: fetchImpl as unknown as typeof fetch,
|
|
})
|
|
|
|
expect(result.status).toBe('ok')
|
|
expect(result.items.length).toBeGreaterThan(0)
|
|
expect(result.items.every((item) => Boolean(item.sourceUrl))).toBe(true)
|
|
const speechUrl = urls.find((url) => url.includes('/discursos'))
|
|
expect(speechUrl).toContain('dataInicio=2024-01-01')
|
|
expect(speechUrl).toContain('dataFim=2024-12-31')
|
|
})
|
|
|
|
it('degrades to an explicit gap on an HTTP failure', async () => {
|
|
const fetchImpl = async () => jsonResponse({}, false, 500)
|
|
const result = await fetchCamaraActivity({ fetchImpl: fetchImpl as unknown as typeof fetch })
|
|
expect(result.status).toBe('gap')
|
|
expect(result.items).toEqual([])
|
|
expect(result.reason).toMatch(/Câmara/)
|
|
})
|
|
|
|
it('builds the propositions URL with the deputy id', () => {
|
|
expect(propositionsUrl(178857, 2)).toContain('idDeputadoAutor=178857')
|
|
expect(propositionsUrl(178857, 2)).toContain('pagina=2')
|
|
})
|
|
})
|
|
|
|
describe('speechWindows', () => {
|
|
it('chunks the mandate into ≤4-year windows (the API rejects wider ranges)', () => {
|
|
const windows = speechWindows('2015-01-01', '2026-09-17')
|
|
expect(windows.length).toBeGreaterThan(1)
|
|
for (const window of windows) {
|
|
const span = Number(window.to.slice(0, 4)) - Number(window.from.slice(0, 4))
|
|
expect(span).toBeLessThanOrEqual(3)
|
|
}
|
|
expect(windows[0]).toEqual({ from: '2015-01-01', to: '2018-12-31' })
|
|
expect(windows.at(-1)?.to).toBe('2026-09-17')
|
|
})
|
|
|
|
it('keeps a single short window when the range is under four years', () => {
|
|
expect(speechWindows('2024-01-01', '2024-12-31')).toEqual([
|
|
{ from: '2024-01-01', to: '2024-12-31' },
|
|
])
|
|
})
|
|
})
|