# Avisos — Estratégia de Testes

> Não há suíte automatizada do módulo na `v6`. A v7 precisa cobrir a exclusividade
> (um único aviso ativo), o upload/recompressão e a exibição do modal.

---

## 1. Estratégia

| Tipo | Objeto | Ferramenta (v7) |
|---|---|---|
| Feature | Home do portal com/sem aviso ativo | Pest/PHPUnit + `RefreshDatabase` + `Storage::fake` |
| Feature | CRUD e status no CMS | Pest + usuário com permissões `anuncios.*` |
| Feature | Ativação/desativação (exclusividade) | Pest (caso mais crítico) |
| Unit | Regra de normalização/redimensionamento da imagem | PHPUnit + `Storage::fake` |
| Integração | Migração `contents`+`links`+`archives` -> `announcements` | Teste de migração |

## 2. Fixtures e seeds

| Item | Origem |
|---|---|
| Categoria `anuncios` (id 28) | `v6/database/seeds/CategoryContentTableSeeder.php:161-166` |
| Módulo "Avisos" (id 26) | `v6/database/migrations/2024_11_01_150206_create_modulos_table.php:178-183` |
| Permissões `anuncios.*` | `v6/database/seeds/ResourcesTableSeeder.php:252-258` |
| View SQL `anuncios` | `v6/database/migrations/2023_04_10_152724_create_view_to_anuncios.php` |

```php
// exemplo ilustrativo — v7
class AnnouncementFactory extends Factory
{
    protected $model = Announcement::class;

    public function definition(): array
    {
        return [
            'title' => fake()->sentence(3),
            'url' => fake()->url(),
            'image_path' => 'avisos/exemplo.jpg',
            'open_in_new_tab' => false,
            'active' => false,
        ];
    }

    public function active(): static
    {
        return $this->state(['active' => true]);
    }
}
```

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

| # | Cenário | Arrange | Act | Assert |
|---|---|---|---|---|
| T1 | Home sem aviso ativo | nenhum aviso ativo | `get('/')` | status 200; HTML **não** contém `id="anuncio"` |
| T2 | Home com aviso ativo | 1 aviso `active = true` com imagem | `get('/')` | HTML contém `id="anuncio"`, o título como `alt` e a URL em `href` |
| T3 | Link com "abrir em nova guia" | `open_in_new_tab = true` | `get('/')` | âncora com `target="_blank"` |
| T4 | Link sem nova guia | `open_in_new_tab = false` | `get('/')` | âncora **sem** `target` |
| T5 | Aviso inativo não aparece | 1 aviso `active = false` | `get('/')` | HTML não contém `id="anuncio"` |
| T6 | Dois avisos ativos (dados inconsistentes) | 2 avisos `active = true` | `get('/')` | apenas um é exibido (comportamento `first()`); registrar o risco |
| T7 | Aviso sem imagem | aviso ativo com `image_path = null` | `get('/')` | v6: erro 500; v7: imagem padrão |
| T8 | Módulo "Avisos" desabilitado | `modulos.status` do slug `avisos` = 0 | `get('/')` | layout `modelo1` não renderiza o modal |
| T9 | Modal abre automaticamente | aviso ativo | `get('/')` | HTML contém o script que dispara o clique (`#btn-anuncio`) |

## 4. Casos de teste — CMS

| # | Cenário | Arrange | Act | Assert |
|---|---|---|---|---|
| T10 | Listagem carrega o DataTable | usuário com `anuncios.*` | `get('/cms/conteudos/anuncios')` | status 200; HTML contém `tabela_anuncios` |
| T11 | DataTable JSON | 2 avisos (1 ativo, 1 inativo) | `get('/cms/datatable/anuncios')` | 2 linhas; `status_name` = "Ativado"/"Desativado" |
| T12 | Criar aviso sem imagem | usuário com `.create` | `post('/cms/conteudo', ['title' => 'Aviso', 'link' => 'https://exemplo.gov.br', 'category_contents_id' => 28])` | falha de validação (`archive_highlight` required) |
| T13 | Criar aviso completo | usuário com `.create`; arquivo PNG | `post` com `archive_highlight` | `contents`, `links` e `archives` (highlight = 1) criados; `links.isBlank` conforme o checkbox |
| T14 | Título acima de 120 | idem | `post` | falha (`max:120`) |
| T15 | Link ausente | idem | `post` sem `link` | falha (`link` required) |
| T16 | Ativar desativa os demais | 2 avisos, ambos ativos | `post('/cms/set-status', ['contents_id' => $id])` | apenas o alvo fica `status = true` |
| T17 | Desativar não afeta os outros | 1 ativo, 1 inativo | `post('/cms/set-status', ['contents_id' => $id])` no ativo | ativo vira inativo; o inativo permanece inativo |
| T18 | Editar sem reenviar imagem | aviso existente | `put('/cms/conteudo/{id}', ['title' => 'Aviso', 'link' => 'https://exemplo.gov.br', 'category_contents_id' => 28])` sem arquivo | imagem anterior preservada |
| T19 | Excluir aviso | aviso existente | `delete('/cms/conteudo/{id}')` | soft delete; some da view `anuncios` |
| T20 | Exclusão em lote | 2 avisos | `post('/cms/conteudo/deleteAll', ['toDelete' => [10, 11]])` | ambos removidos |

## 5. Casos de teste — Dados e view SQL

| # | Cenário | Arrange | Act | Assert |
|---|---|---|---|---|
| T21 | View reflete status | 2 avisos (1 ativo) | consultar `anuncios` | `status_name` correto em cada linha |
| T22 | View ignora soft delete | 1 aviso excluído | consultar a view | linha ausente |
| T23 | Migração de dados | 1 aviso com `contents` + `links` + `archives` | rodar a migração v7 | linha em `announcements` com `title`, `url`, `open_in_new_tab`, `image_path` e `active` coerentes |
| T24 | Migração mantém a imagem | idem | conferir o storage | arquivo presente em `storage/app/public/avisos/aviso.jpg` |

## 6. Como rodar

```bash
# legado (v6) — verificação manual
cd /home/one/projects/maxima/v7/v6
php artisan route:list --name=set-status
php artisan serve
# cms:        http://localhost:8000/cms/conteudos/anuncios   (requer login)
# datatable:  http://localhost:8000/cms/datatable/anuncios
# portal:     http://localhost:8000/  (o modal abre sozinho)

# v7 (proposto)
php artisan test --filter=Announcement
php artisan test --group=avisos
```

## 7. Critérios de aceite

1. T1–T24 passam na v7.
2. T16 é o teste de aceite do negócio: **nunca** pode haver dois avisos ativos.
3. T7 documenta a correção do erro 500 quando falta imagem.
4. T23/T24 garantem que os avisos publicados no município não se perdem na migração.
5. Teste de view: o componente do modal renderiza uma única vez, independente do layout.
