teqo-backup/tests/int/campaignMunicipalityUpdatePageData.int.spec.ts
Francisco Solla 368f138282 feat(campaign): C88 deliberação na atualização — responsável, fio e resolvido (Closes #397)
Migration 20260823_161905_add_municipality_update_deliberation + access por mutationKind + ilha client nas 3 superfícies + specs unit/int/e2e.
2026-08-23 18:58:26 -03:00

200 lines
6.8 KiB
TypeScript

// @vitest-environment node
import { getPayload, type Payload } from 'payload'
import { beforeAll, describe, expect, it } from 'vitest'
import config from '@/payload.config'
import {
loadMunicipalityUpdatesFeed,
loadMunicipalityUpdatesPreview,
municipalityUpdatesPageSize,
parseMunicipalityUpdateFeedParams,
} from '@/utilities/municipality/municipalityUpdatePageData'
import { installCampaignFixtures } from '../helpers/campaignFixtures'
let payload: Payload
const campaignFixtures = installCampaignFixtures({
getPayload: () => payload,
setPayload: (nextPayload) => {
payload = nextPayload
},
})
type CapturedQuery = {
text: string
values: unknown[]
}
const capturePoolQueries = async <Result>(
run: () => Promise<Result>,
): Promise<{ queries: CapturedQuery[]; result: Result }> => {
const pool = payload.db.pool
const originalOwnQueryDescriptor = Object.getOwnPropertyDescriptor(pool, 'query')
const originalQueryIdentity = pool.query
const queries: CapturedQuery[] = []
pool.query = function (this: typeof pool, ...args: Parameters<typeof pool.query>) {
const first = args[0]
const config =
typeof first === 'string' ? null : (first as { text?: string; values?: unknown[] })
queries.push({
text: typeof first === 'string' ? first : (config?.text ?? ''),
values: config?.values ?? (Array.isArray(args[1]) ? args[1] : []),
})
return Reflect.apply(originalQueryIdentity, this, args)
} as typeof pool.query
try {
return { queries, result: await run() }
} finally {
if (originalOwnQueryDescriptor) {
Object.defineProperty(pool, 'query', originalOwnQueryDescriptor)
} else {
delete (pool as { query?: typeof pool.query }).query
}
expect(pool.query).toBe(originalQueryIdentity)
expect(Object.getOwnPropertyDescriptor(pool, 'query')).toEqual(originalOwnQueryDescriptor)
const healthCheck = await pool.query<{ value: number }>('select 1::int as value')
expect(healthCheck.rows).toEqual([{ value: 1 }])
}
}
const queryLimit = ({ text, values }: CapturedQuery): unknown => {
const placeholder = text.match(/\blimit\s+\$(\d+)/i)?.[1]
return placeholder ? values[Number(placeholder) - 1] : undefined
}
describe('campaign municipality update page data queries', () => {
beforeAll(async () => {
payload = await getPayload({ config: await config })
})
it('parses feed params with a strict positive page', () => {
expect(parseMunicipalityUpdateFeedParams({})).toEqual({ page: 1 })
expect(parseMunicipalityUpdateFeedParams({ updatePage: '2' })).toEqual({
page: 2,
})
expect(parseMunicipalityUpdateFeedParams({ updatePage: '0' })).toEqual({
page: 1,
})
expect(parseMunicipalityUpdateFeedParams({ updatePage: ['3'] })).toEqual({
page: 3,
})
})
it('loads only the paginated feed and its authors', async () => {
const user = await campaignFixtures().createCampaignUser('coordinator', {
name: campaignFixtures().value('Coordenação geral'),
email: `${campaignFixtures().value('updates-loader')}@example.com`,
password: campaignFixtures().value('password'),
})
const municipality = await campaignFixtures().getMunicipality()
for (let index = 0; index < 4; index += 1) {
await payload.create({
collection: 'municipalityUpdate',
data: {
municipality: municipality.id,
author: user.id,
polarity: 'neutra',
body: `Atualização ${index}`,
},
depth: 0,
})
}
const { queries, result } = await capturePoolQueries(() =>
loadMunicipalityUpdatesFeed(payload, user, municipality.id, { page: 1 }),
)
const updateQueries = queries.filter(({ text }) => /\bfrom\s+"municipality_update"/i.test(text))
const updateDataQueries = updateQueries.filter((query) => queryLimit(query) !== undefined)
const authorQueries = queries.filter(({ text }) => /\bfrom\s+"campaign_user"/i.test(text))
expect(updateDataQueries).toHaveLength(1)
expect(queryLimit(updateDataQueries[0]!)).toBe(municipalityUpdatesPageSize)
expect(authorQueries.length).toBeGreaterThanOrEqual(1)
expect(result.updates).toHaveLength(4)
expect(result.totalDocs).toBe(4)
expect(result.updates[0]).toMatchObject({
polarity: 'neutra',
authorName: user.name,
})
})
it('paginates the unified feed', async () => {
const user = await campaignFixtures().createCampaignUser('coordinator')
const municipality = await campaignFixtures().getMunicipality()
for (let index = 0; index < 3; index += 1) {
await payload.create({
collection: 'municipalityUpdate',
data: {
municipality: municipality.id,
author: user.id,
polarity: 'neutra',
body: `Nota ${index}`,
},
depth: 0,
})
}
await payload.create({
collection: 'municipalityUpdate',
data: {
municipality: municipality.id,
author: user.id,
polarity: 'ruim',
urgent: true,
body: 'Urgente único',
},
depth: 0,
})
const all = await loadMunicipalityUpdatesFeed(payload, user, municipality.id, { page: 1 })
expect(all.totalDocs).toBe(4)
expect(all.updates.some((update) => update.body === 'Urgente único' && update.urgent)).toBe(
true,
)
})
it('loads only the latest-three preview', async () => {
const user = await campaignFixtures().createCampaignUser('coordinator')
const municipality = await campaignFixtures().getMunicipality()
for (let index = 0; index < 4; index += 1) {
await payload.create({
collection: 'municipalityUpdate',
data: {
municipality: municipality.id,
author: user.id,
polarity: 'neutra',
body: `Prévia ${index}`,
},
depth: 0,
})
}
const preview = await loadMunicipalityUpdatesPreview(payload, user, municipality.id)
expect(preview.updates).toHaveLength(3)
expect(preview.updates[0]?.body).toBe('Prévia 3')
})
it('denies leaders read access to municipality update feeds', async () => {
const coordinator = await campaignFixtures().createCampaignUser('coordinator')
const leaderAccount = await campaignFixtures().createCampaignUser('leader')
const municipality = await campaignFixtures().getMunicipality()
const contact = await campaignFixtures().createContact()
await campaignFixtures().createLeadership({
contact: contact.id,
municipalities: [municipality.id],
user: leaderAccount.id,
supportStatus: 'engajado',
})
await campaignFixtures().createMunicipalityUpdate({
municipality: municipality.id,
author: coordinator.id,
polarity: 'neutra',
body: 'Registro da coordenação',
})
await expect(
loadMunicipalityUpdatesFeed(payload, leaderAccount, municipality.id, { page: 1 }),
).rejects.toThrow(/permissão/i)
})
})