# Pauta do Dia — Testes

> **Slug:** `pauta-do-dia` · **Módulo:** `modulos.id = 42` · **Novo na v6**
> A v6 **não possui nenhum teste automatizado** para este módulo (nem para o
> CRUD genérico de `contents` que o serve). As sessões abaixo descrevem o
> que deve ser verificado e como verificar na v6 (manualmente) e na v7
> (automatizado).

---

## 1. Estratégia

| Camada | Escopo | Ferramenta sugerida (v7) |
|---|---|---|
| Unitário | Extração/ordenação do número de sessão; ordenação canônica do model. | PHPUnit + Pest. |
| Feature (portal) | Listagem, paginação, detalhe, 404, anexos. | Laravel HTTP tests (`get('/pauta-do-dia')`). |
| Feature (CMS) | Listagem (endpoint DataTable), cadastro, edição, exclusão, permissões. | HTTP tests com usuário autenticado e papéis. |
| Integração (banco) | View `pauta_do_dia` x consulta Eloquent; consistência da ordenação portal x CMS. | Teste com banco real (PostgreSQL) da v6; na v7, sem view. |
| Manual (v6) | Conferência do comportamento no navegador. | Checklist abaixo. |

## 2. Fixtures necessárias (v6)

Nenhuma factory do módulo existe na v6. Para montar o cenário, o operador
precisa criar os dados pelo próprio CMS (`/cms/conteudos/cadastrar/pauta-do-dia`)
ou por script.

| Fixture | Conteúdo |
|---|---|
| Categoria | `category_contents` com `slug = 'pauta-do-dia'` (já criada pelo seed `CategoryContentTableSeeder.php:212`). |
| Pauta A | `title = "19ª Sessão"`, `publication_date = 2026-03-10 09:00:00`, `text = "<p>Conteúdo da 19ª sessão</p>"`, `slug = "19a-sessao"`. |
| Pauta B | `title = "18ª Sessão"`, `publication_date = 2026-03-10 14:00:00`, mesmo dia, mesmo formato. |
| Pauta C | `title = "20ª Sessão"`, `publication_date = 2026-03-11 09:00:00`, dia posterior. |
| Pauta D | `title = "Reunião"`, `publication_date = 2026-03-10 18:00:00` — título **sem dígitos**. |
| Pauta E | `title = "15ª Sessão 2026"`, `publication_date = 2026-03-10 10:00:00` — caso patológico (ano entra no número). |
| Anexos | Um PDF com `view_pdf = true`; um `.docx` comum; uma imagem com `highlight = true`. |

> Todos os registros devem estar em `contents` com
> `category_contents_id` = id da categoria `pauta-do-dia` (38 no banco de
> referência) e `deleted_at IS NULL`.

## 3. Casos de teste — Portal (feature)

| # | Arrange | Act | Assert |
|---|---|---|---|
| P1 | Pautas A, B, C, D, E publicadas | `GET /pauta-do-dia` | Resposta 200; título "Pautas do dia" presente; paginação presente. |
| P2 | Pautas A, B, D no mesmo dia | `GET /pauta-do-dia` | Ordem dos títulos: **E (número maior por digits) conforme a regra**, B e D conforme o número extraído; **19ª antes de 18ª**; título sem dígitos ("Reunião") por último no grupo do dia. |
| P3 | Pauta C em dia posterior | `GET /pauta-do-dia` | A pauta do dia mais recente aparece antes das do dia anterior. |
| P4 | 20 pautas cadastradas | `GET /pauta-do-dia` | Exibe 15 itens (padrão de `paginate()`) e a paginação; `GET /pauta-do-dia?page=2` exibe os 5 restantes. |
| P5 | Pauta A com texto HTML longo | `GET /pauta-do-dia` | O resumo aparece sem tags HTML e com no máximo 200 caracteres. |
| P6 | Nenhuma pauta | `GET /pauta-do-dia` | Exibe "Nenhuma registro encontrado." e HTTP 200. |
| P7 | Pauta A existente | `GET /pauta-do-dia/19a-sessao` | HTTP 200; título, data e hora ("10/03/2026 às 09:00"), autor/fonte e corpo renderizado. |
| P8 | Slug inexistente | `GET /pauta-do-dia/nao-existe` | HTTP **404**. |
| P9 | Slug de uma pauta de **outra** categoria | `GET /pauta-do-dia/{slug_de_noticia}` | HTTP **404** (o filtro por categoria é aplicado). |
| P10 | Pauta A com imagem `highlight = 1` | `GET /pauta-do-dia/19a-sessao` | A imagem de destaque é renderizada com `url($path)`. |
| P11 | Pauta A com anexo `view_pdf = true` | `GET /pauta-do-dia/19a-sessao` em desktop (User-Agent sem mobile) | O componente `files-list` é incluído; o PDF aparece em `iframe`; o mesmo arquivo **não** aparece na lista de download. |
| P12 | Mesma pauta, User-Agent de celular | `GET /pauta-do-dia/19a-sessao` | O `iframe` do PDF **não** é renderizado; os anexos aparecem na lista de download. |
| P13 | Pauta A com anexo `.docx` | `GET /pauta-do-dia/19a-sessao` | Item "Arquivos para Download" com link `url($archive->path)`. |
| P14 | Anexo cujo arquivo não existe no disco | `GET /pauta-do-dia/19a-sessao` | O anexo **não** é listado (filtro `file_exist`). |
| P15 | Pauta com `status = false` | `GET /pauta-do-dia` | **Comportamento atual (a registrar como bug)**: a pauta aparece mesmo sem estar publicada. |

## 4. Casos de teste — CMS (feature)

| # | Arrange | Act | Assert |
|---|---|---|---|
| C1 | Operador com permissão `pauta-do-dia.index` | `GET /cms/conteudos/pauta-do-dia` | HTTP 200; tabela `#tabela_pauta-do-dia` presente; título "Lista de Pautas do Dia". |
| C2 | Operador sem a permissão | `GET /cms/conteudos/pauta-do-dia` | Redirecionamento com aviso "Você não tem autorização para acessar esta funcionalidade!" (middleware `check.route.permission`). |
| C3 | Qualquer operador | `GET /cms/datatable/pauta-do-dia` | JSON no formato DataTables; `data` contém `value`, `titulo`, `data_e_hora`, `id`, `slug`, `edit`, `delete`. |
| C4 | Pautas A e B no mesmo dia | `GET /cms/datatable/pauta-do-dia` com `order[0][column]=3&order[0][dir]=desc` | A "19ª Sessão" aparece antes da "18ª Sessão" (mesma regra do portal). |
| C5 | Pauta A e B | `GET /cms/datatable/pauta-do-dia` com `order[0][column]=2` (Título) | Ordem **alfabética** pelo título (a ordenação por número só existe na coluna de data). |
| C6 | Operador com `pauta-do-dia.create` | `GET /cms/conteudos/pauta-do-dia` | Botão "Cadastrar Pauta do Dia" presente. |
| C7 | Operador sem `pauta-do-dia.create` | `GET /cms/conteudos/pauta-do-dia` | Botão "Cadastrar Pauta do Dia" ausente. |
| C8 | Operador com `pauta-do-dia.delete` | `GET /cms/conteudos/pauta-do-dia` | Checkbox mestre e botão de exclusão em lote presentes. |
| C9 | Formulário de cadastro | `POST /cms/conteudo` sem `title` | Erro de validação em `title` ("required"). |
| C10 | Formulário de cadastro | `POST /cms/conteudo` com `title` de 121 caracteres | Erro de validação (limite real é 120, apesar da dica de 150 no formulário). |
| C11 | Formulário de cadastro | `POST /cms/conteudo` sem `publication_date` | Erro de validação em `publication_date`. |
| C12 | Formulário com `archive_highlight` de 6 MB | `POST /cms/conteudo` | Erro de validação (máximo 5 MB). |
| C13 | Formulário com anexo `.exe` | `POST /cms/conteudo` | Erro de validação (`mimes`) ou mensagem de extensões não permitidas. |
| C14 | Cadastro válido | `POST /cms/conteudo` | Redirecionamento com mensagem de sucesso; registro em `contents` com `category_contents_id` da categoria; slug gerado a partir do título. |
| C15 | Título já existente | `POST /cms/conteudo` | O slug recebe sufixo numérico (sem colisão de URL). |
| C16 | Cadastro válido | `POST /cms/conteudo` | Registro na tabela de atividades (`activity log`) com "Cadastro de conteúdo". |
| C17 | Edição | `PUT /cms/conteudo/{id}` | Dados atualizados; "Edição de conteúdo" registrado na auditoria. |
| C18 | Exclusão | `DELETE /cms/conteudo/{id}` | Resposta JSON de sucesso; `contents.deleted_at` preenchido; "Exclusão de conteúdo" na auditoria. |
| C19 | Exclusão em lote | `POST /cms/conteudo/deleteAll` com `toDelete[]` | Conteúdos removidos (soft delete). |
| C20 | Abrir a listagem | `GET /cms/conteudos/pauta-do-dia` | `menu_orders.clicks` incrementado para o slug `pauta-do-dia`. |
| C21 | DataTable | Clic no botão "Compartilhar no facebook" | Abre `https://www.facebook.com/sharer/sharer.php?u={origin}/pauta-do-dia/{slug}` em nova aba. |
| C22 | DataTable | Clic no botão "Editar" | Abre `/cms/conteudos/editar/pauta-do-dia/{id}`. |
| C23 | DataTable | Exportar | Gera Excel e PDF com as colunas visíveis, exceto "Ações". |

## 5. Casos de teste — Banco / view SQL (v6)

| # | Arrange | Act | Assert |
|---|---|---|---|
| B1 | Base com pautas | `select * from pauta_do_dia` | Uma linha por pauta não excluída da categoria; `value` sequencial; `data_e_hora` no formato "DD/MM/YYYY às HH24:MI". |
| B2 | Pauta com `deleted_at` preenchido | `select * from pauta_do_dia` | A pauta **não** aparece na view. |
| B3 | Pauta em subcategoria | `select * from pauta_do_dia` | A pauta **não** aparece (a view considera apenas a categoria raiz). |
| B4 | Duas categorias com o mesmo slug | `select * from pauta_do_dia` | **Erro** do PostgreSQL (subquery escalar com mais de uma linha) — risco real documentado. |
| B5 | Comparar portal x CMS | Listar títulos nos dois lados | Mesma ordem (garantia manual de que PHP e JS concordam). |
| B6 | `contents.status = false` | `select * from pauta_do_dia` | A pauta **aparece** (comportamento atual, sem filtro de publicação). |

## 6. Comandos exatos

### 6.1 Verificação manual na v6 (containers)

```bash
cd /home/one/projects/maxima/v7

# Conteúdo da view
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select pg_get_viewdef('pauta_do_dia'::regclass, true)"

# Pautas cadastradas
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select c.id, c.title, c.publication_date, c.slug, c.status from contents c join category_contents cc on cc.id = c.category_contents_id where cc.slug = 'pauta-do-dia' and c.deleted_at is null order by c.publication_date desc"

# Simulação da ordenação do portal (mesmo SQL do orderByRaw)
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select title from contents c join category_contents cc on cc.id = c.category_contents_id where cc.slug = 'pauta-do-dia' and c.deleted_at is null order by c.publication_date::date DESC, COALESCE(NULLIF(regexp_replace(c.title, '[^0-9]', '', 'g'), ''), '0')::numeric DESC, c.id DESC"

# Anexos de uma pauta
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select id, name, extension, highlight, view_pdf, path from archives where contents_id = 123"

# Telemetria de cliques do menu
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select id, slug, clicks, modulo_id from menu_orders where slug = 'pauta-do-dia'"
```

### 6.2 Testes automatizados na v7 (Laravel 11/12)

```bash
# Suíte completa
php artisan test

# Somente o módulo
php artisan test --filter=PautaDoDia

# Arquivo específico
php artisan test tests/Feature/Portal/PautaDoDiaTest.php
php artisan test tests/Feature/Cms/PautaDoDiaManagementTest.php
php artisan test tests/Unit/PautaDoDiaOrderingTest.php

# Com cobertura apenas do módulo
php artisan test --coverage --min=80 --filter=PautaDoDia

# Banco de testes (PostgreSQL)
php artisan migrate:fresh --env=testing --seed
```

Comandos de apoio:

```bash
# Rotas do módulo
php artisan route:list --path=pauta-do-dia

# Limpar cache de rotas/views após alterar o módulo
php artisan optimize:clear

# Verificar a ordenação no console (tinker)
php artisan tinker
>>> App\Models\PautaDoDia::query()->ordenada()->pluck('title');
```

## 7. Checklist manual no navegador (v6)

1. Abrir `/pauta-do-dia` e confirmar data, título, resumo e paginação.
2. Clicar em uma pauta e confirmar título, autor/fonte, data **e hora**, imagem
   de destaque, texto, PDF embutido e anexos.
3. Abrir `/pauta-do-dia/inexistente` e confirmar a página 404.
4. No CMS, abrir `/cms/conteudos/pauta-do-dia` e confirmar colunas, ordenação
   (clicar em "Data e Hora" duas vezes), botões de ação e exportações.
5. Cadastrar uma pauta com título "21ª Sessão" e data do mesmo dia; conferir
   que ela aparece **acima** da "19ª Sessão" no portal.
6. Cadastrar uma pauta com título "22ª Sessão 2026" e observar a posição
   (comportamento patológico documentado em `05-REGRAS-E-PORTABILIDADE.md`).
7. Editar o `slug` e confirmar que a URL antiga deixa de funcionar (404) e a
   nova funciona.
8. Excluir a pauta e confirmar que ela some da listagem pública e do CMS.
9. Abrir a tela do CMS e conferir o incremento de `menu_orders.clicks`.

## 8. Cobertura de teste esperada na v7

| Classe de teste | Quantidade mínima | Foco |
|---|---|---|
| `tests/Unit/PautaDoDiaOrderingTest.php` | 6 | Ordenação por data, por número, nulos, desempate por id, paridade PHP/JS legada. |
| `tests/Feature/Portal/PautaDoDiaIndexTest.php` | 8 | Paginação, ordem, estado vazio, filtro de status. |
| `tests/Feature/Portal/PautaDoDiaShowTest.php` | 7 | Detalhe, 404, slug de outra categoria, anexos, destaque, PDF. |
| `tests/Feature/Cms/PautaDoDiaManagementTest.php` | 9 | Permissões, validação, CRUD, auditoria, exclusão em lote. |
| `tests/Feature/Api/PautaDoDiaApiTest.php` | 6 | Resource JSON, filtros, paginação, códigos HTTP. |

Critério de aceitação da v7: **100 % dos casos da seção 3 e 4** implementados
como testes automatizados, sem casos pendentes, e a ordenação da v6
reproduzida exatamente (ver `06-MIGRACAO-MODERNA.md`, seção 8).
