354 lines
16 KiB
PHP
354 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Suíte de testes automatizados do fluxo de autenticação (Workbloom ERP).
|
|
* Executa todos os cenários requeridos com validação rigorosa de regras.
|
|
*/
|
|
|
|
require_once __DIR__ . '/../vendor/autoload.php';
|
|
|
|
use Dotenv\Dotenv;
|
|
use Ramsey\Uuid\Uuid;
|
|
use KrothiumAPI\Database\DBManager;
|
|
use WorkbloomERP\Security\JwtService;
|
|
use WorkbloomERP\Security\AuthContext;
|
|
use WorkbloomERP\Middleware\AuthMiddleware;
|
|
use WorkbloomERP\Module\Auth\DTO\LoginDTO;
|
|
use WorkbloomERP\Module\Auth\Repository\AuthRepository;
|
|
use WorkbloomERP\Module\Auth\Service\AuthService;
|
|
|
|
$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
|
|
$dotenv->load();
|
|
|
|
$db = DBManager::getConnection();
|
|
$repo = new AuthRepository($db);
|
|
$jwt = new JwtService();
|
|
$service = new AuthService($repo, $jwt);
|
|
|
|
$passed = 0;
|
|
$failed = 0;
|
|
|
|
function assertTest(string $title, bool $condition, ?string $detail = null): void {
|
|
global $passed, $failed;
|
|
if ($condition) {
|
|
echo " [PASS] {$title}\n";
|
|
$passed++;
|
|
} else {
|
|
echo " [FAIL] {$title}" . ($detail ? " - {$detail}" : "") . "\n";
|
|
$failed++;
|
|
}
|
|
}
|
|
|
|
echo "\n=======================================================\n";
|
|
echo " INICIANDO BATERIA DE TESTES DE AUTENTICAÇÃO\n";
|
|
echo "=======================================================\n\n";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 1. Login com e-mail válido
|
|
// ---------------------------------------------------------------------------
|
|
echo "Cenário 1: Login com e-mail válido\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'admin@workbloom.com.br', 'senha' => 'Admin@123456']);
|
|
$res = $service->login($dto);
|
|
|
|
assertTest("Geração do token JWT", !empty($res['token']));
|
|
assertTest("Tipo do token é Bearer", $res['token_type'] === 'Bearer');
|
|
assertTest("Tempo de expiração retornado", $res['expires_in'] > 0);
|
|
assertTest("Dados do usuário presentes", isset($res['usuario']['email']) && $res['usuario']['email'] === 'admin@workbloom.com.br');
|
|
assertTest("UUID presente no usuário", !empty($res['usuario']['uuid']));
|
|
assertTest("ID numérico NÃO exposto no usuário", !isset($res['usuario']['id']));
|
|
assertTest("Senha hash NÃO exposta no retorno", !isset($res['usuario']['senha_hash']));
|
|
assertTest("Empresas vinculadas presentes", is_array($res['empresas']) && count($res['empresas']) >= 1);
|
|
assertTest("UUID presente na empresa", !empty($res['empresas'][0]['uuid']));
|
|
assertTest("ID numérico NÃO exposto na empresa", !isset($res['empresas'][0]['id']));
|
|
assertTest("Perfil ADMIN identificado na empresa", $res['empresas'][0]['perfil'] === 'ADMIN');
|
|
assertTest("Flag de acesso a todas as filiais presente", isset($res['empresas'][0]['acesso_todas_filiais']));
|
|
} catch (Throwable $e) {
|
|
assertTest("Execução do login por e-mail", false, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 2. Login com nome de usuário válido
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 2: Login com nome de usuário válido\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'admin', 'senha' => 'Admin@123456']);
|
|
$res = $service->login($dto);
|
|
assertTest("Login por username autenticado com sucesso", !empty($res['token']));
|
|
assertTest("ID do usuário consistente", $res['usuario']['nome_usuario'] === 'admin');
|
|
} catch (Throwable $e) {
|
|
assertTest("Execução do login por username", false, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 3. Login com senha inválida
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 3: Login com senha inválida\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'admin', 'senha' => 'SenhaIncorreta@999']);
|
|
$service->login($dto);
|
|
assertTest("Bloqueio de senha incorreta", false, "Deveria ter lançado RuntimeException");
|
|
} catch (RuntimeException $e) {
|
|
assertTest("Rejeitado com código 401", $e->getCode() === 401);
|
|
assertTest("Mensagem genérica de credenciais inválidas", $e->getMessage() === 'Credenciais inválidas.');
|
|
} catch (Throwable $e) {
|
|
assertTest("Bloqueio de senha incorreta", false, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 4. Login com usuário inexistente (prevenção contra enumeração)
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 4: Tentativa com usuário inexistente\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'usuario_fantasma_xyz@workbloom.com.br', 'senha' => 'Admin@123456']);
|
|
$service->login($dto);
|
|
assertTest("Bloqueio de usuário inexistente", false, "Deveria ter lançado RuntimeException");
|
|
} catch (RuntimeException $e) {
|
|
assertTest("Rejeitado com código 401", $e->getCode() === 401);
|
|
assertTest("Mesma mensagem genérica do erro de senha (anti-enumeração)", $e->getMessage() === 'Credenciais inválidas.');
|
|
} catch (Throwable $e) {
|
|
assertTest("Bloqueio de usuário inexistente", false, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Criação temporária de usuários de teste para cenários avançados
|
|
// ---------------------------------------------------------------------------
|
|
$testUserUuidInativo = Uuid::uuid7()->toString();
|
|
$testUserUuidDeletado = Uuid::uuid7()->toString();
|
|
$testUserUuidSemEmpresa = Uuid::uuid7()->toString();
|
|
$testUserUuidMultiEmpresa = Uuid::uuid7()->toString();
|
|
|
|
$senhaHashPadrao = password_hash('Teste@123456', PASSWORD_ARGON2ID);
|
|
|
|
// 5. Usuário inativo
|
|
$db->insert('usuario', [
|
|
'uuid' => $testUserUuidInativo,
|
|
'nome_completo' => 'Usuário Inativo Teste',
|
|
'email' => 'inativo@workbloom.com.br',
|
|
'nome_usuario' => 'user_inativo',
|
|
'senha_hash' => $senhaHashPadrao,
|
|
'ativo' => 0,
|
|
]);
|
|
$idInativo = (int)$db->fetchColumn("SELECT id FROM usuario WHERE uuid = :uuid LIMIT 1", ['uuid' => $testUserUuidInativo]);
|
|
|
|
// 6. Usuário logicamente excluído (deleted_at)
|
|
$db->insert('usuario', [
|
|
'uuid' => $testUserUuidDeletado,
|
|
'nome_completo' => 'Usuário Deletado Teste',
|
|
'email' => 'deletado@workbloom.com.br',
|
|
'nome_usuario' => 'user_deletado',
|
|
'senha_hash' => $senhaHashPadrao,
|
|
'ativo' => 1,
|
|
]);
|
|
$idDeletado = (int)$db->fetchColumn("SELECT id FROM usuario WHERE uuid = :uuid LIMIT 1", ['uuid' => $testUserUuidDeletado]);
|
|
$db->execute("UPDATE usuario SET deleted_at = CURRENT_TIMESTAMP WHERE id = :id", ['id' => $idDeletado]);
|
|
|
|
// 7. Usuário sem vínculo com empresa
|
|
$db->insert('usuario', [
|
|
'uuid' => $testUserUuidSemEmpresa,
|
|
'nome_completo' => 'Usuário Sem Empresa',
|
|
'email' => 'semempresa@workbloom.com.br',
|
|
'nome_usuario' => 'user_semempresa',
|
|
'senha_hash' => $senhaHashPadrao,
|
|
'ativo' => 1,
|
|
]);
|
|
$idSemEmpresa = (int)$db->fetchColumn("SELECT id FROM usuario WHERE uuid = :uuid LIMIT 1", ['uuid' => $testUserUuidSemEmpresa]);
|
|
|
|
// 8. Usuário multiempresa com perfis distintos
|
|
$db->insert('usuario', [
|
|
'uuid' => $testUserUuidMultiEmpresa,
|
|
'nome_completo' => 'Usuário Multiempresa',
|
|
'email' => 'multiempresa@workbloom.com.br',
|
|
'nome_usuario' => 'user_multiempresa',
|
|
'senha_hash' => $senhaHashPadrao,
|
|
'ativo' => 1,
|
|
]);
|
|
$idMultiEmpresa = (int)$db->fetchColumn("SELECT id FROM usuario WHERE uuid = :uuid LIMIT 1", ['uuid' => $testUserUuidMultiEmpresa]);
|
|
|
|
// Cria 2 empresas adicionais de teste
|
|
$empresaUuid1 = Uuid::uuid7()->toString();
|
|
$empresaUuid2 = Uuid::uuid7()->toString();
|
|
$db->insert('empresa', [
|
|
'uuid' => $empresaUuid1,
|
|
'tipo_estabelecimento' => 'MATRIZ',
|
|
'status' => 'ATIVO',
|
|
'nome_empresarial' => 'EMPRESA ALFA LTDA',
|
|
'nome_fantasia' => 'Alfa Matriz',
|
|
'cnpj' => '11111111000101',
|
|
'regime_tributario' => 1,
|
|
]);
|
|
$idEmpresaAlfa = (int)$db->fetchColumn("SELECT id FROM empresa WHERE uuid = :uuid LIMIT 1", ['uuid' => $empresaUuid1]);
|
|
|
|
$db->insert('empresa', [
|
|
'uuid' => $empresaUuid2,
|
|
'tipo_estabelecimento' => 'FILIAL',
|
|
'status' => 'ATIVO',
|
|
'nome_empresarial' => 'EMPRESA BETA FILIAL LTDA',
|
|
'nome_fantasia' => 'Beta Filial',
|
|
'cnpj' => '11111111000202',
|
|
'regime_tributario' => 1,
|
|
]);
|
|
$idEmpresaBeta = (int)$db->fetchColumn("SELECT id FROM empresa WHERE uuid = :uuid LIMIT 1", ['uuid' => $empresaUuid2]);
|
|
|
|
// Vincula usuário multiempresa às 2 empresas com perfis diferentes
|
|
$db->insert('usuario_empresa', [
|
|
'usuario_id' => $idMultiEmpresa,
|
|
'empresa_id' => $idEmpresaAlfa,
|
|
'perfil' => 'ADMIN',
|
|
'acesso_todas_filiais' => 1,
|
|
]);
|
|
$db->insert('usuario_empresa', [
|
|
'usuario_id' => $idMultiEmpresa,
|
|
'empresa_id' => $idEmpresaBeta,
|
|
'perfil' => 'EMISSOR',
|
|
'acesso_todas_filiais' => 0,
|
|
]);
|
|
|
|
try {
|
|
// ---------------------------------------------------------------------------
|
|
// Cenário 5: Tentativa com usuário inativo
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 5: Tentativa com usuário inativo (ativo = false)\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'inativo@workbloom.com.br', 'senha' => 'Teste@123456']);
|
|
$service->login($dto);
|
|
assertTest("Bloqueio de usuário inativo", false, "Deveria ter lançado RuntimeException 403");
|
|
} catch (RuntimeException $e) {
|
|
assertTest("Rejeitado com código 403", $e->getCode() === 403);
|
|
assertTest("Mensagem indica usuário inativo", str_contains($e->getMessage(), 'inativo'));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cenário 6: Tentativa com usuário com deleted_at preenchido
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 6: Tentativa com usuário excluído (deleted_at preenchido)\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'deletado@workbloom.com.br', 'senha' => 'Teste@123456']);
|
|
$service->login($dto);
|
|
assertTest("Bloqueio de usuário excluído", false, "Deveria ter lançado RuntimeException 401");
|
|
} catch (RuntimeException $e) {
|
|
assertTest("Rejeitado como não encontrado (401)", $e->getCode() === 401);
|
|
assertTest("Mensagem genérica de credenciais inválidas", $e->getMessage() === 'Credenciais inválidas.');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cenário 7: Usuário sem vínculo com empresa
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 7: Usuário sem vínculo com empresa\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'semempresa@workbloom.com.br', 'senha' => 'Teste@123456']);
|
|
$res = $service->login($dto);
|
|
assertTest("Login efetuado mesmo sem empresa vinculada", !empty($res['token']));
|
|
assertTest("Lista de empresas vazia", is_array($res['empresas']) && count($res['empresas']) === 0);
|
|
} catch (Throwable $e) {
|
|
assertTest("Login sem empresa", false, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cenário 8: Usuário multiempresa com múltiplos perfis
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 8: Usuário com múltiplas empresas e perfis distintos\n";
|
|
try {
|
|
$dto = LoginDTO::fromArray(['login' => 'multiempresa@workbloom.com.br', 'senha' => 'Teste@123456']);
|
|
$res = $service->login($dto);
|
|
assertTest("Autenticação com sucesso", !empty($res['token']));
|
|
assertTest("Exatamente 2 empresas vinculadas", count($res['empresas']) === 2);
|
|
|
|
$perfilMap = [];
|
|
$filiaisMap = [];
|
|
foreach ($res['empresas'] as $emp) {
|
|
$perfilMap[$emp['nome_fantasia']] = $emp['perfil'];
|
|
$filiaisMap[$emp['nome_fantasia']] = (bool)$emp['acesso_todas_filiais'];
|
|
}
|
|
|
|
assertTest("Perfil ADMIN na Empresa Alfa", ($perfilMap['Alfa Matriz'] ?? null) === 'ADMIN');
|
|
assertTest("Flag todas as filiais = true na Empresa Alfa", ($filiaisMap['Alfa Matriz'] ?? null) === true);
|
|
assertTest("Perfil EMISSOR na Empresa Beta", ($perfilMap['Beta Filial'] ?? null) === 'EMISSOR');
|
|
assertTest("Flag todas as filiais = false na Empresa Beta", ($filiaisMap['Beta Filial'] ?? null) === false);
|
|
} catch (Throwable $e) {
|
|
assertTest("Multiempresa", false, $e->getMessage());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cenário 9: Validação do Token JWT e Middleware
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 9: Validação do Token JWT e Middleware de Proteção\n";
|
|
// Token forjado / adulterado
|
|
try {
|
|
$jwt->validateToken('token.invalido.falso');
|
|
assertTest("Rejeição de token adulterado", false, "Deveria ter lançado RuntimeException");
|
|
} catch (RuntimeException $e) {
|
|
assertTest("Token forjado rejeitado com código 401", $e->getCode() === 401);
|
|
}
|
|
|
|
// Token expirado
|
|
try {
|
|
$expiredJwt = new JwtService(expiresIn: -10);
|
|
$expiredData = $expiredJwt->generateToken(['id' => 1, 'sub' => 'uuid-teste']);
|
|
$jwt->validateToken($expiredData['token']);
|
|
assertTest("Rejeição de token expirado", false, "Deveria ter lançado RuntimeException");
|
|
} catch (RuntimeException $e) {
|
|
assertTest("Token expirado rejeitado com código 401", $e->getCode() === 401);
|
|
}
|
|
|
|
// Middleware sem header
|
|
unset($_SERVER['HTTP_AUTHORIZATION']);
|
|
$middleware = new AuthMiddleware($jwt);
|
|
$midRes = $middleware->handle();
|
|
assertTest("Middleware bloqueia requisição sem token", is_array($midRes) && ($midRes['block'] ?? false) === true);
|
|
assertTest("Código retornado pelo middleware é 401", ($midRes['response_code'] ?? null) === 401);
|
|
|
|
// Middleware com token válido
|
|
$tokenValido = $res['token'];
|
|
$_SERVER['HTTP_AUTHORIZATION'] = "Bearer {$tokenValido}";
|
|
AuthContext::clear();
|
|
$midResValido = $middleware->handle();
|
|
assertTest("Middleware aceita requisição com Bearer Token válido", $midResValido === true);
|
|
assertTest("AuthContext populado com o UUID do usuário", AuthContext::getUserUuid() === $testUserUuidMultiEmpresa);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cenário 10: Consulta ao Perfil (endpoint me / getProfile)
|
|
// ---------------------------------------------------------------------------
|
|
echo "\nCenário 10: Consulta ao Perfil do usuário autenticado (GET /api/v1/auth/me)\n";
|
|
try {
|
|
$profile = $service->getProfile(AuthContext::getUserUuid());
|
|
assertTest("Perfil recuperado com sucesso via UUID", !empty($profile['usuario']['uuid']));
|
|
assertTest("ID numérico NÃO exposto no perfil do usuário", !isset($profile['usuario']['id']));
|
|
assertTest("Nome completo presente", $profile['usuario']['nome_completo'] === 'Usuário Multiempresa');
|
|
assertTest("Senha hash NÃO exposta no perfil", !isset($profile['usuario']['senha_hash']));
|
|
assertTest("Lista de empresas presente no perfil", count($profile['empresas']) === 2);
|
|
assertTest("ID numérico NÃO exposto nas empresas do perfil", !isset($profile['empresas'][0]['id']));
|
|
assertTest("UUID presente nas empresas do perfil", !empty($profile['empresas'][0]['uuid']));
|
|
} catch (Throwable $e) {
|
|
assertTest("Consulta de perfil", false, $e->getMessage());
|
|
}
|
|
|
|
} finally {
|
|
// Limpeza dos dados temporários criados nos testes
|
|
$db->execute("DELETE FROM usuario_empresa WHERE usuario_id IN (:u1, :u2, :u3, :u4)", [
|
|
'u1' => $idInativo,
|
|
'u2' => $idDeletado,
|
|
'u3' => $idSemEmpresa,
|
|
'u4' => $idMultiEmpresa,
|
|
]);
|
|
$db->execute("DELETE FROM usuario WHERE id IN (:u1, :u2, :u3, :u4)", [
|
|
'u1' => $idInativo,
|
|
'u2' => $idDeletado,
|
|
'u3' => $idSemEmpresa,
|
|
'u4' => $idMultiEmpresa,
|
|
]);
|
|
$db->execute("DELETE FROM empresa WHERE id IN (:e1, :e2)", [
|
|
'e1' => $idEmpresaAlfa,
|
|
'e2' => $idEmpresaBeta,
|
|
]);
|
|
unset($_SERVER['HTTP_AUTHORIZATION']);
|
|
AuthContext::clear();
|
|
}
|
|
|
|
echo "\n=======================================================\n";
|
|
echo " RESULTADO DOS TESTES: {$passed} passaram, {$failed} falharam\n";
|
|
echo "=======================================================\n\n";
|
|
|
|
if ($failed > 0) {
|
|
exit(1);
|
|
}
|