# VIAP — Plano de Testes

> Estratégia, casos concretos, fixtures e comandos para testar o módulo VIAP
> na `v6` (legado) e na `v7` (reconstrução).

---

## 1. Estratégia

| Tipo | Escopo | Ferramenta |
|---|---|---|
| **Feature (HTTP)** | Rotas do CMS (autenticação, permissões, CRUD, exclusão em lote), rotas públicas (filtros, detalhe, exportação) | PHPUnit + `Illuminate\Foundation\Testing` |
| **Feature (Banco)** | Gravação real de VIAP, geração de slug, vínculo do pivot `viap_has_archives`, view `view_viap` | `DatabaseTransactions` (padrão já usado em `v6/tests/DashboardTestCase.php:7`) |
| **Unit** | `Viap::display_title`, `Viap::referenceMonths()`, `generateSlug()`, `slugify()`, `gerarTxt()` | PHPUnit puro |
| **Smoke** | Renderização das 12 views do portal (modelo1..6) e das 4 views do CMS | `assertSee` / smoke test de rota |

Regras:

1. **Nenhum teste pode gravar fora do ambiente de teste.** A suíte existente
   usa `DatabaseTransactions` (`v6/tests/DashboardTestCase.php:37-51`).
2. Todo teste de rota do CMS **precisa** de um usuário com papel — a
   autorização é feita por middleware, não pela rota.
3. Testes de exportação devem verificar **cabeçalhos HTTP**
   (`Content-Type`, `Content-Disposition`), não o binário.

## 2. Fixtures / factories necessárias

O VIAP **não tem factory** na v6 (`v6/database/factories/` contém apenas
`HighlightFactory`, `ManualAreaFactory`, `ManualFactory`, `UserFactory`).
Para a v7:

```php
// database/factories/ViapFactory.php  (exemplo ilustrativo)
class ViapFactory extends Factory
{
    protected $model = Viap::class;

    public function definition(): array
    {
        return [
            'publication_date' => now(),
            'reference_month'  => 3,
            'reference_year'   => 2026,
            'link_type'        => Viap::LINK_GABINETE,
            'councilman_id'    => Councilman::factory(),
            'organ_id'         => null,
            'status'           => true,
            'slug'             => 'marco-2026-gabinete-fulano',
            'amount'           => null,
            'description'      => null,
        ];
    }

    public function paraOrgao(): static
    {
        return $this->state(fn () => [
            'link_type' => Viap::LINK_OUTRO_ORGAO,
            'councilman_id' => null,
            'organ_id' => Content::factory(),
        ]);
    }

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

Fixtures de apoio (v7):

| Fixture | Origem | Uso |
|---|---|---|
| `Viap::factory()` | nova | registro base |
| `Councilman::factory()` | nova | parlamentar (`councilmen`) |
| `Content::factory()` + `CategoryContent` com `slug='orgaos'` | existente/adaptação | órgão |
| `Archive::factory()` | existente | anexo |
| `modulos` / `resources` / `menu_orders` / `roles` com `slug='viap'` | `ViapSeeder` | presença do módulo no menu |

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

O seed do módulo (`v6/database/seeds/ViapSeeder.php`) precisa ser executado no
ambiente de teste, **antes** dos testes que dependem do menu:

```php
// tests/TestCase.php (exemplo ilustrativo)
protected function setUp(): void
{
    parent::setUp();
    $this->seed(ViapSeeder::class);
}
```

> **Cuidado:** o `ViapSeeder` faz `updateOrCreate` em `modulos`,
> `resources` e `menu_orders` e **mescla** permissões no papel
> `administrador`. Em ambiente de teste com `DatabaseTransactions` isso é
> revertido ao final de cada teste — mas o papel `administrador` **precisa
> existir** para o passo 4 rodar (o seed o ignora silenciosamente caso não
> exista).

## 4. Casos de teste — Feature (CMS)

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| F-01 | Listagem exige login | sem sessão | `GET /cms/viap` | `302` para `/login` |
| F-02 | Formulário exige login | sem sessão | `GET /cms/viap/create` | `302` para `/login` |
| F-03 | Cadastro exige login | sem sessão | `POST /cms/viap` com payload válido | `302` para `/login` e **nenhum** registro criado |
| F-04 | Exclusão em lote exige login | sem sessão | `POST /cms/viap/delete-por-lote` | `302` para `/login` e **nenhum** registro removido |
| F-05 | Remoção de anexo exige login | sem sessão | `POST /cms/viap/ajax-delete-files` | `302` para `/login` e o anexo continua no banco |
| F-06 | DataTable exige login | sem sessão | `GET /cms/datatable/viap` | `302` para `/login` |
| F-07 | Usuário com `viap.index` vê a listagem | papel `administrador` | `GET /cms/viap` | `200`, HTML contém `tabela_viap` |
| F-08 | Listagem traz o nome do vínculo | VIAP de gabinete + VIAP de órgão | `GET /cms/datatable/viap` | JSON contém `linked_name` = nome do parlamentar / título do órgão |
| F-09 | Cadastro cria o registro e o slug | payload válido | `POST /cms/viap` | `302` para `viap.index`; linha em `viap` com `slug = 'marco-2026-gabinete-…'` |
| F-10 | Validação: mês inválido | `reference_month = 13` | `POST /cms/viap` | `302` de volta + erros de validação; nenhum registro criado |
| F-11 | Validação: tipo de vínculo inválido | `link_type = 'outro'` | `POST /cms/viap` | erro de validação |
| F-12 | Validação: gabinete exige parlamentar | `link_type = gabinete`, `councilman_id` vazio | `POST /cms/viap` | erro de validação |
| F-13 | Validação: órgão exige `organ_id` | `link_type = outro_orgao`, `organ_id` vazio | `POST /cms/viap` | erro de validação |
| F-14 | Edição atualiza o registro | registro existente | `PUT /cms/viap/{id}` | `302`; coluna alterada na tabela |
| F-15 | Exclusão individual (soft delete) | registro existente | `DELETE /cms/viap/{id}` | JSON `{"success": …}`; `deleted_at` preenchido; linha ausente da `view_viap` |
| F-16 | Exclusão em lote | 3 registros | `POST /cms/viap/delete-por-lote` com `toDelete[]` | JSON de sucesso; os 3 com `deleted_at` preenchido |
| F-17 | Upload de anexo cria `archives` + pivot | registro com `Storage::fake()` | `POST /cms/viap` com `archive[0][]` | 1 linha em `archives`, 1 em `viap_has_archives`, arquivo no disco |
| F-18 | Remoção de anexo | registro com anexo | `POST /cms/viap/ajax-delete-files` | JSON `{"success": …}`; `archives.deleted_at` preenchido |
| F-19 | Rota `viap.show` do CMS | qualquer usuário logado | `GET /cms/viap/{id}` | **HOJE: 500** (método inexistente) — o teste deve documentar o Gap G-04 até a correção |
| F-20 | Permissão de escrita indevida | papel `basico` (sem `viap.*`) | `POST /cms/viap` | **HOJE: 302 de volta com aviso** (vazamento de escrita) — documenta o Gap G-05 |

## 5. Casos de teste — Feature (Portal)

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| P-01 | Página pública responde 200 | sem sessão | `GET /viap` | `200`, HTML contém "VIAP" |
| P-02 | Sem filtro a tabela vem vazia | 3 VIAPs ativos | `GET /viap` | HTML **não** contém os títulos — comportamento atual (Gap G-01) |
| P-03 | Filtro por mês/ano | VIAPs de vários períodos | `GET /viap?month=3&year=2026` | 200; apenas os registros do período |
| P-04 | Filtro por tipo de vínculo | VIAPs de gabinete e de órgão | `GET /viap?link_type=gabinete` | só os de gabinete |
| P-05 | Filtro por parlamentar | 2 VIAPs do mesmo parlamentar | `GET /viap?councilman_id={id}` | só os dele |
| P-06 | Filtro por órgão | — | `GET /viap?organ_id={id}` | só os do órgão |
| P-07 | Registro inativo não aparece | VIAP com `status = false` | `GET /viap?month=…` | não aparece |
| P-08 | Detalhe por slug | VIAP ativo | `GET /viap/{slug}` | `200`, contém `display_title` |
| P-09 | Detalhe de slug inexistente | — | `GET /viap/nao-existe` | `404` |
| P-10 | Detalhe de registro inativo | VIAP `status = false` | `GET /viap/{slug}` | `404` (`firstOrFail` + `active()`) |
| P-11 | Exportação CSV | 2 VIAPs | `GET /viap/export?type=csv&month=3&year=2026` | `200`, `Content-Type` de planilha, corpo com "Data Publicação" |
| P-12 | Exportação XLSX | — | `GET /viap/export?type=xls` | `200`, `Content-Disposition: attachment; filename=viap.xlsx` |
| P-13 | Exportação PDF | — | `GET /viap/export?type=pdf` | `200`, `Content-Type: application/pdf` |
| P-14 | Exportação TXT (padrão) | — | `GET /viap/export` | `200`, corpo contém `Data Publicação | Referência | Vínculo` |

## 6. Casos de teste — Unit

| # | Caso | Arrange | Act | Assert |
|---|---|---|---|---|
| U-01 | `display_title` para gabinete | VIAP com parlamentar | `$viap->display_title` | `"VIAP Março 2026 Gabinete Fulano"` |
| U-02 | `display_title` para órgão | VIAP com `organ` | `$viap->display_title` | `"VIAP Março 2026 Órgão Secretaria de Saúde"` |
| U-03 | `display_title` sem vínculo | VIAP sem parlamentar/órgão | `$viap->display_title` | contém `Gabinete`/`Órgão` como fallback |
| U-04 | `referenceMonths()` | — | `Viap::referenceMonths()` | 12 chaves; `1 => 'Janeiro'`, `12 => 'Dezembro'` |
| U-05 | `scopeActive` | 2 VIAPs (ativo/inativo) | `Viap::active()->get()` | apenas 1 |
| U-06 | Slug com gabinete | payload mês 3, ano 2026, parlamentar "Fulano de Tal" | `generateSlug()` | `"marco-2026-gabinete-fulano-de-tal"` |
| U-07 | Slug com órgão | payload `outro_orgao` | `generateSlug()` | `"marco-2026-orgao-<slug do título>"` |
| U-08 | Slug sem acento | parlamentar "José Ávila" | `slugify()` | sem acento e sem `//` |
| U-09 | `linked_name` (accessor v7) | — | `$viap->linked_name` | nome do parlamentar / título do órgão / `null` |
| U-10 | TXT tabular | coleção de VIAPs | `gerarTxt()` | 1 cabeçalho + 1 linha por registro, separado por `\n` |

## 7. Como rodar

Legado (`v6`):

```bash
cd v6
composer install
php artisan migrate --env=testing          # cria viap, viap_has_archives e a view view_viap
php artisan db:seed --class=ViapSeeder       # registra o módulo em modulos/resources/menu_orders
./vendor/bin/phpunit --filter=Viap
./vendor/bin/phpunit tests/Feature/ViapCmsTest.php
./vendor/bin/phpunit tests/Feature/ViapPortalTest.php
./vendor/bin/phpunit tests/Unit/ViapTest.php
```

v7 (Laravel 11/12):

```bash
php artisan migrate --env=testing
php artisan db:seed --class=ViapModuleSeeder
php artisan test --filter=Viap
php artisan test --filter=ViapCmsTest
php artisan test --filter=ViapPortalTest
```

## 8. Dados de teste para o ambiente (seed local)

```php
// tests/Feature/ViapTestCase.php  (exemplo ilustrativo)
protected function seedViapData(): void
{
    $councilman = Councilman::factory()->create(['name' => 'Vereador Fulano', 'status' => true]);
    $category   = CategoryContent::factory()->create(['slug' => 'orgaos']);
    $organ      = Content::factory()->create([
        'title' => 'Secretaria de Saúde',
        'category_contents_id' => $category->id,
    ]);

    Viap::factory()->create([
        'publication_date' => now(),
        'reference_month'  => 3,
        'reference_year'   => 2026,
        'link_type'        => Viap::LINK_GABINETE,
        'councilman_id'    => $councilman->id,
        'status'           => true,
        'slug'             => 'marco-2026-gabinete-vereador-fulano',
    ]);

    Viap::factory()->paraOrgao()->create([
        'publication_date' => now(),
        'reference_month'  => 3,
        'reference_year'   => 2026,
        'organ_id'         => $organ->id,
        'status'           => true,
        'slug'             => 'marco-2026-orgao-secretaria-de-saude',
    ]);
}
```

## 9. Checklist de regressão da v7

- [ ] A listagem pública **sem filtro** mostra os registros (correção do Gap G-01).
- [ ] O valor (`amount`) e a descrição aparecem no portal (decisão Q-01).
- [ ] `GET /cms/viap/{id}` responde 200 (Gap G-04).
- [ ] Usuário sem permissão **não** consegue criar/editar/excluir (Gap G-05).
- [ ] O botão de exclusão em lote funciona para o papel `administrador`
      (Gap G-07).
- [ ] O slug permanece **estável** após uma edição (Gap G-13).
- [ ] As exportações mantêm nome de arquivo e colunas (XLSX/CSV/PDF/TXT).
- [ ] O menu "VIAP" aparece para quem tem `viap.index` e o módulo está
      `status = 1` em `modulos`.
