# Fornecedores — Estratégia de Testes

> O módulo **não tem testes** na v6. A suíte existente
> (`v6/tests/`) cobre apenas o Dashboard e a configuração GA.

---

## 1. Precedente de harness

`v6/tests/DashboardTestCase.php`:
`createRole()`, `createUser()`, `setModules()`, `DatabaseTransactions`,
`withoutMiddleware(VerifyCsrfToken::class)`.

Para este módulo:
- `createRole('fornecedor', ['supplier.index', 'supplier.create', ...])`
  — exige que o recurso `supplier` exista no catálogo;
- `setModules(['fornecedores' => true])` — para o item de menu.

---

## 2. Estratégia

| Tipo | Escopo |
|---|---|
| **Feature** | Listagem, CRUD, upload, alternância do ativo, **autorização das rotas de escrita** |
| **Unit** | Sanitização de telefone/WhatsApp, geração de slug |
| **Integração** | View SQL `fornecedores` e a view de compatibilidade |

---

## 3. Fixtures necessárias

### 3.1 Factory

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

    public function definition(): array
    {
        return [
            'name'     => fake()->company(),
            'slug'     => fake()->unique()->slug(),
            'email'    => fake()->companyEmail(),
            'site'     => fake()->url(),
            'phone'    => '11987654321',
            'whatsapp' => '11987654321',
            'address'  => fake()->address(),
            'locale'   => 'https://www.google.com/maps/embed?pb=test',
            'image'    => 'img/images_fornecedores/test.png',
            'icon'     => 'img/icons_fornecedores/test-icon.png',
            'fornecedor_ativo' => false,
        ];
    }

    public function active(): static
    {
        return $this->state(fn () => ['fornecedor_ativo' => true]);
    }
}
```

> O `SuplierTableSeeder` (`v6/database/seeds/SuplierTableSeeder.php`) faz
> `truncate` — **não** deve ser usado em teste.

### 3.2 Seeder de catálogo

```php
// database/seeds/SupplierResourceSeeder.php
class SupplierResourceSeeder extends Seeder
{
    public function run(): void
    {
        Resource::updateOrCreate(
            ['slug' => 'supplier'],
            [
                'name'        => 'Fornecedores',
                'description' => 'Funcionalidade Fornecedores',
                'modulo_id'   => 6,     // modulos.slug = 'fornecedores'
                'created_at'  => date('Y-m-d H:i:s'),
            ]
        );
    }
}
```

---

## 4. Casos de teste

### 4.1 Autorização (o ponto mais crítico)

| # | Arrange | Act | Assert |
|---|---|---|---|
| A1 | **Sem autenticação** | `POST /cms/fornecedores` | **v6: 302/201 (aceito!)**; **v7: 401/403** |
| A2 | Sem autenticação | `PUT /cms/fornecedores/atualizar/1` | **v6: aceito**; **v7: 401/403** |
| A3 | Sem autenticação | `DELETE /cms/fornecedores/excluir/1` | **v6: aceito**; **v7: 401/403** |
| A4 | Sem autenticação | `POST /cms/fornecedores_switch` | **v6: aceito**; **v7: 401/403** |
| A5 | Sem autenticação | `GET /cms/fornecedores` | 302 (tem `check.logged`) |
| A6 | Usuário autenticado sem `supplier.index` | `GET /cms/fornecedores` | 200 (a rota não checa permissão) — v7: 403 |
| A7 | Usuário autenticado sem `supplier.create` | `GET /cms/fornecedores` | o botão "Cadastrar" **ausente** |
| A8 | Perfil com `supplier.index` | `GET /cms/dashboard` | o link "Fornecedores" aparece no menu |
| A9 | Perfil com `supplier.index`, módulo desativado | `GET /cms/dashboard` | o link **não** aparece (`$modulos['fornecedores']`) |

### 4.2 CRUD

| # | Arrange | Act | Assert |
|---|---|---|---|
| M1 | Perfil com `supplier.create` | `POST /cms/fornecedores` com todos os campos | 302 para `supplier.index`; linha em `suppliers` |
| M2 | Idem, `name` com 4 caracteres | `POST` | 302 + erro `min:5` |
| M3 | Idem, `email` inválido | `POST` | 302 + erro de e-mail |
| M4 | Idem, sem `site` | `POST` | 302 + erro `required` (na v6 `site` é NOT NULL) |
| M5 | Idem, sem `locale` | `POST` | 302 + erro `required` |
| M6 | Idem, `phone` = "(11) 98765-4321" | `POST` | gravado `11987654321` (só dígitos) |
| M7 | Idem, `whatsapp` = "+55 11 98765-4321" | `POST` | gravado `5511987654321` |
| M8 | Idem, `phone` = "abcde" | `POST` | **v6**: `dd($th)` (erro 500) por `null` em coluna NOT NULL; **v7**: 302 + erro |
| M9 | Idem, com `image` válida | `POST` | arquivo em `storage/app/public/img/images_fornecedores/`; `image` = caminho relativo |
| M10 | Idem, com `icon` válida | `POST` | arquivo em `storage/app/public/img/icons_fornecedores/` |
| M11 | Idem, com `image` de 2 MB | `POST` | 302 + erro de validação de imagem |
| M12 | Fornecedor existente | `GET /cms/fornecedores/editar/{id}` | 200 + view com os valores |
| M13 | Id inexistente | `GET /cms/fornecedores/editar/99999` | 302 + flash "Registro não encontrado!" |
| M14 | Fornecedor existente | `PUT /cms/fornecedores/atualizar/{id}` | 302 + linha atualizada |
| M15 | Fornecedor existente | `PUT` com novo `name` | o `slug` é recalculado a partir do novo nome (comportamento atual) |
| M16 | Fornecedor existente | `DELETE /cms/fornecedores/excluir/{id}` | 200 + JSON `{"success": ...}`; linha com `deleted_at` |
| M17 | Idem | `GET /cms/datatable/fornecedor` | a linha **não** aparece (view SQL filtra `deleted_at IS NULL`) |

### 4.3 Fornecedor ativo

| # | Arrange | Act | Assert |
|---|---|---|---|
| F1 | Nenhum fornecedor ativo | `POST /cms/fornecedores_switch` com `contents_id` | JSON `{"success": ...}`; o alvo fica ativo |
| F2 | Fornecedor A ativo | `POST /cms/fornecedores_switch` com o id de B | A fica inativo (`null`), B fica ativo |
| F3 | Fornecedor A ativo | `POST /cms/fornecedores_switch` com o id de A | **v6**: A fica **inativo** (o `elseif` não reativa); **v7**: comportamento de toggle ou botão explícito |
| F4 | `contents_id` inexistente | `POST /cms/fornecedores_switch` | **v6**: erro 500 (`null->update()`); **v7**: 404/422 |
| F5 | Dois fornecedores ativos | `GET /cms/suporte` | **v6**: exibe um deles (não determinístico); **v7**: o banco impede |
| F6 | Nenhum fornecedor ativo | `GET /cms/suporte` | 200 + página **vazia** (sem mensagem) — v7: mensagem explícita |
| F7 | Fornecedor ativo | `GET /cms/dashboard` (rodapé) | o rodapé mostra o nome e o site do fornecedor |

### 4.4 Listagem (DataTable)

| # | Arrange | Act | Assert |
|---|---|---|---|
| L1 | 2 fornecedores | `GET /cms/datatable/fornecedor` | JSON com 2 linhas e as chaves `value`, `titulo`, `email`, `telefone`, `endereco`, `icone`, `imagem`, `id`, `status` |
| L2 | 2 fornecedores | idem | `telefone` vem de `phone` e `endereco` de `address` (alias da view) |
| L3 | Idem | idem | **não** há `slug`, `locale` nem `whatsapp` no JSON (limitação da view) |
| L4 | Idem | as flags | `edit`/`delete`/`active`/`inactivate` refletem `has_access('supplier.*')` |

### 4.5 Segurança

| # | Arrange | Act | Assert |
|---|---|---|---|
| S1 | `locale` = "javascript:alert(1)" | `GET /cms/suporte` | **v6**: o `iframe` recebe o valor; **v7**: 422 (validação de URL) |
| S2 | `name` com HTML | `GET /cms/datatable/fornecedor` | o JSON devolve o HTML cru (o DataTable escapa no `render`) |

### 4.6 Integridade do schema

| # | Arrange | Act | Assert |
|---|---|---|---|
| C1 | Base migrada | `DB::table('resources')->where('slug','supplier')->exists()` | **hoje: `false`** — v7: `true` |
| C2 | Schema | `\d suppliers` | `email` e `site` NOT NULL na v6; nullable na v5 |
| C3 | Schema | colunas `telefone`/`endereco` | não existem (resíduo no `$fillable`) |
| C4 | View SQL | `suppliers.fornecedor_ativo` | pode haver mais de um `true` |

---

## 5. Fixtures de view

| View | Variáveis |
|---|---|
| `prefecture.configurations.fornecedor.index` | — (o botão usa `has_access`) |
| `prefecture.configurations.fornecedor.create` | — |
| `prefecture.configurations.fornecedor.edit` | `supplier` (model) |
| `prefecture.configurations.fornecedor.form` | `supplier` (opcional), `errors` |
| `admin.layouts.about` | `@inject('dadosFornecedor', ...)` |

---

## 6. Como rodar

```bash
cd v6/
composer install
./vendor/bin/phpunit --testsuite Feature
./vendor/bin/phpunit tests/Feature/Suppliers/SupplierAuthorizationTest.php
./vendor/bin/phpunit --filter testRotaDeEscritaExigeAutenticacao
```

Ambiente (de `v6/phpunit.xml`): `APP_ENV=testing`,
`CACHE_DRIVER=array`, `SESSION_DRIVER=array`, `QUEUE_DRIVER=sync`.

> A suíte usa `DatabaseTransactions`; o banco de teste precisa estar
> migrado, com a view SQL `fornecedores` **existente** (ela não é criada
> por migration).

---

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

| Arquivo de teste | Casos |
|---|---|
| `tests/Feature/Suppliers/SupplierAuthorizationTest.php` | A1-A9 |
| `tests/Feature/Suppliers/SupplierCrudTest.php` | M1-M17 |
| `tests/Feature/Suppliers/SupplierActiveTest.php` | F1-F7 |
| `tests/Feature/Suppliers/SupplierDatatableTest.php` | L1-L4 |
| `tests/Feature/Suppliers/SupplierSchemaTest.php` | C1-C4 |
| `tests/Unit/PhoneSanitizationTest.php` | M6-M8 |

Os casos **A1-A4** (escrita sem autenticação) são o item de maior
prioridade: na v7, todos devem esperar 401/403.
