# Estagiários — Estratégia de Testes

> A v5 **não possui nenhum teste** para este módulo: em `v5/tests/` existem
> apenas `tests/Feature/ExampleTest.php` e
> `tests/Unit/CnpjAlphanumericValidationTest.php`. O que segue é a suíte
> proposta para a reconstrução (v7) e o roteiro de validação manual do legado.

---

## 1. Estratégia

| Tipo | Ferramenta | Cobertura |
|---|---|---|
| Feature (HTTP) | PHPUnit/Pest + `RefreshDatabase` | Rotas do CMS e do portal, validação, permissões, exportação |
| Unit | PHPUnit | Enum de situação, geração de slug, normalização de datas, serialização de anexos |
| Integração (banco) | PHPUnit com PostgreSQL de teste | View `estagiarios_view`, soft delete, `whereRaw` de busca (acentuação) |

Regras de escrita:

- Nenhum teste deve depender da configuração `configurations.layout` real: o
  teste fixa `layout = 'modelo1'` (ou o template de teste) para não quebrar
  quando a prefeitura trocar o tema.
- Testes do portal **não** exigem sessão; testes do CMS exigem um usuário
  Sentinel com a permissão correspondente.
- Exportações devem ser testadas verificando `Content-Type` e o nome do
  arquivo, não o conteúdo binário.

## 2. Fixtures necessárias

```php
// database/factories/EstagiarioFactory.php
class EstagiarioFactory extends Factory
{
    protected $model = Estagiario::class;

    public function definition(): array
    {
        return [
            'nome'           => 'Maria de Souza',
            'periodo_inicio' => now()->startOfYear()->toDateString(),
            'periodo_fim'    => now()->endOfYear()->toDateString(),
            'lotacao'        => 'Secretaria de Educação e Cultura',
            'situacao'       => SituacaoEstagiario::Ativo,
            'arquivo'        => null,
            'slug'           => 'maria-de-souza-1',
        ];
    }

    public function encerrado(): static
    {
        return $this->state(fn () => ['situacao' => SituacaoEstagiario::Encerrado]);
    }
}
```

| Fixture | Origem no legado | Observação |
|---|---|---|
| `EstagiarioFactory` | **não existe** na v5 | Criar do zero |
| Usuário Sentinel com `estagiario.index/create/edit/delete` | `permissions` + papéis Sentinel | A v5 não tem permissões `estagiario.*` cadastradas |
| Secretarias | view `secretarias` (13 linhas em `contents` com `category_contents.slug = 'secretarias'`) | Criar seed de 3 secretarias para o ambiente de teste |
| Configuração `layout` | `configurations` | Fixar `modelo1` nos testes do portal |
| Arquivo PDF de teste | `storage/app/public/docs` | PDF de 1 KB para exercised o upload e a remoção |

## 3. Casos de teste (v7)

### 3.1 Cadastro no CMS

| # | Arrange | Act | Assert |
|---|---|---|---|
| T01 | Usuário logado com `estagiario.create` | `POST /cms/estagiarios` com `nome`, `periodo_inicio`, `periodo_fim`, `lotacao`, `situacao` válidos | 302 para `cms.estagiarios.index`; registro no banco; mensagem de sucesso |
| T02 | Idem | `POST` sem `nome` | 302 de volta; erro "O nome do estagiário é obrigatório."; nenhum registro criado |
| T03 | Idem | `POST` com `periodo_fim` anterior a `periodo_inicio` | 302 de volta; erro de validação; nenhum registro criado |
| T04 | Idem | `POST` com `periodo_inicio = 31/02/2026` | 302 de volta com erro de formato; **nenhum** registro com `1970-01-01` |
| T05 | Idem | `POST` com `situacao = "Inválida"` | Erro de validação (regra `Rule::enum`) |
| T06 | Idem | `POST` com 2 PDFs | 2 arquivos gravados em `storage/app/public/docs`; `arquivo` contém JSON com 2 itens `{path, description}` |
| T07 | Idem | `POST` com arquivo `.docx` | Erro "Os arquivos devem ser PDFs." |
| T08 | Idem | `POST` com PDF de 25 MB | Erro de tamanho (limite 20 MB) |
| T09 | Usuário **sem** `estagiario.create` | `POST /cms/estagiarios` | 403; nenhum registro criado |
| T10 | Visitante **não logado** | `POST /cms/estagiarios` | Redirecionamento para `login` (nada é criado) |

### 3.2 Edição, anexos e exclusão

| # | Arrange | Act | Assert |
|---|---|---|---|
| T11 | Estagiário existente | `PUT /cms/estagiarios/{id}` alterando o nome | 302; `nome` atualizado; `slug` **inalterado** (regra nova: slug estável) |
| T12 | Estagiário com 1 anexo | `PUT` anexando 1 novo PDF | JSON final com 2 itens; o arquivo antigo continua no disco |
| T13 | Estagiário com 2 anexos | `DELETE /cms/estagiarios/{id}/arquivos/{arquivoId}` | 200; o item sai do banco **e** o arquivo sai do disco |
| T14 | Estagiário com 1 anexo | `DELETE` do único anexo | `arquivo` fica `null` (não `"[]"`) |
| T15 | Estagiário existente | `DELETE /cms/estagiarios/{id}` | 200 com JSON de sucesso; `deleted_at` preenchido; some do portal |
| T16 | `id` inexistente | `DELETE /cms/estagiarios/999999` | 404 |
| T17 | Estagiário com slug `x` | `GET /estagiario/x` | 200 e exibe a ficha |
| T18 | Slug inexistente | `GET /estagiario/nao-existe` | 404 |

### 3.3 Portal público

| # | Arrange | Act | Assert |
|---|---|---|---|
| T19 | 20 estagiários | `GET /estagiarios` | 200; 15 itens na página (paginação de 15 como na v5); links de paginação presentes |
| T20 | Estagiário "José da Silva" | `GET /estagiarios?busca=josé` | 200; o registro aparece (busca com acento) |
| T21 | Estagiário "José da Silva" | `GET /estagiarios?busca=jos%25` | 200; busca tratada como texto (sem wildcard) |
| T22 | Estagiários em secretarias diferentes | `GET /estagiarios?lotacao={secretaria}` | Apenas os da secretaria selecionada |
| T23 | Estagiário `Encerrado` | `GET /estagiarios?situacao=Encerrado` | Apenas encerrados |
| T24 | Sem registros | `GET /estagiarios` | 200 e mensagem "Nenhuma informação encontrada!" |
| T25 | Estagiário com `deleted_at` | `GET /estagiarios` | Registro não aparece |
| T26 | `configurations.layout = 'default'` (template sem a pasta `estagiarios`) | `GET /estagiarios` | Deve funcionar (template único) ou retornar 404 amigável — **nunca** `InvalidArgumentException` de view inexistente |

### 3.4 Exportações

| # | Arrange | Act | Assert |
|---|---|---|---|
| T27 | 40 estagiários, filtro `situacao=Ativo` | `GET /estagiarios?type=xls&situacao=Ativo` | Download `estagiarios.xlsx`; contém **todos** os 40 filtrados (ignora a paginação) |
| T28 | Idem | `GET /estagiarios?type=csv` | Arquivo `.csv` com **CSV real** (cabeçalho e linhas separadas por `;`/`,`), não HTML |
| T29 | Idem | `GET /estagiarios?type=pdf` | Download `estagiarios.pdf`; cabeçalho com nome da prefeitura e data da pesquisa |
| T30 | Idem | `GET /estagiarios?type=txt` | `Content-Type` `text/plain`; uma linha por estagiário |
| T31 | Sem registros | `GET /estagiarios?type=xls` | Arquivo vazio, sem erro 500 |

### 3.5 Permissões e auditoria

| # | Arrange | Act | Assert |
|---|---|---|---|
| T32 | Usuário sem `estagiario.index` | `GET /cms/estagiarios` | 403 ou redirect de "sem autorização" |
| T33 | Qualquer usuário logado | `GET /cms/datatable/estagiario` | **Acesso negado** se não tiver `estagiario.index` (na v5 este endpoint é liberado — ver `05`) |
| T34 | Usuário com `estagiario.edit` | `PUT /cms/estagiarios/{id}` | Registro de auditoria criado (evento `updated`) |
| T35 | Usuário com `estagiario.delete` | `DELETE /cms/estagiarios/{id}` | Registro de auditoria criado (evento `deleted`) |

### 3.6 Unitários

| # | Alvo | Assert |
|---|---|---|
| T36 | `SituacaoEstagiario::cases()` | 3 casos: `Ativo`, `Encerrado`, `Rescindido` |
| T37 | Geração de slug | `maria-de-souza-1` (kebab-case, sem acentos) |
| T38 | Normalização de data `31/12/2026` | `2026-12-31` |
| T39 | Normalização de data inválida | Lança `InvalidArgumentException` (em vez de `1970-01-01`) |
| T40 | `EstagiarioResource` | JSON com as chaves esperadas e sem `arquivo` bruto |

## 4. Testes de regressão do legado (v5)

Verificação manual no banco `prefeituras_v5` (somente leitura):

```bash
# A view só traz registros não excluídos
docker exec v5_3f273858-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "SELECT count(*) FROM estagiarios;"

docker exec v5_3f273858-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "SELECT count(*) FROM estagiarios_view;"

# O recurso e o item de menu existem
docker exec v5_3f273858-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "SELECT id, slug, name FROM resources WHERE slug = 'estagiario';"

docker exec v5_3f273858-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "SELECT id, slug, clicks FROM menu_orders WHERE slug = 'estagiario';"
```

## 5. Como executar

**Legado (v5)** — a suíte existente (não cobre Estagiários):

```bash
cd v5
vendor/bin/phpunit
vendor/bin/phpunit --testsuite Unit
```

Configuração em `v5/phpunit.xml` (suites `Feature` e `Unit`, `bootstrap`
`vendor/autoload.php`, `CACHE_DRIVER=array`, `SESSION_DRIVER=array`,
`QUEUE_DRIVER=sync`).

**Reconstrução (v7, Laravel 11/12)**:

```bash
php artisan test --filter=Estagiario
php artisan test --filter=EstagiarioTest
php artisan test --coverage --min=80
```

## 6. Critérios de aceite para a reconstrução

1. Todos os casos de T01 a T31 passam na v7.
2. A listagem do portal mantém paginação de 15 e os quatro formatos de
   exportação, com os filtros preservados.
3. A exportação CSV entrega CSV válido (não HTML).
4. A exclusão de anexo remove o arquivo do disco.
5. O slug permanece estável após a edição do nome.
6. O endpoint de DataTable exige `estagiario.index`.
7. Toda escrita gera registro de auditoria.
8. A listagem do CMS pagina no banco (não carrega a tabela inteira).
