# Unidade Gestora — Estratégia de Testes

## 1. Estratégia

| Tipo | Escopo | Ferramenta sugerida (v7) |
|---|---|---|
| **Feature (CMS)** | CRUD completo, exclusão em lote, permissões, DataTable JSON. | `PHPUnit` + `Laravel\Dusk` opcional. |
| **Unit (model)** | Regras de unicidade, casts, relações `hasMany`/`belongsTo`. | `PHPUnit` (Pest opcional). |
| **Integração (banco)** | Índice unique parcial, integridade com `biddings`. | `PHPUnit` com `RefreshDatabase` em PostgreSQL. |
| **Regressão (dados)** | Backfill da tabela legada e saneamento de duplicados. | Script de console + asserção. |

> O legado **não possui nenhum teste** do módulo. A única referência a
> `ManagementUnit` em `v6/tests/` é incidental, no teste de widget de licitações
> do Dashboard (`v6/tests/Feature/DashboardLicitacoesWidgetTest.php:8,104,143`),
> que cria o model em memória para montar o cenário. A suíte existente cobre
> apenas o Dashboard.

## 2. Fixtures necessárias

### 2.1 Factory

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

    public function definition(): array
    {
        return [
            'name' => 'Unidade ' . $this->faker->unique()->words(2, true),
            'code' => (string) $this->faker->unique()->numberBetween(1, 9999),
        ];
    }
}
```

### 2.2 Seed de ambiente

| Item | Origem | Preparo |
|---|---|---|
| `resources` com `slug = 'unidade-gestora'` | `v6/database/seeds/ResourcesTableSeeder.php:171` | `Resource::updateOrCreate(['slug' => 'unidade-gestora'], ['name' => 'Unidades Gestoras', 'modulo_id' => 50])` |
| `modulos` com `slug = 'unidade-gestora'` | migration `2024_11_01_150206` | `Modulos::updateOrCreate(['slug' => 'unidade-gestora'], ['name' => 'Unidades Gestoras', 'status' => true])` |
| Papel com as permissões | `v6/database/seeds/RolesTableSeeder.php:62` | Criar papel de teste com `unidade-gestora.*` |

> O `modulos.status` precisa ser `true` no ambiente de teste: com `false`, o
> middleware/Policy (na v7) pode negar o acesso por módulo desabilitado, e o
> editor de perfis não exibe a linha.

### 2.3 Usuários de teste

| Perfil | Permissões | Uso |
|---|---|---|
| `super-admin` | todas (ignora Policy) | happy path |
| `gestor` | `unidade-gestora.index/create/edit/delete` | permissões plenas |
| `leitor` | apenas `unidade-gestora.index` | deve **não** conseguir criar/editar/excluir (teste do bug U8) |
| `anonimo` | — | deve ser redirecionado ao login |

## 3. Casos de teste — CRUD

| # | Cenário | Arrange | Act | Assert |
|---|---|---|---|---|
| C1 | Listagem | 3 unidades, usuário `gestor` | `GET /cms/unidade-gestora` | 200; view `prefecture.management-units.index` |
| C2 | Listagem sem login | — | `GET /cms/unidade-gestora` | redirect para `login` |
| C3 | Listagem sem permissão | usuário `leitor` **sem** `unidade-gestora.index` | `GET /cms/unidade-gestora` | redirect + flash "Você não tem autorização para acessar esta funcionalidade!" |
| C4 | Formulário de criação | usuário `gestor` | `GET /cms/unidade-gestora/create` | 200; view `prefecture.management-units.create` |
| C5 | Criar | — | `POST /cms/unidade-gestora` com `name`, `code` | linha criada; redirect `unidade-gestora.index`; flash de sucesso |
| C6 | Criar sem `name` | — | `POST` sem `name` | erro "O campo nome é obrigatório."; nada gravado |
| C7 | Criar sem `code` | — | `POST` sem `code` | erro "O campo código é obrigatório." |
| C8 | Criar com `name` > 120 | — | `POST` com 121 caracteres | erro de validação (`name.max`) |
| C9 | Criar com nome duplicado | unidade existente | `POST` com o mesmo `name` | erro "O nome já está em uso." |
| C10 | Criar com código duplicado | unidade existente | `POST` com o mesmo `code` | erro "O código já está em uso." |
| C11 | Editar | unidade existente | `GET /cms/unidade-gestora/{id}/edit` | 200; view `prefecture.management-units.edit` |
| C12 | Editar id inexistente | — | `GET /cms/unidade-gestora/999999/edit` | 404 (`findOrFail`) |
| C13 | Atualizar | unidade existente | `PUT /cms/unidade-gestora/{id}` | campos alterados; flash de sucesso |
| C14 | Atualizar mantendo o mesmo `name` | unidade existente | `PUT` com o **mesmo** `name` | **deve passar** (regressão do bug U1) |
| C15 | Atualizar com `name` de outra unidade | 2 unidades | `PUT` com o `name` da outra | **deve falhar** com "O nome já está em uso." (bug U1) |
| C16 | Excluir | unidade existente | `DELETE /cms/unidade-gestora/{id}` | 200 + `{"success": "Unidade Gestora deletada com sucesso."}`; `deleted_at` preenchido |
| C17 | Excluir id inexistente | — | `DELETE /cms/unidade-gestora/999999` | **não deve gerar erro 500** (bug U6) — deve responder 404/422 |
| C18 | Exclusão lógica | unidade com `biddings` | `DELETE` | linha continua no banco com `deleted_at`; FK satisfeita |

## 4. Casos de teste — autorização

| # | Cenário | Act | Assert |
|---|---|---|---|
| A1 | `leitor` tenta criar | `POST /cms/unidade-gestora` | **403/redirect** (bug U8 — hoje é permitido) |
| A2 | `leitor` tenta editar | `PUT /cms/unidade-gestora/{id}` | **403/redirect** |
| A3 | `leitor` tenta excluir | `DELETE /cms/unidade-gestora/{id}` | **403/redirect** |
| A4 | `leitor` tenta exclusão em lote | `POST /cms/ajax/unidade-gestora/delete-por-lote` | **403/redirect** |
| A5 | `leitor` acessa o DataTable | `GET /cms/datatable/unidade-gestora` | 200 com `edit = false`, `delete = false` |
| A6 | `super-admin` acessa tudo | todas as rotas | 200 |

## 5. Casos de teste — exclusão em lote

| # | Cenário | Arrange | Act | Assert |
|---|---|---|---|---|
| L1 | Excluir 2 unidades | 3 unidades | `POST /cms/ajax/unidade-gestora/delete-por-lote` com `toDelete = [1,2]` | JSON `true`; as 2 com `deleted_at`; a 3ª intacta |
| L2 | Nenhum selecionado | — | `POST` com `toDelete = []` | JSON `false` (ou 422) |
| L3 | `toDelete` ausente | — | `POST` sem `toDelete` | **não deve gerar erro 500** (bug U7 — hoje `implode()` estoura) |
| L4 | `toDelete` com valor não numérico | — | `POST` com `toDelete = ["abc"]` | resposta controlada, sem `TypeError` |
| L5 | Log de atividade | — | exclusão em lote | registro em `activity_log` com descrição "Exclusão de uniade gestora" (bug U3 — texto a corrigir) |

## 6. Casos de teste — DataTable

| # | Cenário | Act | Assert |
|---|---|---|---|
| D1 | Payload | `GET /cms/datatable/unidade-gestora` | `data` com `value`, `nome`, `id`, `edit`, `delete`, `active`, `inactivate` |
| D2 | Exclusão lógica aplicada | unidade com `deleted_at` | a linha **não** aparece no JSON (view SQL filtra) |
| D3 | Ordenação | 3 unidades | ordenação por `nome` ascendente (a view é `created_at DESC`, mas o DataTable ordena por `value`/`nome`) |
| D4 | `code` na listagem | — | **deve** incluir `code` na v7 (o legado não expunha) |

## 7. Casos de teste — banco / integridade

| # | Cenário | Assert |
|---|---|---|
| B1 | Índice unique de `name` | INSERT duplicado (sem soft delete) falha com violação de índice |
| B2 | Índice unique de `code` | idem |
| B3 | Soft delete libera o `name` | inserir de novo com o mesmo `name` após o soft delete **deve** funcionar (índice parcial) |
| B4 | FK de `biddings` | `biddings.management_units_id` continua `NOT NULL` e referenciando `management_units.id` |
| B5 | Seed do banco | rodar `ManagementUnitTableSeeder` **não** pode apagar unidades existentes (bug U13) |

## 8. Testes de equivalência funcional (porta a porta)

1. Popular a base v6 com `ManagementUnitTableSeeder` + 3 unidades extras via CMS.
2. Capturar o JSON de `/cms/datatable/unidade-gestora` e o HTML do formulário.
3. Repetir na v7 com o mesmo conjunto de dados.
4. Comparar: colunas, ordenação, mensagens de erro, formato da resposta de
   exclusão (`{"success": "Unidade Gestora deletada com sucesso."}`) e da exclusão em lote (`true`/`false`).

## 9. Como rodar

```bash
# suíte completa
php artisan test

# apenas o módulo
php artisan test --filter=ManagementUnit

# suíte legada (v6) — para comparar comportamento
cd v6 && ./vendor/bin/phpunit
```

Ambiente de teste:

```bash
cp .env.testing.example .env.testing
php artisan migrate:fresh --env=testing --seed
```

> Atenção: o `DatabaseSeeder` da v6 chama `ManagementUnitTableSeeder`
> (`v6/database/seeds/DatabaseSeeder.php:43`), que faz `delete()` físico na
> tabela. Em ambiente de teste isso é aceitável, mas **jamais** em produção.

## 10. Cobertura mínima aceitável

- [ ] C1..C18.
- [ ] A1..A6 (regressão do bug U8).
- [ ] L1..L5 (regressão do bug U7).
- [ ] D1..D4.
- [ ] B1..B5.
- [ ] Teste de não regressão do preenchimento do `select` "Unidade Gestora" no
      formulário de licitação (`biddings.form`).
- [ ] Teste de que a exclusão lógica de uma unidade **não** quebra as licitações
      que a referenciam.
