mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
167 lines
6.3 KiB
TypeScript
167 lines
6.3 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { createHash } from 'node:crypto'
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import { bahiaMunicipalityCodes } from '@/lib/bahiaMunicipalityCodes'
|
|
import { bahiaMunicipalities, territoryForCity } from '@/lib/bahiaTerritories'
|
|
import { bahiaTseCityCodes } from '@/lib/bahiaTseCityCodes'
|
|
import { tseZonesForCity } from '@/lib/bahiaTseZones'
|
|
import {
|
|
getMunicipalityCatalogEntry,
|
|
isMunicipalitySlug,
|
|
municipalityCatalog,
|
|
municipalityCatalogEntriesForCity,
|
|
ZONE_MUNICIPALITY_CITIES,
|
|
} from '@/lib/municipalityCatalog'
|
|
|
|
type Snapshot = {
|
|
municipalityCount: number
|
|
zoneMunicipalityCities: string[]
|
|
identitySha256: string
|
|
entries: Array<{
|
|
slug: string
|
|
name: string
|
|
kind: 'municipio' | 'zona'
|
|
city: string
|
|
region: string
|
|
ibgeCode: string
|
|
tseCityCode: string
|
|
zoneNumber?: number
|
|
tseZones: number[]
|
|
}>
|
|
}
|
|
|
|
const snapshot = JSON.parse(
|
|
readFileSync(new URL('../fixtures/municipality-catalog.snapshot.json', import.meta.url), 'utf8'),
|
|
) as Snapshot
|
|
|
|
describe('Municipality catalog (435 predefined territories)', () => {
|
|
it('has 435 municipalities: 416 whole municipalities + 19 Salvador zones (Camaçari is whole)', () => {
|
|
expect(municipalityCatalog).toHaveLength(435)
|
|
expect(municipalityCatalog.filter((entry) => entry.kind === 'municipio')).toHaveLength(416)
|
|
|
|
const zoneEntries = municipalityCatalog.filter((entry) => entry.kind === 'zona')
|
|
expect(zoneEntries).toHaveLength(19)
|
|
expect(zoneEntries.filter((entry) => entry.city === 'Salvador')).toHaveLength(19)
|
|
expect(municipalityCatalogEntriesForCity('Camaçari')).toHaveLength(1)
|
|
expect(municipalityCatalogEntriesForCity('Camaçari')[0]?.kind).toBe('municipio')
|
|
expect(ZONE_MUNICIPALITY_CITIES).toEqual(['Salvador'])
|
|
})
|
|
|
|
it('has unique, stable slugs and names', () => {
|
|
expect(new Set(municipalityCatalog.map((entry) => entry.slug)).size).toBe(435)
|
|
expect(new Set(municipalityCatalog.map((entry) => entry.name)).size).toBe(435)
|
|
|
|
for (const entry of municipalityCatalog) {
|
|
expect(entry.slug).toMatch(/^[a-z0-9]+(-[a-z0-9]+)*$/)
|
|
}
|
|
})
|
|
|
|
it('covers every Bahia municipality exactly once (Salvador via zones)', () => {
|
|
const municipalityEntries = municipalityCatalog.filter((entry) => entry.kind === 'municipio')
|
|
const coveredAsMunicipality = new Set(municipalityEntries.map((entry) => entry.city))
|
|
|
|
for (const city of bahiaMunicipalities) {
|
|
if (ZONE_MUNICIPALITY_CITIES.includes(city)) {
|
|
expect(coveredAsMunicipality.has(city), city).toBe(false)
|
|
const zoneEntries = municipalityCatalogEntriesForCity(city)
|
|
expect(zoneEntries.length, city).toBeGreaterThan(1)
|
|
expect(new Set(zoneEntries.map((entry) => entry.zoneNumber)).size).toBe(zoneEntries.length)
|
|
expect(
|
|
[...zoneEntries.map((entry) => entry.zoneNumber)].sort((a, b) => (a ?? 0) - (b ?? 0)),
|
|
).toEqual([...tseZonesForCity(city)].sort((a, b) => a - b))
|
|
} else {
|
|
expect(coveredAsMunicipality.has(city), city).toBe(true)
|
|
expect(municipalityCatalogEntriesForCity(city)).toHaveLength(1)
|
|
}
|
|
}
|
|
})
|
|
|
|
it('carries consistent region, codes and zones for every entry', () => {
|
|
for (const entry of municipalityCatalog) {
|
|
expect(entry.region, entry.slug).toBe(territoryForCity(entry.city))
|
|
expect(entry.ibgeCode, entry.slug).toBe(bahiaMunicipalityCodes[entry.city])
|
|
expect(entry.tseCityCode, entry.slug).toBe(bahiaTseCityCodes[entry.city])
|
|
expect(entry.tseZones.length, entry.slug).toBeGreaterThan(0)
|
|
|
|
const officialZones = new Set(tseZonesForCity(entry.city))
|
|
for (const zone of entry.tseZones) {
|
|
expect(officialZones.has(zone), `${entry.slug} zone ${zone}`).toBe(true)
|
|
}
|
|
|
|
if (entry.kind === 'zona') {
|
|
expect(entry.zoneNumber).toBeDefined()
|
|
expect(entry.tseZones).toEqual([entry.zoneNumber])
|
|
expect(entry.name).toBe(`${entry.city} — ZE ${entry.zoneNumber}`)
|
|
} else {
|
|
expect(entry.zoneNumber).toBeUndefined()
|
|
expect(entry.name).toBe(entry.city)
|
|
expect([...entry.tseZones]).toEqual([...tseZonesForCity(entry.city)].sort((a, b) => a - b))
|
|
}
|
|
}
|
|
})
|
|
|
|
it('matches the frozen identity snapshot (slugs/names are public URLs + seed identity)', () => {
|
|
expect(snapshot.municipalityCount).toBe(435)
|
|
expect(snapshot.zoneMunicipalityCities).toEqual([...ZONE_MUNICIPALITY_CITIES])
|
|
|
|
const derived = municipalityCatalog.map(
|
|
({ slug, name, kind, city, region, ibgeCode, tseCityCode, zoneNumber, tseZones }) => ({
|
|
slug,
|
|
name,
|
|
kind,
|
|
city,
|
|
region,
|
|
ibgeCode,
|
|
tseCityCode,
|
|
...(zoneNumber === undefined ? {} : { zoneNumber }),
|
|
tseZones: [...tseZones],
|
|
}),
|
|
)
|
|
expect(derived).toEqual(snapshot.entries)
|
|
|
|
const rows = derived
|
|
.map(
|
|
(entry) =>
|
|
`P\t${entry.slug}\t${entry.name}\t${entry.kind}\t${entry.city}\t${entry.zoneNumber ?? ''}\n`,
|
|
)
|
|
.join('')
|
|
expect(createHash('sha256').update(rows).digest('hex')).toBe(snapshot.identitySha256)
|
|
})
|
|
|
|
it('supports slug lookups', () => {
|
|
expect(getMunicipalityCatalogEntry('feira-de-santana')?.city).toBe('Feira de Santana')
|
|
expect(getMunicipalityCatalogEntry('salvador-ze-1')?.zoneNumber).toBe(1)
|
|
expect(getMunicipalityCatalogEntry('camacari')?.kind).toBe('municipio')
|
|
expect(getMunicipalityCatalogEntry('camacari-ze-170')).toBeUndefined()
|
|
expect(getMunicipalityCatalogEntry('salvador')).toBeUndefined()
|
|
expect(isMunicipalitySlug('salvador-ze-19')).toBe(true)
|
|
expect(isMunicipalitySlug('salvador-ze-20')).toBe(false)
|
|
})
|
|
|
|
it('cuts shared/leaky zones at the municipal boundary (no overlap between municipalities)', () => {
|
|
for (const city of [
|
|
'Itaparica',
|
|
'Vera Cruz',
|
|
'São Sebastião do Passé',
|
|
'Mata de São João',
|
|
'Pojuca',
|
|
]) {
|
|
const entries = municipalityCatalogEntriesForCity(city)
|
|
expect(entries).toHaveLength(1)
|
|
expect(entries[0]?.kind).toBe('municipio')
|
|
}
|
|
|
|
const pairs = new Set<string>()
|
|
for (const entry of municipalityCatalog) {
|
|
for (const zone of entry.tseZones) {
|
|
const key = `${entry.tseCityCode}:${zone}`
|
|
expect(pairs.has(key), key).toBe(false)
|
|
pairs.add(key)
|
|
}
|
|
}
|
|
})
|
|
})
|