teqo-backup/tests/e2e/campaignDemandVisibility.e2e.spec.ts
Francisco Solla 2cc8f2c14d feat(C143): demandas visíveis apenas para responsáveis explícitos + candidato/coordenador
- campaignDemand.responsibles (hasMany → campaignUser, staff elegível) com
  hook beforeValidate que descarta ids não-staff (fail-closed)
- canRead/UpdateCampaignDemand por membership: assessor só vê demandas onde
  é responsável; unrestricted/admin sempre; leader nunca (fim do escopo
  municipal de demandas)
- criador entra automaticamente como responsável no hook de criação (form,
  wizard, rascunhos C90); migration com backfill created_by_id → responsável
- UI: picker multi-select na criação (nova + wizard) e card de gestão no
  detalhe com save full-replace + advisory lock; sugestões = assessores do
  município; rótulo 'Visível para: Responsáveis · Candidato · Coordenador'
- int tests reescritos para a regra nova + 5 casos novos; e2e novo de
  visibilidade (lista/URL/gestão); fixture e2e com createCampaignDemand
- impl plan + changelog; importMap regenerado com S3_* dummy (OPS69)
2026-08-19 23:55:07 -03:00

102 lines
4.1 KiB
TypeScript

import {
campaignPageChrome,
expect,
test,
type CampaignE2EFixture,
} from './fixtures/campaignE2EFixtures.js'
test.describe('C143 — demand visibility (explicit responsibles)', () => {
const setupDemand = async (
campaign: CampaignE2EFixture,
fixtures: CampaignE2EFixture['fixtures'],
) => {
const municipality = await fixtures.claimMunicipality()
const coordinator = await fixtures.createCampaignUser('coordinator', {
name: fixtures.value('Coordenadora C143'),
})
const responsible = await fixtures.createCampaignUser('advisor', {
name: fixtures.value('Assessora Responsável'),
})
const peer = await fixtures.createCampaignUser('advisor', {
name: fixtures.value('Assessor Colega'),
})
await campaign.payload.update({
collection: 'municipality',
id: municipality.id,
data: { advisors: [responsible.id, peer.id] },
depth: 0,
})
const demand = await fixtures.createCampaignDemand({
municipality: municipality.id,
createdBy: coordinator.id,
responsibles: [responsible.id],
title: fixtures.value('Demanda de acesso restrito'),
})
return { municipality, coordinator, responsible, peer, demand }
}
test('hides the demand from a same-municipality advisor who is not responsible', async ({
campaign,
context,
page,
}) => {
const { fixtures } = campaign
const { peer, demand } = await setupDemand(campaign, fixtures)
await campaign.sessionFor(context, peer)
await page.goto(`${campaign.baseURL}/campanha/demandas`)
// The list rendered for him: the empty state proves the page settled
// (and that the demand is absent — the link assertion below is the
// fail-closed complement).
await expect(page.getByText('Nenhuma demanda por aqui')).toBeVisible()
await expect(page.getByRole('link', { name: demand.title })).toHaveCount(0)
// The URL does not open either — the demand simply does not exist for him
// (the default 404 boundary is the root one, no campaign-specific page).
await page.goto(`${campaign.baseURL}/campanha/demandas/${demand.slug}`)
await expect(page.getByText('This page could not be found.')).toBeVisible()
})
test('lets a responsible hand access to a peer and revoke it again', async ({
campaign,
context,
page,
}) => {
const { fixtures } = campaign
const { peer, responsible, demand } = await setupDemand(campaign, fixtures)
const demandURL = `${campaign.baseURL}/campanha/demandas/${demand.slug}`
// The responsible manages the list: add the peer through the search dialog.
await campaign.sessionFor(context, responsible)
await page.goto(demandURL)
await expect(page.getByText('Só quem é responsável vê esta demanda.')).toBeVisible()
// The trigger announces the current selection (e.g. "…: 1 responsável").
await page.getByRole('button', { name: /Responsáveis pela demanda/ }).click()
await page.getByPlaceholder('Buscar assessor…').fill(peer.name)
await page.getByRole('option', { name: peer.name }).click()
// The multi-add dialog stays open (modal) — close it before saving.
await page.keyboard.press('Escape')
await page.getByRole('button', { name: 'Salvar responsáveis' }).click()
await expect(page.getByText('Responsáveis atualizados.')).toBeVisible()
await expect(page.getByRole('button', { name: `Remover ${peer.name}` })).toBeVisible()
// The peer now opens the demand.
await campaign.sessionFor(context, peer)
await page.goto(demandURL)
await expect(campaignPageChrome(page, demand.title)).toBeVisible()
// Back as the responsible, remove the peer and revoke the access again.
await campaign.sessionFor(context, responsible)
await page.goto(demandURL)
await page.getByRole('button', { name: `Remover ${peer.name}` }).click()
await page.getByRole('button', { name: 'Salvar responsáveis' }).click()
await expect(page.getByRole('button', { name: `Remover ${peer.name}` })).toHaveCount(0)
await campaign.sessionFor(context, peer)
await page.goto(demandURL)
await expect(page.getByText('This page could not be found.')).toBeVisible()
})
})