# Configurações da Instituição — Testes

---

## 1. Estratégia

| Camada | Ferramenta sugerida | Escopo |
|---|---|---|
| Feature (CMS) | PHPUnit + `RefreshDatabase` | Renderização da tela, autorização, gravação por aba, efeitos colaterais (`config.json`, cache). |
| Feature (Portal) | PHPUnit + `RefreshDatabase` | Efeitos das chaves no portal público (manutenção, notícias, selos, transparência). |
| Unit | PHPUnit | Regras do `ConfigurationRequest`, validações de GA/Dashboard, `WidgetRegistry`. |
| Integração (paridade) | Script de comparação | Extrair as 57 chaves de `site` do banco v6 e comparar com a tabela de destino na v7. |

Rodar:

```bash
cd /home/one/projects/maxima/v7   # raiz da reconstrução (v7)
php artisan test --filter=InstitutionSettings
php artisan test --filter=Configuration
```

No legado (v6), não há suíte automatizada para este módulo; a verificação é
manual no CMS e por consulta ao banco `prefeituras_v5`.

## 2. Fixtures necessárias

| Fixture | Conteúdo |
|---|---|
| `InstitutionSettingsSeeder` | Cria as 8 categorias de configuração e as 57 chaves de `site` com os mesmos valores de `order`, `type`, `notnull`, `max_input`, `min_input`. |
| `DashboardSettingsSeeder` | Cria as 6 chaves da categoria `dashboard`. |
| `CategoriesSeeder` | Categorias `prefeitura`, `temas`, `outros`, `layout`, `covid`, `barra-privacidade`, `dashboard`. |
| `ModuleSeeder` | `modulos` com `configuracoes-da-instituicao` (status 1), `temas-e-cores`, `lgpd` (status 0), `outras-configuracoes`. |
| `ResourceSeeder` | `resources` com `configuracoes` (modulo_id 1) e `outros` (modulo_id 4). |
| `UserFactory` | Usuário com permissões `configuracoes.index`, `configuracoes.edit`; usuário sem permissão; usuário `super-admin`. |
| `StorageFake` | `Storage::fake('public')` para os uploads de logo e selos. |

## 3. Casos de teste — leitura da tela

| # | Arrange | Act | Assert |
|---|---|---|---|
| L1 | Usuário com `configuracoes.edit` | GET `/cms/configuracoes/editar/configuracoes` | 200; view `prefecture.configurations.configuracoes.edit`; 57 configurações da categoria `site` |
| L2 | Usuário sem `configuracoes.edit` | GET na mesma rota | redirect `dashboard.index` + flash de acesso negado |
| L3 | Usuário anônimo | GET na mesma rota | redirect (middleware `check.logged`) |
| L4 | Usuário sem `mail_recaptcha` | GET na mesma rota | a aba `divEmail` **não** aparece no HTML |
| L5 | Usuário com `dashboard-config.index` | GET na mesma rota | aparecem `divDashboard` e o bloco `Google Analytics` na aba SEO |
| L6 | Usuário com `dashboard-config.index` mas sem `dashboard-config.edit` | GET na mesma rota | inputs de GA presentes com atributo `disabled` |
| L7 | Usuário autenticado | GET `/cms/modulos` | 200; select com `structure_type` preenchido |
| L8 | Usuário autenticado | GET `/cms/datatable/configuracoes` | JSON DataTables com as 87 configurações de todas as categorias |

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

| # | Arrange | Act | Assert |
|---|---|---|---|
| G1 | Payload com `titulo_site` vazio | POST `update.configuration` | 302 de volta; erro de validação `required`; valor no banco inalterado |
| G2 | `titulo_site` com 2 caracteres | POST | erro `min:3` |
| G3 | `titulo_site` com 101 caracteres | POST | erro `max:100` |
| G4 | Usuário sem `configuracoes.edit` | POST | redirect `dashboard.index`; nenhuma linha alterada |
| G5 | Payload válido da aba Institucional | POST | `configurations.value` atualizado; flash de sucesso; redirect com `display=principal` |
| G6 | Payload válido da aba Opções | POST | redirect com `display=options` (aba preservada) |
| G7 | Payload com `app_env=1` | POST | `configurations.value` = `1`; `config.json` com `config[2].app_env = "local"` |
| G8 | Payload com `app_env=0` | POST | `config.json` com `config[2].app_env = "prod"` e `app_debug = "false"` |
| G9 | Payload da aba Mail | POST | `config.json` com `config[0]` contendo as 8 chaves `mail_*` |
| G10 | Payload com `recaptcha_public_key` | POST | `config.json` com `config[1]` preenchido |
| G11 | Payload com `layout = 'modelo9'` | POST | flash de erro; nada gravado |
| G12 | Payload com `layout = 'modelo3'` | POST | gravado sem erro |
| G13 | Payload com chave inexistente `chave_inexistente` | POST | nenhum erro; a chave é ignorada |
| G14 | Upload de `logo` (imagem 1000x500) | POST | arquivo em `storage/app/public/configuracoes/`; largura 275px; `value` = `configuracoes/<nome>` |
| G15 | Upload de `imagem-acesso-rapido` (1000x500) | POST | imagem **não** é redimensionada |
| G16 | Upload de `logo` com arquivo anterior em `configuracoes/` | POST | arquivo anterior removido do disco |
| G17 | POST com `google_analytics_property_id = 'ABC'` | POST | flash “O Property ID deve conter apenas números.”; valor não gravado |
| G18 | POST com `google_analytics_property_id = '123456'` | POST | gravado em claro |
| G19 | POST com `google_analytics_sa_json` sem `private_key` | POST | flash “A Service Account informada é inválida.”; nada gravado |
| G20 | POST com `google_analytics_sa_json` válido | POST | valor gravado **criptografado** (`Crypt::decrypt` devolve o JSON) |
| G21 | POST sem `dashboard-config.edit` mas com chaves GA | POST | chaves GA **ignoradas silenciosamente**, sem erro |
| G22 | POST com `dashboard_widget_refresh_minutes = 2000` | POST | flash de faixa (1..1440); nada gravado |
| G23 | POST com `dashboard_widget_refresh_minutes = 30` | POST | gravado |
| G24 | POST com `dashboard_widgets_enabled[]` marcando widget indisponível | POST | widget indisponível é preservado na lista |
| G25 | POST com `selos_institucionais[0]` sem `posicao` | POST | selo descartado; array salvo vazio |
| G26 | POST com `selos_institucionais[0]` completo + imagem | POST | JSON gravado; arquivo em `selos/` no disco `public`; `selos_institucionais_url` com a URL |
| G27 | Config `selos_institucionais` inexistente | POST | nada é criado (o `createSelosInstitucionais` não tem rota) |
| G28 | POST com `portal_transparencia_ativo` | POST | `Cache::pull('portal_transparencia_config')` invalidado |
| G29 | POST com qualquer chave | POST | `Cache::pull('structureType')` invalidado |

## 5. Casos de teste — efeitos no portal

| # | Arrange | Act | Assert |
|---|---|---|---|
| P1 | `pagina_em_manutencao = 1`, visitante anônimo | GET `/` | view `portal.manutencao.index` |
| P2 | `pagina_em_manutencao = 1`, usuário logado | GET `/` | portal normal |
| P3 | `noticias = 0` | GET `/` | home sem o bloco de notícias |
| P4 | `ultimas_noticias = 0` | GET `/` | sem seção de últimas notícias |
| P5 | `links_rapidos = 0` | GET `/` | sem seção de links rápidos |
| P6 | `contact_link = 0` | GET `/` | sem botão de contato no cabeçalho |
| P7 | `codigo_tempo = '231'` | GET `/` | bloco de tempo presente |
| P8 | `titulo_site` alterado | GET `/` | `<title>` reflete o novo valor |
| P9 | `palavras_chaves` alterado | GET `/` | meta `keywords` reflete o novo valor |
| P10 | `incorporacao_head` com HTML | GET `/` | HTML injetado no `<head>` |
| P11 | `selos_institucionais` com 2 selos | GET `/selos` | 200; view `portal.layouts.modelo1.selos.index` com 2 selos |
| P12 | `portal_transparencia_ativo = 1` | GET `/` | seção de transparência fiscal externa visível |
| P13 | `gemini_api_key` vazia | chamada a `maskTextWithAi()` | usa `env('GEMINI_API_KEY')` como fallback |

## 6. Testes de paridade de dados (v6 -> v7)

| # | Verificação |
|---|---|
| M1 | Exportar as 57 linhas de `site` do banco `prefeituras_v5` e comparar chave a chave com a tabela de destino na v7 (mesmo valor, mesmo tipo, mesmos limites). |
| M2 | Comparar o JSON gerado por `config.json` (v6) com o conteúdo equivalente em `mail_settings` + `recaptcha_settings` + `settings` (v7). |
| M3 | Comparar o HTML das 8 abas (v6) com o HTML das 8 abas (v7) para os mesmos dados. |
| M4 | Verificar que as chaves de `dashboard` (6) migram com a Service Account **criptografada** e que `secret()` continua devolvendo o JSON original. |

## 7. Verificação manual (legado v6)

```bash
# Estado atual das chaves da categoria site
docker exec v6_6f22e1bd-postgres-1 psql -U postgres -d prefeituras_v5 \
  -c 'select id, "order", "key", "type", "notnull", max_input, min_input
      from configurations where category_configurations_id = 1 and deleted_at is null
      order by "order", id;'

# Conferir o config.json gerado pela tela
cat v6/config.json
```

Fluxo manual: abrir `/cms/configuracoes/editar/configuracoes`, salvar cada aba,
recarregar e confirmar que a aba ativa é preservada e que o `config.json` foi
reescrito com data de modificação recente.
