test(miss-73): allocate municipalities, never pin seeded slugs (deadlock class)

Int specs pinning seeded rows (cairu/feira-de-santana/catalog[0..2] or
class-derived fixed slugs) and mutating them deadlocked municipality_rels
under the parallel suite (40P01); e2e specs deep-linking ?municipio=cairu
raced the same row from two workers.

- homeSearchStateDeputies: 2 fixed slugs -> 2 allocations.
- municipalityPageData classe test: rewritten class-AGNOSTIC — allocates
  rows until two distinct territorial classes (same pinned behavior:
  filter narrows, totals honest, classe sort = descending weight), no
  fixed reduto/marginal slugs. nivel test + facet test: plain allocations.
- campaignHomeActions/campaignWizardChrome: 11 hardcoded cairu deep links
  -> claimMunicipality(); hit clicks disambiguate by href (catalog names
  are not prefix-unique); caption/URL assertions use ^-anchored full-URL
  regexes (P3-C template guard). /Cairu/i literals gone.

Verified: int 17/17, e2e 18/18 (dev mode), unit 1106/1106.
This commit is contained in:
Cursor Agent 2026-07-31 05:31:11 +00:00 committed by Francisco Ignácio Fontoura Solla
parent f27e801e25
commit 6b894a04c5
4 changed files with 94 additions and 43 deletions

View file

@ -82,17 +82,22 @@ test.describe('Início — busca global (B47)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
// Claimed, never hardcoded: parallel specs sharing one seeded municipality
// raced each other's mutations on it (miss #73).
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
const search = page.getByLabel('Buscar na campanha')
await search.fill('Cairu')
await search.fill(municipality.name)
const group = page.getByRole('region', { name: 'Resultados da busca' })
await expect(group.getByRole('heading', { name: 'Municípios' })).toBeVisible({
timeout: 15000,
})
await group.getByRole('link', { name: /Cairu/i }).click()
await page.waitForURL(/\/campanha\/municipios\/cairu/)
// Disambiguate by href, not by name: catalog names are not prefix-unique
// ("Conde" also matches "Condeúba" — the unanchored-regex lesson).
await group.locator(`a[href$="/campanha/municipios/${municipality.slug}"]`).click()
await page.waitForURL(new RegExp(`^https?://[^/]+/campanha/municipios/${municipality.slug}$`))
})
})
@ -158,20 +163,26 @@ test.describe('Wizard — busca município (B60)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/atualizar-votos')
await expect(page.getByRole('heading', { name: WIZARD_MUNICIPALITY_STEP_TITLE })).toBeVisible()
const search = page.getByLabel('Buscar município')
await search.fill('Cairu')
await search.fill(municipality.name)
const results = page.getByRole('region', { name: 'Resultados da busca' })
await expect(results.getByRole('link', { name: /Cairu/i })).toBeVisible({ timeout: 15000 })
await results.getByRole('link', { name: /Cairu/i }).click()
const hit = results.locator(`a[href*="municipio=${municipality.slug}"]`)
await expect(hit).toBeVisible({ timeout: 15000 })
await hit.click()
await page.waitForURL(/\/campanha\/acoes\/atualizar-votos\?municipio=cairu/)
await expect(page.getByLabel(/Município em atualização: Cairu/i)).toBeVisible()
await page.waitForURL(
new RegExp(`^https?://[^/]+/campanha/acoes/atualizar-votos\\?municipio=${municipality.slug}$`),
)
await expect(
page.getByLabel(new RegExp(`^Município em atualização: ${municipality.name}`, 'i')),
).toBeVisible()
await expect(page.getByRole('heading', { name: 'Ajustar votos estimados' })).toBeVisible()
})
})
@ -188,9 +199,10 @@ test.describe('Wizard — ajuste de votos (B61 / B77)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/atualizar-votos?municipio=cairu')
await page.goto(`/campanha/acoes/atualizar-votos?municipio=${municipality.slug}`)
const step = wizardVoteStep(page)
await expect(page.getByRole('heading', { name: 'Ajustar votos estimados' })).toBeVisible({
@ -217,9 +229,10 @@ test.describe('Wizard — ajuste de votos (B61 / B77)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/atualizar-votos?municipio=cairu')
await page.goto(`/campanha/acoes/atualizar-votos?municipio=${municipality.slug}`)
await expect(page.getByRole('heading', { name: 'Ajustar votos estimados' })).toBeVisible({
timeout: 15000,
@ -239,11 +252,18 @@ test.describe('Wizard — ajuste de votos (B61 / B77)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/atualizar-votos?municipio=cairu&cenario=pessimistic')
await page.goto(
`/campanha/acoes/atualizar-votos?municipio=${municipality.slug}&cenario=pessimistic`,
)
await page.waitForURL(/\/campanha\/acoes\/atualizar-votos\?municipio=cairu$/)
await page.waitForURL(
new RegExp(
`^https?://[^/]+/campanha/acoes/atualizar-votos\\?municipio=${municipality.slug}$`,
),
)
await expect(page.getByRole('heading', { name: 'Ajustar votos estimados' })).toBeVisible({
timeout: 15000,
})
@ -256,10 +276,11 @@ test.describe('Wizard — atualizar liderança (B70)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
const suffix = Date.now().toString().slice(-8).padStart(8, '0')
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/atualizar-lideranca?municipio=cairu')
await page.goto(`/campanha/acoes/atualizar-lideranca?municipio=${municipality.slug}`)
await expect(page.getByRole('heading', { name: 'Quem coordena por aqui?' })).toBeVisible({
timeout: 15000,
@ -293,8 +314,10 @@ test.describe('Wizard — registrar sinal (B63)', () => {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/registrar-sinal?municipio=cairu')
await page.goto(`/campanha/acoes/registrar-sinal?municipio=${municipality.slug}`)
await expect(page.getByRole('heading', { name: 'Que tipo de sinal?' })).toBeVisible({
timeout: 15000,
@ -319,9 +342,10 @@ test.describe('Wizard — registrar sinal (B63)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/registrar-sinal?municipio=cairu')
await page.goto(`/campanha/acoes/registrar-sinal?municipio=${municipality.slug}`)
await expect(page.getByRole('heading', { name: 'Que tipo de sinal?' })).toBeVisible({
timeout: 15000,
@ -341,9 +365,12 @@ test.describe('Wizard — registrar sinal (B63)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/registrar-sinal?municipio=cairu&entry=update-votes')
await page.goto(
`/campanha/acoes/registrar-sinal?municipio=${municipality.slug}&entry=update-votes`,
)
await expect(page.getByRole('link', { name: 'Pular registro de sinal' })).toBeVisible({
timeout: 15000,

View file

@ -35,9 +35,12 @@ test.describe('Wizard — header mobile (B75)', () => {
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora Geral'),
})
// Claimed, never hardcoded: parallel specs sharing one seeded municipality
// raced each other's mutations on it (miss #73).
const municipality = await fixtures.claimMunicipality()
await campaign.login(page, coordinator.email!, coordinator.password)
await page.goto('/campanha/acoes/atualizar-votos?municipio=cairu')
await page.goto(`/campanha/acoes/atualizar-votos?municipio=${municipality.slug}`)
await expect(page.getByRole('heading', { name: 'Ajustar votos estimados' })).toBeVisible({
timeout: 15000,
@ -45,7 +48,9 @@ test.describe('Wizard — header mobile (B75)', () => {
const topBar = page.locator('[data-slot="campaign-mobile-top-bar"][data-mode="wizard"]')
await expect(topBar.getByText('Ajustar votos', { exact: true })).toBeVisible()
await expect(topBar.getByLabel(/Município em atualização: Cairu/i)).toBeVisible()
await expect(
topBar.getByLabel(new RegExp(`^Município em atualização: ${municipality.name}`, 'i')),
).toBeVisible()
await expect(page.getByRole('link', { name: /Voltar/ })).toBeVisible()
await page.getByRole('link', { name: /Voltar/ }).click()

View file

@ -68,11 +68,13 @@ describe('searchHomeStateDeputies (B52)', () => {
const fixtures = campaignFixtures()
const coordinator = await fixtures.createCampaignUser('coordinator')
const stateDeputy = await fixtures.createStateDeputy({ name: 'Carteira Dobradinha' })
const cairu = await fixtures.getMunicipality('cairu')
const feira = await fixtures.getMunicipality('feira-de-santana')
// Allocated, never pinned slugs: parallel specs mutating the same seeded
// municipality deadlocked municipality_rels (miss #73).
const first = await fixtures.getMunicipality()
const second = await fixtures.getMunicipality()
await setStateDeputyMunicipalitiesBatchRecord(payload, coordinator, {
stateDeputyId: stateDeputy.id,
municipalityIds: [cairu.id, feira.id],
municipalityIds: [first.id, second.id],
assigned: true,
})

View file

@ -5,6 +5,7 @@ import { getPayload } from 'payload'
import { beforeAll, describe, expect, it } from 'vitest'
import { getMunicipalityCatalogEntry, municipalityCatalog } from '@/lib/municipalityCatalog'
import { territorialClassSortWeight } from '@/lib/territorialClassSortWeight'
import config from '@/payload.config'
import { loadMunicipalityListPageBundle } from '@/utilities/municipality/municipalityPageData'
import { computeMunicipalityTerritorialClass } from '@/utilities/municipality/municipalityTerritorialClass'
@ -287,19 +288,27 @@ describe('loadMunicipalityListPageBundle', () => {
const fixtures = campaignFixtures()
const coordinator = await fixtures.createCampaignUser('coordinator')
const redutoEntry = municipalityCatalog.find(
(entry) => computeMunicipalityTerritorialClass(entry.slug).class === 'reduto',
)!
const marginalEntry = municipalityCatalog.find(
(entry) => computeMunicipalityTerritorialClass(entry.slug).class === 'marginal',
)!
const reduto = await fixtures.getMunicipality(redutoEntry.slug)
const marginal = await fixtures.getMunicipality(marginalEntry.slug)
// Two allocated municipalities with DISTINCT territorial classes: the
// behavior pinned here (class filter narrows, totals stay honest, classe
// sort follows the weight table) does not care WHICH classes they are —
// and pinning fixed catalog slugs raced parallel specs mutating the same
// seeded rows (deadlock on municipality_rels, miss #73).
const first = await fixtures.getMunicipality()
const firstClass = computeMunicipalityTerritorialClass(first.slug).class
let second = await fixtures.getMunicipality()
let secondClass = computeMunicipalityTerritorialClass(second.slug).class
for (let attempt = 0; secondClass === firstClass && attempt < 20; attempt++) {
second = await fixtures.getMunicipality()
secondClass = computeMunicipalityTerritorialClass(second.slug).class
}
if (secondClass === firstClass) {
throw new Error('could not allocate municipalities of two distinct classes')
}
const marker = `e10-classe-${Date.now()}`
for (const [municipality, suffix] of [
[reduto, 'reduto'],
[marginal, 'marginal'],
[first, 'filtrada'],
[second, 'fora'],
] as const) {
await payload.update({
collection: 'municipality',
@ -313,29 +322,39 @@ describe('loadMunicipalityListPageBundle', () => {
const filtered = await loadMunicipalityListPageBundle(payload, coordinator, {
q: marker,
class: 'reduto',
class: firstClass,
})
expect(filtered.municipalities.map((row) => row.slug)).toEqual([reduto.slug])
expect(filtered.municipalities.map((row) => row.slug)).toEqual([first.slug])
expect(filtered.totalDocs).toBe(1)
expect(filtered.overview?.municipalityCount).toBe(1)
expect(filtered.municipalities[0]!.territorialClass).toBe('reduto')
expect(filtered.municipalities[0]!.territorialClass).toBe(firstClass)
expect(filtered.municipalities[0]!.territorialClassFactors.length).toBeGreaterThan(0)
// A native sort key would otherwise let Payload paginate before the class
// filter runs, which would hand back a short page with an inflated total.
const byName = await loadMunicipalityListPageBundle(payload, coordinator, {
q: marker,
class: 'reduto',
class: firstClass,
sort: 'name',
})
expect(byName.municipalities.map((row) => row.slug)).toEqual([reduto.slug])
expect(byName.municipalities.map((row) => row.slug)).toEqual([first.slug])
expect(byName.totalDocs).toBe(1)
const sorted = await loadMunicipalityListPageBundle(payload, coordinator, {
q: marker,
sort: 'classe',
})
expect(sorted.municipalities.map((row) => row.slug)).toEqual([reduto.slug, marginal.slug])
// `classe` sorts strongest class first (reduto 4 … marginal 1; every
// catalog row classes into a weighted class, so no null weight exists).
const expectedOrder = [first, second]
.map((municipality) => ({
slug: municipality.slug,
weight:
territorialClassSortWeight[computeMunicipalityTerritorialClass(municipality.slug).class],
}))
.sort((left, right) => (right.weight ?? 0) - (left.weight ?? 0))
.map((entry) => entry.slug)
expect(sorted.municipalities.map((row) => row.slug)).toEqual(expectedOrder)
})
/**
@ -348,9 +367,9 @@ describe('loadMunicipalityListPageBundle', () => {
const fixtures = campaignFixtures()
const coordinator = await fixtures.createCampaignUser('coordinator')
const low = await fixtures.getMunicipality(municipalityCatalog[0]!.slug)
const high = await fixtures.getMunicipality(municipalityCatalog[1]!.slug)
const none = await fixtures.getMunicipality(municipalityCatalog[2]!.slug)
const low = await fixtures.getMunicipality()
const high = await fixtures.getMunicipality()
const none = await fixtures.getMunicipality()
const marker = `e14-nivel-${Date.now()}`
for (const [municipality, suffix, engagementLevel] of [
@ -446,9 +465,7 @@ describe('loadMunicipalityListPageBundle', () => {
data: { priority: 'alta' },
overrideAccess: true,
})
const covered = await fixtures.getMunicipality(
municipalityCatalog.find((entry) => entry.slug !== priorityMunicipality.slug)!.slug,
)
const covered = await fixtures.getMunicipality()
await fixtures.assignMunicipalityAdvisors(covered.id, [advisor.id])
// "Prioritária" lives in the Município popover, so it must not hide the