# Servidor — Estratégia de Testes

> **Módulo:** `servidor` (tabela `modulos`, id 35)
> **Referência:** a v6 **não possui testes** para este módulo. A única ocorrência da
> palavra "servidor" em `v6/tests/` é um comentário sobre formatação de número no
> dashboard (`v6/tests/Feature/DashboardUsuariosWidgetTest.php:235`), sem relação com o
> cadastro de servidores.

---

## 1. Estratégia

| Tipo | Ferramenta sugerida (v7) | Escopo |
|---|---|---|
| **Feature** (HTTP) | PHPUnit + `RefreshDatabase` | CRUD do CMS, exclusão em lote, permissões e listagem |
| **Feature** (portal) | PHPUnit | busca pública de diárias (filtro por nome e CPF) |
| **Unit** | PHPUnit | validação de CPF, normalização do CPF, filtro de campos vazios |
| **Integração** (BD) | PHPUnit | integridade da FK com `requerimento_diarias` |

Regras gerais:

- Usar `RefreshDatabase` e as migrations da v7 (não a view `servidores` do legado).
- Criar usuários do Sentinel com as permissões `servidor.index`, `.create`, `.edit`,
  `.update`, `.delete` e também `.dados-bancarios` (nova, para os dados financeiros).
- Criar um conteúdo na categoria `secretarias` para exercitar a seleção.

## 2. Fixtures / factories necessárias

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

    public function definition(): array
    {
        return [
            'cpf'        => fake()->numerify('###########'),
            'name'       => fake()->name(),
            'cargo'      => fake()->jobTitle(),
            'matricula'  => (string) fake()->unique()->numberBetween(1000, 999999),
            'identidade' => (string) fake()->numberBetween(10000000, 99999999),
            'secretaria_id' => Secretaria::factory(),
        ];
    }

    public function semSecretaria(): static { return $this->state(fn () => ['secretaria_id' => null]); }
    public function excluido(): static { return $this->state(fn () => ['deleted_at' => now()]); }
}
```

| Factory | Uso |
|---|---|
| `ServidorFactory` | servidores com/sem secretaria, com/sem soft delete |
| `SecretariaFactory` | secretaria de lotação (tabela nova da v7) |
| `ContentFactory` (conteúdo na categoria `secretarias`) | enquanto a v7 mantiver a FK para `contents` |
| Usuário Sentinel com permissões | `super-admin` e usuário comum |

CPFs válidos para fixtures: é preciso gerar CPFs com dígitos verificadores corretos
(o `Validate_cpf` recusa sequências repetidas como `11111111111`). Usar um gerador
dedicado em `tests/Support/CpfGenerator.php` ou fixar alguns CPFs válidos conhecidos.

## 3. Casos de teste — CMS

| # | Cenário | Arrange | Act | Assert |
|---|---|---|---|---|
| S1 | Listagem | 3 servidores (1 excluído) | GET `/cms/servidor` | 200; a view renderiza o esqueleto; **não** executa `Servidor::all()` (assert de query count) |
| S2 | DataTable | 3 servidores, 1 excluído | GET `/cms/datatable/servidor` | JSON com 2 linhas; campos `value`, `name`, `cpf`, `id`; flags `edit`/`delete` conforme a permissão |
| S3 | Coluna ID | 3 servidores | GET `/cms/datatable/servidor` | o campo `id` é o `servidor.id` real (na v7; na v6 é um `row_number()`) |
| S4 | Cadastro completo | usuário com `servidor.create` | POST `/cms/servidor` | 302 para `servidor.index`; linha criada com todos os campos |
| S5 | Cadastro sem `name` | — | POST | 422; erro no campo `name` |
| S6 | Cadastro com CPF inválido | — | POST com `cpf = "11111111111"` | 422; mensagem "CPF inválido, verifique e tente novamente." |
| S7 | Cadastro com CPF de menos de 11 dígitos | — | POST | 422 |
| S8 | Cadastro sem `identidade` / `matricula` / `cargo` | — | POST | 422 em cada caso |
| S9 | Cadastro sem secretaria | — | POST com `secretaria = "0"` | linha criada com `secretaria_id = NULL` (sem violar a FK) |
| S10 | Cadastro com secretaria inexistente | — | POST com `secretaria_id = 999999` | 422 (na v6: erro 500 por causa do `dd()`) |
| S11 | Edição | servidor existente | PUT `/cms/servidor/{id}` | 302; campos atualizados; `secretaria_id` preservada |
| S12 | Edição pela tela de cadastro | — | GET `/cms/servidor/{id}/edit` | 200; mesma view de cadastro com os valores preenchidos |
| S13 | Exclusão individual | servidor existente | DELETE `/cms/servidor/{id}` | 200 `{success}`; `deleted_at` preenchido; some da listagem |
| S14 | Exclusão de id inexistente | — | DELETE `/cms/servidor/999999` | resposta de erro coerente (404 na v7; 500 na v6) |
| S15 | Exclusão em lote | 3 servidores | POST `/cms/servidor/destroyAll` com `toDelete[]` | 200 `{success}`; **os 3** com `deleted_at`; a resposta informa a contagem real |
| S16 | Exclusão em lote sem seleção | — | POST com `toDelete` vazio | sem efeito; resposta coerente (a v6 sempre responde sucesso) |
| S17 | Permissões | usuário sem `servidor.create` | GET `/cms/servidor` | 403/redirect; botão "Cadastrar Servidor" ausente |
| S18 | Permissões | usuário sem `servidor.delete` | GET `/cms/datatable/servidor` | campo `delete = false`; sem botão de exclusão em lote |
| S19 | Rota `show` inexistente | — | GET `/cms/servidor/{id}` | **não** deve gerar erro 500 na v7 |
| S20 | Dados bancários | usuário sem `servidor.dados-bancarios` | GET do detalhe | banco/conta/agência **ausentes** da resposta |

## 4. Casos de teste — portal e acoplamentos

| # | Cenário | Arrange | Act | Assert |
|---|---|---|---|---|
| P1 | Busca por nome | servidor "Maria Souza" + requerimento | GET `/requerimento-de-diarias?req_name=Maria` | o requerimento aparece |
| P2 | Busca por CPF | — | GET `/requerimento-de-diarias?cpf=123.456.789-09` | o requerimento aparece |
| P3 | Busca sem acento | servidor "José" | `?req_name=jose` | o resultado é o mesmo (a função `translate` normaliza) |
| P4 | Tentativa de SQL injection | — | `?req_name=' OR '1'='1` | **não** deve alterar o resultado nem gerar erro 500 (a v6 concatena a string no `whereRaw`) |
| P5 | Requerimento com servidor | 1 servidor | POST `/cms/requerimento-diarias` | o vínculo `id_servidor` é gravado |
| P6 | Integridade referencial | servidor com requerimento | DELETE físico do servidor | bloqueado pela FK; na v7, política de "arquivar" em vez de excluir |
| P7 | Select de servidores no requerimento | 30 servidores | GET `/cms/requerimento-diarias/create` | o select é paginado/filtrado (na v6 carrega todos) |

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

| Item | Origem | Finalidade |
|---|---|---|
| `contents` na categoria `secretarias` | módulo de conteúdos | popula o select de secretaria |
| `modulos` id 35 (`slug = 'servidor'`, `status = 1`) | migration `2024_11_01_150206_create_modulos_table.php` | habilita o item de menu do CMS |
| `menu_orders` id 29 (`slug = 'servidor'`, `modulo_id = 35`) | idem | habilita o item de menu |
| `resources` id 47 (`slug = 'servidor'`, `modulo_id = 35`) | idem | permite gerar as permissões `servidor.*` na tela de Perfis |
| `permissions` com slugs `index`, `create`, `edit`, `delete` | `v6/database/seeds/PermissionsTableSeeder.php` | base das permissões por recurso |

## 6. Testes de view

| # | Cenário | Assert |
|---|---|---|
| V1 | Formulário de cadastro | 5 campos obrigatórios marcados; select de secretaria populado; legend "Dados Bancários" presente |
| V2 | Formulário de edição | mesmos campos preenchidos com os dados do servidor; `PUT` implícito presente |
| V3 | Select de secretaria | option inicial com value `0` e texto "Selecione"; demais opções com `value = id` |
| V4 | Listagem | colunas ID, Servidor, CPF, Ações; botões condicionados a `has_access` |

## 7. Como rodar

```bash
# v7 (Laravel moderno)
php artisan test --filter=Servidor
php artisan test tests/Feature/Servidor/CmsCrudTest.php
php artisan test tests/Feature/Servidor/PermissionsTest.php
php artisan test tests/Feature/Servidor/PortalBuscaDiariasTest.php

# cobertura
php artisan test --coverage --min=80
```

No legado (v6, sem testes do módulo), a verificação é manual:

```bash
# listar servidores
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select id, cpf, name, cargo, matricula, identidade, secretaria, deleted_at from servidor order by id desc limit 20;"

# o que a listagem do CMS realmente lê
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select * from servidores limit 20;"

# integridade com requerimentos de diárias
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c "select s.id, s.name, count(rd.id) as requerimentos from servidor s left join requerimento_diarias rd on rd.id_servidor = s.id group by s.id, s.name;"
```

## 8. Matriz de cobertura mínima

| Comportamento | Teste |
|---|---|
| Cadastro com validações | S4, S5, S6, S7, S8 |
| Secretaria opcional e FK | S9, S10 |
| Edição | S11, S12 |
| Exclusão individual e em lote | S13, S14, S15, S16 |
| Permissões | S17, S18, S20 |
| Listagem e colunas | S1, S2, S3 |
| Busca no portal | P1, P2, P3, P4 |
| Acoplamento com Requerimento de Diárias | P5, P6, P7 |
| Views | V1, V2, V3, V4 |
