|
- import { describe, expect, it } from 'vitest'
- import {
- createMunicipalityProfile,
- createMunicipalityContact,
- deleteMunicipalityContact,
- fetchAvailableJurisdictions,
- fetchMunicipalityContacts,
- fetchMunicipalityProfiles,
- fetchPriorCycleDefaults,
- MunicipalityContactValidationError,
- MunicipalityValidationError,
- updateMunicipalityContact,
- updateMunicipalityProfile,
- type MunicipalityContact,
- type MunicipalityProfile,
- type PriorCycleDefaults,
- } from './municipalityContracts'
-
- const makeProfile = (overrides: Partial<MunicipalityProfile> = {}): MunicipalityProfile => ({
- profileId: 'abc123',
- jCode: 'FAIR01',
- displayName: 'Fairview Borough',
- updatedAt: '2026-05-06T12:00:00Z',
- updatedBy: 'user@test.com',
- legacyName: 'Fairview Borough',
- legacyMailingAddress: '100 Main St',
- legacyCityStateZip: 'Fairview, PA 16415',
- ...overrides,
- })
-
- const makeContact = (overrides: Partial<MunicipalityContact> = {}): MunicipalityContact => ({
- contactId: 'contact-1',
- profileId: 'abc123',
- contactType: 'Primary',
- name: 'Ada Clerk',
- roleTitle: 'Town Clerk',
- phone: '555-0100',
- email: 'ada@example.test',
- createdAt: '2026-05-07T12:00:00Z',
- createdBy: 'creator@example.test',
- updatedAt: '2026-05-07T12:00:00Z',
- updatedBy: 'actor@example.test',
- ...overrides,
- })
-
- const makeDefaults = (overrides: Partial<PriorCycleDefaults> = {}): PriorCycleDefaults => ({
- profileId: 'abc123',
- hasPriorCycles: true,
- selectedCycleId: 'cycle-2026-primary',
- emptyStateMessage: '',
- cycles: [
- {
- cycleId: 'cycle-2026-primary',
- cycleName: '2026 Primary',
- completedAt: '2026-05-01T00:00:00Z',
- services: [
- {
- serviceType: 'Addressing',
- summary: 'Standard addressing run',
- values: { Quantity: '1200', Tracking: 'Yes' },
- },
- ],
- },
- ],
- ...overrides,
- })
-
- // ── fetchMunicipalityProfiles ─────────────────────────────────────────────────
-
- describe('fetchMunicipalityProfiles', () => {
- it('returns profiles on 200', async () => {
- const stub = async () =>
- new Response(JSON.stringify([makeProfile()]), { status: 200 })
-
- const result = await fetchMunicipalityProfiles(stub)
-
- expect(result).toHaveLength(1)
- expect(result[0].jCode).toBe('FAIR01')
- expect(result[0].legacyName).toBe('Fairview Borough')
- })
-
- it('throws on non-200', async () => {
- const stub = async () => new Response('{}', { status: 500 })
-
- await expect(fetchMunicipalityProfiles(stub)).rejects.toThrow('500')
- })
- })
-
- // -- municipality contacts ---------------------------------------------------
-
- describe('municipality contact contracts', () => {
- it('fetchMunicipalityContacts returns primary and secondary contacts on 200', async () => {
- const stub = async () =>
- new Response(JSON.stringify([
- makeContact({ contactType: 'Primary', name: 'Main Clerk' }),
- makeContact({ contactId: 'contact-2', contactType: 'Secondary', name: 'Backup Clerk' }),
- ]), { status: 200 })
-
- const result = await fetchMunicipalityContacts('abc123', stub)
-
- expect(result.map((c) => c.contactType)).toEqual(['Primary', 'Secondary'])
- expect(result[0].name).toBe('Main Clerk')
- })
-
- it('createMunicipalityContact posts required and optional fields', async () => {
- let postedBody: unknown
- const stub = async (_input: RequestInfo | URL, init?: RequestInit) => {
- postedBody = JSON.parse(String(init?.body))
- return new Response(JSON.stringify(makeContact()), { status: 201 })
- }
-
- const result = await createMunicipalityContact(
- 'abc123',
- {
- contactType: 'Primary',
- name: 'Ada Clerk',
- roleTitle: 'Town Clerk',
- phone: '555-0100',
- email: 'ada@example.test',
- },
- stub,
- )
-
- expect(result.contactType).toBe('Primary')
- expect(postedBody).toEqual({
- contactType: 'Primary',
- name: 'Ada Clerk',
- roleTitle: 'Town Clerk',
- phone: '555-0100',
- email: 'ada@example.test',
- })
- })
-
- it('updateMunicipalityContact throws validation error for 422', async () => {
- const stub = async () =>
- new Response(JSON.stringify({ error: 'Name is required.' }), { status: 422 })
-
- await expect(
- updateMunicipalityContact('abc123', 'contact-1', { contactType: 'Primary', name: '' }, stub),
- ).rejects.toSatisfy(
- (e) => e instanceof MunicipalityContactValidationError && e.message.includes('Name'),
- )
- })
-
- it('deleteMunicipalityContact succeeds on 204 and throws on failure', async () => {
- const okStub = async () => new Response(null, { status: 204 })
- const failStub = async () => new Response('{}', { status: 500 })
-
- await expect(deleteMunicipalityContact('abc123', 'contact-1', okStub)).resolves.toBeUndefined()
- await expect(deleteMunicipalityContact('abc123', 'contact-1', failStub)).rejects.toThrow('500')
- })
- })
-
- describe('fetchPriorCycleDefaults', () => {
- it('returns read-only prior cycle defaults on 200', async () => {
- const stub = async () =>
- new Response(JSON.stringify(makeDefaults()), { status: 200 })
-
- const result = await fetchPriorCycleDefaults('abc123', stub)
-
- expect(result.hasPriorCycles).toBe(true)
- expect(result.selectedCycleId).toBe('cycle-2026-primary')
- expect(result.cycles[0].services[0].values.Quantity).toBe('1200')
- })
-
- it('returns empty state payload when no prior cycles exist', async () => {
- const stub = async () =>
- new Response(JSON.stringify(makeDefaults({
- hasPriorCycles: false,
- selectedCycleId: null,
- emptyStateMessage: 'No prior cycle defaults available.',
- cycles: [],
- })), { status: 200 })
-
- const result = await fetchPriorCycleDefaults('abc123', stub)
-
- expect(result.hasPriorCycles).toBe(false)
- expect(result.emptyStateMessage).toContain('No prior cycle')
- expect(result.cycles).toEqual([])
- })
-
- it('throws on non-200', async () => {
- const stub = async () => new Response('{}', { status: 404 })
-
- await expect(fetchPriorCycleDefaults('missing', stub)).rejects.toThrow('404')
- })
- })
-
- // ── createMunicipalityProfile ─────────────────────────────────────────────────
-
- describe('createMunicipalityProfile', () => {
- it('returns profile on 200', async () => {
- const stub = async () =>
- new Response(JSON.stringify(makeProfile()), { status: 200 })
-
- const result = await createMunicipalityProfile('FAIR01', 'Fairview', stub)
-
- expect(result.profileId).toBe('abc123')
- expect(result.jCode).toBe('FAIR01')
- })
-
- it('throws MunicipalityValidationError on 422 with descriptive message', async () => {
- const stub = async () =>
- new Response(JSON.stringify({ error: "No legacy jurisdiction found for JCode 'NOPE'." }), {
- status: 422,
- })
-
- await expect(createMunicipalityProfile('NOPE', null, stub)).rejects.toSatisfy(
- (e) => e instanceof MunicipalityValidationError && e.message.includes('NOPE'),
- )
- })
-
- it('throws generic Error on other non-200 status', async () => {
- const stub = async () => new Response('{}', { status: 500 })
-
- await expect(createMunicipalityProfile('FAIR01', null, stub)).rejects.toThrow('500')
- await expect(createMunicipalityProfile('FAIR01', null, stub)).rejects.not.toSatisfy(
- (e) => e instanceof MunicipalityValidationError,
- )
- })
- })
-
- // ── updateMunicipalityProfile ─────────────────────────────────────────────────
-
- describe('updateMunicipalityProfile', () => {
- it('returns updated profile on 200', async () => {
- const stub = async () =>
- new Response(JSON.stringify(makeProfile({ displayName: 'New Name' })), { status: 200 })
-
- const result = await updateMunicipalityProfile('abc123', 'New Name', stub)
-
- expect(result.displayName).toBe('New Name')
- })
-
- it('throws MunicipalityValidationError on 422', async () => {
- const stub = async () =>
- new Response(JSON.stringify({ error: 'Profile not found.' }), { status: 422 })
-
- await expect(updateMunicipalityProfile('ghost', 'X', stub)).rejects.toSatisfy(
- (e) => e instanceof MunicipalityValidationError,
- )
- })
- })
-
- // ── fetchAvailableJurisdictions ───────────────────────────────────────────────
-
- describe('fetchAvailableJurisdictions', () => {
- it('returns jurisdictions on 200', async () => {
- const stub = async () =>
- new Response(
- JSON.stringify([
- { jCode: 'FAIR01', name: 'Fairview Borough' },
- { jCode: 'LAKE02', name: null },
- ]),
- { status: 200 },
- )
-
- const result = await fetchAvailableJurisdictions(stub)
-
- expect(result).toHaveLength(2)
- expect(result[0].jCode).toBe('FAIR01')
- expect(result[0].name).toBe('Fairview Borough')
- expect(result[1].name).toBeNull()
- })
-
- it('throws on non-200', async () => {
- const stub = async () => new Response('{}', { status: 503 })
-
- await expect(fetchAvailableJurisdictions(stub)).rejects.toThrow('503')
- })
- })
-
- // ── MunicipalityValidationError ───────────────────────────────────────────────
-
- describe('MunicipalityValidationError', () => {
- it('has correct name and message', () => {
- const err = new MunicipalityValidationError('JCode not found')
-
- expect(err.name).toBe('MunicipalityValidationError')
- expect(err.message).toBe('JCode not found')
- expect(err).toBeInstanceOf(Error)
- })
- })
|