# Agenda de Eventos — Estratégia de Testes

> Base existente no legado: `v6/tests/Feature/DashboardEventosWidgetTest.php`
> (regressão do widget "Eventos" do dashboard contra a view `agenda_de_eventos`).
> Este arquivo descreve a cobertura **necessária na v7**.

---

## 1. Estratégia

| Tipo | Escopo | Ferramenta sugerida (v7) |
|---|---|---|
| **Feature (CMS)** | Cadastro, edição, exclusão, exclusão em lote, categorias, permissões, upload | Pest ou PHPUnit + `RefreshDatabase` + actingAs |
| **Feature (Portal)** | Lista, filtro por categoria, detalhe, slug inexistente, rascunho invisível | Pest/PHPUnit, sem autenticação |
| **Unit** | Geração de slug, conversão de data, desambiguação `-N`, regra de status | PHPUnit puro |
| **Integração (view)** | Substituição da view SQL por query Eloquent: paridade de colunas e ordenação | Teste que compara o payload JSON antigo vs novo |

Cobertura mínima aceitável: **todas as regras R1–R18** de
`05-REGRAS-E-PORTABILIDADE.md` devem ter ao menos um teste automatizado.

---

## 2. Fixtures / factories necessárias

```php
// database/factories/CalendarEventFactory.php
CalendarEvent::factory()
    ->published()                 // status = true
    ->draft()                     // status = false
    ->withCategory('cultura')     // cria CategoryCalendarEvent e liga o id
    ->withDates('2026-03-01 18:00', '2026-03-01 22:00')
    ->withHighlightMedia()        // cria o registro de mídia com is_highlight = true
    ->withGalleryMedia(3);        // 3 itens de galeria

CategoryCalendarEvent::factory()->withName('Cultura');   // slug automático
Content::factory()->inCategory('secretarias');           // para secretaria_id / orgaos_id
```

Requisitos:

- `User` com papel `super-admin` e outro com papel "Administrador" **sem**
  `agenda-evento.delete` (para testar as Policies);
- storage fake (`Storage::fake('public')`) para os uploads;
- `Configuration` com `key = 'layout'` (o portal aborta 404 sem a view do template).

---

## 3. Casos de teste — CMS

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| T1 | Cadastro válido | usuário logado, categoria existente, título, data inicial, texto | `POST /cms/agenda-evento` | 302 para `agenda-evento.index`; registro criado com `status = true` e slug gerado |
| T2 | Título obrigatório | payload sem `title` | `POST /cms/agenda-evento` | 302 de volta + `session('errors')` com "Por favor, informe um título." |
| T3 | Categoria obrigatória | `category_calendar_events_id = 0` | `POST` | erro "O campo Categoria é obrigatório" |
| T4 | Categoria inexistente | `category_calendar_events_id = 99999` | `POST` | erro de validação (`exists`) |
| T5 | Data de encerramento vazia | `final_date = ''` | `POST` | **não** deve falhar silenciosamente (correção de A7): ou erro de validação claro, ou gravação com `final_date = null` |
| T6 | Slug duplicado | dois eventos com o mesmo título | 2x `POST` | slugs `titulo` e `titulo-2` |
| T7 | Slug limitado a 50 caracteres | título de 120 chars | `POST` | `strlen($event->slug) <= 51` (50 + sufixo) |
| T8 | Rascunho | desmarcar "Publicar" | `POST` | `status = false`; evento **ausente** em `GET /agenda-eventos` |
| T9 | Redirect para galeria | marcar "Ativar Galeria" | `POST` | 302 para `editar-agenda` com `#galeria` na URL |
| T10 | Imagem de destaque | enviar `archive_highlight` (jpg 100 KB) | `POST` | 1 registro em `archives` com `calendar_events_id` e `highlight = 1`; arquivo em `storage/calendar-events/<cat>/<id>/destaque/` |
| T11 | Substituição do destaque | enviar nova imagem no `PUT` | `PUT /cms/agenda-evento/{id}` | continua existindo **um** arquivo com `highlight = 1`; o arquivo antigo é removido do storage |
| T12 | Edição | evento existente | `PUT /cms/agenda-evento/{id}` | campos atualizados; `updated_at` alterado |
| T13 | Exclusão individual | evento existente | `DELETE /cms/agenda-evento/{id}` | JSON `{"success": ...}`; soft delete aplicado (`deleted_at` preenchido); `archives` **preservados** (comportamento legado) |
| T14 | Exclusão em lote | 3 eventos | `POST /cms/agenda-evento/delete-por-lote` com `toDelete=[1,2,3]` | JSON `{"success": ...}`; 3 soft deletes; **log de auditoria** registrado |
| T15 | Exclusão em lote inválida | `toDelete=["abc"]` | `POST` | 422 (novo) ou 500 legado — **comportamento deve ser definido** |
| T16 | Acesso sem login | — | `GET /cms/agenda-evento` | redirect para `dashboard.index` (middleware `check.logged`) |
| T17 | Permissão negada | usuário sem `agenda-evento.create` | `POST /cms/agenda-evento` | 403 (Policy) — no legado a proteção é apenas visual |
| T18 | Categoria: criação | nome "Cultura" | `POST /cms/categoria-agenda-evento` | categoria criada com `slug = 'cultura'` |
| T19 | Categoria: slug duplicado | nome repetido | `POST` | slug `cultura-2` |
| T20 | Categoria: sem nome | `name` vazio | `POST` | comportamento conforme `nullable` da v6 (cria com slug vazio) — **definir** |
| T21 | DataTable do CMS | 2 eventos | `GET /cms/datatable/agenda_evento` | JSON com as colunas `value`, `titulo`, `status`, `data_inicial`, `data_final`, `categoria`, `categoria_slug`, `id`, `galeria`, `slug` |
| T22 | Upload pela galeria | evento com galeria ativa | `POST /cms/galeria` com `table=calendar_event`, `id=<id>` | registro em `archives` com `calendar_events_id` preenchido |
| T23 | Route `show` | evento existente | `GET /cms/agenda-evento/{id}` | 200 com o JSON/HTML do evento (**hoje falha**: o método `show` não existe) |

---

## 4. Casos de teste — Portal

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| P1 | Lista pública | 3 eventos publicados, 1 rascunho | `GET /agenda-eventos` | contém os 3 publicados; **não** contém o rascunho; paginação `links()` presente |
| P2 | Paginação | 13 eventos publicados | `GET /agenda-eventos` | 12 itens na 1ª página; `paginate(12)` |
| P3 | Filtro por categoria | 2 eventos em "cultura", 1 em "educação" | `GET /agenda-eventos/cultura` | apenas os 2 de "cultura"; breadcrumb com o nome da categoria |
| P4 | Categoria inexistente | — | `GET /agenda-eventos/inexistente` | 200 com lista vazia (sem erro) |
| P5 | Detalhe | evento publicado | `GET /agenda-eventos/{cat}/{slug}` | título, período formatado, local, imagem de destaque, texto renderizado |
| P6 | Slug inexistente | — | `GET /agenda-eventos/cat/nao-existe` | **não** deve gerar 500 (correção de A10): 404 ou mensagem "evento não encontrado" |
| P7 | Evento sem categoria | evento com `category_calendar_events_id = null` | `GET /agenda-eventos` | o card **não** pode quebrar (o template acessa `$eventList->categoryCalendarEvents->slug`) |
| P8 | Rascunho acessível por URL direta | evento com `status = false` | `GET /agenda-eventos/{cat}/{slug}` | comportamento a definir: idealmente 404 (hoje o rascunho **aparece**) |
| P9 | Galeria | evento com `galery = true` e 2 mídias | `GET /agenda-eventos/{cat}/{slug}` | seção "Galeria de fotos" com 2 itens |
| P10 | Layout por modelo | `Configuration['layout'] = 'modelo3'` | `GET /agenda-eventos` | a view estende `portal.layouts.modelo3.layout.layout` |
| P11 | Template inexistente | `layout` = valor inválido | `GET /agenda-eventos` | 404 (comportamento de `getTemplateActived`) |

---

## 5. Testes de paridade com o legado (regressão)

Objetivo: garantir que a reescrita não altera a saída observável.

| # | Teste | Assert |
|---|---|---|
| R1 | Contagem do dashboard | `CalendarEvent::count()` (não soft-deleted) == total exibido no widget `Eventos` — replicar `v6/tests/Feature/DashboardEventosWidgetTest.php` |
| R2 | Colunas da listagem do CMS | o JSON antigo (view SQL) e o novo (Eloquent) têm as **mesmas chaves** e os mesmos valores formatados (`status` = "Publicado"/"Rascunho", datas `DD/MM/YYYY`) |
| R3 | Slugs | Para 20 títulos de teste (com acentos, repetidos, > 50 chars), o slug gerado é idêntico ao do legado |
| R4 | Datas | `dd/mm/aaaa hh:mm` -> `aaaa-mm-dd hh:mm` com a mesma conversão de `toUSDataHourFormat` |

---

## 6. Fixtures de view / seed para o ambiente de teste

- `Configuration` com `key = 'layout'` apontando para um template existente
  (`modelo1`..`modelo5`).
- `CategoryContent` com slug `secretarias` e `orgaos` e conteúdos vinculados
  (para os selects do formulário).
- `modulos` com `slug = 'agenda-de-eventos'` (o dashboard depende do módulo estar ativo).
- Categoria de conteúdo `agenda-eventos` + item de menu "Agenda" (menu do portal).
- Usuário `super-admin` + papel "Administrador" com as permissões
  `agenda-evento.*`.

---

## 7. Como rodar

Legado (v6):

```bash
cd v6
./vendor/bin/phpunit tests/Feature/DashboardEventosWidgetTest.php
```

v7 (proposto):

```bash
php artisan test --filter=CalendarEvent
php artisan test --filter=PortalAgenda
```

Ambiente necessário: PostgreSQL (a view `agenda_de_eventos` e as views `CREATE OR
REPLACE VIEW` das migrations exigem Postgres), storage gravável e o `.env` de teste com
`DB_CONNECTION=pgsql`.
