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); }