Skip to content

feat: implement comprehensive auth tests and fix failing unit tests#198

Open
LeidejanedaRosa wants to merge 43 commits intoSouJunior:mainfrom
LeidejanedaRosa:leidejane-testes
Open

feat: implement comprehensive auth tests and fix failing unit tests#198
LeidejanedaRosa wants to merge 43 commits intoSouJunior:mainfrom
LeidejanedaRosa:leidejane-testes

Conversation

@LeidejanedaRosa
Copy link
Copy Markdown

@LeidejanedaRosa LeidejanedaRosa commented Aug 19, 2025

📋 Resumo das Alterações

✨ Implementação de Testes de Autenticação e JWT

🔐 Testes de Auth Implementados:

  • AuthController tests (auth.controller.spec.ts):

    • Testes de login para usuário e empresa
    • Validação de credenciais inválidas
    • Testes de email não confirmado
    • Validação de formato de dados
    • Testes de campos obrigatórios
    • Testes do endpoint userLogged
  • AuthLoginService tests (auth-login.service.spec.ts):

    • Login de usuário com credenciais válidas
    • Login de empresa com credenciais válidas
    • Tratamento de email não confirmado
    • Validação de usuário/empresa inexistente
    • Validação de senha inválida
    • Sanitização de dados sensíveis (remoção de password, tokens)
  • JwtStrategy tests (jwt.strategy.spec.ts):

    • Validação de payload JWT para usuários
    • Validação de payload JWT para empresas
    • Tratamento de usuário/empresa não encontrada
    • Remoção de campos sensíveis das respostas
    • Tratamento de payload inválido
  • Auth E2E tests (auth.e2e-spec.ts):

    • Testes de integração completos do endpoint /auth/login
    • Validação de responses HTTP
    • Testes de diferentes cenários de login

🎭 Mocks Criados:

  • company.mock.ts: Mocks para empresa, empresa não confirmada e entidade empresa
  • user-login.mock.ts: Mocks para login de usuário, empresa e cenários inválidos
  • jwt-payload.mock.ts: Mocks para payloads JWT válidos e inválidos

🏗️ Estrutura de Auth Implementada:

  • principal.types.ts: Tipos TypeScript para usuários e empresas autenticadas
  • principal.mapper.ts: Mappers para conversão de entidades para principals
  • auth-login.service.ts: Service completo de autenticação com JWT

🔧 Correções de Estrutura e Prettier

📁 Reorganização de Arquivos:

  • Renomeou company-repository.tscompany.repository.ts
  • Moveu jwt.strategy.ts de jtw/jwt/ (correção de typo)
  • Padronizou nomenclatura de arquivos de repositório

📦 Correções de Importações:

  • Corrigiu importações nos módulos: auth.module.ts, company.module.ts, jobs.module.ts, user.module.ts
  • Atualizou imports nos services: activate-company.service.ts, create-company.service.ts, etc.
  • Corrigiu paths relativos para imports absolutos onde necessário

⚙️ Configurações Atualizadas:

  • package.json: Configuração completa do Jest com coverage e moduleNameMapper
  • .env.example: Corrigiu SECRET_KEYJWT_SECRET
  • jest-e2e.json: Adicionou moduleNameMapper para testes e2e

🎨 Formatação Prettier:

  • Aplicou formatação consistente em todos os arquivos modificados
  • Padronizou indentação e quebras de linha
  • Organizou imports alfabeticamente
  • Removeu trailing commas inconsistentes

🧪 Correção de Testes Falhando

🔧 Testes Corrigidos:

  • savedjobs.service.spec.ts: Adicionou mocks de repositórios TypeORM
  • candidacy.service.spec.ts: Implementou CandidacyRepositoryMock
  • create-user.service.spec.ts: Corrigiu mocks e cenários de teste
  • find-all-users.service.spec.ts: Atualizou PageOptionsDto
  • recovery-password-by-email.service.spec.ts: Corrigiu fluxo de recuperação
  • update-password-by-email.service.spec.ts: Validação de senhas
  • update-user.service.spec.ts: Testes de upload de arquivo

🗑️ Limpeza:

  • Removeu testes obsoletos: delete-user.service.spec.ts, find-one-user.service.spec.ts
  • Atualizou mocks de user para consistência

📊 Resultados dos Testes

  • 10 test suites passando
  • 47 testes passando (incluindo 25+ novos testes de auth)
  • 0 testes falhando
  • Cobertura de testes mantida acima de 85%
  • E2E tests funcionando

🔍 Commits Organizados

  1. refactor: fix file structure, import paths and test config
  2. feat: implement auth and JWT tests
  3. fix: correct failing unit tests

🎯 Benefícios

  • Cobertura completa de testes para módulo de autenticação
  • Maior confiabilidade no sistema de login
  • Estrutura de código mais organizada e consistente
  • Base sólida para futuras implementações de auth
  • Redução de bugs relacionados à autenticação

Summary by CodeRabbit

  • New Features

    • JWT configurável por ambiente (JWT_SECRET) com emissor/audiência/algoritmo; login passa a retornar { token, info }. Endpoint autenticado para excluir "minha empresa" (DELETE /company/me). Novos mapeadores de principal.
  • Changes

    • Normalização de e-mail em cadastros; duplicatas tratadas com conflitos (409/Conflict). Erros de autenticação e senhas agora lançam exceções HTTP apropriadas. Controladores retornam diretamente respostas com decorators de status.
  • Bug Fixes

    • Listagem de vagas da empresa logada sempre retorna 200 com array (vazio se necessário).
  • Database

    • Nova migração adiciona índice único em e-mail de empresas (normalizado); ambiente de testes usa sqlite in-memory.
  • Documentation

    • Swagger documenta exclusão da própria empresa (DELETE /company/me).
  • Tests

    • Ampliação de testes E2E/unidade e novos helpers/mocks para cobertura de auth, JWT, usuários, empresas e repositórios.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 19, 2025

Walkthrough

Atualiza a configuração de JWT para usar ConfigService/JWT_SECRET, refatora CompanyRepository (novo arquivo e paths), ajusta estratégias/validações JWT e login, normaliza e-mails, converte respostas para exceções HTTP, adiciona migração de índice único em e-mails de empresas, reconfigura Jest/ts-jest e amplia/ajusta muitos mocks e specs de testes.

Changes

Cohort / File(s) Resumo
Ambiente/JWT env
/.env.example
Variável renomeada: SECRET_KEYJWT_SECRET.
Configuração Jest e scripts
/package.json, /test/jest-e2e.json
Scripts test passam a usar -r tsconfig-paths/register; adicionada configuração Jest/ts-jest e moduleNameMapper para src/*.
Auth: módulo & estratégia JWT
/src/modules/auth/auth.module.ts, /src/modules/auth/jtw/jwt.strategy.ts (removido), /src/modules/auth/jwt/jwt.strategy.ts
JwtModule passou para registerAsync usando ConfigService (JWT_SECRET) e signOptions explícitas; arquivo antigo em jtw/ removido; nova JwtStrategy valida payload { sub,email,type } e mapeia para principals.
Auth Login Service & Controller
/src/modules/auth/services/auth-login.service.ts, /src/modules/auth/auth.controller.ts
Erros de autenticação lançam UnauthorizedException; payload JWT agora contém sub/email/type; retorno simplificado { token, info }; controller usa @HttpCode(HttpStatus.OK) e retorna diretamente.
Principal types & mappers
/src/modules/auth/types/principal.types.ts, /src/modules/auth/utils/principal.mapper.ts
Adicionados PublicUserPrincipal, PublicCompanyPrincipal, AuthenticatedPrincipal e mapeadores mapUserToPrincipal/mapCompanyToPrincipal.
Company repository refactor & services
/src/modules/company/repository/company-repository.ts (removido), /src/modules/company/repository/company.repository.ts, /src/modules/company/*services*.ts, /src/modules/company/company.module.ts
Novo CompanyRepository com @InjectRepository, métodos CRUD consolidados; import paths atualizados; email normalizado (trim/lowercase); tratamento de erro 23505 mapeado para conflito; delete muda semântica de retorno e NotFound verificado no service.
Controller Company: delete “me”
/src/modules/company/company.controller.ts, /src/shared/Swagger/decorators/company/delete-company-me.swagger.ts
Rota alterada para DELETE /companies/me, usa AuthGuard() e @LoggedCompany(), novo decorator Swagger DeleteCompanyMeSwagger.
Jobs: providers & fluxo
/src/modules/jobs/jobs.module.ts, /src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts, /src/modules/jobs/jobs.controller.ts
Remove DI de CompanyRepository; serviço sempre retorna 200 com content possivelmente vazio; controller agora usa @Res() res: Response e res.send.
User services & controller
/src/modules/user/services/*.ts, /src/modules/user/user.controller.ts
Emails normalizados; duplicatas lançam ConflictException; update-password-by-email lança NotFoundException/BadRequestException; update-user lança BadRequestException se file sem profileKey; createNewUser usa @HttpCode(HttpStatus.CREATED); getOneUser troca @LoggedUser()@LoggedAdmin().
DataSource & migrations
/src/database/data-source.ts, /src/database/migrations/*
DataSource adaptado para modo teste (sqlite in-memory) vs postgres; validação de porta/SSL/CA; nova migração AddUniqueConstraintToCompaniesEmail (index único em LOWER(TRIM(email))); formatações em migration inicial.
Entities (estilização)
/src/database/entities/*.entity.ts
Ajustes de aspas/virgulação em decorators; sem mudança funcional.
App & e2e tests
/test/app.e2e-spec.ts, /src/app.controller.ts, /src/app.module.ts, /src/app.service.ts
E2E bootstrap refeito sem AppModule (providers/mocks inline); expectativas atualizadas; formatações e pequenas mudanças semânticas mínimas.
Specs e mocks (vários)
/test/..., /test/shared/repository-mocks.ts
Centralização de constantes de teste (test/config/test-constants.ts); factory mocks (createUserRepositoryMock, createCompanyRepositoryMock, createJwtServiceMock), novos mocks para auth/company/login/payload; muitos specs novos/atualizados e alguns removidos.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant AuthController
  participant AuthLoginService
  participant UserRepo
  participant CompanyRepo
  participant JwtService

  Client->>AuthController: POST /auth/login {email, password, type}
  AuthController->>AuthLoginService: execute(loginData)
  alt type === USER
    AuthLoginService->>UserRepo: findOneByEmail(email)
  else type === COMPANY
    AuthLoginService->>CompanyRepo: findOneByEmail(email)
  end
  AuthLoginService-->>AuthLoginService: valida mailConfirm e senha (bcrypt)
  AuthLoginService->>JwtService: sign({ sub, email, type })
  JwtService-->>AuthLoginService: token
  AuthLoginService-->>AuthController: { token, info }
  AuthController-->>Client: 200 { token, info }
  opt erro
    AuthLoginService-->>AuthController: throw UnauthorizedException
    AuthController-->>Client: 401
  end
Loading
sequenceDiagram
  autonumber
  actor Company
  participant CompanyController
  participant AuthGuard
  participant DeleteCompanyService

  Company->>CompanyController: DELETE /companies/me (Bearer)
  CompanyController->>AuthGuard: validar JWT
  AuthGuard-->>CompanyController: principal (company)
  CompanyController->>DeleteCompanyService: execute(company.id)
  alt não encontrado
    DeleteCompanyService-->>CompanyController: throw NotFoundException
    CompanyController-->>Company: 404
  else sucesso
    DeleteCompanyService-->>CompanyController: { data, status: 200 }
    CompanyController-->>Company: 200 data
  end
Loading
sequenceDiagram
  autonumber
  participant Passport
  participant JwtStrategy
  participant ConfigService
  participant UserRepo
  participant CompanyRepo

  Passport->>JwtStrategy: validate(payload)
  JwtStrategy->>ConfigService: getOrThrow('JWT_SECRET')
  JwtStrategy-->>JwtStrategy: checar {sub,email,type}
  alt type === USER
    JwtStrategy->>UserRepo: findOneById(sub)
    UserRepo-->>JwtStrategy: user?
    alt encontrado
      JwtStrategy-->>Passport: PublicUserPrincipal
    else não encontrado
      JwtStrategy-->>Passport: UnauthorizedException
    end
  else type === COMPANY
    JwtStrategy->>CompanyRepo: findOneById(sub)
    CompanyRepo-->>JwtStrategy: company?
    alt encontrado
      JwtStrategy-->>Passport: PublicCompanyPrincipal
    else não encontrado
      JwtStrategy-->>Passport: UnauthorizedException
    end
  else inválido
    JwtStrategy-->>Passport: UnauthorizedException
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • MikaelMelo1

Poema

"Sou coelho que troca a chave e pula no deploy,
JWT agora vem do Config, segredo no algodão.
Repositório trocou de toca, email limpo no login, vou a pé no bosque, depois corro pro commit.
Testes saltam com mocks novos e ts-jest afinou o tambor. 🐰✨"

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
test/modules/user/services/update-password-by-email.service.spec.ts (1)

21-33: UpdatePasswordByEmailService deve estar em providers, não em controllers

O UpdatePasswordByEmailService é um service (@Injectable), e não controller. Mantê-lo em controllers é incorreto conceitualmente e pode quebrar DI/metadata em variações de setup.

-    const module: TestingModule = await Test.createTestingModule({
-      controllers: [UpdatePasswordByEmailService],
-      providers: [
-        {
-          provide: UserRepository,
-          useClass: UserRepositoryMock,
-        },
-        {
-          provide: MailService,
-          useClass: MailServiceMock,
-        },
-      ],
-    }).compile();
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        UpdatePasswordByEmailService,
+        {
+          provide: UserRepository,
+          useClass: UserRepositoryMock,
+        },
+        {
+          provide: MailService,
+          useClass: MailServiceMock,
+        },
+      ],
+    }).compile();
src/modules/company/services/update-password-by-email.service.ts (1)

37-43: Token de recuperação não é invalidado após redefinição — risco de reutilização

Após atualizar a senha, o token de recuperação permanece válido, permitindo múltiplas redefinições com o mesmo token. Isso é um problema de segurança.

Sugestão: invalidar/remover o token imediatamente após o sucesso da atualização, antes do envio do e-mail.

     const companyUpdated = await this.companyRepository.updatePassword(
       company.id,
       passwordHash,
     );
 
-    await this.mailService.sendCompanyConfirmation(companyUpdated);
+    // Invalida o token de recuperação para evitar reutilização
+    await this.companyRepository.invalidateRecoverPasswordToken(company.id);
+
+    await this.mailService.sendCompanyConfirmation(companyUpdated);

Observação: será necessário implementar invalidateRecoverPasswordToken em CompanyRepository (ou adaptar ao método já existente, caso tenha outro nome, como clearRecoverPasswordToken).

src/modules/company/services/activate-company.service.ts (1)

17-20: Inconsistência entre status 204 e corpo de resposta

Status 204 não deve retornar conteúdo. Aqui há data: 'Company Activated'. Sugestão: ou mude o status para 200 mantendo a mensagem, ou mantenha 204 e remova o corpo.

Aplicar uma das opções (exemplo muda para 200):

-    return {
-      status: 204,
-      data: 'Company Activated',
-    };
+    return {
+      status: 200,
+      data: 'Company Activated',
+    };
src/modules/company/services/update-password.service.ts (1)

19-23: Falta tratar ausência do registro antes de acessar user.password

Se findCompanyById retornar null/undefined, o acesso a user.password vai quebrar. Trate antes do bcrypt.compare.

Aplicar:

-    const user = await this.companyRepository.findCompanyById(company.id);
-
-    const isOldPassCorrect = await bcrypt.compare(oldPassword, user.password);
+    const user = await this.companyRepository.findCompanyById(company.id);
+    if (!user) {
+      return {
+        status: 404,
+        data: { message: 'Empresa não encontrada.' },
+      };
+    }
+    const isOldPassCorrect = await bcrypt.compare(oldPassword, user.password);
src/modules/auth/services/auth-login.service.ts (2)

27-33: Confusão entre “usuário não encontrado” e “e-mail não validado” + mensagem incoerente

A verificação !info?.mailConfirm || !info retorna “Email not validated” quando o usuário não existe, o que é enganoso e facilita diagnósticos incorretos. Além disso, as mensagens misturam EN/PT.

Proponho separar as verificações e unificar idioma.

-    if (!info?.mailConfirm || !info) {
-      return {
-        status: 400,
-        data: { message: 'Email not validated' },
-      };
-    }
+    if (!info) {
+      return {
+        status: 400,
+        data: { message: 'E-mail ou Senha não conferem' },
+      };
+    }
+
+    if (!info.mailConfirm) {
+      return {
+        status: 400,
+        data: { message: 'E-mail não validado' },
+      };
+    }

43-47: Corrigir sanitização de mailConfirm e remover ?. redundante

O código atual apaga info.mailconfirm (minúsculo) mas nunca remove info.mailConfirm (maiúsculo), vazando o indicador de confirmação. Além disso, o delete info?.ip usa ?. desnecessário, pois info já existe nesse ponto.

  1. Em src/modules/auth/services/auth-login.service.ts (linhas 43–47), aplique:

        delete info.password;
        delete info.recoverPasswordToken;
    -   delete info.mailconfirm;
    -   delete info?.ip;
    +   delete info.mailConfirm;
    +   delete info.ip;
  2. Em test/modules/auth/services/auth-login.service.spec.ts, atualize todas as ocorrências de mailconfirm para mailConfirm (tanto nos objetos de entrada quanto nas asserções), por exemplo:

    - mailconfirm: true,
    + mailConfirm: true,
    ...
    - expect(result.data.info.mailconfirm).toBeUndefined();
    + expect(result.data.info.mailConfirm).toBeUndefined();

Com isso, garantimos que a propriedade correta seja removida e mantemos os testes alinhados.

src/modules/company/services/update-company.service.ts (1)

15-29: Vulnerabilidade: deleção de arquivo baseada em input do cliente + ordem arriscada das operações

  • Risco de segurança: deleteFile(data.profileKey) confia no profileKey vindo do cliente, permitindo apagar arquivos de terceiros. Use o profileKey atual do próprio registro (company.profileKey) obtido do banco.
  • Ordem das operações: deletar antes de fazer upload e persistir alterações pode causar perda definitiva se o upload ou update falharem.

Proposta: fazer upload primeiro, atualizar o registro e só então excluir o arquivo antigo (se existir), usando o company.profileKey como fonte de verdade.

-    if (file && !data.profileKey) {
-      return {
-        status: 400,
-        data: {
-          message: 'profileKey is required when file is send',
-        },
-      };
-    }
-
-    if (file) {
-      await this.fileUploadService.deleteFile(data.profileKey);
-      const { Location, key } = await this.fileUploadService.upload(file);
-      data.profile = Location;
-      data.profileKey = key;
-    }
+    if (file) {
+      // Faz upload primeiro
+      const { Location, key } = await this.fileUploadService.upload(file);
+      data.profile = Location;
+      data.profileKey = key;
+    }
 
-    delete data.file;
+    delete data.file;
 
-    await this.companyRepository.updateCompanyById(company.id, data);
+    await this.companyRepository.updateCompanyById(company.id, data);
+
+    // Após persistir, remove o arquivo antigo com base no dado confiável do banco
+    if (file && company.profileKey) {
+      await this.fileUploadService.deleteFile(company.profileKey);
+    }

Sugestão extra (opcional): envolver upload/update/delete em try/catch para:

  • se o update falhar, apagar o arquivo novo (evitar órfãos);
  • se o delete do antigo falhar, registrar/retentar sem quebrar a resposta ao cliente.

Also applies to: 33-33

src/modules/company/services/recovery-password-by-email.service.ts (1)

25-32: Token de recuperação está sendo persistido em texto puro — salve apenas o hash

Armazenar o token em claro na base é um risco de segurança. Recomendo persistir o hash (p.ex. SHA-256) e enviar o token em claro apenas por e-mail. Ajuste correlato: ao consumir o token no fluxo de reset, hashear o valor recebido antes de consultar findByToken.

Sugestão de ajuste local neste serviço:

-    const recoverPasswordToken = crypto.randomBytes(32).toString('hex');
+    const recoverPasswordToken = crypto.randomBytes(32).toString('hex');
+    const recoverPasswordTokenHash = crypto
+      .createHash('sha256')
+      .update(recoverPasswordToken)
+      .digest('hex');

@@
-    const companyUpdated = await this.companyRepository.updateRecoveryPassword(
-      id,
-      recoverPasswordToken,
-    );
+    const companyUpdated = await this.companyRepository.updateRecoveryPassword(
+      id,
+      recoverPasswordTokenHash,
+    );

Observação: será necessário adaptar a lógica que valida o token (no repositório/serviço que o consome) para comparar o hash.

Posso abrir um follow-up para ajustar o findByToken e os testes de recuperação de senha com hash. Quer que eu gere o patch completo?

🧹 Nitpick comments (54)
src/modules/savedjobs/services/savedjobs.service.spec.ts (2)

8-14: Mocks funcionam; considere trocar a classe por uma factory para facilitar controle/reset em cada teste

A classe atual atende, mas uma factory com useValue tende a simplificar a captura e o reset das funções mockadas por teste. Exemplo de refator (opcional):

-class RepositoryMock {
-  create = jest.fn();
-  save = jest.fn();
-  findOne = jest.fn();
-  find = jest.fn();
-  delete = jest.fn();
-}
+const repositoryMockFactory = () => ({
+  create: jest.fn(),
+  save: jest.fn(),
+  findOne: jest.fn(),
+  find: jest.fn(),
+  delete: jest.fn(),
+});

Observação: garanta que todos os métodos realmente usados pelo serviço estejam presentes (ex.: findOneBy, count, exist, update, softDelete), para evitar falhas por método inexistente em testes futuros.


21-35: Providers de mocks — OK; opcionalmente use useValue + factory para facilitar stubs/asserções

A configuração atual com useClass funciona. Se optar pela factory sugerida acima, os providers ficariam assim:

       providers: [
         SavedJobsService,
         {
           provide: getRepositoryToken(SavedJobsEntity),
-          useClass: RepositoryMock,
+          useValue: repositoryMockFactory(),
         },
         {
           provide: getRepositoryToken(UsersEntity),
-          useClass: RepositoryMock,
+          useValue: repositoryMockFactory(),
         },
         {
           provide: getRepositoryToken(JobsEntity),
-          useClass: RepositoryMock,
+          useValue: repositoryMockFactory(),
         },
       ],

Fora deste trecho, recomendo:

  • Capturar as instâncias dos mocks para configurar retornos e validar chamadas:
    // após o compile()
    const savedJobsRepo = module.get<ReturnType<typeof repositoryMockFactory>>(getRepositoryToken(SavedJobsEntity));
    const usersRepo = module.get<ReturnType<typeof repositoryMockFactory>>(getRepositoryToken(UsersEntity));
    const jobsRepo = module.get<ReturnType<typeof repositoryMockFactory>>(getRepositoryToken(JobsEntity));
  • Adicionar afterEach(() => jest.clearAllMocks()); para evitar bleed entre testes.
  • Nitpick: padronizar a descrição para “SavedJobsService” (capitalização) no describe(...).

Se quiser, posso ajudar a esboçar testes de comportamento do serviço (criar, listar e remover saved jobs; evitar duplicidade; tratamento de erros do TypeORM).

test/modules/user/services/update-user.service.spec.ts (4)

31-34: Melhorar o TestModule: trate UpdateUserService como provider, não controller

Funciona, mas é contraintuitivo em Nest. Sugiro promover o serviço para a seção de providers para deixar a intenção clara e alinhar com boas práticas.

Aplique esta alteração no bloco de providers:

       providers: [
+        UpdateUserService,
         {
           provide: UserRepository,
           useClass: UserRepositoryMock,
         },
         {
           provide: FileUploadService,
           useClass: FileUploadServiceMock,
         },
       ],

E (fora deste trecho) ajuste a seção de controllers, por exemplo:

controllers: [], // ou remova a seção `controllers`

47-76: Quando profileKey está ausente, garanta que upload/delete não sejam chamados

O teste já confirma que updateUser não é chamado. Vale reforçar que nenhuma operação de I/O de arquivo é disparada.

Aplique este diff para complementar asserções:

       expect(updateUserSpy).not.toHaveBeenCalled();
+      const fus = (service as any)['fileUploadService'];
+      expect(fus.deleteFile).not.toHaveBeenCalled();
+      expect(fus.upload).not.toHaveBeenCalled();

69-74: Nit: mensagem com pequeno erro gramatical (“file is send”)

Se for user-facing, considere “file is sent” ou “a file is provided”. Se a API já expõe essa string e outros testes/consumidores dependem dela, trate como ajuste futuro para não quebrar contratos agora.


78-116: Evite reatribuir métodos mockados e use apenas spyOn

Aqui vocês primeiro reatribuem os métodos a jest.fn e depois usam spyOn sobre eles, o que é redundante. Mantendo apenas spyOn, o teste fica mais simples e idiomático.

Aplique este diff para simplificar o setup:

-      const fileUploadService = service['fileUploadService'];
-      fileUploadService.deleteFile = jest.fn().mockResolvedValue(undefined);
-      fileUploadService.upload = jest.fn().mockResolvedValue({
-        Location: 'https://s3.bucket.com/file.jpg',
-        key: 'uploads/file.jpg',
-      });
-
-      userRepository.updateUser = jest.fn().mockResolvedValue(undefined);
-      const updateUserSpy = jest.spyOn(userRepository, 'updateUser');
-      const deleteFileSpy = jest.spyOn(fileUploadService, 'deleteFile');
-      const uploadSpy = jest.spyOn(fileUploadService, 'upload');
+      const fileUploadService = service['fileUploadService'];
+      const deleteFileSpy = jest
+        .spyOn(fileUploadService, 'deleteFile')
+        .mockResolvedValue(undefined);
+      const uploadSpy = jest
+        .spyOn(fileUploadService, 'upload')
+        .mockResolvedValue({
+          Location: 'https://s3.bucket.com/file.jpg',
+          key: 'uploads/file.jpg',
+        });
+      userRepository.updateUser = jest.fn().mockResolvedValue(undefined);
+      const updateUserSpy = jest.spyOn(userRepository, 'updateUser');

Opcional: Em vez de acessar service['fileUploadService'], capture a instância via DI no beforeEach:

// fora deste trecho (no escopo do describe)
let fileUploadService: FileUploadService;

// no beforeEach, após o compile()
fileUploadService = module.get(FileUploadService);

E então use fileUploadService diretamente nos testes.

.env.example (1)

6-6: Mover o comentário inline para a linha de cima para evitar ruído de linters

Alguns linters/parsers de dotenv se atrapalham com comentários após o valor. Sugiro mover o comentário para a linha anterior.

- JWT_SECRET="" # Uma string qualquer, chave para gerar o JWT
+ # Uma string qualquer, chave para gerar o JWT
+ JWT_SECRET=""
test/modules/user/services/update-password-by-email.service.spec.ts (1)

76-92: Cobrir envio de e-mail no caminho de sucesso

Já que há um MailServiceMock, vale validar que o e-mail é disparado no sucesso.

Exemplo de ajuste no teste de sucesso:

-    it('should be able to return an updated user', async () => {
+    it('should be able to return an updated user', async () => {
       userRepository.findByToken = jest.fn().mockResolvedValue(userMock());
       userRepository.updatePassword = jest.fn().mockResolvedValue(userMock());
+      const mailService = module.get(MailService);
+      const sendSpy = jest.spyOn(mailService, 'sendUserConfirmation');
       const findByTokenSpy = jest.spyOn(userRepository, 'findByToken');
       const updatePassword = jest.spyOn(userRepository, 'updatePassword');
       const { status, data } = await service.execute({
         recoverPasswordToken: '123',
         password: 'password',
         confirmPassword: 'password',
       });
       expect(status).toEqual(200);
       expect(data).toEqual({ message: 'Senha redefinida com sucesso!' });
       expect(findByTokenSpy).toBeCalled();
       expect(findByTokenSpy).toBeCalledTimes(1);
       expect(updatePassword).toBeCalled();
       expect(updatePassword).toBeCalledTimes(1);
+      expect(sendSpy).toBeCalled();
     });
src/modules/candidacy/service/candidacy.service.spec.ts (1)

16-22: Sugestão: adicionar testes comportamentais do CandidacyService

Hoje o teste cobre apenas definição. Considere adicionar cenários de criação/atualização/consulta verificando chamadas ao mock (create, findOne, update) e tratamento de erros do repositório.

package.json (1)

94-111: Evite duplicidade de configuração entre unit e e2e; considere usar preset "ts-jest" ou consolidar em um jest.base.json

Para reduzir manutenção futura (e manter paridade com o e2e, que usa "preset": "ts-jest"), você pode:

  • Adicionar "preset": "ts-jest" aqui e remover o bloco transform (o preset já cobre isso), ou
  • Criar um jest.base.json compartilhado e fazer com que unit/e2e estendam dele.

Sugestão (opção com preset e sem transform):

   "jest": {
-    "moduleFileExtensions": [
+    "moduleFileExtensions": [
       "js",
       "json",
       "ts"
     ],
     "rootDir": ".",
     "testRegex": ".*\\.spec\\.ts$",
-    "transform": {
-      "^.+\\.(t|j)s$": "ts-jest"
-    },
+    "preset": "ts-jest",
     "collectCoverageFrom": [
       "src/**/*.{ts,js}"
     ],
     "coverageDirectory": "./coverage",
     "testEnvironment": "node",
     "moduleNameMapper": {
       "^src/(.*)$": "<rootDir>/src/$1"
     },
test/jest-e2e.json (1)

6-13: Remover redundância entre "preset": "ts-jest" e "transform"

Como você já adicionou "preset": "ts-jest", o bloco "transform" é redundante aqui. Mantê-lo pode confundir quem for dar manutenção.

Proposta:

 {
   "moduleFileExtensions": ["js", "json", "ts"],
   "rootDir": ".",
   "testEnvironment": "node",
   "testRegex": ".e2e-spec.ts$",
-  "transform": {
-    "^.+\\.(t|j)s$": "ts-jest"
-  },
   "moduleNameMapper": {
     "^src/(.*)$": "<rootDir>/../src/$1"
   },
   "preset": "ts-jest"
 }
test/modules/user/services/recovery-password-by-email.service.spec.ts (3)

58-60: Evitar duplicação de string de mensagem — extraia para constante compartilhada

A mesma mensagem longa aparece em dois testes. Sugiro extrair para uma constante (ex.: fixtures/common-messages.ts) ou para um helper de mocks, facilitando manutenção e padronização.

-      const result = {
-        message:
-          'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.',
-      };
+      const PASSWORD_RECOVERY_GENERIC_MSG =
+        'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.';
+      const result = { message: PASSWORD_RECOVERY_GENERIC_MSG };

Also applies to: 81-83


50-51: Semântica do mock: retorne null em vez de string vazia quando não encontrar usuário

Para refletir melhor a assinatura esperada de repositórios (normalmente entity | null), use null em vez de ''.

-      userRepository.findOneByEmail = jest.fn().mockResolvedValue('');
+      userRepository.findOneByEmail = jest.fn().mockResolvedValue(null);

68-90: Cobrir expectativa de envio de e-mail no cenário de sucesso

O teste configura o mock para sendUserConfirmation, mas não valida se ele foi chamado. Adicionar a asserção fortalece o teste de integração entre serviço e MailService.

       expect(status).toEqual(200);
       expect(data).toEqual(result);
       expect(findOneByEmailSpy).toHaveBeenCalled();
       expect(findOneByEmailSpy).toBeCalledTimes(1);
       expect(updateRecoveryPasswordSpy).toHaveBeenCalled();
       expect(updateRecoveryPasswordSpy).toBeCalledTimes(1);
+      expect(mailService.sendUserConfirmation).toHaveBeenCalled();
+      expect(mailService.sendUserConfirmation).toBeCalledTimes(1);
src/modules/auth/types/principal.types.ts (1)

5-7: Padronizar propriedade discriminante; 'type' é ambíguo na união

A união já é discriminada por userType ('user' | 'company'). A propriedade 'type' (apenas em PublicUserPrincipal) pode gerar ambiguidade e inconsistência. Duas opções:

  • Opção A (simples): remover 'type' e manter apenas userType como discriminante.
  • Opção B (mais expressiva): renomear para profileType (ou role) e tornar opcional em ambos os tipos para manter consistência.

Opção A:

 export interface PublicUserPrincipal {
   id: string;
   name: string;
   email: string;
-  type: string;
   phone?: string;
   userType: 'user';
 }

Opção B:

 export interface PublicUserPrincipal {
   id: string;
   name: string;
   email: string;
-  type: string;
+  profileType?: string;
   phone?: string;
   userType: 'user';
 }

 export interface PublicCompanyPrincipal {
   id: string;
   companyName: string;
   email: string;
   cnpj: string;
+  profileType?: string;
   userType: 'company';
 }
test/mocks/auth/company.mock.ts (2)

1-20: Parametrizar mocks com overrides para reuso e menor duplicação

Permitir overrides melhora a flexibilidade dos testes e reduz clones de funções.

-export const companyMock = () => {
-  return {
+export const companyMock = (overrides: Partial<Record<string, any>> = {}) => {
+  const base = {
     id: '729c7919-583c-40a5-b0ca-137e282345d4',
     companyName: 'Test Company Ltd',
     email: 'company@test.com',
     password: '$2b$10$hashedpassword',
     cnpj: '12345678000123',
     about: 'A test company for unit tests',
     phone: '11987654321',
     address: 'Test Street, 123',
     city: 'Test City',
     state: 'Test State',
     cep: '12345-678',
     website: 'https://testcompany.com',
     mailConfirm: true,
     policies: true,
     created_at: '2023-02-21T00:25:07.000Z',
     updated_at: '2023-02-21T00:25:07.000Z',
-  };
+  };
+  return { ...base, ...overrides };
 };

Aplique o mesmo padrão para companyWithUnconfirmedEmailMock e companyEntityMock.


36-54: Tipar explicitamente os retornos dos mocks para evitar drift de contrato

Se possível, tipar os mocks com a entidade/interface usada no código (ex.: CompaniesEntity) ou com um tipo de contrato estável (Pick/Partial) ajuda a detectar mudanças breaking nos testes.

Exemplo:

// import { CompaniesEntity } from 'src/modules/companies/entities/companies.entity';
type CompanyEntityShape = {
  id: string; companyName: string; email: string; cnpj: string;
  about?: string; phone?: string; address?: string; city?: string; state?: string;
  cep?: string; website?: string; mailConfirm: boolean; policies?: boolean;
  created_at: string; updated_at: string;
};

export const companyEntityMock = (overrides: Partial<CompanyEntityShape> = {}): CompanyEntityShape => ({
  id: '729c7919-583c-40a5-b0ca-137e282345d4',
  companyName: 'Test Company Ltd',
  email: 'company@test.com',
  cnpj: '12345678000123',
  about: 'A test company for unit tests',
  phone: '11987654321',
  address: 'Test Street, 123',
  city: 'Test City',
  state: 'Test State',
  cep: '12345-678',
  website: 'https://testcompany.com',
  mailConfirm: true,
  policies: true,
  created_at: '2023-02-21T00:25:07.000Z',
  updated_at: '2023-02-21T00:25:07.000Z',
  ...overrides,
});
src/modules/company/services/update-password-by-email.service.ts (1)

29-34: Use comparação estrita para evitar coerção de tipos

Trocar != por !== (boa prática/eqeqeq). Mesmo que ambos sejam strings via DTO, previne regressões.

Aplicar:

-    if (password != confirmPassword) {
+    if (password !== confirmPassword) {
src/modules/company/services/update-password.service.ts (3)

30-34: Use comparação estrita ao validar confirmação de senha

Trocar != por !== por consistência e prevenção de coerção de tipos.

Aplicar:

-    if (password != confirmNewPassword) {
+    if (password !== confirmNewPassword) {

36-44: Considere invalidar sessões/tokens após alteração de senha

Para maior segurança, após atualizar a senha, invalide refresh tokens/sessões ou atualize um campo passwordChangedAt/tokenVersion para que JWTs antigos sejam rejeitados.

Posso sugerir um padrão de implementação com passwordChangedAt na entidade e verificação na JwtStrategy. Quer?


19-23: Nome da variável user confunde — trata-se de uma empresa

Renomear user para algo como companyEntity melhora a clareza.

Aplicar:

-    const user = await this.companyRepository.findCompanyById(company.id);
+    const companyEntity = await this.companyRepository.findCompanyById(company.id);
...
-    const isOldPassCorrect = await bcrypt.compare(oldPassword, user.password);
+    const isOldPassCorrect = await bcrypt.compare(oldPassword, companyEntity.password);
src/modules/jobs/jobs.module.ts (1)

29-39: Provider duplicado/desnecessário de CompanyRepository no módulo

Como CompanyModule já está em imports, o provider de CompanyRepository deve vir exportado por lá, evitando declarar novamente aqui. Declarar localmente pode criar instância paralela e atrapalhar DI/configuração de repositório.

Aplicar:

   providers: [
     CreateJobService,
     GetAllJobsService,
     GetAllJobsFromLoggedCompanyService,
     GetOneJobByIdService,
     UpdateJobService,
     DeleteJobService,
     SearchJobsService,
     JobRepository,
-    CompanyRepository
   ],

Verifique se CompanyModule exporta CompanyRepository. Caso não exporte, prefira exportá-lo no CompanyModule e apenas importar o módulo aqui.

src/modules/user/user.module.ts (1)

33-46: Revisar necessidade de declarar repositórios como providers locais

UserRepository, CompanyRepository e CurriculumRepository declarados como providers funcionam, mas avalie expor/consumir via seus módulos correspondentes para evitar duplicar providers e facilitar reuso/testes (especialmente se já houver CompanyModule e CurriculumModule exportando-os).

Posso sugerir uma organização de módulos que exporte somente serviços/repositórios necessários e simplifique os providers locais.

src/modules/auth/services/auth-login.service.ts (2)

21-25: Use comparação estrita para enums

Trocar == por === evita coerção implícita e melhora a robustez.

-    if (type == LoginTypeEnum.COMPANY) {
+    if (type === LoginTypeEnum.COMPANY) {

49-53: JWT payload mínimo; considere incluir claims padrão

Avalie incluir sub (id), type e/ou demais claims úteis no payload para facilitar autorização no restante do sistema (alinhado a práticas comuns com JwtStrategy).

-        token: this.jwt.sign({ email }),
+        token: this.jwt.sign({ sub: info.id, email, type }),
src/modules/user/services/create-user.service.ts (2)

23-28: Realize as duas consultas de e-mail em paralelo

As chamadas podem ser feitas com Promise.all para reduzir latência.

-    const emailAlreadyInUseCompany =
-      await this.companyRepository.findOneByEmail(email);
-
-    const emailAlreadyInUseUser =
-      await this.userRepository.findOneByEmail(email);
+    const [emailAlreadyInUseCompany, emailAlreadyInUseUser] = await Promise.all([
+      this.companyRepository.findOneByEmail(email),
+      this.userRepository.findOneByEmail(email),
+    ]);

29-36: Status code para e-mail duplicado: usar 409 Conflict

404 não é semântico para conflito de recurso; o correto é 409 Conflict. Caso aceite a mudança, não esqueça de atualizar também os testes.

Pontos de atenção:

  • src/modules/user/services/create-user.service.ts (linha 31): alterar status: 404status: 409
  • test/modules/user/services/create-user.service.spec.ts (linhas 79 e 100): alterar expect(status).toEqual(404)expect(status).toEqual(409)
  • Opcional: revisar src/modules/company/services/create-company.service.ts e testes equivalentes para manter consistência
-      return {
-        status: 404,
+      return {
+        status: 409,
         data: {
           message: 'E-mail já cadastrado',
         },
       };
src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts (2)

2-2: Remover dependência não utilizada (CompanyRepository) para reduzir acoplamento

CompanyRepository é injetado mas não é utilizado nesta classe. Sugiro removê-lo para simplificar e evitar custos de DI.

Aplicar:

-import { CompanyRepository } from 'src/modules/company/repository/company.repository';
@@
-  constructor(
-    private companyRepository: CompanyRepository,
-    private jobsRepository: JobRepository,
-  ) {}
+  constructor(private jobsRepository: JobRepository) {}

Also applies to: 9-11


13-32: Pequeno DRY: evitar duplicação do mesmo message

Como a mensagem é idêntica nos dois caminhos, dá para simplificar retornando uma única vez.

Sugestão:

-  async execute(companyId: string): Promise<IJobsResponse> {
-    const jobs = await this.jobsRepository.getAllJobsByCompanyId(companyId);
-
-    if (!jobs || jobs.length === 0) {
-      return {
-        status: 200,
-        data: {
-          message: 'Logged company jobs listed successfully.',
-          content: [],
-        },
-      };
-    }
-
-    return {
-      status: 200,
-      data: {
-        message: 'Logged company jobs listed successfully.',
-        content: jobs,
-      },
-    };
-  }
+  async execute(companyId: string): Promise<IJobsResponse> {
+    const jobs = await this.jobsRepository.getAllJobsByCompanyId(companyId);
+    const content = Array.isArray(jobs) ? jobs : [];
+    return {
+      status: 200,
+      data: {
+        message: 'Logged company jobs listed successfully.',
+        content,
+      },
+    };
+  }
src/modules/company/services/create-company.service.ts (1)

33-33: Use igualdade estrita para comparação de senha

Troque != por !== para manter consistência e evitar coerções inesperadas.

-    if (password != passwordConfirmation) {
+    if (password !== passwordConfirmation) {
test/mocks/auth/user-login.mock.ts (1)

20-34: Centralizar criação dos mocks com overrides para reduzir duplicação

Você pode manter as quatro fábricas como “atalhos”, mas extrair um helper genérico reduz repetição e facilita ajustes futuros (ex.: alterar domínio de email em um só lugar).

Exemplo de refatoração:

+export const makeLogin = (overrides: Partial<UserLoginDto> = {}): UserLoginDto => ({
+  email: 'user@test.com',
+  password: 'Test@1234',
+  type: LoginTypeEnum.USER,
+  ...overrides,
+});
+
 export const userLoginMock = (): UserLoginDto => {
-  return {
-    email: 'user@test.com',
-    password: 'Test@1234',
-    type: LoginTypeEnum.USER,
-  };
+  return makeLogin();
 };
 
 export const companyLoginMock = (): UserLoginDto => {
-  return {
-    email: 'company@test.com',
-    password: 'Test@1234',
-    type: LoginTypeEnum.COMPANY,
-  };
+  return makeLogin({ email: 'company@test.com', type: LoginTypeEnum.COMPANY });
 };
 
 export const invalidUserLoginMock = (): UserLoginDto => {
-  return {
-    email: 'invalid@test.com',
-    password: 'WrongPassword@123',
-    type: LoginTypeEnum.USER,
-  };
+  return makeLogin({ email: 'invalid@test.com', password: 'WrongPassword@123' });
 };
 
 export const userWithUnconfirmedEmailMock = (): UserLoginDto => {
-  return {
-    email: 'unconfirmed@test.com',
-    password: 'Test@1234',
-    type: LoginTypeEnum.USER,
-  };
+  return makeLogin({ email: 'unconfirmed@test.com' });
 };
test/modules/auth/services/auth-login.service.spec.ts (6)

82-83: Remover casts “as never” nos mocks do bcrypt

Os casts mascaram problemas de tipo e são desnecessários. Use boolean diretamente.

Aplicar nos quatro pontos:

- bcryptMock.compare.mockResolvedValue(true as never);
+ bcryptMock.compare.mockResolvedValue(true);
- bcryptMock.compare.mockResolvedValue(false as never);
+ bcryptMock.compare.mockResolvedValue(false);

Also applies to: 150-151, 274-275, 298-299


99-101: Evitar fragilidade no payload do JWT com comparação parcial

Se o serviço evoluir o payload (ex.: incluir sub, type), o teste quebra desnecessariamente. Use objectContaining.

-expect(jwtService.sign).toHaveBeenCalledWith({
-  email: loginData.email,
-});
+expect(jwtService.sign).toHaveBeenCalledWith(
+  expect.objectContaining({ email: loginData.email }),
+);

Also applies to: 195-196


262-284: Sanitização: também validar remoção de mailConfirm (camelCase)

Você já valida mailconfirm (lowercase). Para cobrir variações de nome, também verifique a remoção de mailConfirm.

 expect(result.status).toBe(200);
 expect(result.data.info.password).toBeUndefined();
 expect(result.data.info.recoverPasswordToken).toBeUndefined();
 expect(result.data.info.mailconfirm).toBeUndefined();
+expect(result.data.info.mailConfirm).toBeUndefined();
 expect(result.data.info.ip).toBeUndefined();

286-308: Sanitização (empresa): validar remoção de mailConfirm

Mesma sugestão do caso de usuário para cobrir ambas as variações.

 expect(result.status).toBe(200);
 expect(result.data.info.password).toBeUndefined();
 expect(result.data.info.recoverPasswordToken).toBeUndefined();
 expect(result.data.info.mailconfirm).toBeUndefined();
+expect(result.data.info.mailConfirm).toBeUndefined();
 expect(result.data.info.ip).toBeUndefined();

71-165: DRY: parametrizar cenários similares de login (usuário/empresa)

Os blocos “User Login” e “Company Login” repetem a mesma estrutura de Arrange/Act/Assert. Você pode usar test.each com uma tabela de cenários para reduzir duplicação e facilitar manutenção.

Posso enviar um exemplo de tabela de cenários cobrindo sucesso, email não confirmado, inexistente e senha inválida para ambos os tipos.

Also applies to: 167-259


104-123: Mensagens de erro divergentes entre suites

Aqui os testes consideram "Email not validated". No E2E há um caso com "Please confirm your email..." e code "EMAIL_NOT_CONFIRMED". Recomendo padronizar mensagens/códigos (idealmente com constantes).

Quer que eu proponha um enum/objeto de erro padronizado (message + code) e ajuste os testes?

Also applies to: 219-233

test/modules/auth/auth.e2e-spec.ts (3)

60-61: Alinhar ValidationPipe com produção (whitelist/forbid/transform)

Sem opções, campos extras passam silenciosamente e tipos não são transformados. Recomendo espelhar a config de produção (se houver).

- app.useGlobalPipes(new ValidationPipe());
+ app.useGlobalPipes(
+  new ValidationPipe({
+    whitelist: true,
+    forbidNonWhitelisted: true,
+    transform: true,
+  }),
+);

19-19: Tipagem do serviço mockado para melhor segurança de tipos

Use jest.Mocked ao invés de any.

- let authLoginService: any;
+ let authLoginService: jest.Mocked<AuthLoginService>;
- authLoginService = moduleFixture.get<AuthLoginService>(AuthLoginService);
+ authLoginService = moduleFixture.get(AuthLoginService) as jest.Mocked<AuthLoginService>;

Also applies to: 61-62


21-27: Providers não utilizados no E2E

UserRepository e CompanyRepository não são usados nessa suite. Remover reduz ruído e setup desnecessário.

-  const mockUserRepository = {
-    findOneByEmail: jest.fn(),
-  };
+  // removido: repositórios não utilizados neste módulo de teste

-  const mockCompanyRepository = {
-    findOneByEmail: jest.fn(),
-  };
+  // removido: repositórios não utilizados neste módulo de teste
-        {
-          provide: UserRepository,
-          useValue: mockUserRepository,
-        },
-        {
-          provide: CompanyRepository,
-          useValue: mockCompanyRepository,
-        },
+        // removidos: repositórios não utilizados nesta suite

Also applies to: 49-55

test/modules/auth/strategy/jwt.strategy.spec.ts (2)

35-39: Remover dependência desnecessária de process.env nos testes

O JwtStrategy já recebe o segredo via ConfigService mockado (getOrThrow). O set/delete de process.env.JWT_SECRET é redundante e pode causar efeitos colaterais entre testes.

-  beforeEach(async () => {
-    process.env.JWT_SECRET = 'test-secret-key';
+  beforeEach(async () => {
 
     const configServiceMock = {
       getOrThrow: jest.fn().mockReturnValue('test-secret-key'),
     };
-  afterEach(() => {
-    delete process.env.JWT_SECRET;
-  });
+  // afterEach não precisa manipular process.env

Also applies to: 66-68


84-85: Usar comparação parcial para reduzir fragilidade

Caso o strategy acrescente novos campos no principal retornado, toEqual pode quebrar. Prefira toMatchObject.

-expect(result).toEqual(expectedUser);
+expect(result).toMatchObject(expectedUser);
-expect(result).toEqual(expectedCompany);
+expect(result).toMatchObject(expectedCompany);

Also applies to: 100-101

test/modules/auth/controller/auth.controller.spec.ts (2)

67-69: Usar HttpStatus para melhorar clareza

Substituir números mágicos (200/400) por HttpStatus.OK/BAD_REQUEST melhora legibilidade e evita erros.

Exemplo:

- expect(res.status).toHaveBeenCalledWith(200);
+ expect(res.status).toHaveBeenCalledWith(200 /* HttpStatus.OK */);

Ou importe e use diretamente HttpStatus do @nestjs/common.

Also applies to: 87-89, 103-106, 120-123


17-22: Utilitário de resposta compartilhado entre suites

O mockResponse aparece com a mesma ideia em outras suites. Considerar extrair para test/utils/http.mock.ts e reutilizar.

Posso extrair e ajustar as imports nas três suites de teste.

Also applies to: 50-90, 91-140

src/modules/auth/jwt/jwt.strategy.ts (3)

7-11: Use os mapeadores de principal e tipagem explícita para sanitizar dados sensíveis

Atualmente os imports mapUserToPrincipal/mapCompanyToPrincipal estão sem uso, e a validação retorna objetos “ad hoc” com userType. Recomendo tipar o retorno como Promise<AuthenticatedPrincipal> e utilizar os mapeadores para garantir sanitização consistente (evitando expor campos como recoverPasswordToken, etc.) e remover os comentários eslint-disable.

Aplique este diff:

-  async validate(payload: { email: string }) {
+  async validate(payload: { email: string }): Promise<AuthenticatedPrincipal> {
@@
-      if (user) {
-        // eslint-disable-next-line @typescript-eslint/no-unused-vars
-        const { password, ...safeUser } = user;
-        return { ...safeUser, userType: 'user' };
-      }
+      if (user) {
+        return mapUserToPrincipal(user);
+      }
@@
-      if (company) {
-        // eslint-disable-next-line @typescript-eslint/no-unused-vars
-        const { password, ...safeCompany } = company;
-        return { ...safeCompany, userType: 'company' };
-      }
+      if (company) {
+        return mapCompanyToPrincipal(company);
+      }

Also applies to: 32-44, 50-54


57-62: Simplifique o bloco try/catch ou normalize os erros

O catch atual apenas relança o mesmo erro. Você pode remover o try/catch por completo (deixando a exceção propagar) ou mapear erros inesperados para UnauthorizedException para não vazar detalhes internos.

Opção 1 — Remover o catch:

-    } catch (error) {
-      if (error instanceof UnauthorizedException) {
-        throw error;
-      }
-      throw error;
-    }
+    } catch (error) {
+      throw error;
+    }

Opção 2 — Normalizar erros não autorizados:

-    } catch (error) {
-      if (error instanceof UnauthorizedException) {
-        throw error;
-      }
-      throw error;
-    }
+    } catch (error) {
+      throw new UnauthorizedException('User not found or not authorized!');
+    }

33-35: Padronize o idioma das mensagens de erro (PT-BR)

O projeto usa mensagens em PT-BR em vários serviços. Considere padronizar aqui também para consistência.

-      throw new UnauthorizedException('Invalid payload or email');
+      throw new UnauthorizedException('Payload ou e-mail inválido');
@@
-      throw new UnauthorizedException('User not found or not authorized!');
+      throw new UnauthorizedException('Usuário não encontrado ou não autorizado!');

Also applies to: 56-56

test/modules/user/services/create-user.service.spec.ts (3)

35-51: Registre o CreateUserService como provider (não como controller) no TestingModule

Embora funcione, é incomum registrar um service em controllers. Prefira adicioná-lo em providers para aderir ao padrão do Nest no ambiente de testes.

-    const module: TestingModule = await Test.createTestingModule({
-      controllers: [CreateUserService],
-      providers: [
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        CreateUserService,
         {
           provide: UserRepository,
           useClass: UserRepositoryMock,
         },
         {
           provide: CompanyRepository,
           useClass: CompanyRepositoryMock,
         },
         {
           provide: MailService,
           useClass: MailServiceMock,
         },
       ],
     }).compile();

109-143: Valide explicitamente a sanitização de dados sensíveis no retorno

O teste compara com userMock() (que não tem campos sensíveis), mas é útil checar explicitamente que password, recoverPasswordToken e ip foram removidos.

       const { data, status } = await service.execute(createUserDto, req);
       expect(status).toEqual(201);
       expect(data).toEqual(userMock());
+      expect(data).not.toHaveProperty('password');
+      expect(data).not.toHaveProperty('recoverPasswordToken');
+      expect(data).not.toHaveProperty('ip');

24-27: Evite assertions arriscadas de tipo do Request

mockRequest retorna Partial<Request> e é convertido via as Request. Alternativas: tipar como Pick<Request, 'ip'> e/ou encapsular o cast no helper para centralizar o risco.

Exemplo:

const mockRequest = (): Request => ({ ip: '127.0.0.1' } as unknown as Request);

Also applies to: 77-79, 98-100, 133-135

src/modules/auth/auth.module.ts (2)

17-29: Evite hardcodes de issuer/audience/algoritmo; centralize em config

Os valores de issuer, audience e algorithm estão fixos aqui e na estratégia. Sugiro centralizar via ConfigService (e/ou constantes), reduzindo risco de divergência.

Exemplo (pseudo-config):

useFactory: async (config: ConfigService) => ({
  secret: config.getOrThrow<string>('JWT_SECRET'),
  signOptions: {
    expiresIn: config.get('JWT_EXPIRES_IN', '24h'),
    algorithm: config.get<'HS256' | 'HS512'>('JWT_ALGORITHM', 'HS256'),
    issuer: config.get('JWT_ISSUER', 'vagas-api'),
    audience: config.get('JWT_AUDIENCE', 'vagas-api-users'),
  },
})

15-15: Remover ConfigModule.forRoot duplicado em auth.module.ts

O ConfigModule.forRoot({ isGlobal: true }) já está declarado globalmente em app.module.ts e, por isso, não é necessário importá-lo novamente em módulos filhos.

  • Em src/modules/auth/auth.module.ts, delete a linha abaixo dentro de imports:
    - ConfigModule.forRoot({ isGlobal: true }),
src/modules/company/repository/company.repository.ts (2)

66-73: Padronize o tratamento de erro no primeiro findOne de updateCompanyById

O primeiro findOneBy não usa .catch(handleError), diferentemente das demais operações.

-    const company = await this.companyRepository.findOneBy({ id });
+    const company = await this.companyRepository
+      .findOneBy({ id })
+      .catch(handleError);

47-60: Mensagens de erro em inglês; considere padronizar para PT-BR

Para consistência com os serviços (que retornam PT-BR), ajuste as mensagens de NotFoundException/validações.

Exemplos:

  • 'Invalid company ID' -> 'ID de empresa inválido'
  • 'Company not found' -> 'Empresa não encontrada'
  • 'Company not found after update' -> 'Empresa não encontrada após atualizar'

Also applies to: 102-117, 150-156, 164-176, 183-190

Comment thread src/modules/auth/utils/principal.mapper.ts
Comment thread src/modules/company/repository/company.repository.ts
Comment thread test/modules/auth/auth.e2e-spec.ts
Comment thread test/modules/auth/auth.e2e-spec.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (20)
src/database/data-source.ts (3)

31-31: Evitar NaN e ambiguidade no parse da porta.

parseInt sem base explícita e sem fallback pode resultar em NaN se a env não estiver definida. Prefira Number.parseInt com radix 10 e um padrão sensato.

-      port: parseInt(TYPEORM_PORT),
+      port: Number.parseInt(TYPEORM_PORT ?? '5432', 10),

24-25: Desacoplar paths de entities do layout de build (src/dist).

Usar caminhos diferentes para src e dist torna o config sensível ao modo de execução. Uma alternativa é ancorar em __dirname e aceitar ambos os sufixos .ts/.js, funcionando em dev, test (ts-jest/ts-node) e produção.

-      entities: ['src/database/entities/*.entity.ts'],
+      entities: [__dirname + '/entities/*.entity.{ts,js}'],
-      entities: ['dist/database/entities/*.entity.js'],
+      entities: [__dirname + '/entities/*.entity.{ts,js}'],

Observação: Se existirem subpastas de entities, expanda o glob conforme a necessidade (por exemplo, '/entities/**/*.entity.{ts,js}').

Also applies to: 35-35


18-26: Fortalecer detecção de ambiente de teste e default de synchronize

Atualmente,

  • isTestEnvironment só considera DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:', que pode não cobrir todos os cenários de CI ou outros runners.
  • synchronize depende de DB_SYNCHRONIZE, mas não há nenhum ponto no projeto onde essa variável seja setada (verificação via rg). Se ela não existir, acaba virando false e os testes podem falhar silenciosamente.

Sugestão de refatoração opcional:

• Incluir NODE_ENV === 'test' na detecção de ambiente de teste.
• Forçar synchronize: true por padrão dentro do branch de teste (permitindo override via DB_SYNCHRONIZE, se necessário).

Arquivo: src/database/data-source.ts

- const isTestEnvironment = DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:';
+ const isTestEnvironment =
+   NODE_ENV === 'test' || (DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:');

 export const typeormConfig: DataSourceOptions = isTestEnvironment
   ? {
       type: 'sqlite',
       database: ':memory:',
       entities: ['src/database/entities/*.entity.ts'],
-      synchronize: DB_SYNCHRONIZE === 'true',
+      // Em ambiente de teste, sincronizar por padrão (override via DB_SYNCHRONIZE se precisar)
+      synchronize: DB_SYNCHRONIZE ? DB_SYNCHRONIZE === 'true' : true,
       dropSchema: true,
     }
test/mocks/user/get-all-user.mock.ts (1)

35-43: Verificar inconsistência entre meta e data (itemCount vs tamanho de data)

meta.itemCount está como 1, mas data possui 4 itens. Se algum teste/assert depende disso, pode gerar confusão.

Sugestão: alinhar itemCount com data.length ou documentar que este mock simula paginação com contagem diferente do payload retornado.

test/mocks/user/user.mock.ts (1)

45-46: Evitar datas dinâmicas nos mocks para reduzir flakiness

new Date() pode introduzir variação entre execuções e dificultar asserts/snapshots. Considere fixar a data (como já fazem nos outros mocks) para previsibilidade.

Aplicar este diff:

-    created_at: new Date(),
-    updated_at: new Date(),
+    created_at: new Date('2023-02-21T00:25:07.000Z'),
+    updated_at: new Date('2023-02-21T00:25:07.000Z'),

Opcionalmente, centralize em test-constants (ex.: TEST_DATES.FIXED_ISO) e referencie aqui.

test/modules/auth/services/auth-login.service.spec.ts (7)

20-22: Tipagem do mock do bcrypt e remoção de casts “as never”

O cast “as never” nos mockResolvedValue indica atrito de tipos. Simplifique o mock do bcrypt para garantir tipagem adequada e evitar casts.

Aplique este diff para padronizar o mock:

-jest.mock('bcrypt');
-const bcryptMock = bcrypt as jest.Mocked<typeof bcrypt>;
+jest.mock('bcrypt', () => ({ compare: jest.fn() }));
+const bcryptMock = jest.mocked(bcrypt);

Depois disso, você pode usar bcryptMock.compare.mockResolvedValue(true)/false sem casts.


83-83: Remover casts “as never” nos mocks do bcrypt

Após ajustar o mock do bcrypt, elimine os casts.

Aplique este diff nos pontos indicados:

-        bcryptMock.compare.mockResolvedValue(true as never);
+        bcryptMock.compare.mockResolvedValue(true);
-        bcryptMock.compare.mockResolvedValue(false as never);
+        bcryptMock.compare.mockResolvedValue(false);

Also applies to: 151-151, 179-179, 245-245, 275-275, 299-299


96-103: Afrouxar acoplamento do payload do JWT nas asserções

Se no futuro o payload incluir claims adicionais (ex.: sub, role), os testes vão quebrar sem necessidade.

Aplique este diff:

-        expect(jwtService.sign).toHaveBeenCalledWith({
-          email: loginData.email,
-        });
+        expect(jwtService.sign).toHaveBeenCalledWith(
+          expect.objectContaining({ email: loginData.email }),
+        );

Repita o mesmo ajuste no cenário de empresa.

Also applies to: 191-198


96-99: Evitar dependência direta de TEST_PASSWORDS na verificação do compare

Use a senha do próprio mock do usuário/empresa no expect. Isso reduz o acoplamento ao detalhe de implementação do mock.

Aplique este diff:

-        expect(bcryptMock.compare).toHaveBeenCalledWith(
-          loginData.password,
-          TEST_PASSWORDS.HASHED,
-        );
+        expect(bcryptMock.compare).toHaveBeenCalledWith(
+          loginData.password,
+          user.password,
+        );
-        expect(bcryptMock.compare).toHaveBeenCalledWith(
-          loginData.password,
-          TEST_PASSWORDS.HASHED,
-        );
+        expect(bcryptMock.compare).toHaveBeenCalledWith(
+          loginData.password,
+          company.password,
+        );

Also applies to: 191-194


72-166: Reduzir duplicação com testes parametrizados (it.each / describe.each)

Os quatro cenários (sucesso, e-mail não confirmado, inexistente, senha inválida) se repetem para usuário e empresa. Parametrizar deixaria o spec mais enxuto e fácil de manter.

Exemplo de esqueleto:

describe.each([
  { kind: 'user', loginMock: userLoginMock, repo: 'userRepository' },
  { kind: 'company', loginMock: companyLoginMock, repo: 'companyRepository' },
])('$kind login', ({ loginMock, repo }) => {
  it('success', async () => { /* ... */ });
  it('unconfirmed email', async () => { /* ... */ });
  it('not found', async () => { /* ... */ });
  it('invalid password', async () => { /* ... */ });
});

Also applies to: 168-260


262-285: Sanitização: confirmar intenção em torno de mailConfirm vs mailconfirm

Os testes garantem remoção de mailconfirm (snake/camel misto) mas não validam mailConfirm. Se mailConfirm também for considerado sensível, inclua assert para removê-lo; se não, documente que faz parte do payload público.

Posso ajudar a ajustar os testes conforme a decisão.

Also applies to: 287-310


117-119: Códigos HTTP para autenticação inválida

Os cenários de credenciais inválidas/e-mail não validado retornam 400. Em muitas APIs, usa-se 401/403. Se o contrato do serviço for 400, ok; caso contrário, pode valer alinhar o spec.

Confirme com o contrato/Swagger do endpoint qual deve ser o status padrão.

Also applies to: 133-135, 155-156, 211-213, 227-229, 249-250

test/config/test-constants.ts (3)

43-52: Tokens e segredos de teste

Boa prática isolar o JWT_SECRET de teste e o token de recuperação. Apenas observação: semanticamente JWT_SECRET não é “token”; se quiser, pode separar em TEST_SECRETS/JWT para semântica mais clara.


60-67: Nomenclatura de e-mails não confirmados

UNCONFIRMED_COMPANY está sendo usado também em cenários de usuário (ver userWithUnconfirmedEmailMock nos specs). Considere adicionar TEST_EMAILS.UNCONFIRMED_USER para clareza semântica.

Posso abrir um follow-up com o ajuste e o uso nos mocks.


76-80: Sugestão: adicionar datas fixas de teste

Para evitar flakiness com new Date() em mocks (ex.: userEntityMock), considere centralizar uma data fixa aqui.

Exemplo:

export const TEST_DATES = {
  FIXED_ISO: '2023-02-21T00:25:07.000Z',
} as const;

E nos mocks: new Date(TEST_DATES.FIXED_ISO).

src/modules/auth/jwt/jwt.strategy.ts (1)

31-58: Tipar retorno de validate e remover try/catch redundante

  • O try/catch atual apenas relança o erro sem agregar valor.
  • Tipar o retorno como AuthenticatedPrincipal melhora contratos e ajuda nos testes/DI.

Sugestão de ajuste:

@@
-import {
-  mapUserToPrincipal,
-  mapCompanyToPrincipal,
-} from '../utils/principal.mapper';
+import {
+  mapUserToPrincipal,
+  mapCompanyToPrincipal,
+} from '../utils/principal.mapper';
+import { AuthenticatedPrincipal } from '../types/principal.types';
@@
-  async validate(payload: { email: string }) {
+  async validate(payload: { email: string }): Promise<AuthenticatedPrincipal> {
     if (!payload || !payload.email) {
       throw new UnauthorizedException('Invalid payload or email');
     }
-
-    try {
-      const user = await this.userRepository.findOneByEmail(payload.email);
-
-      if (user) {
-        return mapUserToPrincipal(user);
-      }
-
-      const company = await this.companyRepository.findOneByEmail(
-        payload.email,
-      );
-
-      if (company) {
-        return mapCompanyToPrincipal(company);
-      }
-
-      throw new UnauthorizedException('User not found or not authorized!');
-    } catch (error) {
-      if (error instanceof UnauthorizedException) {
-        throw error;
-      }
-      throw error;
-    }
+    const user = await this.userRepository.findOneByEmail(payload.email);
+    if (user) {
+      return mapUserToPrincipal(user);
+    }
+
+    const company = await this.companyRepository.findOneByEmail(payload.email);
+    if (company) {
+      return mapCompanyToPrincipal(company);
+    }
+
+    throw new UnauthorizedException('User not found or not authorized!');
   }
test/app.e2e-spec.ts (1)

12-20: Trocar beforeEach/afterEach por beforeAll/afterAll neste spec

Para este spec único, inicializar/derrubar a app uma vez reduz overhead e flakiness.

-  beforeEach(async () => {
+  beforeAll(async () => {
@@
-  afterEach(async () => {
+  afterAll(async () => {

Also applies to: 42-46

test/modules/user/services/create-user.service.spec.ts (1)

35-52: Registrar CreateUserService como provider (não controller)

No Nest, serviços devem ser declarados em providers. Manter o serviço em controllers funciona por acaso, mas foge do padrão e pode quebrar DI em refactors.

-    const module: TestingModule = await Test.createTestingModule({
-      controllers: [CreateUserService],
-      providers: [
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [],
+      providers: [
+        CreateUserService,
         {
           provide: UserRepository,
           useClass: UserRepositoryMock,
         },
         {
           provide: CompanyRepository,
           useClass: CompanyRepositoryMock,
         },
         {
           provide: MailService,
           useClass: MailServiceMock,
         },
       ],
     }).compile();
@@
-    service = module.get(CreateUserService);
+    service = module.get(CreateUserService);

Also applies to: 54-58

src/modules/company/repository/company.repository.ts (2)

44-49: Meta de paginação deve refletir a coluna de ordenação efetivamente usada

Hoje o PageMetaDto recebe o orderByColumn original (potencialmente inválido), enquanto a consulta usa a coluna saneada (orderColumn). Isso pode gerar inconsistência em responses.

Sugestão: passar uma cópia dos pageOptions com orderByColumn = orderColumn.

-    const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });
+    const safePageOptions = { ...pageOptionsDto, orderByColumn: orderColumn } as PageOptionsDto;
+    const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto: safePageOptions });

Also applies to: 61-63


29-43: Micro-otimização: mover allowedOrderColumns para constante de módulo

Evita re-alocação a cada chamada e melhora legibilidade.

-    const allowedOrderColumns = [
+    const allowedOrderColumns = [
       'id',
       'companyName',
       'email',
       'cnpj',
       'companyType',
       'companySize',
       'uf',
       'companySite',
       'description',
       'created_at',
       'updated_at',
       'mailConfirm',
-    ];
+    ] as const;

Opcionalmente, defina fora do método como const ALLOWED_ORDER_COLUMNS = [...] as const;.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7103bf9 and 26ba24c.

📒 Files selected for processing (17)
  • src/database/data-source.ts (2 hunks)
  • src/modules/auth/jwt/jwt.strategy.ts (1 hunks)
  • src/modules/company/repository/company.repository.ts (1 hunks)
  • test/app.e2e-spec.ts (1 hunks)
  • test/config/test-constants.ts (1 hunks)
  • test/mocks/auth/company.mock.ts (1 hunks)
  • test/mocks/auth/jwt-payload.mock.ts (1 hunks)
  • test/mocks/auth/user-login.mock.ts (1 hunks)
  • test/mocks/user/create-user.mock.ts (1 hunks)
  • test/mocks/user/get-all-user.mock.ts (1 hunks)
  • test/mocks/user/user-update.mock.ts (1 hunks)
  • test/mocks/user/user.mock.ts (2 hunks)
  • test/modules/auth/auth.e2e-spec.ts (1 hunks)
  • test/modules/auth/services/auth-login.service.spec.ts (1 hunks)
  • test/modules/auth/strategy/jwt.strategy.spec.ts (1 hunks)
  • test/modules/user/services/create-user.service.spec.ts (7 hunks)
  • test/modules/user/services/update-password-by-email.service.spec.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • test/mocks/auth/user-login.mock.ts
  • test/mocks/user/create-user.mock.ts
  • test/modules/user/services/update-password-by-email.service.spec.ts
  • test/modules/auth/auth.e2e-spec.ts
  • test/modules/auth/strategy/jwt.strategy.spec.ts
  • test/mocks/auth/jwt-payload.mock.ts
  • test/mocks/auth/company.mock.ts
🧰 Additional context used
🧬 Code Graph Analysis (7)
test/mocks/user/user.mock.ts (1)
test/config/test-constants.ts (5)
  • TEST_IDS (55-58)
  • TEST_USER_DATA (77-80)
  • TEST_EMAILS (61-67)
  • TEST_TOKENS (44-52)
  • TEST_PASSWORDS (7-41)
test/mocks/user/user-update.mock.ts (1)
test/config/test-constants.ts (3)
  • TEST_USER_DATA (77-80)
  • TEST_EMAILS (61-67)
  • TEST_PASSWORDS (7-41)
test/mocks/user/get-all-user.mock.ts (1)
test/config/test-constants.ts (1)
  • TEST_EMAILS (61-67)
test/modules/auth/services/auth-login.service.spec.ts (4)
test/mocks/auth/user-login.mock.ts (4)
  • userLoginMock (5-11)
  • userWithUnconfirmedEmailMock (29-35)
  • invalidUserLoginMock (21-27)
  • companyLoginMock (13-19)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
test/config/test-constants.ts (2)
  • TEST_PASSWORDS (7-41)
  • TEST_EMAILS (61-67)
test/mocks/auth/company.mock.ts (2)
  • companyMock (8-27)
  • companyWithUnconfirmedEmailMock (29-41)
src/modules/company/repository/company.repository.ts (12)
src/modules/auth/jwt/jwt.strategy.ts (1)
  • Injectable (12-59)
src/modules/company/services/create-company.service.ts (1)
  • Injectable (8-67)
src/modules/company/services/delete-company.service.ts (1)
  • Injectable (4-17)
src/modules/company/services/find-all-company.service.ts (1)
  • Injectable (7-18)
src/modules/company/services/recovery-password-by-email.service.ts (1)
  • Injectable (6-44)
src/modules/company/services/update-company.service.ts (1)
  • Injectable (7-42)
src/modules/company/services/update-password-by-email.service.ts (1)
  • Injectable (7-49)
src/modules/company/services/update-password.service.ts (1)
  • Injectable (8-50)
src/shared/utils/handle-error.util.ts (1)
  • handleError (3-14)
src/shared/pagination/pageOptions.dto.ts (1)
  • PageOptionsDto (6-42)
src/shared/pagination/page.dto.ts (1)
  • PageDto (4-14)
src/shared/pagination/pageMeta.dto.ts (1)
  • PageMetaDto (3-21)
src/modules/auth/jwt/jwt.strategy.ts (2)
src/modules/company/repository/company.repository.ts (1)
  • Injectable (14-210)
src/modules/auth/utils/principal.mapper.ts (2)
  • mapUserToPrincipal (8-17)
  • mapCompanyToPrincipal (19-29)
test/modules/user/services/create-user.service.spec.ts (3)
test/config/test-constants.ts (2)
  • TEST_EMAILS (61-67)
  • TEST_PASSWORDS (7-41)
test/mocks/user/create-user.mock.ts (1)
  • createUserMock (9-17)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
🔇 Additional comments (19)
src/database/data-source.ts (1)

20-27: Boa adição: configuração dedicada para testes (SQLite in-memory).

Optar por SQLite em memória com dropSchema para testes isola muito bem o ambiente e acelera a suíte. Mantendo migrações fora desse branch reduz o atrito de setup. Sem objeções aqui.

test/mocks/user/get-all-user.mock.ts (2)

1-1: Centralização de e-mails via TEST_EMAILS — bom ajuste de consistência

Importar TEST_EMAILS elimina hardcodes e facilita manutenção dos mocks.


9-9: Substituição de strings hardcoded por constantes

Trocar os e-mails repetidos por TEST_EMAILS.USER evita divergência futura entre mocks e melhora a legibilidade.

Also applies to: 16-16, 23-23, 30-30

test/mocks/user/user-update.mock.ts (2)

1-5: Uso de constantes centralizadas para dados e credenciais

Importações de TEST_EMAILS/TEST_USER_DATA/TEST_PASSWORDS padronizam o mock e evitam magic strings.


9-12: LGTM no payload do userUpdateMock

Troca de literais por constantes melhora a robustez dos testes e a coerência entre suites.

test/mocks/user/user.mock.ts (4)

2-8: Padronização de mocks com test-constants

Adoção de TEST_IDS/TEST_EMAILS/TEST_USER_DATA/TEST_PASSWORDS/TEST_TOKENS mantém os mocks alinhados ao resto da base de testes.


12-15: userMock com dados centralizados — bom

Menos risco de divergência entre suites que consomem este mock.


24-31: userUpdateRecoveryMock com token de recuperação parametrizado

Ótimo usar TEST_TOKENS.RECOVERY_TOKEN em vez de valores fixos.


37-44: userEntityMock: senha básica via constante — ok

Ajuste deixa claro o nível de complexidade esperado para este mock específico.

test/modules/auth/services/auth-login.service.spec.ts (1)

1-18: Setup da suíte e mocks das dependências — bem estruturado

Cobertura boa dos cenários de login (usuário/empresa) e sanitização, com repositórios/jwt/bcrypt mockados corretamente.

test/config/test-constants.ts (1)

6-41: Centralização de senhas de teste — excelente para evitar hardcoding

A coleção cobre casos de uso relevantes (válida, inválida, simples, etc.) e facilita manutenção/parametrização via env.

src/modules/auth/jwt/jwt.strategy.ts (2)

21-28: Configuração JWT robusta (issuer/audience/algorithms) — bom trabalho

A estratégia restringe algoritmo (HS256), valida expiração e configura issuer/audience. Alinha com boas práticas.


21-28: Verificação concluída: issuer, audience e algoritmo estão alinhados

Ambos os locais usam os mesmos valores:

  • src/modules/auth/auth.module.ts
    • issuer: 'vagas-api'
    • audience: 'vagas-api-users'
    • algorithm: 'HS256'
  • src/modules/auth/jwt/jwt.strategy.ts
    • issuer: 'vagas-api'
    • audience: 'vagas-api-users'
    • algorithms: ['HS256']
test/app.e2e-spec.ts (1)

52-56: Asserções da rota raiz estão objetivas e úteis

Cobertura do conteúdo HTML com checagem de trechos-chave (marca, SVG e link do LinkedIn) ficou boa.

test/modules/user/services/create-user.service.spec.ts (2)

91-109: Cenário “e-mail de empresa existente” bem coberto

Valida o curto-circuito quando o e-mail já está em uso por empresa. Mensagem e status coerentes com a camada de serviço.


110-144: Teste de sucesso garante sanitização e side effects

  • Garante que dados sensíveis não vazem (password, token, ip).
  • Garante envio de e-mail de confirmação.
    Bom equilíbrio entre comportamento e contrato de retorno.
src/modules/company/repository/company.repository.ts (3)

26-64: Mitigação de SQL Injection no orderBy e uso de getManyAndCount — ótimo

  • Whitelist de colunas para ordenação evita injeção via orderBy.
  • Substituição por getManyAndCount reduz roundtrips.
    Endereça apontamentos de revisões anteriores.

197-209: Exclusão com verificação prévia e mensagem padronizada

Fluxo claro: valida existência, executa delete e retorna payload enxuto com mensagem. Ok.


15-19: Verificação concluída: nenhuma referência a company_repository encontrada

  • O comando rg -n -C2 -P '\bcompany_repository\b' --type ts não retornou resultados.
  • Todos os usos estão consistentes com companyRepository.

Comment thread src/database/data-source.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/database/data-source.ts (1)

41-55: Endereça o feedback anterior de SSL com defaults seguros

Agora usa comparação estrita e rejectUnauthorized padrão como true, com aviso quando desativado. Excelente.

🧹 Nitpick comments (5)
src/database/data-source.ts (5)

1-3: Coloque reflect-metadata antes de qualquer outro import (nit)

Evita edge cases com decorators em tempo de execução. É pouco provável afetar aqui, mas é um padrão seguro.

-import 'dotenv/config';
-import { DataSource, DataSourceOptions } from 'typeorm';
-import 'reflect-metadata';
+import 'reflect-metadata';
+import 'dotenv/config';
+import { DataSource, DataSourceOptions } from 'typeorm';

38-40: Permita SSL “inseguro” intencional sem CA quando explicitamente solicitado

Hoje, sem CA o ssl é omitido mesmo se DB_SSL_REJECT_UNAUTHORIZED estiver setado para desativar a verificação. Em provedores que exigem SSL mas não fornecem CA (e.g. alguns Neon/Heroku setups), isso impede conexão. Sugiro habilitar o objeto ssl sem ca quando o override inseguro for explícito.

-  const normalizedCACert = normalizeCACert(CA_CERT);
-  if (!normalizedCACert) return undefined;
-
-  // Default to secure setting (reject unauthorized certificates)
-  const rejectUnauthorized =
-    DB_SSL_REJECT_UNAUTHORIZED === 'false' || DB_SSL_REJECT_UNAUTHORIZED === '0'
-      ? false
-      : true;
+  const normalizedCACert = normalizeCACert(CA_CERT);
+  // Default to secure setting (reject unauthorized certificates)
+  const rejectUnauthorized =
+    DB_SSL_REJECT_UNAUTHORIZED === 'false' || DB_SSL_REJECT_UNAUTHORIZED === '0'
+      ? false
+      : true;
+  // Sem CA e sem override para insecure => não habilita SSL
+  if (!normalizedCACert && rejectUnauthorized) return undefined;
@@
-  return {
-    ca: normalizedCACert,
-    rejectUnauthorized,
-  };
+  return {
+    ...(normalizedCACert ? { ca: normalizedCACert } : {}),
+    rejectUnauthorized,
+  };

35-37: Considerar flag para habilitar SSL também fora de produção (opcional)

Cenário dev que acessa DB gerenciado pode exigir SSL. Uma flag como DB_SSL_ENABLE=true permitiria ativar SSL em development de forma explícita, mantendo default seguro.


64-71: Melhore DX no SQLite in-memory: default de synchronize e glob de entidades

  • Para testes em memória, synchronize pode defaultar para true quando não definido, reduzindo atrito.
  • O glob **/*.entity.ts cobre subdiretórios sob entities.
-      entities: ['src/database/entities/*.entity.ts'],
-      synchronize: DB_SYNCHRONIZE === 'true',
+      entities: ['src/database/entities/**/*.entity.ts'],
+      synchronize: DB_SYNCHRONIZE ? DB_SYNCHRONIZE === 'true' : true,

73-85: Torne o port resiliente a env ausente e use base 10

Evita NaN caso TYPEORM_PORT não esteja setada e documenta a base.

-      port: parseInt(TYPEORM_PORT),
+      port: TYPEORM_PORT ? parseInt(TYPEORM_PORT, 10) : 5432,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b85c894 and e742ad8.

📒 Files selected for processing (2)
  • src/database/data-source.ts (2 hunks)
  • test/modules/auth/strategy/jwt.strategy.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/modules/auth/strategy/jwt.strategy.spec.ts
🔇 Additional comments (1)
src/database/data-source.ts (1)

19-28: Normalização de CA com \\n\n é ótima

Resolve um problema comum ao carregar certificados via env. Implementação enxuta e correta.

Comment thread src/database/data-source.ts
Comment thread src/database/data-source.ts
Copy link
Copy Markdown
Contributor

@MikaelMelo1 MikaelMelo1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alguns pontos para corrigir.

Comment thread src/modules/auth/auth.module.ts
Comment thread src/modules/company/repository/company.repository.ts
Comment thread src/modules/company/services/delete-company.service.ts
Comment thread src/modules/company/services/update-company.service.ts
Comment thread test/modules/auth/auth.e2e-spec.ts
Comment thread test/mocks/user/get-all-user.mock.ts
Comment thread test/mocks/auth/company.mock.ts
Comment thread test/config/test-constants.ts
Comment thread test/app.e2e-spec.ts
…rdized naming

- Replace duplicate UUIDs with unique identifiers for USER_ID and COMPANY_ID
- Rename TEST_EMAILS.USER to TEST_EMAILS.DEFAULT_USER for clarity
- Maintain RFC-compliant IP addresses for documentation
- Improve test isolation and prevent ID conflicts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/modules/user/services/update-password-by-email.service.ts (1)

39-44: Invalidar o token de recuperação após o uso.

Hoje o token permanece válido após redefinição, permitindo reuso indevido caso vazado. Invalide-o (limpar token/definir used_at/expirar) imediatamente após updatePassword.

Proposta mínima no serviço:

   await this.mailService.sendUserConfirmation(userUpdated);
 
-  return {
+  // Opcional: invalidar token de recuperação
+  await this.userRepository.clearRecoverPasswordToken(userUpdated.id);
+
+  return {
     status: 200,
     data: { message: 'Senha redefinida com sucesso!' },
   };

E no repositório (fora deste trecho), adicione um método semelhante:

async clearRecoverPasswordToken(userId: string): Promise<void> {
  // zere o token e, se houver, campos de expiração/uso
}
src/modules/user/services/create-user.service.ts (1)

39-45: Sanitização sem mutar a entidade retornada e tratamento de violação de unique

Evite mutar o objeto do ORM e sanitize via destructuring. Trate também erro de unique no insert.

Aplica-se aqui:

-    delete response.password;
-    delete response.recoverPasswordToken;
-    delete response.ip;
-
-    await this.mailService.sendUserCreationConfirmation(response);
-
-    return response;
+    // Tratamento de erro de unique (Postgres 23505; ajuste conforme driver)
+    // try/catch pode envolver a criação se preferir capturar aqui.
+    const { password: _pw, recoverPasswordToken: _rpt, ip: _ip, ...safe } = response;
+    try {
+      await this.mailService.sendUserCreationConfirmation(safe);
+    } catch (e) {
+      // opcional: não bloquear criação de usuário por falha de e-mail
+      // this.logger?.warn(`Falha ao enviar e-mail de criação: ${e?.message}`);
+    }
+    return safe;
♻️ Duplicate comments (5)
src/database/data-source.ts (2)

13-17: Garanta documentação e provisionamento das novas DB_ no .env.example e CI.*

Comentário já levantado anteriormente; reforço para evitar flakiness em testes/CI.


93-98: Gatilho de ambiente de teste depende só de DB_ — inclua NODE_ENV === 'test'.*

Comentário já feito antes; reforço para evitar dependência rígida de DB_TYPE/DB_DATABASE nos testes.

Aplica-se com o patch abaixo (não bloqueante, mas recomendado):

-const isTestEnvironment = DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:';
+const isTestEnvironment =
+  NODE_ENV === 'test' || (DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:');
src/modules/company/services/delete-company.service.ts (1)

8-21: Simplifique o serviço e deixe o status para o controller.

O repositório já lança NotFoundException e retorna { message: ... }; a checagem de affected aqui é morta. Para separar responsabilidades (em linha com o feedback anterior), retorne apenas o payload e defina o @HttpCode() no controller.

-import { Injectable, NotFoundException, HttpStatus } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
@@
-  async execute(id: string): Promise<{ data: object; status: number }> {
-    const data = await this.companyRepository.deleteCompanyById(id);
-
-    if (
-      !data ||
-      (typeof data === 'object' && 'affected' in data && data.affected === 0)
-    ) {
-      throw new NotFoundException(`Company with id ${id} not found`);
-    }
-
-    return {
-      data,
-      status: HttpStatus.OK,
-    };
-  }
+  async execute(id: string): Promise<object> {
+    return this.companyRepository.deleteCompanyById(id);
+  }
test/modules/auth/auth.e2e-spec.ts (2)

133-161: Remover spy redundante: já é um jest.fn()

O serviço já é mockado; basta verificar que não foi chamado. Menos ruído e menos teardown.

-      const executeSpy = jest.spyOn(authLoginService, 'execute');
-
       const response = await request(app.getHttpServer())
         .post('/auth/login')
         .send(invalidLoginData)
         .expect(400);
 
-      expect(executeSpy).toHaveBeenCalledTimes(0);
+      expect(authLoginService.execute).not.toHaveBeenCalled();
 ...
-      executeSpy.mockRestore();

163-189: Remover spy redundante no cenário de campos obrigatórios

Repita o padrão sem spy e apenas afirme que o mock não foi chamado.

-      const executeSpy = jest.spyOn(authLoginService, 'execute');
-
       const response = await request(app.getHttpServer())
         .post('/auth/login')
         .send(incompleteLoginData)
         .expect(400);
 
-      expect(executeSpy).toHaveBeenCalledTimes(0);
+      expect(authLoginService.execute).not.toHaveBeenCalled();
 ...
-      executeSpy.mockRestore();
🧹 Nitpick comments (51)
src/modules/user/services/update-password-by-email.service.ts (4)

25-27: OK ao lançar NotFound quando o token não resolve usuário.

Considere mensagem mais neutra para não revelar estado do token (“Link inválido ou expirado.”), caso o endpoint seja exposto publicamente.


29-31: Use comparação estrita.

Troque != por !== para consistência e evitar coerção implícita.

Aplicar:

-    if (password != confirmPassword) {
+    if (password !== confirmPassword) {

32-33: Parametrizar custo do hash.

Extraia 10 para configuração (ex.: via ConfigService: BCRYPT_SALT_ROUNDS) para facilitar tuning entre ambientes.


41-44: Formato de retorno no serviço vs. controller.

Services geralmente retornam apenas dados; o status HTTP é responsabilidade do controller. Não é bloqueante, mas avaliem padronizar no projeto.

test/modules/user/services/update-password-by-email.service.spec.ts (3)

50-65: Ajustar matcher do Jest e valor de retorno do mock.

  • Prefira toThrow(NotFoundException) em vez de instanciar a exceção; é menos frágil.
  • Retorne null/undefined no mock para refletir melhor o contrato do repositório.

Aplicar:

-      userRepository.findByToken = jest.fn().mockResolvedValue('');
+      userRepository.findByToken!.mockResolvedValue(null as any);
...
-      ).rejects.toThrow(new NotFoundException('Usuário não encontrado!'));
+      ).rejects.toThrow(NotFoundException);
+
+      // Opcional: validar payload da exceção sem acoplar a instância
+      await expect(
+        service.execute({
+          recoverPasswordToken: TEST_PASSWORDS.TOKEN,
+          password: TEST_PASSWORDS.SIMPLE,
+          confirmPassword: TEST_PASSWORDS.SIMPLE,
+        }),
+      ).rejects.toMatchObject({
+        response: { message: 'Usuário não encontrado!' },
+      });

67-82: Mesmo ajuste para BadRequest.

Use o tipo da exceção e, opcionalmente, valide o payload.

Aplicar:

-      userRepository.findByToken = jest.fn().mockResolvedValue(userMock());
+      userRepository.findByToken!.mockResolvedValue(userMock() as any);
...
-      ).rejects.toThrow(new BadRequestException('As senhas não conferem!'));
+      ).rejects.toThrow(BadRequestException);

Opcional de payload (similar ao caso 404):

await expect(service.execute({...})).rejects.toMatchObject({
  response: { message: 'As senhas não conferem!' },
});

84-105: Cobrir envio de e-mail e fortalecer verificação do hash.

  • Falta assert para mailService.sendUserConfirmation.
  • Verifique que o segundo argumento de updatePassword parece um hash bcrypt.

Aplicar:

       expect(userRepository.updatePassword).toHaveBeenCalledWith(
         TEST_IDS.USER_ID,
-        expect.any(String),
+        expect.stringMatching(/^\$2[aby]\$\d{2}\$/),
       );
+      expect(mailService.sendUserConfirmation).toHaveBeenCalledWith(updatedUser);
src/modules/candidacy/service/candidacy.service.spec.ts (2)

5-11: Factory de mock ok; considere fortalecer o tipo (opcional).

Para evitar “método faltando” em futuras mudanças, você pode trocar Partial<> por Pick<> focando apenas nos métodos usados.

Ajuste sugerido:

-const createCandidacyRepositoryMock = (): jest.Mocked<
-  Partial<CandidacyRepository>
-> => ({
+const createCandidacyRepositoryMock = (): jest.Mocked<
+  Pick<CandidacyRepository, 'createCandidacy' | 'findAllByUserId' | 'updateStatus'>
+> => ({
   createCandidacy: jest.fn(),
   findAllByUserId: jest.fn(),
   updateStatus: jest.fn(),
 });

33-35: Limpeza de mocks: considerar resetAllMocks e fechar o módulo (opcional).

clearAllMocks limpa histórico, mas mantém implementações. Se você alternar implementações entre testes, prefira resetAllMocks. Também é saudável fechar o TestingModule para evitar handles abertos.

Sugestões:

-afterEach(() => {
-  jest.clearAllMocks();
-});
+afterEach(async () => {
+  jest.resetAllMocks();
+  await moduleRef?.close?.();
+});

E fora deste hunk, capture o módulo para fechá-lo:

// no topo do describe
let moduleRef: TestingModule;

// dentro do beforeEach, após compilar
moduleRef = module;
test/modules/user/services/recovery-password-by-email.service.spec.ts (6)

10-13: Fábrica de mocks do UserRepository OK; considere tipagem mais forte (DeepMocked).

Para reduzir Partial/any e manter assinaturas corretas, avalie usar jest-mock-extended.

Exemplo (fora do trecho):

import { mockDeep, DeepMocked } from 'jest-mock-extended';

let userRepository: DeepMocked<UserRepository>;
beforeEach(() => {
  userRepository = mockDeep<UserRepository>();
});

15-17: Confirme se sendUserConfirmation é o método certo para recuperação de senha.

O nome sugere confirmação de cadastro. Se houver algo como sendPasswordRecovery/sendResetPassword, alinhar melhora a semântica e evita confusão de template.


21-22: Tipagem dos mocks é ok; evite reatribuir funções, prefira spyOn().mockResolvedValue(...).

Isso mantém a mesma referência de mock e facilita asserts de chamadas.


29-38: Coloque RecoveryPasswordByEmail em providers, não em controllers.

É um service (@Injectable). Mantê-lo em providers segue o padrão do Nest e evita ruídos futuros.

-    const module: TestingModule = await Test.createTestingModule({
-      controllers: [RecoveryPasswordByEmail],
-      providers: [
+    const module: TestingModule = await Test.createTestingModule({
+      // Nenhum controller necessário para testar um service
+      controllers: [],
+      providers: [
+        RecoveryPasswordByEmail,
         {
           provide: UserRepository,
           useValue: userRepository,
         },
         {
           provide: MailService,
           useValue: mailService,
         },
       ],
     }).compile();

59-60: Mensagem genérica correta (não vaza existência); elimine duplicação e reforce que nenhum e-mail é enviado.

  • Extraia a mensagem para uma constante para DRY.
  • Adicione assert de que o mail não foi enviado neste caso.
  • Retorne null (ou undefined) no mock de “não encontrado” em vez de '' para refletir melhor o contrato típico.

Sugestões (parte fora do trecho):

// perto do topo do arquivo
const GENERIC_MSG =
  'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.';
-      const result = {
-        message:
-          'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.',
-      };
+      const result = { message: GENERIC_MSG };
-      userRepository.findOneByEmail = jest.fn().mockResolvedValue('');
+      userRepository.findOneByEmail = jest.fn().mockResolvedValue(null);
       expect(updateRecoveryPasswordSpy).not.toHaveBeenCalled();
+      expect(mailService.sendUserConfirmation).not.toHaveBeenCalled();

82-84: Quando o usuário existe, valide o envio do e-mail e argumentos; use a constante de mensagem.

  • Assert explícito do disparo do e-mail (e, se possível, dos argumentos).
  • Reutilize a constante da mensagem para evitar divergências.
-      const result = {
-        message:
-          'Caso esse e-mail esteja cadastrado no sistema, será encaminhado para ele uma mensagem de orientação sobre os próximos passos para a redefinição da senha.',
-      };
+      const result = { message: GENERIC_MSG };
       expect(updateRecoveryPasswordSpy).toHaveBeenCalled();
       expect(updateRecoveryPasswordSpy).toBeCalledTimes(1);
+      expect(mailService.sendUserConfirmation).toHaveBeenCalledTimes(1);
+      // Opcional: validar payload conforme assinatura do serviço/template
+      // expect(mailService.sendUserConfirmation).toHaveBeenCalledWith(expect.objectContaining({ email: userMock().email }), expect.anything());
test/examples/individual-mocks-guide.spec.ts (4)

7-14: Centralize a fábrica de mocks e tipagem mais precisa (evitar Partial)

Boa fábrica. Para evitar “falsos verdes” quando a interface do repositório mudar, recomendo tipar com Pick<> em vez de Partial<> e considerar mover essa fábrica para um helper compartilhado (ex.: test/factories/user-repository.mock.ts) para reuso entre specs.

-  const createUserRepositoryMock = (): jest.Mocked<
-    Partial<UserRepository>
-  > => ({
+  const createUserRepositoryMock = (): jest.Mocked<
+    Pick<UserRepository, 'findOneById' | 'createUser' | 'updateUser' | 'findOneByEmail'>
+  > => ({

16-18: Limpe os mocks entre casos de teste

Você já recria os mocks no beforeEach, mas limpar chamadas/implementações ajuda a evitar vazamento acidental em alterações futuras.

   beforeEach(async () => {
     userRepository = createUserRepositoryMock();
   });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+    jest.restoreAllMocks();
+  });

48-65: Asserte também o número de chamadas de createUser

Complementa a verificação de comportamento e previne criações duplicadas indesejadas.

       expect(userRepository.createUser).toHaveBeenCalledWith(newUserData);
+      expect(userRepository.createUser).toHaveBeenCalledTimes(1);

67-80: Asserte o número de chamadas de findOneByEmail no caso “email existente”

Garante que nenhuma rechecagem inesperada ocorreu.

       expect(userRepository.createUser).not.toHaveBeenCalled();
+      expect(userRepository.findOneByEmail).toHaveBeenCalledTimes(1);
test/modules/user/services/update-user.service.spec.ts (2)

83-95: Evite sombreamento de variável e reatribuição de métodos; use o mock injetado

O const fileUploadService = service['fileUploadService'] sombreia a variável externa e reatribuir métodos dificulta o rastreio de chamadas. Use o mock já injetado e apenas configure mockResolvedValue.

-      const fileUploadService = service['fileUploadService'];
-      fileUploadService.deleteFile = jest.fn().mockResolvedValue(undefined);
-      fileUploadService.upload = jest.fn().mockResolvedValue({
+      fileUploadService.deleteFile!.mockResolvedValue(undefined);
+      fileUploadService.upload!.mockResolvedValue({
         Location: 'https://s3.bucket.com/file.jpg',
         key: 'uploads/file.jpg',
       });
 
-      userRepository.updateUser = jest.fn().mockResolvedValue(undefined);
+      userRepository.updateUser = jest.fn().mockResolvedValue(undefined);
       const updateUserSpy = jest.spyOn(userRepository, 'updateUser');
-      const deleteFileSpy = jest.spyOn(fileUploadService, 'deleteFile');
-      const uploadSpy = jest.spyOn(fileUploadService, 'upload');
+      const deleteFileSpy = jest.spyOn(fileUploadService, 'deleteFile');
+      const uploadSpy = jest.spyOn(fileUploadService, 'upload');

116-121: Padronize toHaveBeenCalledTimes

Use o mesmo matcher em todo o arquivo.

-      expect(updateUserSpy).toBeCalledTimes(1);
+      expect(updateUserSpy).toHaveBeenCalledTimes(1);
@@
-      expect(updateUserSpy).toBeCalledTimes(1);
+      expect(updateUserSpy).toHaveBeenCalledTimes(1);
@@
-      expect(updateUserSpy).toBeCalledTimes(1);
+      expect(updateUserSpy).toHaveBeenCalledTimes(1);

Also applies to: 141-143, 152-155

src/database/data-source.ts (1)

19-22: Normalização de CA — ok; considerar suporte opcional a base64/arquivo.

Muitos CIs fornecem o CA como base64 ou caminho de arquivo. Avalie aceitar CA_CERT_BASE64/CA_CERT_PATH além de CA_CERT (não bloqueante).

src/modules/user/services/create-user.service.ts (2)

18-21: Evite mutar o DTO; construa um payload tipado e explícito

Não atribua ip no DTO nem faça delete de campos no DTO. Construa um payload (Omit<CreateUserDto, 'confirmPassword'> & { ip: string }) e passe-o ao repositório.

Aplicável dentro deste trecho:

-  async execute(data: CreateUserDto, req: Request) {
-    const { email, password } = data;
-    data['ip'] = req.ip;
+  async execute(data: CreateUserDto, req: Request) {
+    const { email, password, confirmPassword, ...rest } = data;
+    const normalizedEmail = email.trim().toLowerCase();
+    const payload = {
+      ...rest,
+      email: normalizedEmail,
+      ip: req.ip,
+      password: await bcrypt.hash(password, 10),
+    };

-    data.password = await bcrypt.hash(password, 10);
-
-    delete data.confirmPassword;
-
-    const response = await this.userRepository.createUser(data);
+    const response = await this.userRepository.createUser(payload);

Also applies to: 33-37


30-30: Mensagem de erro OK; considere i18n/uniformização

Texto em pt-BR consistente com o resto da API. Se houver i18n centralizado, mova para catálogo.

src/modules/user/user.controller.ts (4)

72-75: POST create: retorno direto + 201 — ótimo; @HttpCode(HttpStatus.CREATED) é redundante

POST já retorna 201 por padrão no Nest; o decorator pode ser removido se quiser reduzir ruído.

-  @HttpCode(HttpStatus.CREATED)
   async createNewUser(@Body() createUser: CreateUserDto, @Req() req: Request) {
     return this.createUserService.execute(createUser, req);
   }

119-121: deleteUser agora retorna diretamente — alinhar padrão de respostas do controller

Alguns endpoints usam @Res() e outros retorno direto. Padronizar melhora interceptors/pipes/filters.

Confirme se DeleteUserService.execute retorna payload adequado (sem envelope de status) para manter consistência com createNewUser. Se quiser, posso enviar um patch para unificar o controller inteiro.


134-143: Padronização: evitar @Res() aqui também e retornar diretamente

Sugestão para uniformizar com createNewUser.

Aplicável neste bloco:

-  async updatePasswordAfterEmail(
-    @Body() updatePassword: CreatePasswordHashDto,
-    @Res() res: Response,
-  ) {
-    const { data, status } =
-      await this.updatePasswordByEmailService.execute(updatePassword);
-    return res.status(status).send(data);
-  }
+  async updatePasswordAfterEmail(
+    @Body() updatePassword: CreatePasswordHashDto,
+  ) {
+    const { data } =
+      await this.updatePasswordByEmailService.execute(updatePassword);
+    return data;
+  }

106-112: Tipo do arquivo no @UploadedFile

Opcional: tipar como Express.Multer.File para melhor DX.

-    @UploadedFile('file') file,
+    @UploadedFile('file') file: Express.Multer.File,
src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts (2)

9-18: Formato de resposta: alinhar com padrão de outros serviços (status no payload vs. HTTP status)

Aqui o service devolve { status, data }, enquanto outros (ex.: create-user) retornam apenas o objeto. Padronize para evitar tratamentos especiais no controller/caller.

Se decidir mover o status para o controller, o retorno pode ser só data. Posso propor um patch coordenando interface (IJobsResponse) e controller.


15-17: Consistência de idioma nas mensagens

Outros módulos usam pt-BR. Sugestão de ajuste de string.

-        message: 'Logged company jobs listed successfully.',
+        message: 'Vagas da empresa logada listadas com sucesso.',
src/modules/jobs/jobs.controller.ts (2)

15-15: Import de Response fica obsoleto com a refatoração

Se remover @Res, elimine este import.

-import { Response } from 'express';

80-86: Remova o decorator @Res() e retorne apenas o DTO

No método getAllLoggedCompanyJobs em jobs.controller.ts, retire o parâmetro @Res() para que o Nest trate o status e o corpo da resposta. Como o serviço sempre retorna status: 200, basta devolver data.

   async getAllLoggedCompanyJobs(
     @LoggedCompany() company: CompaniesEntity,
-    @Res() res: Response,
   ) {
-    const { data, status } = await this.getAllJobsFromLoggedCompany.execute(
-      company.id,
-    );
-    return res.status(status).send(data);
+    const { data } = await this.getAllJobsFromLoggedCompany.execute(company.id);
+    return data;
   }
src/modules/auth/jwt/jwt.strategy.ts (1)

21-28: Parâmetros JWT fixos em código

Issuer/audience hardcoded dificultam rotação/ambientes. Sugiro mover para config (com defaults).

Exemplo:

super({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  ignoreExpiration: false,
  secretOrKey: jwtSecret,
  algorithms: ['HS256'],
  issuer: this.configService.get<string>('JWT_ISSUER') ?? 'vagas-api',
  audience: this.configService.get<string>('JWT_AUDIENCE') ?? 'vagas-api-users',
});
test/app.e2e-spec.ts (1)

14-31: E2E parcial (sem AppModule); considere um teste E2E “full” com AppModule

Este setup valida o controller/serviço isolados. Para cobrir middlewares/pipes/guards globais, adicione um segundo spec usando imports: [AppModule].

src/modules/auth/services/auth-login.service.ts (4)

21-25: Use igualdade estrita.

Evite coerção implícita no check de tipo.

-    if (type == LoginTypeEnum.COMPANY) {
+    if (type === LoginTypeEnum.COMPANY) {

27-29: Reordene a verificação nula para legibilidade.

Primeiro cheque a existência de info, depois mailConfirm. Semântica idêntica, leitura melhor.

-    if (!info?.mailConfirm || !info) {
+    if (!info || !info.mailConfirm) {

37-41: Evite mutar a entidade e assine o JWT de forma assíncrona.

  • Em vez de delete em múltiplas chaves (potencial efeito colateral em entidades do TypeORM), derive um objeto público por destruturação.
  • Prefira signAsync para não bloquear o event loop.
-    delete info.password;
-    delete info.recoverPasswordToken;
-    delete info.mailConfirm;
-    delete info?.ip;
+    const {
+      password: _password,
+      recoverPasswordToken: _recoverPasswordToken,
+      mailConfirm: _mailConfirm,
+      ip: _ip,
+      ...publicInfo
+    } = info;
@@
-    return {
-      token: this.jwt.sign(jwtPayload),
-      info,
-    };
+    const token = await this.jwt.signAsync(jwtPayload);
+    return {
+      token,
+      info: publicInfo,
+    };

Also applies to: 51-54


19-19: Tipagem explícita para info.

Evite any; use um union das entidades ou um tipo “principal público” já mapeado no projeto.

Posso ajustar para algo como let info: UsersEntity | CompaniesEntity | null; e alinhar com o principal.mapper se desejar.

test/modules/auth/services/auth-login.service-refactored.spec.ts (4)

145-151: Evite chamar service.execute duas vezes no mesmo teste.

Chame uma vez e reutilize a mesma Promise para asserções de tipo e mensagem.

-        await expect(service.execute(loginData)).rejects.toThrow(
-          UnauthorizedException,
-        );
-        await expect(service.execute(loginData)).rejects.toThrow(
-          'E-mail ou Senha não conferem',
-        );
+        const execPromise = service.execute(loginData);
+        await expect(execPromise).rejects.toBeInstanceOf(UnauthorizedException);
+        await expect(execPromise).rejects.toThrow('E-mail ou Senha não conferem');

121-125: Asserte remoção de mailConfirm e payload do JWT.

Garante sanitização e conteúdo do token.

         expect(result.token).toBe('fake-jwt-token');
         expect(result.info).toBeDefined();
         expect(result.info).not.toHaveProperty('password');
         expect(result.info).not.toHaveProperty('recoverPasswordToken');
+        expect(result.info).not.toHaveProperty('mailConfirm');
+        expect(jwtService.sign).toHaveBeenCalledWith(
+          expect.objectContaining({
+            sub: user.id,
+            email: user.email,
+            type: loginData.type,
+          }),
+        );

194-204: Repita as asserções de sanitização e payload no cenário de empresa.

         expect(result.token).toBe('fake-company-jwt-token');
         expect(result.info).toBeDefined();
         expect(result.info).not.toHaveProperty('password');
+        expect(result.info).not.toHaveProperty('mailConfirm');
         expect(companyRepository.findOneByEmail).toHaveBeenCalledWith(
           loginData.email,
         );
         expect(bcryptMock.compare).toHaveBeenCalledWith(
           loginData.password,
           TEST_PASSWORDS.HASHED,
         );
+        expect(jwtService.sign).toHaveBeenCalledWith(
+          expect.objectContaining({
+            sub: company.id,
+            email: company.email,
+            type: loginData.type,
+          }),
+        );

101-105: Cobrir o fluxo de senha inválida.

Falta o caso “usuário existe, mas bcrypt.compare retorna false”. Sugiro adicionar um teste dedicado.

Posso abrir um commit com algo como:

it('should throw UnauthorizedException for wrong password', async () => {
  const loginData = userLoginMock();
  const user = { ...userMock(), password: TEST_PASSWORDS.HASHED, mailConfirm: true } as any;

  userRepository.findOneByEmail!.mockResolvedValue(user);
  bcryptMock.compare.mockResolvedValue(false as never);

  const execPromise = service.execute(loginData);
  await expect(execPromise).rejects.toBeInstanceOf(UnauthorizedException);
  await expect(execPromise).rejects.toThrow('E-mail ou Senha não conferem');

  expect(userRepository.findOneByEmail).toHaveBeenCalledWith(loginData.email);
  expect(jwtService.sign).not.toHaveBeenCalled();
});
test/modules/auth/auth.e2e-spec.ts (3)

21-21: Tipar o mock do serviço para evitar any

Use jest.Mocked<AuthLoginService> para melhor autocompletar e checagem estática.

-  let authLoginService: any;
+  let authLoginService: jest.Mocked<AuthLoginService>;

40-41: Ajustar tipo no moduleFixture.get

Garanta o cast consistente com o tipo mockado.

-    authLoginService = moduleFixture.get<AuthLoginService>(AuthLoginService);
+    authLoginService = moduleFixture.get(AuthLoginService) as jest.Mocked<AuthLoginService>;

113-131: Evitar teste duplicado de 401 com a mesma mensagem

O caso “e-mail não confirmado” repete o cenário de credenciais inválidas com a mesma exceção/mensagem. Sugestões:

  • Diferenciar a mensagem (ex.: “E-mail não confirmado”).
  • Ou simular payload/usuário com mailConfirm: false e garantir que a exceção reflita esse motivo.
test/modules/user/services/create-user.service.spec.ts (2)

70-83: Evitar executar o serviço duas vezes no mesmo teste

Reaproveite a mesma Promise para checar o tipo e a mensagem da exceção sem duplicar chamada.

-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        ConflictException,
-      );
-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        'E-mail já cadastrado',
-      );
+      const act = service.execute(createUserDto, req);
+      await expect(act).rejects.toThrow(ConflictException);
+      await expect(act).rejects.toThrow('E-mail já cadastrado');

91-107: Mesma otimização para cenário de e-mail já usado por empresa

Evita segunda invocação desnecessária ao SUT.

-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        ConflictException,
-      );
-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        'E-mail já cadastrado',
-      );
+      const act = service.execute(createUserDto, req);
+      await expect(act).rejects.toThrow(ConflictException);
+      await expect(act).rejects.toThrow('E-mail já cadastrado');
test/mocks/auth/company.mock.ts (1)

52-60: ID “sobrescrito” é idêntico ao original; sem efeito

O companyWithUnconfirmedEmailMock redefine id para o mesmo valor de TEST_IDS.COMPANY_ID. Use um UUID diferente para evitar colisões em testes.

-    id: '829c7919-583c-40a5-b0ca-137e282345d5',
+    id: '9a9c7919-583c-40a5-b0ca-137e282345d6',
src/modules/company/repository/company.repository.ts (3)

29-42: Tornar a whitelist “const” para tipagem mais segura

Adicionar as const ajuda o TS a restringir valores válidos e evita erros de digitação.

-    const allowedOrderColumns = [
+    const allowedOrderColumns = [
       'id',
       'companyName',
       'email',
       'cnpj',
       'companyType',
       'companySize',
       'uf',
       'companySite',
       'description',
       'created_at',
       'updated_at',
       'mailConfirm',
-    ];
+    ] as const;

21-24: Evitar mutar DTO de entrada em createCompany

Desestruture para um novo objeto antes de salvar.

-    delete data.passwordConfirmation;
-    return this.companyRepository.save(data).catch(handleError);
+    const { passwordConfirmation: _omit, ...toSave } = data as any;
+    return this.companyRepository.save(toSave).catch(handleError);

93-109: Índices recomendados para colunas consultadas com frequência

Considere adicionar índices nas colunas: email, cnpj, recoverPasswordToken para acelerar findOneBy*.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9ff5f74 and c6b7feb.

📒 Files selected for processing (35)
  • src/database/data-source.ts (2 hunks)
  • src/modules/auth/auth.controller.ts (2 hunks)
  • src/modules/auth/jwt/jwt.strategy.ts (1 hunks)
  • src/modules/auth/services/auth-login.service.ts (2 hunks)
  • src/modules/candidacy/service/candidacy.service.spec.ts (1 hunks)
  • src/modules/company/company.controller.ts (3 hunks)
  • src/modules/company/repository/company.repository.ts (1 hunks)
  • src/modules/company/services/delete-company.service.ts (1 hunks)
  • src/modules/jobs/jobs.controller.ts (3 hunks)
  • src/modules/jobs/jobs.module.ts (0 hunks)
  • src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts (1 hunks)
  • src/modules/savedjobs/services/savedjobs.service.spec.ts (1 hunks)
  • src/modules/user/services/create-user.service.ts (3 hunks)
  • src/modules/user/services/update-password-by-email.service.ts (2 hunks)
  • src/modules/user/user.controller.ts (5 hunks)
  • test/app.e2e-spec.ts (1 hunks)
  • test/config/test-constants.ts (1 hunks)
  • test/examples/individual-mocks-guide.spec.ts (1 hunks)
  • test/mocks/auth/company.mock.ts (1 hunks)
  • test/mocks/auth/jwt-payload.mock.ts (1 hunks)
  • test/mocks/auth/user-login.mock.ts (1 hunks)
  • test/mocks/user/create-user.mock.ts (1 hunks)
  • test/mocks/user/get-all-user.mock.ts (1 hunks)
  • test/mocks/user/user-update.mock.ts (1 hunks)
  • test/mocks/user/user.mock.ts (2 hunks)
  • test/modules/auth/auth.e2e-spec.ts (1 hunks)
  • test/modules/auth/controller/auth.controller.spec.ts (1 hunks)
  • test/modules/auth/services/auth-login.service-refactored.spec.ts (1 hunks)
  • test/modules/auth/services/auth-login.service.spec.ts (1 hunks)
  • test/modules/auth/strategy/jwt.strategy.spec.ts (1 hunks)
  • test/modules/user/services/create-user.service.spec.ts (2 hunks)
  • test/modules/user/services/find-all-users.service.spec.ts (1 hunks)
  • test/modules/user/services/recovery-password-by-email.service.spec.ts (3 hunks)
  • test/modules/user/services/update-password-by-email.service.spec.ts (1 hunks)
  • test/modules/user/services/update-user.service.spec.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/modules/jobs/jobs.module.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • test/modules/auth/services/auth-login.service.spec.ts
  • test/mocks/user/create-user.mock.ts
  • test/config/test-constants.ts
  • test/modules/auth/controller/auth.controller.spec.ts
  • test/mocks/auth/jwt-payload.mock.ts
  • test/mocks/user/user-update.mock.ts
  • test/modules/auth/strategy/jwt.strategy.spec.ts
  • test/mocks/user/get-all-user.mock.ts
  • test/modules/user/services/find-all-users.service.spec.ts
  • test/mocks/auth/user-login.mock.ts
  • test/mocks/user/user.mock.ts
  • src/modules/savedjobs/services/savedjobs.service.spec.ts
🧰 Additional context used
🧬 Code graph analysis (13)
src/modules/company/repository/company.repository.ts (9)
src/modules/company/services/delete-company.service.ts (1)
  • Injectable (4-23)
src/modules/auth/jwt/jwt.strategy.ts (1)
  • Injectable (12-59)
src/modules/company/services/activate-company.service.ts (1)
  • Injectable (4-22)
src/modules/company/services/create-company.service.ts (1)
  • Injectable (8-67)
src/modules/company/services/find-all-company.service.ts (1)
  • Injectable (7-18)
src/modules/company/services/update-company.service.ts (1)
  • Injectable (7-42)
src/modules/company/services/recovery-password-by-email.service.ts (1)
  • Injectable (6-44)
src/modules/company/services/update-password-by-email.service.ts (1)
  • Injectable (7-49)
src/modules/company/services/update-password.service.ts (1)
  • Injectable (8-50)
test/examples/individual-mocks-guide.spec.ts (1)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
src/modules/auth/auth.controller.ts (1)
src/modules/auth/dtos/user-login.dto.ts (1)
  • UserLoginDto (12-42)
test/modules/auth/services/auth-login.service-refactored.spec.ts (4)
test/mocks/auth/user-login.mock.ts (4)
  • userLoginMock (5-11)
  • userWithUnconfirmedEmailMock (29-35)
  • invalidUserLoginMock (21-27)
  • companyLoginMock (13-19)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
test/config/test-constants.ts (1)
  • TEST_PASSWORDS (7-47)
test/mocks/auth/company.mock.ts (1)
  • publicCompanyMock (32-50)
src/modules/auth/jwt/jwt.strategy.ts (3)
src/modules/auth/services/auth-login.service.ts (1)
  • Injectable (10-56)
src/modules/company/repository/company.repository.ts (1)
  • Injectable (14-189)
src/modules/auth/utils/principal.mapper.ts (2)
  • mapUserToPrincipal (8-17)
  • mapCompanyToPrincipal (19-29)
test/modules/user/services/update-user.service.spec.ts (3)
test/config/test-constants.ts (1)
  • TEST_USER_DATA (101-107)
test/mocks/user/user.mock.ts (2)
  • userEntityMock (35-48)
  • userMock (10-20)
test/mocks/user/user-update.mock.ts (1)
  • userUpdateMock (7-14)
src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts (1)
src/modules/jobs/interfaces/interfaces.ts (1)
  • IJobsResponse (9-11)
src/modules/company/services/delete-company.service.ts (1)
src/modules/company/repository/company.repository.ts (1)
  • Injectable (14-189)
src/modules/user/user.controller.ts (3)
src/modules/user/dtos/create-user.dto.ts (1)
  • CreateUserDto (16-64)
src/modules/auth/decorator/logged-admin.decorator.ts (1)
  • LoggedAdmin (7-18)
src/modules/company/company.controller.ts (1)
  • updatePassword (127-137)
test/modules/auth/auth.e2e-spec.ts (4)
test/mocks/auth/user-login.mock.ts (3)
  • userLoginMock (5-11)
  • companyLoginMock (13-19)
  • invalidUserLoginMock (21-27)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
test/mocks/auth/company.mock.ts (1)
  • publicCompanyMock (32-50)
test/config/test-constants.ts (2)
  • TEST_PASSWORDS (7-47)
  • TEST_EMAILS (85-91)
test/mocks/auth/company.mock.ts (1)
test/config/test-constants.ts (5)
  • TEST_IDS (61-64)
  • TEST_COMPANY_DATA (94-98)
  • TEST_EMAILS (85-91)
  • TEST_PASSWORDS (7-47)
  • TEST_USER_DATA (101-107)
test/modules/user/services/create-user.service.spec.ts (3)
test/config/test-constants.ts (3)
  • TEST_IPS (71-82)
  • TEST_EMAILS (85-91)
  • TEST_PASSWORDS (7-47)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
test/mocks/user/create-user.mock.ts (1)
  • createUserMock (9-17)
test/modules/user/services/update-password-by-email.service.spec.ts (2)
test/config/test-constants.ts (2)
  • TEST_PASSWORDS (7-47)
  • TEST_IDS (61-64)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
🔇 Additional comments (28)
src/modules/user/services/update-password-by-email.service.ts (1)

1-5: Boa troca para exceções HTTP do Nest.

Alinha o serviço ao padrão de erro do framework e simplifica os testes.

test/modules/user/services/update-password-by-email.service.spec.ts (2)

9-16: Boas fábricas de mocks.

Clareza e menor boilerplate nos testes.


23-43: Configuração de módulo de teste está enxuta e correta.

DI com useValue facilita inspeção das chamadas.

src/modules/candidacy/service/candidacy.service.spec.ts (4)

15-15: Tipagem do mock na suíte está correta.

Mantém verificação estática suficiente sem over-mock.


18-18: Recriação do mock por teste — boa prática.

Evita vazamento de estado entre testes.


21-27: Injeção via useValue do repositório mock — alinhada ao padrão Nest.

Configuração simples e clara do provider para o service sob teste.


2-3: Aprovado — injeção confirmada por classe
Confirmado que o CandidacyService injeta CandidacyRepository diretamente no construtor (src/modules/candidacy/service/candidacy.service.ts:13).

test/modules/user/services/recovery-password-by-email.service.spec.ts (1)

25-27: beforeEach isolado com fábricas — bom.

Garantia de estado limpo a cada teste.

test/examples/individual-mocks-guide.spec.ts (1)

20-33: LGTM no fluxo “find user”

Caminho feliz e asserts de argumentos/chamadas estão corretos.

src/database/data-source.ts (4)

1-1: dotenv carregado cedo — ok.

Boa prática garantir o carregamento das envs no topo.


24-45: Validação robusta de porta — ok.

Boa mensagem de erro e checagem de faixa 1–65535.


47-66: Validação de envs PostgreSQL — ok.

Condições claras e erro acionável. Sem solicitações.


122-124: Instanciação do DataSource — ok.

Export público preservado, sem breaking change.

src/modules/user/user.controller.ts (1)

98-98: getOneUser: troca de ordem dos parâmetros está OK; uso de @LoggedAdmin coerente

Sem impactos funcionais.

src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts (2)

3-3: Import de IJobsResponse — OK

Coerente com a assinatura do service.


7-7: Remoção do CompanyRepository do construtor — ótimo

DI mais enxuta e sem dependência não utilizada.

src/modules/jobs/jobs.controller.ts (1)

49-49: Injeção do serviço OK

A dependência GetAllJobsFromLoggedCompanyService no construtor está correta.

src/modules/auth/jwt/jwt.strategy.ts (1)

19-19: Uso de getOrThrow('JWT_SECRET') é acertado

Boa prática: falha cedo quando a env está ausente.

src/modules/company/company.controller.ts (2)

70-73: Refator leve no create: OK

Desestruturação { data, status } e retorno via res mantêm comportamento.


118-121: Refator leve no update-password-by-email: OK

Padrão consistente com os demais handlers do controller.

src/modules/auth/auth.controller.ts (1)

26-29: Remoção do @Res e uso de @HttpCode(200): alinhado ao Nest

Melhora testabilidade e reaproveita pipes/interceptors globais. Boa.

test/app.e2e-spec.ts (2)

37-41: Fechamento do app no afterEach: bom cuidado

Evita vazamento de handles no Jest.


47-51: Asserções mais ricas no GET /

Cobrem conteúdo relevante da landing. LGTM.

src/modules/auth/services/auth-login.service.ts (3)

2-2: Import renomeado está correto.

Caminho atualizado para company.repository consistente com a padronização do PR.


4-4: Uso de UnauthorizedException é apropriado.

Unifica a semântica de erro para credenciais inválidas.


34-35: Boa prática: mensagem de erro genérica.

Mantém a resposta indistinguível para e-mail inexistente, não confirmado ou senha inválida.

src/modules/company/repository/company.repository.ts (2)

26-64: Boa correção: whitelist no orderBy + getManyAndCount

Mitiga injeção em coluna de ordenação e reduz roundtrips ao banco.


1-189: Não há referências a company_repository; convenção está consistente
A busca por this.company_repository em todo o projeto não retornou nenhum resultado, indicando que todas as injeções usam corretamente companyRepository.

Likely an incorrect or invalid review comment.

Comment thread src/database/data-source.ts
Comment thread src/database/data-source.ts
Comment thread src/modules/auth/jwt/jwt.strategy.ts
Comment thread src/modules/company/company.controller.ts Outdated
Comment thread src/modules/user/services/create-user.service.ts
Comment thread test/mocks/auth/company.mock.ts
Comment thread test/mocks/auth/company.mock.ts
Comment thread test/modules/user/services/create-user.service.spec.ts
Comment thread test/modules/user/services/update-user.service.spec.ts
Comment thread test/modules/user/services/update-user.service.spec.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
package.json (1)

51-51: Dependência circular/ inválida: "linkedin-backend": "file:".

O pacote depende de si mesmo (mesmo name) via file: sem caminho, o que pode quebrar a instalação ou gerar comportamento indefinido. Remova essa entrada.

-    "linkedin-backend": "file:",
src/modules/user/services/update-user.service.ts (1)

22-27: Evite perda do arquivo antigo se o upload falhar: faça upload primeiro e delete depois.

Hoje, se deleteFile suceder e upload falhar, você perde o arquivo anterior. Sugestão:

-    if (file) {
-      await this.fileUploadService.deleteFile(data?.profileKey);
-      const { Location, key } = await this.fileUploadService.upload(file);
-      data.profile = Location;
-      data.profileKey = key;
-    }
+    if (file) {
+      const oldKey = data?.profileKey;
+      const { Location, key } = await this.fileUploadService.upload(file);
+      data.profile = Location;
+      data.profileKey = key;
+      if (oldKey) {
+        await this.fileUploadService.deleteFile(oldKey);
+      }
+    }
src/modules/company/services/create-company.service.ts (2)

28-35: Status code para email já cadastrado deve ser 409 (Conflict), não 404.

A verificação prévia já identifica conflito de unicidade. Sugestão:

-      return {
-        status: 404,
+      return {
+        status: 409,
         data: {
           message: 'E-mail já cadastrado',
         },
       };

46-55: Status code para CNPJ duplicado também deve ser 409 (Conflict).

Mantém consistência com o caso de email:

-      return {
-        status: 404,
+      return {
+        status: 409,
         data: {
           message: `This CNPJ is already in use`,
         },
       };
src/modules/company/company.controller.ts (1)

87-90: Corrigir uso de @UploadedFile: não recebe string; tipar o arquivo

@UploadedFile('file') é inválido. O nome do campo já vem do FileInterceptor('file'). Ajuste para:

-    @UploadedFile('file') file,
+    @UploadedFile() file: Express.Multer.File,
♻️ Duplicate comments (5)
src/database/data-source.ts (1)

13-17: Documente e propague novas variáveis DB_ no .env.example e CI.*

As chaves DB_TYPE, DB_DATABASE, DB_SYNCHRONIZE e DB_SSL_REJECT_UNAUTHORIZED precisam estar no .env.example e definidas nos pipelines de CI/testes para evitar quebras.

Sugestão de checagem:

#!/bin/bash
set -euo pipefail
echo "Verificando DB_* no .env.example"
rg -n -H -C0 -g ".env.example" -e '^(DB_TYPE|DB_DATABASE|DB_SYNCHRONIZE|DB_SSL_REJECT_UNAUTHORIZED)=' || echo "⚠️  Faltam chaves DB_* no .env.example"
src/modules/auth/jwt/jwt.strategy.ts (1)

31-70: Ótima correção: validação vinculando sub + type e checagem de e-mail

A validação agora consulta por ID de acordo com o tipo e confirma o e-mail, eliminando a vulnerabilidade de resolver tokens entre usuário/empresa e garantindo invalidação após troca de e-mail. Boa!

test/modules/user/services/update-user.service.spec.ts (1)

27-38: LGTM: serviço corretamente registrado em providers

O ponto levantado no review anterior foi endereçado: UpdateUserService está em providers e controllers está vazio. Boa!

test/modules/user/services/create-user.service.spec.ts (1)

35-52: LGTM: DI do módulo de teste acertada

CreateUserService em providers e controllers: []. Alinha com as boas práticas de Nest.

test/mocks/auth/company.mock.ts (1)

9-18: LGTM: fallback de hash bcrypt estável

Boa adoção de um hash estático para ambientes sem TEST_HASHED_BCRYPT_PASSWORD. Endereça o risco apontado anteriormente.

🧹 Nitpick comments (38)
package.json (4)

19-21: Simplifique scripts de teste; remover node -r tsconfig-paths/register node_modules/.bin/jest.

  • moduleNameMapper já resolve src/... no Jest; o tsconfig-paths/register aqui é redundante.
  • Invocar node_modules/.bin/jest diretamente reduz portabilidade; prefira apenas jest.
-    "test:watch": "node -r tsconfig-paths/register node_modules/.bin/jest --watch",
-    "test:cov": "node -r tsconfig-paths/register node_modules/.bin/jest --coverage",
+    "test:watch": "jest --watch",
+    "test:cov": "jest --coverage",

101-103: Restringir o transform do Jest apenas para TypeScript.

Transformar .js via ts-jest é desnecessário e pode degradar performance. Foque em .ts.

-    "transform": {
-      "^.+\\.(t|j)s$": "ts-jest"
-    },
+    "transform": {
+      "^.+\\.ts$": "ts-jest"
+    },

100-100: Confirme se somente *.spec.ts deve rodar.

Se também houver *.test.ts, ajuste o regex.

-    "testRegex": ".*\\.spec\\.ts$",
+    "testRegex": ".*\\.(spec|test)\\.ts$",

50-50: Remover dependência suspeita/dispensável "install".

O pacote install é frequentemente desnecessário e já houve histórico de confusão/supply-chain. Se não é usado, elimine.

-    "install": "^0.13.0",
src/database/data-source.ts (5)

24-45: Porta do banco: trate string vazia como “não informada”.

Hoje "" lança erro; recomendo cair no defaultPort quando vazia (comportamento mais tolerante e consistente com “não definida”).

Aplicar:

   if (portString === undefined) return defaultPort;

   const trimmed = portString.trim();
-  if (trimmed.length === 0 || !/^\d+$/.test(trimmed)) {
-    throw new TypeError(
-      `Invalid database port: "${portString}". Port must be an integer between 1 and 65535.`,
-    );
-  }
+  if (trimmed.length === 0) return defaultPort;
+  if (!/^\d+$/.test(trimmed)) {
+    throw new TypeError(
+      `Invalid database port: "${portString}". Port must be an integer between 1 and 65535.`,
+    );
+  }

47-66: Validação de envs do Postgres — considere incluir TYPEORM_PORT opcionalmente.

Se a intenção for exigir porta customizada, adicione TYPEORM_PORT aos obrigatórios; se não, mantenha fora para permitir o defaultPort.

Exemplo (somente se desejarem exigir a porta):

-  const requiredVars = [
+  const requiredVars = [
     'TYPEORM_HOST',
     'TYPEORM_USERNAME',
     'TYPEORM_PASSWORD',
     'TYPEORM_DATABASE',
-  ];
+    // 'TYPEORM_PORT', // descomente se quiser tornar obrigatório
+  ];

68-95: SSL/TLS: tipar retorno e usar ssl: true quando sem CA (limpa any).

Funciona como está, mas dá pra evitar any e deixar explícito o fallback para CAs do sistema.

Aplicar:

+import type { ConnectionOptions as TLSConnectionOptions } from 'node:tls';
@@
-const createSSLConfig = () => {
+const createSSLConfig = (): boolean | TLSConnectionOptions | undefined => {
   if (NODE_ENV !== 'production') return undefined;

   const normalizedCACert = normalizeCACert(CA_CERT);

   const rejectUnauthorized =
     DB_SSL_REJECT_UNAUTHORIZED === 'false' || DB_SSL_REJECT_UNAUTHORIZED === '0'
       ? false
       : true;

   if (!rejectUnauthorized) {
     console.warn(
       '⚠️  WARNING: Database SSL is configured with rejectUnauthorized: false. ' +
         'This setting bypasses certificate validation and should only be used in development. ' +
         'Set DB_SSL_REJECT_UNAUTHORIZED=true or remove this environment variable for secure production deployment.',
     );
   }
-
-  const sslConfig: any = {
-    rejectUnauthorized,
-  };
-
-  if (normalizedCACert) {
-    sslConfig.ca = normalizedCACert;
-  }
-
-  return sslConfig;
+  // Sem CA explícito: usa CAs do sistema; com CA: objeto detalhado
+  return normalizedCACert
+    ? ({ ca: normalizedCACert, rejectUnauthorized } as TLSConnectionOptions)
+    : (rejectUnauthorized ? true : ({ rejectUnauthorized } as TLSConnectionOptions));
 };

100-110: Semântica de isDevelopment.

NODE_ENV === 'development' || NODE_ENV !== 'production' é equivalente a “não produção”. Simplifique e use um nome que reflita isso.

Aplicar (e referenciar nas funções abaixo):

-const getEntityPaths = () => {
-  return isDevelopment
+const getEntityPaths = () => {
+  return isNonProduction
     ? ['src/database/entities/*.entity.ts']
     : ['dist/database/entities/*.entity.js'];
 };
 
-const getMigrationPaths = () => {
-  return isDevelopment
+const getMigrationPaths = () => {
+  return isNonProduction
     ? ['src/database/migrations/*.ts', 'src/database/migrations/seeds/*.ts']
     : ['dist/database/migrations/*.js', 'dist/database/migrations/seeds/*.js'];
 };

97-99: Detectar ambiente de teste também por NODE_ENV
O isTestEnvironment só dispara quando DB_TYPE==='sqlite' && DB_DATABASE===':memory:', mas não há definição dessas variáveis nos testes (nenhum .env* ou config do Jest), então em npm test ele fica sempre false. Sugiro:

-const isTestEnvironment = DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:';
+const isTestEnvironment =
+  NODE_ENV === 'test' || (DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:');

Além disso, const isDevelopment = NODE_ENV === 'development' || NODE_ENV !== 'production'; avalia true para qualquer NODE_ENV exceto production, tornando o nome impreciso. Use const isDevelopment = NODE_ENV === 'development' ou renomeie para isNonProduction.

src/modules/user/services/update-user.service.ts (1)

15-16: Tipagem do parâmetro file (qualidade e DX).

Considere tipar como file?: Express.Multer.File para ganhar autocompletar e validação estática.

src/modules/company/services/create-company.service.ts (3)

19-27: Ótimo: normalização de email antes de consultar/persistir.

Para evitar repetição, extraia a normalização para um helper compartilhado (ex.: normalizeEmail(email: string)), reutilizando em outros fluxos (update, auth, etc.).


37-44: Escolha status mais apropriado para senha divergente (400/422).

404 não é semântico para validação. Sugestão usar 400 (Bad Request) ou 422:

-      return {
-        status: 404,
+      return {
+        status: 400,
         data: {
           message: 'As senhas precisam ser idênticas',
         },
       };

52-53: Consistência de idioma nas mensagens.

As demais mensagens estão em PT-BR. Considere padronizar: "Este CNPJ já está em uso".

test/shared/repository-mocks.ts (2)

22-39: Padronize nomenclatura dos métodos (consistência “findAllCompany”)

Se no código de produção a listagem é pluralizada (ex.: findAllCompanies), alinhe aqui para evitar confusão futura. Se o nome singular for o oficial no repositório, ignore.


41-47: Considere mocks estritos tipados ou jest-mock-extended

Para pegar incoerências de assinatura em compile-time, você pode:

  • Tipar os jest.fn com generics (retorno/parâmetros), ou
  • Usar jest-mock-extended e criar mockDeep<JwtService>()/mock<JwtService>().

Exemplo com generics mínimos:

-export const createJwtServiceMock = (): jest.Mocked<Partial<JwtService>> => ({
-  sign: jest.fn(),
-  signAsync: jest.fn(),
-  verify: jest.fn(),
-  verifyAsync: jest.fn(),
-  decode: jest.fn(),
-});
+export const createJwtServiceMock = (): jest.Mocked<Partial<JwtService>> => ({
+  sign: jest.fn<(payload: any, options?: any) => string>(),
+  signAsync: jest.fn<(payload: any, options?: any) => Promise<string>>(),
+  verify: jest.fn<(token: string, options?: any) => any>(),
+  verifyAsync: jest.fn<(token: string, options?: any) => Promise<any>>(),
+  decode: jest.fn<(token: string, options?: any) => string | object | null>(),
+});
src/modules/company/company.controller.ts (3)

81-86: Documente upload no Swagger (content-type e schema)

Adicione @ApiConsumes('multipart/form-data') e um @ApiBody com o campo file para refletir o upload:

 @UseGuards(AuthGuard())
 @UseInterceptors(FileInterceptor('file'))
+@ApiConsumes('multipart/form-data')
+@ApiBody({
+  schema: {
+    type: 'object',
+    properties: { file: { type: 'string', format: 'binary' } },
+  },
+})
 @Put('edit')

Não esqueça de incluir ApiConsumes/ApiBody no import de @nestjs/swagger.


84-86: Rota semântica: considere PUT /company/me em vez de edit

Mantém consistência com DELETE /company/me e evita verbos no path.


138-143: Confirme requisitos de autorização no activateCompany

Se a ativação não for pública, adicione @UseGuards(AuthGuard()) e @ApiBearerAuth(). Se o id for UUID, use ParseUUIDPipe:

-  async activateCompany(@Param('id') id: string, @Res() res: Response) {
+  @UseGuards(AuthGuard())
+  @ApiBearerAuth()
+  async activateCompany(@Param('id', new ParseUUIDPipe()) id: string, @Res() res: Response) {
src/modules/user/services/create-user.service.ts (4)

25-30: Paralelize as checagens de e-mail para reduzir latência

As duas consultas são independentes; use Promise.all:

-    const emailAlreadyInUseCompany =
-      await this.companyRepository.findOneByEmail(normalizedEmail);
-
-    const emailAlreadyInUseUser =
-      await this.userRepository.findOneByEmail(normalizedEmail);
+    const [emailAlreadyInUseCompany, emailAlreadyInUseUser] = await Promise.all([
+      this.companyRepository.findOneByEmail(normalizedEmail),
+      this.userRepository.findOneByEmail(normalizedEmail),
+    ]);

35-39: Evite mutar o DTO de entrada

Crie um payload próprio; melhora previsibilidade e typing.

-    data.password = await bcrypt.hash(password, 10);
-    data.email = normalizedEmail; // Use normalized email for insert
-
-    delete data.confirmPassword;
+    const createPayload = {
+      ...data,
+      email: normalizedEmail,
+      password: await bcrypt.hash(password, 10),
+    };
+    delete (createPayload as any).confirmPassword;

Ajuste a chamada posterior para usar createPayload.


40-56: Não deixe falha no envio de e-mail bloquear criação de usuário

O try/catch engloba também o .sendUserCreationConfirmation(...). Se o e-mail falhar, a conta não será criada. Sugestão: trate erro de DB separado do envio, e tolere (ou logue) falha de e-mail.

-    try {
-      const response = await this.userRepository.createUser(data);
-
-      delete response.password;
-      delete response.recoverPasswordToken;
-      delete response.ip;
-
-      await this.mailService.sendUserCreationConfirmation(response);
-
-      return response;
-    } catch (error) {
+    let response;
+    try {
+      response = await this.userRepository.createUser(createPayload);
+      delete response.password;
+      delete response.recoverPasswordToken;
+      delete response.ip;
+    } catch (error) {
       if (error.code === '23505') {
         throw new ConflictException('E-mail já cadastrado');
       }
       throw error;
     }
+    try {
+      await this.mailService.sendUserCreationConfirmation(response);
+    } catch {
+      // log opcional: falha no envio de e-mail não impede o sucesso do signup
+    }
+    return response;

Você prefere que o signup falhe se o e-mail não for enviado, ou devemos apenas logar?


51-54: Generalize detecção de violação de unicidade (Postgres x SQLite)

Em testes/E2E com SQLite, o código de erro não é '23505'. Considere um helper isUniqueViolation(err) que cheque code/errno/message para PG e SQLite.

Posso enviar um util isUniqueViolation e ajustar os catch?

src/modules/auth/jwt/jwt.strategy.ts (2)

54-55: Comparação de e-mail deve ser case-insensitive

E-mails normalmente são tratados como case-insensitive; normalizar reduz falsos negativos na validação.

-        if (user.email !== payload.email) {
+        if (user.email.toLowerCase() !== payload.email.toLowerCase()) {
           throw new UnauthorizedException('Email mismatch');
         }
...
-        if (company.email !== payload.email) {
+        if (company.email.toLowerCase() !== payload.email.toLowerCase()) {
           throw new UnauthorizedException('Email mismatch');
         }

Also applies to: 66-67


74-79: Preserve a causa do erro para observabilidade

Encapsular exceções desconhecidas como 401 perde contexto. Preserve a causa (Nest >=10) ou logue.

-      throw new UnauthorizedException('Authentication failed');
+      throw new UnauthorizedException('Authentication failed', { cause: error as Error });

Se a versão do Nest não suportar cause, use Logger.debug/Logger.error com error.

test/modules/auth/strategy/jwt.strategy.spec.ts (5)

65-107: Garanta que apenas o repositório do ramo USER seja consultado

Valida o caminho feliz de USER; adicione assert para garantir que o repositório de empresas não foi tocado.

       expect(result).toEqual(expectedUserPrincipal);
       expect(userRepository.findOneById).toHaveBeenCalledWith(payload.sub);
+      expect(companyRepository.findOneById).not.toHaveBeenCalled();

109-136: Faça o espelho para COMPANY: repositório de usuários não deve ser chamado

Mantém o teste mais estrito quanto ao fluxo.

       expect(result).toEqual(expectedCompanyPrincipal);
       expect(companyRepository.findOneById).toHaveBeenCalledWith(payload.sub);
+      expect(userRepository.findOneById).not.toHaveBeenCalled();

150-201: Evite dependência da ordem de propriedades

Comparar Object.keys diretamente é frágil; ordene as chaves ou use toMatchObject.

-      const actualUserFields = Object.keys(userResult);
-
-      expect(actualUserFields).toEqual(expectedUserFields);
+      const actualUserFields = Object.keys(userResult).sort();
+      expect(actualUserFields).toEqual([...expectedUserFields].sort());

225-235: Mesmo ajuste para COMPANY: compare conjuntos de campos, não ordem

Mantém os testes estáveis a mudanças inocentes de ordem.

-      const actualCompanyFields = Object.keys(companyResult);
-      expect(actualCompanyFields).toEqual(expectedCompanyFields);
+      const actualCompanyFields = Object.keys(companyResult).sort();
+      expect(actualCompanyFields).toEqual([...expectedCompanyFields].sort());

243-262: Cobrir cenários negativos adicionais (tipo inválido, campos vazios, mismatch de e-mail e empresa inexistente)

A suíte já cobre ausência de campos e usuário inexistente. Sugiro incluir casos abaixo para fortalecer a estratégia.

   describe('validate', () => {
@@
     it('should throw UnauthorizedException for undefined payload', async () => {
       await expect(strategy.validate(undefined as any)).rejects.toThrow(
         'Invalid payload: missing required fields',
       );
 
       expect(userRepository.findOneById).not.toHaveBeenCalled();
       expect(companyRepository.findOneById).not.toHaveBeenCalled();
     });
+
+    it('should throw UnauthorizedException for invalid payload type', async () => {
+      const payload: any = { sub: 'id', email: TEST_EMAILS.DEFAULT_USER, type: 'ADMIN' };
+      await expect(strategy.validate(payload)).rejects.toThrow('Invalid payload type');
+      expect(userRepository.findOneById).not.toHaveBeenCalled();
+      expect(companyRepository.findOneById).not.toHaveBeenCalled();
+    });
+
+    it('should throw UnauthorizedException when company is not found', async () => {
+      const payload = companyJwtPayloadMock();
+      companyRepository.findOneById!.mockResolvedValue(null as any);
+      await expect(strategy.validate(payload)).rejects.toThrow('Company not found');
+      expect(companyRepository.findOneById).toHaveBeenCalledWith(payload.sub);
+    });
+
+    it('should throw UnauthorizedException on email mismatch (USER)', async () => {
+      const payload = jwtPayloadMock();
+      userRepository.findOneById!.mockResolvedValue({
+        id: payload.sub,
+        name: TEST_USER_DATA.NAME,
+        email: 'different+' + TEST_EMAILS.DEFAULT_USER,
+        type: 'USER',
+        phone: TEST_USER_DATA.PHONE,
+      } as any);
+      await expect(strategy.validate(payload)).rejects.toThrow('Email mismatch');
+    });
   });
test/config/test-constants.ts (3)

32-41: Evite lançar erro na importação do módulo de constantes

Lançar no carregamento do módulo torna a suíte frágil. Prefira getter que só lança quando a constante é usada.

-  // Senha hasheada com bcrypt simulada
-  // Requer um hash bcrypt válido; sem fallback para evitar testes frágeis
-  HASHED_BCRYPT:
-    process.env.TEST_HASHED_BCRYPT_PASSWORD ??
-    (() => {
-      throw new Error(
-        'Defina TEST_HASHED_BCRYPT_PASSWORD com um hash bcrypt válido (ex.: $2b$10$..., 60 chars).',
-      );
-    })(),
+  // Senha hasheada com bcrypt simulada
+  // Getter: só valida/lança quando alguém de fato consome a constante
+  get HASHED_BCRYPT(): string {
+    const v = process.env.TEST_HASHED_BCRYPT_PASSWORD;
+    if (!v) {
+      throw new Error(
+        'Defina TEST_HASHED_BCRYPT_PASSWORD com um hash bcrypt válido (ex.: $2b$10$..., 60 chars).'
+      );
+    }
+    return v;
+  },

55-61: Evite colisão semântica entre RECOVERY_TOKEN e IDs

O valor atual de RECOVERY_TOKEN é um UUID semelhante aos IDs de teste; pode confundir leituras/logs. Sugestão: use um UUID distinto.

   RECOVERY_TOKEN:
-    process.env.TEST_RECOVERY_TOKEN || '729c7919-583c-40a5-b0ca-137e282345d4',
+    process.env.TEST_RECOVERY_TOKEN || 'd6d4c2b1-1f9a-4c77-9a2e-3b6c9c2a5f11',

69-85: Adicionar exemplo IPv6 reservado (RFC 3849) pode ajudar testes

Opcional incluir 2001:db8::1 para cenários IPv6, mantendo o caráter seguro/documental.

Exemplo:

// RFC 3849 - prefixo 2001:db8::/32 reservado para documentação
IPV6_EXAMPLE: process.env.TEST_IP_IPV6_EXAMPLE || '2001:db8::1',
test/modules/user/services/update-user.service.spec.ts (2)

75-82: Evitar acessar propriedade privada do service nos testes

Ao invés de service['fileUploadService'], use o provider mock já injetado (mais claro e estável).

Aplique:

-      const fileUploadService = service['fileUploadService'];
-      fileUploadService.deleteFile = jest.fn().mockResolvedValue(undefined);
-      fileUploadService.upload = jest.fn().mockResolvedValue({
+      fileUploadService.deleteFile!.mockResolvedValue(undefined);
+      fileUploadService.upload!.mockResolvedValue({
         Location: 'https://s3.bucket.com/file.jpg',
         key: 'uploads/file.jpg',
       });

Also applies to: 85-87


51-53: Reduzir uso redundante de jest.spyOn sobre funções já mockadas

Os métodos do repositório já são jest.fn() via factory; pode-se assertar direto, evitando spyOn.

Exemplo (primeiro teste):

-      const updateUserSpy = jest.spyOn(userRepository, 'updateUser');
+      const updateUserMock = userRepository.updateUser as jest.Mock;
...
-      expect(updateUserSpy).not.toHaveBeenCalled();
+      expect(updateUserMock).not.toHaveBeenCalled();

Repita nos demais testes onde spyOn é aplicado ao mesmo mock.

Also applies to: 72-73, 84-85, 111-113, 117-118, 133-135, 139-145

test/modules/user/services/create-user.service.spec.ts (3)

72-77: Evitar chamar o service duas vezes no mesmo caso e assertar a mensagem corretamente

O teste chama service.execute duas vezes. Além disso, para mensagens de HttpException, é mais robusto validar o payload.

Aplique:

-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        ConflictException,
-      );
-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        'E-mail já cadastrado',
-      );
+      await expect(service.execute(createUserDto, req)).rejects.toMatchObject({
+        response: expect.objectContaining({ message: 'E-mail já cadastrado' }),
+      });

96-101: Mesma melhoria no caso de e-mail já existente na empresa

Consolidar numa única asserção com o payload da exceção.

Aplique:

-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        ConflictException,
-      );
-      await expect(service.execute(createUserDto, req)).rejects.toThrow(
-        'E-mail já cadastrado',
-      );
+      await expect(service.execute(createUserDto, req)).rejects.toMatchObject({
+        response: expect.objectContaining({ message: 'E-mail já cadastrado' }),
+      });

138-145: Fortalecer a asserção de hash de senha

Se o service hashea a senha, considerar validar o formato de hash (ex.: bcrypt) em vez de expect.any(String).

Sugestão:

-      expect(userRepository.createUser).toHaveBeenCalledWith({
+      expect(userRepository.createUser).toHaveBeenCalledWith({
         ...createUserDto,
         ip: TEST_IPS.LOCALHOST,
-        password: expect.any(String),
+        password: expect.stringMatching(/^\$2[aby]\$\d{2}\$[./A-Za-z0-9]{53}$/),
       });
test/mocks/auth/company.mock.ts (1)

20-41: Consistência de campos e mocks públicos/privados

  • Uso de companySite está alinhado com a entidade/mapper.
  • publicCompanyMock omite password e recoverPasswordToken. Ótimo.

Pequeno cuidado: este fallback não evita um eventual throw de import no próprio test-constants.ts se a env não estiver definida globalmente. Se for um problema fora do CI, vale mover o throw para runtime ou prover um fallback lá também.

Posso abrir um PR para ajustar test/config/test-constants.ts e remover o throw em tempo de import (substituindo por fallback + warning)?

Also applies to: 43-61

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 276af93 and 098a8ac.

📒 Files selected for processing (27)
  • package.json (2 hunks)
  • src/app.controller.ts (1 hunks)
  • src/app.module.ts (1 hunks)
  • src/app.service.ts (1 hunks)
  • src/database/data-source.ts (2 hunks)
  • src/database/entities/applications.entity.ts (1 hunks)
  • src/database/entities/comments.entity.ts (1 hunks)
  • src/database/entities/companies.entity.ts (1 hunks)
  • src/database/entities/curriculum.entity.ts (1 hunks)
  • src/database/entities/users.entity.ts (1 hunks)
  • src/database/migrations/1753987121418-InitialSchema.ts (1 hunks)
  • src/database/migrations/1757172957047-AddUniqueConstraintToCompaniesEmail.ts (1 hunks)
  • src/modules/auth/jwt/jwt.strategy.ts (1 hunks)
  • src/modules/company/company.controller.ts (3 hunks)
  • src/modules/company/services/create-company.service.ts (3 hunks)
  • src/modules/user/services/create-user.service.ts (2 hunks)
  • src/modules/user/services/update-user.service.ts (2 hunks)
  • test/config/test-constants.ts (1 hunks)
  • test/mocks/auth/company.mock.ts (1 hunks)
  • test/modules/auth/services/auth-login.service.spec.ts (1 hunks)
  • test/modules/auth/strategy/jwt.strategy.spec.ts (1 hunks)
  • test/modules/user/services/create-user.service.spec.ts (2 hunks)
  • test/modules/user/services/find-all-users.service.spec.ts (1 hunks)
  • test/modules/user/services/recovery-password-by-email.service.spec.ts (3 hunks)
  • test/modules/user/services/update-password-by-email.service.spec.ts (1 hunks)
  • test/modules/user/services/update-user.service.spec.ts (1 hunks)
  • test/shared/repository-mocks.ts (1 hunks)
✅ Files skipped from review due to trivial changes (8)
  • src/database/entities/curriculum.entity.ts
  • src/app.module.ts
  • src/database/entities/comments.entity.ts
  • src/database/migrations/1753987121418-InitialSchema.ts
  • src/database/entities/users.entity.ts
  • src/app.controller.ts
  • src/app.service.ts
  • src/database/entities/applications.entity.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/modules/auth/services/auth-login.service.spec.ts
  • test/modules/user/services/find-all-users.service.spec.ts
  • test/modules/user/services/update-password-by-email.service.spec.ts
  • test/modules/user/services/recovery-password-by-email.service.spec.ts
🧰 Additional context used
🧬 Code graph analysis (6)
test/modules/user/services/update-user.service.spec.ts (4)
test/shared/repository-mocks.ts (1)
  • createUserRepositoryMock (5-20)
test/config/test-constants.ts (1)
  • TEST_USER_DATA (104-110)
test/mocks/user/user.mock.ts (2)
  • userEntityMock (35-48)
  • userMock (10-20)
test/mocks/user/user-update.mock.ts (1)
  • userUpdateMock (7-14)
src/modules/company/company.controller.ts (3)
src/modules/user/user.controller.ts (1)
  • updatePassword (149-160)
src/shared/Swagger/decorators/company/delete-company-by-id.swagger.ts (1)
  • DeleteCompanyByIdSwagger (7-28)
src/modules/auth/decorator/logged-company.decorator.ts (1)
  • LoggedCompany (7-20)
test/mocks/auth/company.mock.ts (1)
test/config/test-constants.ts (5)
  • TEST_PASSWORDS (10-50)
  • TEST_IDS (64-67)
  • TEST_COMPANY_DATA (97-101)
  • TEST_EMAILS (88-94)
  • TEST_USER_DATA (104-110)
src/modules/auth/jwt/jwt.strategy.ts (1)
src/modules/auth/utils/principal.mapper.ts (2)
  • mapUserToPrincipal (8-17)
  • mapCompanyToPrincipal (19-29)
test/modules/auth/strategy/jwt.strategy.spec.ts (3)
test/config/test-constants.ts (4)
  • TEST_TOKENS (53-61)
  • TEST_EMAILS (88-94)
  • TEST_PASSWORDS (10-50)
  • TEST_USER_DATA (104-110)
test/shared/repository-mocks.ts (2)
  • createUserRepositoryMock (5-20)
  • createCompanyRepositoryMock (22-39)
test/mocks/auth/jwt-payload.mock.ts (2)
  • jwtPayloadMock (3-11)
  • companyJwtPayloadMock (13-21)
test/modules/user/services/create-user.service.spec.ts (4)
test/config/test-constants.ts (3)
  • TEST_IPS (74-85)
  • TEST_EMAILS (88-94)
  • TEST_PASSWORDS (10-50)
test/shared/repository-mocks.ts (2)
  • createUserRepositoryMock (5-20)
  • createCompanyRepositoryMock (22-39)
test/mocks/user/user.mock.ts (1)
  • userMock (10-20)
test/mocks/user/create-user.mock.ts (1)
  • createUserMock (9-17)
🔇 Additional comments (13)
package.json (1)

109-111: Garanta mapeamento idêntico no Jest E2E.

O moduleNameMapper cobre src/... aqui; verifique se o test/jest-e2e.json tem o mesmo mapeamento para evitar falhas de import nos testes E2E.

src/database/data-source.ts (5)

1-1: Carga antecipada de env via dotenv/config — ok.

Boa prática para inicializar as variáveis antes de qualquer leitura.


19-22: Normalização do CA do SSL — ok.

Conversão de \\n\n evita erros comuns com variáveis injetadas.


112-115: Validação condicional de envs — ok.

Executar a checagem apenas fora de teste é o comportamento esperado.


116-125: Config de teste (SQLite in-memory) — ok.

dropSchema: true e synchronize default true quando indefinido deixam os testes rápidos e determinísticos.


126-135: Config Postgres — ok com SSL condicionado.

Uso de getEntityPaths/getMigrationPaths e createSSLConfig deixa a config limpa para cada ambiente.

src/modules/user/services/update-user.service.ts (1)

17-19: Boa troca para BadRequestException.

Alinha com o padrão Nest e permite tratamento homogêneo de erros.

src/modules/company/services/create-company.service.ts (1)

60-84: Tratamento de erro dependente de Postgres (código 23505).

Nos testes com SQLite, o código do erro muda (ex.: SQLITE_CONSTRAINT). Se quiser unificar comportamento, trate por error.code de cada driver ou use instanceof quando disponível no client. Alternativamente, para serviços Nest, lançar uma ConflictException quando detectar UniqueViolation.

test/shared/repository-mocks.ts (1)

5-20: Mocks de repositório estão consistentes e úteis para DI em testes

Boa extração; nomes e superfícies cobrem os principais cenários de testes.

src/modules/company/company.controller.ts (2)

69-71: Formatação do call está ok

A quebra em múltiplas linhas melhora leitura sem alterar comportamento.


117-119: Formatação ok

Sem mudanças funcionais.

src/modules/user/services/create-user.service.ts (1)

21-37: Ótima inclusão: normalização de e-mail + ConflictException

Ajuste elimina duplicações por casing/whitespace e retorna 409 adequado.

src/modules/auth/jwt/jwt.strategy.ts (1)

21-28: Alinhamento de issuer/audience confirmado
Os valores issuer: 'vagas-api' e audience: 'vagas-api-users' em jwt.strategy.ts coincidem exatamente com os definidos em JwtModule.registerAsync em auth.module.ts.

Comment thread src/database/entities/companies.entity.ts Outdated
Comment thread src/modules/auth/jwt/jwt.strategy.ts
Comment thread src/modules/company/company.controller.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (11)
test/modules/auth/strategy/jwt.strategy.spec.ts (7)

26-35: Evite vazamento de estado de ambiente: restaure JWT_SECRET após os testes

Capture o valor original e restaure no final do suite para não contaminar outros testes.

 describe('JwtStrategy', () => {
   let strategy: JwtStrategy;
   let userRepository: jest.Mocked<Partial<UserRepository>>;
   let companyRepository: jest.Mocked<Partial<CompanyRepository>>;
+  let originalJwtSecret: string | undefined;

+  beforeAll(() => {
+    originalJwtSecret = process.env.JWT_SECRET;
+  });

   beforeEach(async () => {
-    process.env.JWT_SECRET = TEST_TOKENS.JWT_SECRET;
+    process.env.JWT_SECRET = TEST_TOKENS.JWT_SECRET;
@@
   afterEach(() => {
     jest.clearAllMocks();
   });
+
+  afterAll(() => {
+    if (originalJwtSecret === undefined) {
+      delete process.env.JWT_SECRET;
+    } else {
+      process.env.JWT_SECRET = originalJwtSecret;
+    }
+  });

Also applies to: 57-59, 263-263


65-107: Asserções negativas: garanta que só o repositório esperado é chamado no fluxo do usuário

Evita falso‑positivo se a strategy consultar ambos acidentalmente.

       expect(result).toEqual(expectedUserPrincipal);
-      expect(userRepository.findOneById).toHaveBeenCalledWith(payload.sub);
+      expect(userRepository.findOneById).toHaveBeenCalledWith(payload.sub);
+      expect(userRepository.findOneById).toHaveBeenCalledTimes(1);
+      expect(companyRepository.findOneById).not.toHaveBeenCalled();

109-136: Asserções negativas no fluxo da empresa

Espelha o teste anterior para cobrir o caminho exclusivo da empresa.

       expect(result).toEqual(expectedCompanyPrincipal);
-      expect(companyRepository.findOneById).toHaveBeenCalledWith(payload.sub);
+      expect(companyRepository.findOneById).toHaveBeenCalledWith(payload.sub);
+      expect(companyRepository.findOneById).toHaveBeenCalledTimes(1);
+      expect(userRepository.findOneById).not.toHaveBeenCalled();

182-193: Não dependa da ordem das chaves ao validar “campos exatos” (usuário)

A ordem de inserção pode variar; compare conjuntos ordenados para robustez.

-      const actualUserFields = Object.keys(userResult);
-
-      expect(actualUserFields).toEqual(expectedUserFields);
+      const actualUserFields = Object.keys(userResult).sort();
+      expect(actualUserFields).toEqual([...expectedUserFields].sort());
+      expect(actualUserFields).toHaveLength(expectedUserFields.length);

225-235: Não dependa da ordem das chaves ao validar “campos exatos” (empresa)

Mesma melhoria do caso de usuário.

-      const actualCompanyFields = Object.keys(companyResult);
-
-      expect(actualCompanyFields).toEqual(expectedCompanyFields);
+      const actualCompanyFields = Object.keys(companyResult).sort();
+      expect(actualCompanyFields).toEqual([...expectedCompanyFields].sort());
+      expect(actualCompanyFields).toHaveLength(expectedCompanyFields.length);

1-3: Erros menos frágeis: valide o tipo UnauthorizedException em vez da mensagem

Mensagens mudam com facilidade. Prefira verificar a classe da exceção.

+import { UnauthorizedException } from '@nestjs/common';
@@
-      await expect(strategy.validate(payload)).rejects.toThrow(
-        'User not found',
-      );
+      await expect(strategy.validate(payload)).rejects.toThrow(
+        UnauthorizedException,
+      );
@@
-      await expect(strategy.validate(invalidPayload as any)).rejects.toThrow(
-        'Invalid payload: missing required fields',
-      );
+      await expect(strategy.validate(invalidPayload as any)).rejects.toThrow(
+        UnauthorizedException,
+      );
@@
-      await expect(strategy.validate(undefined as any)).rejects.toThrow(
-        'Invalid payload: missing required fields',
-      );
+      await expect(strategy.validate(undefined as any)).rejects.toThrow(
+        UnauthorizedException,
+      );

Also applies to: 138-148, 243-258


11-15: Evite literais de IDs; use os IDs dos test-constants

Mantém consistência e reduz risco de divergência.

 import {
   TEST_PASSWORDS,
   TEST_USER_DATA,
   TEST_EMAILS,
   TEST_TOKENS,
+  TEST_IDS,
 } from '../../../config/test-constants';
@@
-      const user = {
-        id: '729c7919-583c-40a5-b0ca-137e282345d4',
+      const user = {
+        id: TEST_IDS.USER_ID,
@@
-        id: '729c7919-583c-40a5-b0ca-137e282345d4',
+        id: TEST_IDS.USER_ID,

Also applies to: 68-70, 153-154

test/mocks/auth/company.mock.ts (4)

9-18: Fallback de senha: garanta um hash bcrypt válido por padrão

A ordem atual pode retornar HASHED (“hashedPassword”), quebrando comparações bcrypt.compare. Priorize um hash bcrypt estável.

-const getTestPassword = () => {
-  try {
-    return TEST_PASSWORDS.HASHED_BCRYPT;
-  } catch (error) {
-    return (
-      TEST_PASSWORDS.HASHED ||
-      '$2b$10$K8gF4Z.vH5P.qL2N3mR8qOzGJ5jH9xW2pM6rQ7sT4uV8yA3bC1dE6'
-    );
-  }
-};
+// Hash bcrypt estável da string "password" (padrão de testes)
+const BCRYPT_HASH_STUB =
+  '$2b$10$N9qo8uLOickgx2ZMRZo5i.ezWxZmf0RduCMsZ/HXXIQK5vCq1vZ1S';
+
+const getTestPassword = () => {
+  try {
+    return TEST_PASSWORDS.HASHED_BCRYPT;
+  } catch {
+    return BCRYPT_HASH_STUB;
+  }
+};

38-39: Consistência de tipos: datas como Date em vez de string

Outros mocks usam Date. Uniformizar evita asserts frágeis de tipo.

-    created_at: '2023-02-21T00:25:07.000Z',
-    updated_at: '2023-02-21T00:25:07.000Z',
+    created_at: new Date('2023-02-21T00:25:07.000Z'),
+    updated_at: new Date('2023-02-21T00:25:07.000Z'),
@@
-    created_at: '2023-02-21T00:25:07.000Z',
-    updated_at: '2023-02-21T00:25:07.000Z',
+    created_at: new Date('2023-02-21T00:25:07.000Z'),
+    updated_at: new Date('2023-02-21T00:25:07.000Z'),

Also applies to: 58-59


63-71: Evite ID literal duplicado: promova para TEST_IDS.UNCONFIRMED_COMPANY_ID

Centraliza e documenta o valor.

 export const companyWithUnconfirmedEmailMock = () => {
   return {
     ...companyEntityMock(),
-    id: '829c7919-583c-40a5-b0ca-137e282345d5',
+    id: TEST_IDS.UNCONFIRMED_COMPANY_ID,
     companyName: 'Unconfirmed Company Ltd',

Código adicional fora deste arquivo (em test/config/test-constants.ts):

 export const TEST_IDS = {
   USER_ID: '729c7919-583c-40a5-b0ca-137e282345d4',
-  COMPANY_ID: '829c7919-583c-40a5-b0ca-137e282345d5',
+  COMPANY_ID: '829c7919-583c-40a5-b0ca-137e282345d5',
+  UNCONFIRMED_COMPANY_ID: '829c7919-583c-40a5-b0ca-137e282345d5',
 } as const;

43-61: Renomear ou restringir publicCompanyMock para evitar ambiguidade de principal
O mock retorna campos de entidade (about, phone, address, city, state, cep, companySite, mailConfirm, policies, created_at, updated_at) além dos campos de PublicCompanyPrincipal (id, companyName, email, cnpj, userType). Se a intenção é mockar o principal, restrinja o retorno a esses campos; caso contrário, renomeie para companyEntityMock (ou similar) para refletir corretamente seu uso.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 098a8ac and ef39f98.

📒 Files selected for processing (2)
  • test/mocks/auth/company.mock.ts (1 hunks)
  • test/modules/auth/strategy/jwt.strategy.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
test/mocks/auth/company.mock.ts (1)
test/config/test-constants.ts (5)
  • TEST_PASSWORDS (10-50)
  • TEST_IDS (64-67)
  • TEST_COMPANY_DATA (97-101)
  • TEST_EMAILS (88-94)
  • TEST_USER_DATA (104-110)
test/modules/auth/strategy/jwt.strategy.spec.ts (3)
test/config/test-constants.ts (4)
  • TEST_TOKENS (53-61)
  • TEST_EMAILS (88-94)
  • TEST_PASSWORDS (10-50)
  • TEST_USER_DATA (104-110)
test/shared/repository-mocks.ts (2)
  • createUserRepositoryMock (5-20)
  • createCompanyRepositoryMock (22-39)
test/mocks/auth/jwt-payload.mock.ts (2)
  • jwtPayloadMock (3-11)
  • companyJwtPayloadMock (13-21)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (4)
src/database/data-source.ts (4)

13-16: Documentar novas variáveis DB_ no .env.example e pipeline.*

Inclua DB_TYPE, DB_DATABASE, DB_SYNCHRONIZE, DB_SSL_REJECT_UNAUTHORIZED no .env.example e configure-as no CI/ambientes de teste.


68-96: SSL em produção agora tem fallback sem CA e default seguro — ótimo.

TLS permanece ativo mesmo sem CA_CERT, e o warning para rejectUnauthorized: false está presente.


97-99: Simplificar isDevelopment e tornar isTestEnvironment mais resiliente.

  • NODE_ENV === 'development' || NODE_ENV !== 'production'NODE_ENV !== 'production'.
  • Considere aceitar NODE_ENV === 'test' como gatilho de teste para não depender apenas de DB_TYPE/DB_DATABASE.
-const isTestEnvironment = DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:';
-const isDevelopment = NODE_ENV === 'development' || NODE_ENV !== 'production';
+const isTestEnvironment =
+  NODE_ENV === 'test' ||
+  (DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:');
+const isDevelopment = NODE_ENV !== 'production';

100-110: Alternância src/dist para entities/migrations — boa correção.

Evita falhas no start:dev e no build.

🧹 Nitpick comments (9)
src/shared/Swagger/decorators/company/delete-company-me.swagger.ts (4)

2-2: Documente o esquema de segurança (JWT) no Swagger

A rota exige autenticação; adicione @ApiBearerAuth() para exibir o cadeado e permitir “Authorize” no Swagger UI.

-import { ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';

 export function DeleteCompanyMeSwagger() {
   return applyDecorators(
+    ApiBearerAuth(),
     ApiOperation({
       summary: 'Excluir minha empresa',
     }),

Also applies to: 5-5


10-12: Considere 204 No Content para DELETE (se não houver corpo de resposta)

Se o endpoint não retorna body, padronize como 204 e use @ApiNoContentResponse. Caso retorne um objeto/mensagem, mantenha 200.

-import { ApiOperation, ApiResponse } from '@nestjs/swagger';
+import { ApiOperation, ApiResponse, ApiNoContentResponse } from '@nestjs/swagger';
@@
-    ApiResponse({
-      status: 200,
-      description: 'Empresa excluída com sucesso',
-    }),
+    ApiNoContentResponse({
+      description: 'Empresa excluída com sucesso',
+    }),

13-20: Use decoradores específicos de erro e avalie adicionar 403

Troque ApiResponse genérico por ApiUnauthorizedResponse/ApiNotFoundResponse e inclua ApiForbiddenResponse se houver restrição de papel (ex.: apenas empresas).

-import { ApiOperation, ApiResponse } from '@nestjs/swagger';
+import {
+  ApiOperation,
+  ApiResponse,
+  ApiUnauthorizedResponse,
+  ApiNotFoundResponse,
+  ApiForbiddenResponse
+} from '@nestjs/swagger';
@@
-    ApiResponse({
-      status: 401,
-      description: 'Não autorizado',
-    }),
+    ApiUnauthorizedResponse({ description: 'Não autorizado' }),
+    ApiForbiddenResponse({ description: 'Proibido' }),
-    ApiResponse({
-      status: 404,
-      description: 'Empresa não encontrada',
-    }),
+    ApiNotFoundResponse({ description: 'Empresa não encontrada' }),

4-4: Anote o tipo de retorno da factory

Pequeno ganho de clareza; a factory é usada em métodos.

-export function DeleteCompanyMeSwagger() {
+export function DeleteCompanyMeSwagger(): MethodDecorator {
src/modules/auth/jwt/jwt.strategy.ts (3)

50-53: Normalize e-mail para lower-case antes da comparação

O restante do código cria/guarda e-mails em lower-case; alinhar aqui evita falsos negativos por variação de maiúsculas/minúsculas.

Patch proposto:

-    const normalizedEmail = payload.email.trim();
+    const normalizedEmail = payload.email.trim().toLowerCase();
@@
-        if (user.email !== normalizedEmail) {
+        if (user.email?.toLowerCase() !== normalizedEmail) {
           throw new UnauthorizedException('Email mismatch');
         }
@@
-        if (company.email !== normalizedEmail) {
+        if (company.email?.toLowerCase() !== normalizedEmail) {
           throw new UnauthorizedException('Email mismatch');
         }

Also applies to: 70-72, 82-84


62-90: Reduza vazamento de informações nas mensagens de erro

Mensagens distintas (“User not found”, “Email mismatch”) ajudam enumeração. Considere resposta genérica única.

Exemplo mínimo:

-          throw new UnauthorizedException('User not found');
+          throw new UnauthorizedException('Unauthorized');
@@
-          throw new UnauthorizedException('Email mismatch');
+          throw new UnauthorizedException('Unauthorized');
@@
-          throw new UnauthorizedException('Company not found');
+          throw new UnauthorizedException('Unauthorized');
@@
-        throw new UnauthorizedException('Invalid payload type');
+        throw new UnauthorizedException('Unauthorized');

Confira se os testes não dependem do texto exato da mensagem (apenas do tipo da exceção).


19-28: Padronizar emissor, audiência e algoritmo via ConfigService

  • Em src/modules/auth/jwt/jwt.strategy.ts, substituir:
    algorithms: ['HS256'],
    issuer: 'vagas-api',
    audience: 'vagas-api-users',
    por
    algorithms: [this.configService.get<string>('JWT_ALG') ?? 'HS256'],
    issuer: this.configService.get<string>('JWT_ISSUER') ?? 'vagas-api',
    audience: this.configService.get<string>('JWT_AUDIENCE') ?? 'vagas-api-users',
  • Em src/modules/auth/auth.module.ts (no objeto signOptions), substituir:
    algorithm: 'HS256',
    issuer: 'vagas-api',
    audience: 'vagas-api-users',
    por
    algorithm: configService.get<string>('JWT_ALG') ?? 'HS256',
    issuer: configService.get<string>('JWT_ISSUER') ?? 'vagas-api',
    audience: configService.get<string>('JWT_AUDIENCE') ?? 'vagas-api-users',
src/database/data-source.ts (2)

68-96: Tipar retorno do createSSLConfig e evitar any.

Melhora de DX e segurança de tipos no objeto SSL.

+import type { TlsOptions } from 'tls';
...
-const createSSLConfig = () => {
+const createSSLConfig = (): TlsOptions | undefined => {
   if (NODE_ENV !== 'production') return undefined;
@@
-  const rejectUnauthorized =
-    DB_SSL_REJECT_UNAUTHORIZED === 'false' || DB_SSL_REJECT_UNAUTHORIZED === '0'
-      ? false
-      : true;
+  const rejectUnauthorized =
+    !/^(false|0)$/i.test(DB_SSL_REJECT_UNAUTHORIZED ?? '');
@@
-  const sslConfig: any = {
+  const sslConfig: TlsOptions = {
     rejectUnauthorized,
   };

125-136: Evitar synchronize em produção por segurança.

Mesmo controlado por ENV, é fácil um deslize habilitar synchronize em prod. Sugiro forçar false quando NODE_ENV === 'production'.

-      synchronize: DB_SYNCHRONIZE === 'true',
+      synchronize: NODE_ENV === 'production' ? false : DB_SYNCHRONIZE === 'true',
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef39f98 and f24ae81.

📒 Files selected for processing (5)
  • src/database/data-source.ts (2 hunks)
  • src/database/migrations/1757172957047-AddUniqueConstraintToCompaniesEmail.ts (1 hunks)
  • src/modules/auth/jwt/jwt.strategy.ts (1 hunks)
  • src/modules/company/company.controller.ts (4 hunks)
  • src/shared/Swagger/decorators/company/delete-company-me.swagger.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/modules/company/company.controller.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/modules/auth/jwt/jwt.strategy.ts (5)
src/modules/company/services/create-company.service.ts (1)
  • Injectable (8-86)
src/modules/user/services/create-user.service.ts (1)
  • Injectable (10-57)
src/modules/auth/services/auth-login.service.ts (1)
  • Injectable (10-56)
src/modules/company/repository/company.repository.ts (1)
  • Injectable (14-189)
src/modules/auth/utils/principal.mapper.ts (2)
  • mapUserToPrincipal (8-17)
  • mapCompanyToPrincipal (19-29)
🔇 Additional comments (10)
src/shared/Swagger/decorators/company/delete-company-me.swagger.ts (2)

6-8: LGTM no resumo/descrição da operação

Resumo claro e consistente com a rota “me”.


1-1: Verifique a capitalização do diretório “Swagger”

Confirme que todos os imports usam exatamente shared/Swagger/.... Em ambientes case-sensitive, Swagger vs swagger quebra a resolução.

src/modules/auth/jwt/jwt.strategy.ts (2)

31-48: Validação defensiva do payload: ótimo

Type-guards + checagem de campos obrigatórios resolveram os riscos anteriores com .trim() e tipos não-string.


90-95: Tratamento de erros consistente

Rethrow de Unauthorized e fallback para mensagem genérica estão corretos.

src/database/data-source.ts (4)

19-22: Normalização do CA do banco: bom ajuste.

Converter \\n em quebras reais evita erros de TLS quando o CA vem por ENV.


24-45: Validação de porta robusta — LGTM.

Checks claros com mensagens precisas e faixa 1–65535. Bom uso de default.


112-114: Validação de ENV do Postgres apenas fora de teste — confirme CLIs.

Garanta que comandos de migração/seeders que importam o data-source recebam as variáveis necessárias, para não disparar este throw em ambientes locais/CI.


116-124: Config de teste (SQLite in-memory) está consistente.

synchronize defaulta para true e dropSchema ativo — bom para isolamento.

src/database/migrations/1757172957047-AddUniqueConstraintToCompaniesEmail.ts (2)

1-5: Boa nomenclatura e estrutura da Migration.

Classe e assinatura seguem o padrão do TypeORM. Sem ressalvas aqui.


26-30: Down simétrico, mas atenção: DROP INDEX CONCURRENTLY também exige ausência de transação.

Mantém a simetria; apenas garanta que a execução do down não esteja dentro de transação.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Sep 6, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
7.7% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/database/data-source.ts (3)

47-66: Exigir e documentar envs obrigatórias do Postgres

A validação está boa. Garanta que essas chaves estejam no .env.example e configuradas no CI (comentário prévio já solicitou isso).

Execute:

#!/bin/bash
set -euo pipefail
echo "Checando .env.example para TYPEORM_* e DB_*:"
fd -H -a -t f '.env.example' || { echo "Sem .env.example"; exit 1; }
rg -n -H -g ".env.example" -e '^TYPEORM_HOST=' -e '^TYPEORM_PORT=' -e '^TYPEORM_USERNAME=' -e '^TYPEORM_PASSWORD=' -e '^TYPEORM_DATABASE=' -e '^DB_TYPE=' -e '^DB_DATABASE=' -e '^DB_SYNCHRONIZE=' -e '^DB_SSL_REJECT_UNAUTHORIZED=' || {
  echo "Alguma(s) variável(is) pode(m) estar faltando no .env.example."
}

68-96: SSL em produção agora está alinhado com as recomendações anteriores

  • rejectUnauthorized por padrão em true e aviso quando desativado.
  • CA normalizada via normalizeCACert.
  • Fallback para TLS mesmo sem CA (objeto com rejectUnauthorized).

97-99: Confirme que os testes setam DB_TYPE=sqlite e DB_DATABASE=:memory: (ou adote NODE_ENV=test)

Isto já foi apontado antes; valide que seus scripts/jest definem essas variáveis, senão cairá no Postgres.

#!/bin/bash
set -euo pipefail
echo "Procurando definição de DB_TYPE/DB_DATABASE nos scripts de teste e configs:"
rg -n -C2 '\b(DB_TYPE|DB_DATABASE)\b|:memory:' package.json -g 'jest*.json' -g 'jest*.js' 2>/dev/null || true
🧹 Nitpick comments (2)
src/database/data-source.ts (2)

68-96: Tipar retorno do createSSLConfig (evitar any)

Defina um tipo explícito compatível com TypeORM/pg e acrescente a importação de tipo.

+import type { TlsOptions } from 'tls';
@@
-const createSSLConfig = () => {
+const createSSLConfig = (): boolean | TlsOptions | undefined => {

97-99: Simplifique isDevelopment e torne o gatilho de teste mais resiliente

  • NODE_ENV === 'development' || NODE_ENV !== 'production' é redundante; equivale a NODE_ENV !== 'production'.
  • Considere reconhecer NODE_ENV === 'test' como modo de teste, reduzindo dependência de DB_TYPE/DB_DATABASE.
-const isTestEnvironment = DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:';
-const isDevelopment = NODE_ENV === 'development' || NODE_ENV !== 'production';
+const isTestEnvironment =
+  NODE_ENV === 'test' || (DB_TYPE === 'sqlite' && DB_DATABASE === ':memory:');
+const isDevelopment = NODE_ENV !== 'production';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f24ae81 and c1e3333.

📒 Files selected for processing (2)
  • src/database/data-source.ts (2 hunks)
  • src/database/migrations/1757172957047-AddUniqueConstraintToCompaniesEmail.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/database/migrations/1757172957047-AddUniqueConstraintToCompaniesEmail.ts
🔇 Additional comments (4)
src/database/data-source.ts (4)

19-22: CA normalizada corretamente

Substituição de \\n por quebras reais e retorno undefined para input vazio estão corretos.


24-45: Validação de porta robusta

Checagens de formato e faixa [1–65535] com mensagens claras — perfeito.


100-110: Paths src vs dist: atenção para ambientes “staging/homolog”

Como a escolha usa NODE_ENV !== 'production', qualquer NODE_ENV diferente de 'production' apontará para src/*.ts. Se seu “staging” roda build compilado, garanta que NODE_ENV seja 'production' lá, ou ajuste a lógica para refletir o runtime real.


116-124: Branch de testes bem configurado

Uso de SQLite em memória, dropSchema: true e synchronize default true quando não definido favorecem testes rápidos e isolados.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants