# Outras Configurações — Testes

> Estratégia de testes para reconstruir o módulo `outras-configuracoes` em
> Laravel 11/12. O legado **não possui testes** para este módulo
> (`v5/tests/` e `v6/tests/` não cobrem `ConfigurationController`); os casos
> abaixo foram derivados do comportamento observado em v6.

---

## 1. Estratégia

| Tipo | Escopo | Ferramenta sugerida |
|---|---|---|
| Feature (HTTP) | Rotas do CMS, permissões, redirects, JSON do `DELETE` | PHPUnit + `Laravel\Testing\TestCase`, factories |
| Feature (listagem) | Endpoint de listagem com filtro por categoria e paginação | Pest ou PHPUnit |
| Unit | Normalização de `key`, montagem das regras dinâmicas, mapeamento de tipo | PHPUnit puro |
| Integração | Efeito das chaves técnicas (`mail_*`, `app_env`, `gemini_api_key`) no `config.json` e no boot da aplicação | Teste de boot / smoke test |

Regras de teste herdadas do comportamento atual (não podem quebrar):

1. `GET /cms/configuracoes/editar/outros` com `outros.edit` → 200 e tela de listagem.
2. Sem `outros.edit` → redirect para `dashboard.index` + flash de erro.
3. `GET /cms/configuracoes` sem `outros.index` → redirect para `dashboard.index`.
4. `DELETE /cms/configuracoes/{id}` → 200 `{"success": ...}` e linha removida da listagem.
5. `store()` normaliza `key` com `str_slug($key, '_')`.
6. `status_create = true` mantém a `key` inalterada após `update()`.

## 2. Fixtures e factories

```php
// exemplo ilustrativo — database/factories/ConfiguracaoFactory.php
class ConfiguracaoFactory extends Factory
{
    protected $model = Configuracao::class;

    public function definition(): array
    {
        return [
            'key' => $this->faker->unique()->slug(),
            'value' => $this->faker->word(),
            'type' => TipoConfiguracao::Texto->value,
            'title' => $this->faker->sentence(3),
            'categoria_configuracao_id' => CategoriaConfiguracao::factory(),
            'notnull' => false,
            'max_input' => 191,
            'min_input' => 0,
            'order' => 1,
            'status_create' => false,
        ];
    }

    public function categoriaOutros(): static
    {
        return $this->state(fn () => [
            'categoria_configuracao_id' => CategoriaConfiguracao::where('slug', 'outros')->value('id'),
        ]);
    }
}
```

| Fixture | Origem / observação |
|---|---|
| Usuário `super-admin` | Papel do Sentinel; na v7, `User::factory()->withRole('super-admin')` |
| Usuário com `outros.index` e `outros.edit` |Permissões `configuracoes.*` e `outros.*` conforme `resources` |
| Usuário sem permissões | Base para os testes de redirect |
| Categorias | As 8 categorias com os slugs `site`, `prefeitura`, `temas`, `outros`, `layout`, `covid`, `barra-privacidade`, `dashboard` |
| Configurações |Uma por categoria; a categoria `outros` **vazia** por padrão (estado real do banco) |

## 3. Casos de teste (Arrange / Act / Assert)

### 3.1 Leitura e menu

| # | Arrange | Act | Assert |
|---|---|---|---|
| T01 | Usuário autenticado com `outros.index` | `GET /cms/configuracoes` | 200; view `prefecture.configurations.index` |
| T02 | Usuário sem `outros.index` | `GET /cms/configuracoes` | redirect `dashboard.index`; flash de aviso |
| T03 | Usuário com `outros.index` e `outros.edit` | `GET /cms/configuracoes/editar/outros` | 200; renderiza a **listagem** (não formulário de categoria) |
| T04 | Usuário com `outros.index` e **sem** `outros.edit` | `GET /cms/configuracoes/editar/outros` | redirect `dashboard.index`; flash "Você não tem permissão para acessar este conteúdo" |
| T05 | Usuário `super-admin` | `GET /cms/configuracoes/editar/outros` | 200 e log "Listou outras configurações" |
| T06 | `modulos.status = 0` para `outras-configuracoes` | Render da sidebar | Item "Outras Configurações" **ausente** |

### 3.2 Listagem (endpoint AJAX)

| # | Arrange | Act | Assert |
|---|---|---|---|
| T07 | 2 configurações em `outros`, 3 em `site`, 1 em `dashboard` | `GET /cms/datatable/configuracoes` | JSON traz as 6 linhas (comportamento legado: sem filtro) |
| T08 | Mesma base | `GET /api/cms/configuracoes?categoria=outros` (v7) | JSON traz **apenas** as 2 de `outros` |
| T09 | Configuração com `deleted_at` preenchido | `GET /cms/datatable/configuracoes` | Linha **ausente** (view SQL filtra `deleted_at IS NULL`) |
| T10 | Usuário sem `configuracoes.edit` | `GET /cms/datatable/configuracoes` | `edit = false` em todas as linhas |
| T11 | Usuário sem `configuracoes.delete` | `GET /cms/datatable/configuracoes` | `delete = false` em todas as linhas |

### 3.3 Cadastro

| # | Arrange | Act | Assert |
|---|---|---|---|
| T12 | Usuário **não** `super-admin` | `GET /cms/configuracoes/create` | redirect `dashboard.index`; flash de aviso |
| T13 | Usuário `super-admin` | `GET /cms/configuracoes/create` | 200; view `prefecture.configurations.create`; categorias carregadas |
| T14 | `super-admin`, payload `key = "Chave de Teste"` | `POST /cms/configuracoes` | Linha criada com `key = "chave_de_teste"` (`Str::slug` com `_`) |
| T15 | `super-admin`, `category_configurations_id = 4` | `POST /cms/configuracoes` | Linha criada na categoria `outros`; aparece na listagem com `categoria = 'outros'` |
| T16 | `super-admin`, payload sem `title` | `POST /cms/configuracoes` | 302 de volta com erro "O campo título é obrigatório" |
| T17 | `super-admin`, `type = file` sem `value` | `POST /cms/configuracoes` | Criação **permitida** (valor não é required para arquivo) |
| T18 | `super-admin`, `type = text` sem `value` | `POST /cms/configuracoes` | Erro "O campo valor é obrigatório" |
| T19 | `super-admin`, upload `logo.png` | `POST /cms/configuracoes` | Arquivo em `configuracoes/file_....png`; `value` = caminho relativo |

### 3.4 Edição

| # | Arrange | Act | Assert |
|---|---|---|---|
| T20 | Usuário não `super-admin` | `GET /cms/configuracoes/{id}/edit` | redirect `dashboard.index` |
| T21 | `super-admin` | `GET /cms/configuracoes/{id}/edit` | 200; view `prefecture.configurations.edit` |
| T22 | Configuração com `status_create = true` | `PUT /cms/configuracoes/{id}` com outra `key` | `key` **inalterada** (campo desabilitado não é enviado) |
| T23 | Configuração com `status_create = false` | `PUT /cms/configuracoes/{id}` com `key = "Nova Chave"` | `key = "nova_chave"` |
| T24 | Configuração `type = file` com arquivo existente | `PUT` com novo arquivo | Arquivo antigo removido (`Storage::delete`), novo em `configuracoes/img_*.ext` |
| T25 | `super-admin` | `PUT /cms/configuracoes/{id}` sem `value` e `type = file` | Atualização aceita |

### 3.5 Exclusão

| # | Arrange | Act | Assert |
|---|---|---|---|
| T26 | `super-admin`, configuração existente | `DELETE /cms/configuracoes/{id}` | 200; corpo contém `success`; `deleted_at` preenchido (soft delete) |
| T27 | `super-admin`, configuração já removida | `DELETE /cms/configuracoes/{id}` | 500 com `erro` |
| T28 | Após T26 | `GET /cms/datatable/configuracoes` | Linha ausente |

### 3.6 Efeitos colaterais

| # | Arrange | Act | Assert |
|---|---|---|---|
| T29 | Configuração `mail_host` na categoria `site` | `PUT` na rota por categoria | `config.json` regenerado com `config[0].mail_host` atualizado |
| T30 | `app_env = 1` enviado na rota por categoria | `POST /cms/update/configuration` | `app_env` gravado como `local`; `app_debug` como `true`/`false` |
| T31 | Usuário sem `configuracoes.edit` | `POST /cms/update/configuration` | redirect `dashboard.index`; nada gravado |

## 4. Fixtures de view/seed para ambiente de teste

| Seed | Caminho | Papel no teste |
|---|---|---|
| `CategoryConfigurationsTableSeeder` | `v6/database/seeds/CategoryConfigurationsTableSeeder.php` | Garante as 8 categorias (ids 1-8) |
| `ConfigurationsTableSeeder` | `v6/database/seeds/ConfigurationsTableSeeder.php` | Popula `site`, `temas`, `covid`, `barra-privacidade`; **deixa `outros` vazia** |
| `ResourcesTableSeeder` | `v6/database/seeds/ResourcesTableSeeder.php` | Cria o recurso `outros` ligado ao módulo 4 |

> Reproduzir o estado real do banco é essencial: o teste que valida a
> listagem "vazia" do `index()` só faz sentido se a categoria `outros` estiver
> sem configurações.

## 5. Como rodar

```bash
# Laravel 11/12 (projeto v7)
php artisan test --filter=Configuracao
php artisan test --testsuite=Feature

# Coverage
php artisan test --coverage --min=80

# Migrar do zero o banco de testes
php artisan migrate:fresh --seed
```

Comandos úteis durante a migração:

```bash
# conferir se há chaves duplicadas antes de criar índice único
php artisan tinker --execute="DB::table('configurations')->select('key', DB::raw('count(*) as total'))->groupBy('key')->having('total','>',1)->get();"

# confirmar que a categoria outros está vazia
php artisan tinker --execute="DB::table('configurations')->where('category_configurations_id',4)->count();"
```

## 6. Testes que hoje existem no legado

[NÃO VERIFICADO] Não há testes automatizados para este módulo no legado. O
comportamento atual do `v6` foi reproduzido manualmente sobre o container
`v6_6f22e1bd-postgres-1` (banco `prefeituras_v5`, somente leitura), conforme as
consultas descritas em `02-MODELAGEM.md`.
