# Usuários — Estratégia de Testes

> O módulo de usuários **não tem testes** na v6. A suíte existente
> (`v6/tests/`) cobre apenas o Dashboard (24 arquivos em
> `v6/tests/Feature/Dashboard*Test.php`) e a configuração GA.
> Este dossiê define a cobertura necessária para a v7.

---

## 1. Precedente de harness

`v6/tests/DashboardTestCase.php` já resolve os problemas mais ingratos do
legado e deve ser reutilizado como base:

| Helper | O que faz |
|---|---|
| `createRole($slug, array $permissions)` | cria um perfil Sentinel com as permissões `recurso.acao` |
| `createUser($roleSlug, array $permissions)` | cria e **autentica** um usuário Sentinel com o perfil; retorna `App\Admin\User` |
| `setModules(array $statuses)` | liga/desliga módulos por slug em `modulos.status` |
| `DatabaseTransactions` | rollback automático — não polui a base |
| `withoutMiddleware(VerifyCsrfToken::class)` | dispensa o token CSRF |
| `require_once app/Library/Helper/appHelpers.php` | garante que `has_access()` esteja carregado (o Composer **não** autoloja esse arquivo) |

> O comentário do próprio harness registra um problema do ambiente: o
> `config/app.php` legado gera `E_NOTICE` no bootstrap, e o
> `phpunit.xml` tem `convertNoticesToExceptions="true"`. O harness
> suprime o notice **apenas** durante o bootstrap.

---

## 2. Estratégia

| Tipo | Escopo | Ferramenta |
|---|---|---|
| **Feature** | Rotas do CMS: matrix de permissões, CRUD, ativar/inativar, login, menu | Laravel HTTP tests (`$this->get/post/put/delete`) + `assertViewHas` |
| **Unit** | `has_access()`, `CheckRoutePermission::hasAccessSegment()`, `User::createPermissions()` | PHPUnit puro |
| **Integração** | Migração de `roles.permissions` (JSON) para tabela | Teste sobre a base real + `DatabaseTransactions` |

Regras de ouro:

1. **Toda ação destrutiva ou de escrita precisa de um teste de autorização.**
2. **Todo caso de teste de permissão precisa de 3 atores**: sem perfil
   (`basico`), com perfil parcial, e `super-admin`.
3. **A base de teste precisa ter `resources` e `permissions` populadas** —
   use `ResourcesTableSeeder` e `PermissionsTableSeeder`, ou um seeder
   dedicado de catálogo mínimo.

---

## 3. Fixtures necessárias

### 3.1 Factories

A factory existente `v6/database/factories/UserFactory.php` está **quebrada**:
aponta para `App\User::class`, classe que não existe (o model é
`App\Admin\User`). Na v7:

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

    public function definition(): array
    {
        return [
            'name'     => fake()->name(),
            'email'    => fake()->unique()->safeEmail(),
            'password' => Hash::make('secret123'),
            'is_active'=> true,
        ];
    }

    public function inactive(): static { return $this->state(fn () => ['is_active' => false]); }
    public function superAdmin(): static
    {
        return $this->afterCreating(fn (User $u) => $u->assignRole('super-admin'));
    }
}
```

Factories a criar: `RoleFactory`, `PermissionFactory`, `CapabilityFactory`,
`ResourceFactory`, `ModuleFactory`.

### 3.2 Seeders de ambiente de teste

| Seed | Caminho | Papel |
|---|---|---|
| `RolesTableSeeder` | `v6/database/seeds/RolesTableSeeder.php` | 6 perfis, incluindo o `administrador` com o JSON de permissões |
| `PermissionsTableSeeder` | `v6/database/seeds/PermissionsTableSeeder.php` | as 9 ações atômicas |
| `ResourcesTableSeeder` | `v6/database/seeds/ResourcesTableSeeder.php` | catálogo de recursos |
| `UserTableSeeder` | `v6/database/seeds/UserTableSeeder.php` | cria `suporte@maxima.inf.br` |

> Atenção: `UserTableSeeder` **não** é idempotente quanto ao perfil — ele
> apenas cria o usuário; o vínculo de perfil vem de `RoleUsersTableSeeder`.

### 3.3 Catálogo mínimo para a matriz de permissões

A matriz renderizada em `v6/resources/views/admin/role/form.blade.php` exige,
para cada recurso, uma linha na tabela `resources` e uma linha na tabela
`permissions`. O seeding de teste deve garantir:

- `permissions`: as 9 ações (`index`, `create`, `edit`, `delete`, `inactive`,
  `inactivate`, `restore`, `show`, `publish`);
- `resources`: ao menos `usuario`, `perfil`, `permissao`, `recurso`;
- `modulos`: uma linha ativa para o módulo `usuarios` (id 11).

---

## 4. Casos de teste

### 4.1 Autorização (`has_access`)

| # | Arrange | Act | Assert |
|---|---|---|---|
| A1 | Usuário sem perfil | `has_access('usuario.index')` | `false` |
| A2 | Usuário com perfil `leitor` (sem permissões) | `has_access('usuario.index')` | `false` |
| A3 | Perfil com `usuario.index = true` | `has_access('usuario.index')` | `true` |
| A4 | Perfil com `usuario.index = true` | `has_access(['usuario.index','perfil.index'])` | `true` (união) |
| A5 | Perfil com `usuario.index = false` | `has_access('usuario.index')` | `false` (valor `false` no JSON é respeitado) |
| A6 | `super-admin` | `has_access('qualquer.coisa')` | `true` |
| A7 | Sem autenticação | `has_access('usuario.index')` | `false` |
| A8 | Autenticado | `has_access()` (sem argumento) | `true` (compatibilidade com o legado) |

### 4.2 Controller de usuários

| # | Arrange | Act | Assert |
|---|---|---|---|
| U1 | Perfil sem `usuario.index` | `GET /cms/usuario` | redirect para `dashboard.index` + flash de aviso |
| U2 | Perfil com `usuario.index` | `GET /cms/usuario` | 200 + view `admin.user.index` |
| U3 | Perfil com `usuario.create` | `POST /cms/usuario` com e-mail válido | 302 para `usuario.index`; linha em `users`; `role_users` com o vínculo |
| U4 | Idem | `POST /cms/usuario` com e-mail já existente | 302 + erro de validação `unique` |
| U5 | Idem | `POST /cms/usuario` sem senha | 302 + erro `password.required` |
| U6 | Idem | `POST /cms/usuario` com `password_confirmation` diferente | 302 + erro `confirmed` |
| U7 | `basico` (sem `perfil.create`) seleciona `super-admin` | `POST /cms/usuario` | 302 + flash "Você não tem permissão para criar usuário com o perfil selecionado." e **nenhum** usuário criado |
| U8 | `super-admin` seleciona `super-admin` | `POST /cms/usuario` | 302 + usuário criado com o perfil |
| U9 | `basico` edita usuário `super-admin` | `GET /cms/usuario/{id}/edit` | 302 + flash de aviso |
| U10 | Usuário edita a si mesmo sem `usuario.edit` | `GET /cms/usuario/{id}/edit` | 200 (regra "ou ser o próprio usuário") |
| U11 | Usuário ativo | `POST /cms/usuario/{id}/status` | 200 + JSON `{"success": ...}`; linha em `activations` removida |
| U12 | Usuário inativo | `POST /cms/usuario/{id}/status` | 200 + JSON `{"success": ...}`; ativação criada |
| U13 | Perfil sem `usuario.inactivate` | `DELETE /cms/usuario/{id}/inactivate` | 302 + flash de aviso |
| U14 | Usuário já inativo | `DELETE /cms/usuario/{id}/inactivate` | **500** com `{"erro": ...}` (comportamento atual — registrar como bug) |
| U15 | Perfil sem `usuario.restore` | `GET /cms/usuario/{id}/restore` | 302 + flash de aviso |
| U16 | `super-admin` | `GET /cms/usuario/{id}/restore` | 302 + ativação criada |

### 4.3 Perfis e permissões

| # | Arrange | Act | Assert |
|---|---|---|---|
| P1 | Perfil sem `perfil.index` | `GET /cms/perfil` | 302 (bloqueado pelo `check.route.permission`) |
| P2 | Perfil com `perfil.index` | `GET /cms/perfil` | 200 + view `admin.role.index` |
| P3 | Qualquer usuário autenticado | `POST /cms/perfil` com `permissions[usuario.create]=1` | **200/302 e perfil criado** — comprova a escalada de privilégio do item 3.1 do dossiê 05. Na v7 o teste deve esperar **403**. |
| P4 | `super-admin` | `POST /cms/perfil` com nome duplicado | 302 + erro `unique:roles` |
| P5 | `super-admin` | `POST /cms/perfil` | perfil criado; `roles.permissions` (ou `role_capability` na v7) contém a chave marcada |
| P6 | Perfil com `perfil.edit` | `PUT /cms/perfil/{id}` com a matriz toda desmarcada | o perfil **perde todas** as permissões (comportamento atual — ver 3.3 do dossiê 05) |
| P7 | Não-`super-admin` | `GET /cms/perfil/{idSuperAdmin}/edit` | 302 + flash de aviso |
| P8 | Não-`super-admin` | `GET /cms/permissao` | 302 para `dashboard.index` |
| P9 | `super-admin` | `GET /cms/permissao` | 200 |

### 4.4 Menu e visibilidade

| # | Arrange | Act | Assert |
|---|---|---|---|
| M1 | Perfil sem `usuario.*` | `GET /cms/dashboard` | o HTML **não** contém o link "Usuários" |
| M2 | Perfil com `usuario.index` | `GET /cms/dashboard` | o HTML contém o link "Usuários" |
| M3 | Perfil com `usuario.index` mas sem `perfil.index` | `GET /cms/dashboard` | contém "Usuários", **não** contém "Perfis" |
| M4 | Perfil com `perfil.index` e `recurso.index` | `GET /cms/dashboard` | contém "Perfis" e "Recursos" |
| M5 | `super-admin` | `GET /cms/dashboard` | contém todos os itens |

### 4.5 Login e ativação

| # | Arrange | Act | Assert |
|---|---|---|---|
| L1 | Usuário com senha | `POST /login` | 302 para `/cms`; `last_login` preenchido; `activity_log` com "Login no sistema" |
| L2 | Senha errada | `POST /login` | 200 (view de login) + flash "Usuário ou senha incorreto(s)." |
| L3 | Usuário inativo | `POST /login` | o Sentinel recusa a autenticação (sem ativação) |
| L4 | Usuário com ativação pendente | `GET /activate/{email}/{code}` | redirect para `/login`; ativação concluída |
| L5 | Código inválido | `GET /activate/{email}/{code}` | redirect para a raiz |

### 4.6 Auditoria

| # | Arrange | Act | Assert |
|---|---|---|---|
| D1 | Usuário autenticado | `POST /cms/usuario` | 1 linha em `activity_log` com `description` contendo "Cadastro de usuário" e `properties.Detalhe` com id, nome e perfis |
| D2 | Idem | `PUT /cms/usuario/{id}` | 1 linha com "Edição de usuário" |
| D3 | Idem | `DELETE /cms/usuario/{id}/inactivate` | 1 linha com "Inativou usuário" |
| D4 | Idem | `GET /cms/usuario` | 1 linha com "Listou usuários" e `ip_address`/`browser` preenchidos |

---

## 5. Fixtures de view

Para testar as telas (não apenas o código de retorno), as views exigem:

| View | Variáveis obrigatórias |
|---|---|
| `admin.user.index` | `title` |
| `admin.user.list` | `id` (id da tabela) |
| `admin.user.create` | `roles` (Collection), `title` |
| `admin.user.edit` | `user`, `roles`, `title` |
| `admin.user.form` | `user` (opcional), `errors` |
| `admin.role.index` | `title` |
| `admin.role.create` / `edit` | `resources`, `permissions`, `role` (opcional), `title` |
| `admin.permission.index` | `title` |

> `admin.user.show`, `admin.user.roles` e `admin.user.profile.edit` **não
> existem** na v6 — não há como testá-las sem antes criá-las (ver
> `08-GAPS-E-DIVIDAS.md`).

---

## 6. Como rodar

```bash
# na raiz de v6/
composer install
./vendor/bin/phpunit --testsuite Feature
./vendor/bin/phpunit --testsuite Unit
./vendor/bin/phpunit tests/Feature/UsuariosAuthorizationTest.php
./vendor/bin/phpunit --filter testNaoSuperAdminNaoAtribuiSuperAdmin
```

Configuração de ambiente (de `v6/phpunit.xml`):

```xml
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
```

> A suíte usa `DatabaseTransactions` (rollback), **não** `RefreshDatabase`.
> Isso significa que o banco de teste precisa já estar **migrado e
> semeado** com `resources`, `permissions`, `roles` e `modulos`.

---

## 7. Cobertura mínima aceitável para a v7

| Arquivo de teste | Casos |
|---|---|
| `tests/Feature/Usuarios/AuthorizationTest.php` | A1-A8, M1-M5 |
| `tests/Feature/Usuarios/UserManagementTest.php` | U1-U16 |
| `tests/Feature/Usuarios/RoleManagementTest.php` | P1-P9 |
| `tests/Feature/Usuarios/LoginTest.php` | L1-L5 |
| `tests/Feature/Usuarios/AuditLogTest.php` | D1-D4 |
| `tests/Unit/CapabilityKeyTest.php` | formato da chave `recurso.acao` |
| `tests/Feature/Usuarios/PermissionMigrationTest.php` | migração do JSON para tabela |

Meta: **cobertura de 100% das actions** de `UserController`, `RoleController`
e `PermissionController`, com ênfase nas branches de autorização.
