# Fila de Espera SUS — Testes

> Versão analisada: `v5`. Estratégia proposta para a reconstrução na v7,
> usando os defeitos reais encontrados no legado como casos de regressão.

## 1. Estratégia

| Camada | Ferramenta | Escopo |
|---|---|---|
| Feature (HTTP) | PHPUnit + `Tests\TestCase` | Rotas do CMS e do portal, autorização, validação, filtros, exportações. |
| Unit | PHPUnit | Model: geração de slug, máscara do cartão, casts, soft delete. |
| Integração | PHPUnit + `RefreshDatabase` (PostgreSQL) | Constraints reais (`slug` único, `is_show` default, `softDeletes`). |

A v5 **não possui nenhum teste** para o módulo: a pasta `v5/tests/` não
contém casos relacionados a `FilaEsperaSus`. Isso, somado à tabela vazia no
banco, explica por que os defeitos A1 a A5 (ver `05-REGRAS-E-PORTABILIDADE.md`)
permaneceram invisíveis.

## 2. Fixtures necessárias

```php
// v7/database/factories/FilaEsperaSusFactory.php
class FilaEsperaSusFactory extends Factory
{
    protected $model = FilaEsperaSus::class;

    public function definition(): array
    {
        return [
            'sus_card' => (string) fake()->unique()->numerify('#############'),
            'update_date' => now()->toDateString(),
            'register_date' => now()->subMonth()->toDateString(),
            'patients_count' => fake()->numberBetween(1, 500),
            'specialty' => 'Cardiologia',
            'procedure' => 'Consulta',
            'avg_wait_time' => fake()->numberBetween(0, 365),
            'priority_criteria' => 'Ordem de chegada',
            'contact' => '(99) 99999-9999',
            'queue_status' => 'em_espera',
            'is_show' => true,
        ];
    }

    public function unpublished(): static
    {
        return $this->state(fn () => ['is_show' => false]);
    }
}
```

Estados úteis: `unpublished()`, `withAttachments(int $count)` (cria PDFs
fakes com `Storage::fake('fila-espera-sus')`).

## 3. Casos de teste — CMS

| # | Caso | Arranjo | Ação | Asserção |
|---|---|---|---|---|
| C1 | Criar registro válido | usuário com `fila-espera-sus.create` | `POST /cms/fila-espera-sus` com todos os campos | 302 para `fila-espera-sus.index`; registro criado; `is_show = true`; `slug` no formato `especialidade-procedimento-id`. |
| C2 | Slug acompanha a edição (**bug A1**) | registro com `specialty = 'Cardiologia'` | `PUT /cms/fila-espera-sus/{id}` com `specialty = 'Ortopedia'` | `slug` deve conter `ortopedia` (no legado, mantém o valor antigo — teste de regressão que falha na v5). |
| C3 | Validação de campos obrigatórios | — | `POST` sem `sus_card` | 422; mensagem "Cartão SUS" presente. |
| C4 | Cartão SUS não numérico | — | `POST` com `sus_card = 'abc'` | 422. |
| C5 | Cartão SUS com 16 dígitos | — | `POST` com 16 dígitos | 422 (regra `digits_between:1,15`). |
| C6 | Anexo não PDF | — | `POST` com `arquivo[]` de texto | 422 em `arquivo.0`. |
| C7 | Upload grava no disco e serializa JSON | Storage fake | `POST` com 2 PDFs e descrições | `file_path` é JSON com 2 itens; arquivos existem no disco; descrição preservada. |
| C8 | Extensão do arquivo é fixa | Storage fake | `POST` com PDF nomeado `x.php` | Arquivo salvo com extensão `.pdf` (**bug A5** — na v5 seria `.php`). |
| C9 | Exclusão lógica | registro existente | `DELETE /cms/fila-espera-sus/{id}` | Resposta 200 com `success`; `deleted_at` preenchido; registro ausente do portal. |
| C10 | Exclusão em lote (**bug A3**) | 3 registros | `POST /cms/fila-espera-sus/excluir-lote` com `ids` | Os 3 registros têm `deleted_at` preenchido (na v5, nada é excluído). |
| C11 | Remoção de anexo | registro com 2 anexos | `DELETE /cms/fila-espera-sus/{id}/arquivos/{uuid}` | `file_path` fica com 1 item; o item removido é o correto (na v5 o índice posicional pode remover o errado). |
| C12 | Permissão insuficiente | usuário sem `fila-espera-sus.index` | `GET /cms/fila-espera-sus` | 302 com mensagem de acesso negado; registro não listado. |
| C13 | Usuário não autenticado | — | `GET /cms/fila-espera-sus` | 302 para `login`. |
| C14 | Auditoria gerada | — | `POST` de registro | 1 linha em `activity_log` com `log_name = 'fila-espera-sus'`, **sem** `sus_card` em `properties` (**bug A7**). |
| C15 | Despublicação pelo CMS | — | `PUT` com `is_show = false` | `is_show = false` (na v5 é forçado a `true` — bug A8). |
| C16 | DataTable do CMS mascarando o cartão | — | `GET /cms/datatable/fila-espera-sus` | JSON com `sus_card` mascarado e flags `edit`/`delete` por permissão. |

## 4. Casos de teste — Portal

| # | Caso | Arranjo | Ação | Asserção |
|---|---|---|---|---|
| P1 | Listagem pública | 20 registros publicados, 1 não publicado | `GET /fila-espera-sus` | 200; contém os 20 publicados; **não** contém o não publicado. |
| P2 | Paginação | 40 registros | `GET /fila-espera-sus?page=2` | 15 itens na página; total correto. |
| P3 | Ordenação padrão | registros com `update_date` distintos | `GET /fila-espera-sus` | Mais recente primeiro; empate resolvido por `specialty` ascendente. |
| P4 | Filtro por especialidade | 2 especialidades | `GET /fila-espera-sus?specialty=Cardiologia` | Somente registros de Cardiologia. |
| P5 | Filtro por prioridade | 2 critérios | `GET /fila-espera-sus?priority_criteria=...` | Somente registros do critério. |
| P6 | Busca textual case-insensitive | registro com `specialty = 'Cardiologia'` | `GET /fila-espera-sus?busca=cardio` | Registro encontrado (comportamento do `ilike`). |
| P7 | Busca não revela cartão completo | registro com cartão `123456789012345` | `GET /fila-espera-sus?busca=123456789012345` | **Nenhum** registro retornado ou resposta genérica (bug A7 — na v5 o cartão é encontrado). |
| P8 | Cartão mascarado na resposta | — | `GET /fila-espera-sus` | HTML não contém o número completo do cartão. |
| P9 | Detalhe por slug | registro publicado | `GET /fila-espera-sus/{slug}` | 200 com os atributos e os anexos. |
| P10 | Detalhe de registro despublicado (**bug A2**) | registro com `is_show = false` | `GET /fila-espera-sus/{slug}` | 404 (na v5, retorna 200). |
| P11 | Detalhe inexistente | — | `GET /fila-espera-sus/nao-existe` | 404. |
| P12 | Exportação XLSX | 3 registros | `GET /fila-espera-sus?type=xls` | 200; cabeçalho `Content-Disposition` com `fila-espera-sus.xlsx`; 3 linhas. |
| P13 | Exportação CSV | 3 registros | `GET /fila-espera-sus?type=csv` | 200; CSV com cabeçalho e 3 linhas. |
| P14 | Exportação PDF com cabeçalho (**bug A4**) | 3 registros | `GET /fila-espera-sus?type=pdf` | PDF contém o título do módulo e a data da pesquisa (na v5, não contém). |
| P15 | Exportação TXT | 3 registros | `GET /fila-espera-sus?type=txt` | 200; 3 linhas separadas por `\|`; cabeçalho fixo. |
| P16 | Exportação respeita os filtros | 2 especialidades | `GET /fila-espera-sus?type=csv&specialty=Cardiologia` | CSV contém apenas Cardiologia. |
| P17 | Tipo desconhecido cai em TXT | — | `GET /fila-espera-sus?type=qqq` | 200 com conteúdo `text/plain`. |
| P18 | Selects de filtro | registros publicados | `GET /fila-espera-sus` | `specialties` e `priorities` contêm só valores publicados e distintos. |
| P19 | Layout ativo sem o módulo (**bug A10**) | `configurations.layout = 'default'` | `GET /fila-espera-sus` | Página renderiza com o layout padrão (na v5, erro de view inexistente). |

## 5. Casos de teste — Model

| # | Caso | Asserção |
|---|---|---|
| M1 | Slug gerado no `saving` | `Str::slug(specialty-procedure-id)`. |
| M2 | Slug único | segundo registro com o mesmo texto gera slug distinto (id entra na chave). |
| M3 | Máscara do cartão | `sus_card_masked` oculta 3 dígitos iniciais e 2 finais; cartão com 5 dígitos ou menos sai intacto. |
| M4 | Soft delete | `deleted_at` preenchido e registros fora do *scope* por padrão. |
| M5 | Casts | `update_date` e `register_date` são `Carbon`; `is_show` é `bool`; `file_path` é `array`. |
| M6 | Descrição do evento de log | contém a especialidade do registro. |

## 6. Fixtures de view e seed

- **Seed de ambiente de teste:** 3 especialidades, 2 critérios de
  prioridade, 3 status de fila e 30 registros (20 publicados, 5 não
  publicados, 5 com soft delete) para exercitar paginação, filtros e
  exportações.
- **Seed do menu e da permissão:** recriar o item de menu do CMS
  (`slug = 'fila-espera-sus'`) e o `resources` correspondente, linked ao
  módulo (`modulo_id`).
- **Views de teste:** para o portal, renderizar a listagem e o detalhe em
  todos os layouts ativos (`modelo1` a `modelo6`) e garantir que nenhum
  layout quebre — a v5 replica as telas 6 vezes e o `default` não possui o
  módulo.

## 7. Como rodar

Na v5 (Laravel 5.5, PHP 7.2):

```bash
cd v5
./vendor/bin/phpunit --filter FilaEsperaSusTest
./vendor/bin/phpunit tests/Feature/FilaEsperaSusTest.php
```

Na v7 (Laravel 11/12, PHP 8.x):

```bash
php artisan test --filter=FilaEsperaSus
php artisan test --testsuite=Feature --filter=FilaEsperaSusPortalTest
```

Análise estática e estilo (a v5 não usa Laravel Pint):

```bash
./vendor/bin/phpstan analyse app/Models/FilaEsperaSus.php app/Http/Controllers/Prefecture/FilaEsperaSusController.php
```

> Observação: builds, testes e linters **não** foram executados durante a
> elaboração deste dossiê — o módulo é documentado a partir da leitura do
> código e da consulta somente-leitura ao banco `prefeituras_v5`.
