feat: implement comprehensive auth tests and fix failing unit tests#198
feat: implement comprehensive auth tests and fix failing unit tests#198LeidejanedaRosa wants to merge 43 commits intoSouJunior:mainfrom
Conversation
WalkthroughAtualiza 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
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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poema
✨ Finishing Touches
🧪 Generate unit tests
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. Comment |
There was a problem hiding this comment.
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 controllersO
UpdatePasswordByEmailServiceé um service (@Injectable), e não controller. Mantê-lo emcontrollersé 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çãoApó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
invalidateRecoverPasswordTokenemCompanyRepository(ou adaptar ao método já existente, caso tenha outro nome, comoclearRecoverPasswordToken).src/modules/company/services/activate-company.service.ts (1)
17-20: Inconsistência entre status 204 e corpo de respostaStatus 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 acessaruser.passwordSe
findCompanyByIdretornarnull/undefined, o acesso auser.passwordvai quebrar. Trate antes dobcrypt.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 incoerenteA verificação
!info?.mailConfirm || !inforetorna “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 demailConfirme remover?.redundanteO código atual apaga
info.mailconfirm(minúsculo) mas nunca removeinfo.mailConfirm(maiúsculo), vazando o indicador de confirmação. Além disso, odelete info?.ipusa?.desnecessário, poisinfojá existe nesse ponto.
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;Em
test/modules/auth/services/auth-login.service.spec.ts, atualize todas as ocorrências demailconfirmparamailConfirm(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 noprofileKeyvindo do cliente, permitindo apagar arquivos de terceiros. Use oprofileKeyatual 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.profileKeycomo 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 hashArmazenar 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
findByTokene 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 testeA classe atual atende, mas uma factory com
useValuetende 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 useuseValue+ factory para facilitar stubs/asserçõesA configuração atual com
useClassfunciona. 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 controllerFunciona, 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 chamadosO 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 spyOnAqui 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
fileUploadServicediretamente nos testes..env.example (1)
6-6: Mover o comentário inline para a linha de cima para evitar ruído de lintersAlguns 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 sucessoJá 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 CandidacyServiceHoje 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.jsonPara 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 compartilhadaA 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árioPara 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 sucessoO 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ãoA 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çãoPermitir 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 contratoSe 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 tiposTrocar
!=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 senhaTrocar
!=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 senhaPara maior segurança, após atualizar a senha, invalide refresh tokens/sessões ou atualize um campo
passwordChangedAt/tokenVersionpara que JWTs antigos sejam rejeitados.Posso sugerir um padrão de implementação com
passwordChangedAtna entidade e verificação naJwtStrategy. Quer?
19-23: Nome da variáveluserconfunde — trata-se de uma empresaRenomear
userpara algo comocompanyEntitymelhora 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óduloComo
CompanyModulejá está emimports, o provider deCompanyRepositorydeve 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
CompanyModuleexportaCompanyRepository. Caso não exporte, prefira exportá-lo noCompanyModulee 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,CompanyRepositoryeCurriculumRepositorydeclarados como providers funcionam, mas avalie expor/consumir via seus módulos correspondentes para evitar duplicar providers e facilitar reuso/testes (especialmente se já houverCompanyModuleeCurriculumModuleexportando-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 enumsTrocar
==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ãoAvalie incluir
sub(id),typee/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 paraleloAs chamadas podem ser feitas com
Promise.allpara 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
404nã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: 404→status: 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 mesmomessageComo 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 senhaTroque
!=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çãoVocê 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 bcryptOs 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 parcialSe 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 mailConfirmMesma 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 suitesAqui 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 tiposUse 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 E2EUserRepository 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 suiteAlso applies to: 49-55
test/modules/auth/strategy/jwt.strategy.spec.ts (2)
35-39: Remover dependência desnecessária de process.env nos testesO 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.envAlso applies to: 66-68
84-85: Usar comparação parcial para reduzir fragilidadeCaso 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 clarezaSubstituir 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 suitesO 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íveisAtualmente os imports
mapUserToPrincipal/mapCompanyToPrincipalestão sem uso, e a validação retorna objetos “ad hoc” comuserType. Recomendo tipar o retorno comoPromise<AuthenticatedPrincipal>e utilizar os mapeadores para garantir sanitização consistente (evitando expor campos comorecoverPasswordToken, etc.) e remover os comentárioseslint-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 errosO 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
UnauthorizedExceptionpara 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 TestingModuleEmbora funcione, é incomum registrar um service em
controllers. Prefira adicioná-lo emproviderspara 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 retornoO teste compara com
userMock()(que não tem campos sensíveis), mas é útil checar explicitamente quepassword,recoverPasswordTokeneipforam 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
mockRequestretornaPartial<Request>e é convertido viaas Request. Alternativas: tipar comoPick<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 configOs valores de
issuer,audienceealgorithmestão fixos aqui e na estratégia. Sugiro centralizar viaConfigService(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.tsO
ConfigModule.forRoot({ isGlobal: true })já está declarado globalmente emapp.module.tse, 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 updateCompanyByIdO primeiro
findOneBynã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-BRPara 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
There was a problem hiding this comment.
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 desynchronizeAtualmente,
isTestEnvironmentsó consideraDB_TYPE === 'sqlite' && DB_DATABASE === ':memory:', que pode não cobrir todos os cenários de CI ou outros runners.synchronizedepende deDB_SYNCHRONIZE, mas não há nenhum ponto no projeto onde essa variável seja setada (verificação viarg). Se ela não existir, acaba virandofalsee os testes podem falhar silenciosamente.Sugestão de refatoração opcional:
• Incluir
NODE_ENV === 'test'na detecção de ambiente de teste.
• Forçarsynchronize: truepor padrão dentro do branch de teste (permitindo override viaDB_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 flakinessnew 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)/falsesem casts.
83-83: Remover casts “as never” nos mocks do bcryptApó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çõesSe 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 compareUse 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 mailconfirmOs 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álidaOs 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 testeBoa 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 confirmadosUNCONFIRMED_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 testePara 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 specPara 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 usadaHoje 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óduloEvita 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.
📒 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ênciaImportar TEST_EMAILS elimina hardcodes e facilita manutenção dos mocks.
9-9: Substituição de strings hardcoded por constantesTrocar 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 credenciaisImportações de TEST_EMAILS/TEST_USER_DATA/TEST_PASSWORDS padronizam o mock e evitam magic strings.
9-12: LGTM no payload do userUpdateMockTroca 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-constantsAdoçã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 — bomMenos 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 — okAjuste 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 estruturadoCobertura 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 hardcodingA 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 trabalhoA 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 alinhadosAmbos 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 úteisCobertura 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 cobertoValida 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 padronizadaFluxo claro: valida existência, executa delete e retorna payload enxuto com mensagem. Ok.
15-19: Verificação concluída: nenhuma referência acompany_repositoryencontrada
- O comando
rg -n -C2 -P '\bcompany_repository\b' --type tsnão retornou resultados.- Todos os usos estão consistentes com
companyRepository.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/database/data-source.ts (1)
41-55: Endereça o feedback anterior de SSL com defaults segurosAgora usa comparação estrita e
rejectUnauthorizedpadrão comotrue, com aviso quando desativado. Excelente.
🧹 Nitpick comments (5)
src/database/data-source.ts (5)
1-3: Coloquereflect-metadataantes 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 solicitadoHoje, sem CA o
sslé omitido mesmo seDB_SSL_REJECT_UNAUTHORIZEDestiver 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 objetosslsemcaquando 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=truepermitiria ativar SSL emdevelopmentde 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,
synchronizepode defaultar paratruequando não definido, reduzindo atrito.- O glob
**/*.entity.tscobre subdiretórios sobentities.- 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 oportresiliente a env ausente e use base 10Evita
NaNcasoTYPEORM_PORTnã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.
📒 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é ótimaResolve um problema comum ao carregar certificados via env. Implementação enxuta e correta.
MikaelMelo1
left a comment
There was a problem hiding this comment.
Alguns pontos para corrigir.
…ared mock classes
…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
There was a problem hiding this comment.
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 uniqueEvite 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_ — incluaNODE_ENV === 'test'.*Comentário já feito antes; reforço para evitar dependência rígida de
DB_TYPE/DB_DATABASEnos 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
NotFoundExceptione retorna{ message: ... }; a checagem deaffectedaqui é 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: Removerspyredundante: já é umjest.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: Removerspyredundante no cenário de campos obrigatóriosRepita o padrão sem
spye 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
10para 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/undefinedno 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
updatePasswordparece 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<>porPick<>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: considerarresetAllMockse fechar o módulo (opcional).
clearAllMockslimpa histórico, mas mantém implementações. Se você alternar implementações entre testes, prefiraresetAllMocks. Também é saudável fechar oTestingModulepara 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 sesendUserConfirmationé 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, prefiraspyOn().mockResolvedValue(...).Isso mantém a mesma referência de mock e facilita asserts de chamadas.
29-38: ColoqueRecoveryPasswordByEmailemproviders, não emcontrollers.É 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(ouundefined) 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 (evitarPartial)Boa fábrica. Para evitar “falsos verdes” quando a interface do repositório mudar, recomendo tipar com
Pick<>em vez dePartial<>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 testeVocê 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 decreateUserComplementa 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 defindOneByEmailno 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 injetadoO
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 configuremockResolvedValue.- 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: PadronizetoHaveBeenCalledTimesUse 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ícitoNã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çãoTexto 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)é redundantePOST 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 controllerAlguns endpoints usam
@Res()e outros retorno direto. Padronizar melhora interceptors/pipes/filters.Confirme se
DeleteUserService.executeretorna payload adequado (sem envelope de status) para manter consistência comcreateNewUser. Se quiser, posso enviar um patch para unificar o controller inteiro.
134-143: Padronização: evitar@Res()aqui também e retornar diretamenteSugestã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@UploadedFileOpcional: tipar como
Express.Multer.Filepara 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 mensagensOutros 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 deResponsefica obsoleto com a refatoraçãoSe remover
@Res, elimine este import.-import { Response } from 'express';
80-86: Remova o decorator@Res()e retorne apenas o DTONo método
getAllLoggedCompanyJobsemjobs.controller.ts, retire o parâmetro@Res()para que o Nest trate o status e o corpo da resposta. Como o serviço sempre retornastatus: 200, basta devolverdata.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ódigoIssuer/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 AppModuleEste 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, depoismailConfirm. 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
deleteem múltiplas chaves (potencial efeito colateral em entidades do TypeORM), derive um objeto público por destruturação.- Prefira
signAsyncpara 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 parainfo.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 oprincipal.mapperse desejar.test/modules/auth/services/auth-login.service-refactored.spec.ts (4)
145-151: Evite chamarservice.executeduas 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 demailConfirme 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.compareretorna 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 evitaranyUse
jest.Mocked<AuthLoginService>para melhor autocompletar e checagem estática.- let authLoginService: any; + let authLoginService: jest.Mocked<AuthLoginService>;
40-41: Ajustar tipo nomoduleFixture.getGaranta 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 mensagemO 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: falsee 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 testeReaproveite 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 empresaEvita 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 efeitoO
companyWithUnconfirmedEmailMockredefineidpara o mesmo valor deTEST_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 seguraAdicionar
as constajuda 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 emcreateCompanyDesestruture 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ênciaConsidere adicionar índices nas colunas:
cnpj,recoverPasswordTokenpara acelerarfindOneBy*.
📜 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.
📒 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
useValuefacilita 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 viauseValuedo 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 oCandidacyServiceinjetaCandidacyRepositorydiretamente 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 coerenteSem impactos funcionais.
src/modules/jobs/services/get-all-jobs-from-logged-company.service.ts (2)
3-3: Import de IJobsResponse — OKCoerente com a assinatura do service.
7-7: Remoção do CompanyRepository do construtor — ótimoDI mais enxuta e sem dependência não utilizada.
src/modules/jobs/jobs.controller.ts (1)
49-49: Injeção do serviço OKA dependência
GetAllJobsFromLoggedCompanyServiceno construtor está correta.src/modules/auth/jwt/jwt.strategy.ts (1)
19-19: Uso degetOrThrow('JWT_SECRET')é acertadoBoa prática: falha cedo quando a env está ausente.
src/modules/company/company.controller.ts (2)
70-73: Refator leve no create: OKDesestruturação
{ data, status }e retorno viaresmantêm comportamento.
118-121: Refator leve no update-password-by-email: OKPadrão consistente com os demais handlers do controller.
src/modules/auth/auth.controller.ts (1)
26-29: Remoção do@Rese uso de@HttpCode(200): alinhado ao NestMelhora testabilidade e reaproveita pipes/interceptors globais. Boa.
test/app.e2e-spec.ts (2)
37-41: Fechamento do app noafterEach: bom cuidadoEvita 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.repositoryconsistente 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 noorderBy+getManyAndCountMitiga injeção em coluna de ordenação e reduz roundtrips ao banco.
1-189: Não há referências acompany_repository; convenção está consistente
A busca porthis.company_repositoryem todo o projeto não retornou nenhum resultado, indicando que todas as injeções usam corretamentecompanyRepository.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
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) viafile: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
deleteFilesuceder euploadfalhar, 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 doFileInterceptor('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.examplee CI.*As chaves DB_TYPE, DB_DATABASE, DB_SYNCHRONIZE e DB_SSL_REJECT_UNAUTHORIZED precisam estar no
.env.examplee 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-mailA 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 providersO 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
CreateUserServiceem providers econtrollers: []. Alinha com as boas práticas de Nest.test/mocks/auth/company.mock.ts (1)
9-18: LGTM: fallback de hash bcrypt estávelBoa 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; removernode -r tsconfig-paths/register node_modules/.bin/jest.
moduleNameMapperjá resolvesrc/...no Jest; otsconfig-paths/registeraqui é redundante.- Invocar
node_modules/.bin/jestdiretamente reduz portabilidade; prefira apenasjest.- "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
.jsviats-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.tsdeve 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 nodefaultPortquando 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 incluirTYPEORM_PORTopcionalmente.Se a intenção for exigir porta customizada, adicione
TYPEORM_PORTaos obrigatórios; se não, mantenha fora para permitir odefaultPort.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 usarssl: truequando sem CA (limpaany).Funciona como está, mas dá pra evitar
anye 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 deisDevelopment.
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
OisTestEnvironmentsó dispara quandoDB_TYPE==='sqlite' && DB_DATABASE===':memory:', mas não há definição dessas variáveis nos testes (nenhum.env*ou config do Jest), então emnpm testele 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 qualquerNODE_ENVexcetoproduction, tornando o nome impreciso. Useconst isDevelopment = NODE_ENV === 'development'ou renomeie paraisNonProduction.src/modules/user/services/update-user.service.ts (1)
15-16: Tipagem do parâmetrofile(qualidade e DX).Considere tipar como
file?: Express.Multer.Filepara 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).
404não é semântico para validação. Sugestão usar400(Bad Request) ou422:- 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 oujest-mock-extendedPara pegar incoerências de assinatura em compile-time, você pode:
- Tipar os
jest.fncom generics (retorno/parâmetros), ou- Usar
jest-mock-extendede criarmockDeep<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@ApiBodycom o campofilepara 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/ApiBodyno import de@nestjs/swagger.
84-86: Rota semântica: considerePUT /company/meem vez deeditMantém consistência com
DELETE /company/mee evita verbos no path.
138-143: Confirme requisitos de autorização noactivateCompanySe a ativação não for pública, adicione
@UseGuards(AuthGuard())e@ApiBearerAuth(). Se o id for UUID, useParseUUIDPipe:- 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ênciaAs 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 entradaCrie 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árioO
try/catchengloba 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 helperisUniqueViolation(err)que chequecode/errno/messagepara PG e SQLite.Posso enviar um util
isUniqueViolatione ajustar oscatch?src/modules/auth/jwt/jwt.strategy.ts (2)
54-55: Comparação de e-mail deve ser case-insensitiveE-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 observabilidadeEncapsular 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, useLogger.debug/Logger.errorcomerror.test/modules/auth/strategy/jwt.strategy.spec.ts (5)
65-107: Garanta que apenas o repositório do ramo USER seja consultadoValida 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 chamadoManté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 propriedadesComparar
Object.keysdiretamente é frágil; ordene as chaves ou usetoMatchObject.- 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 ordemManté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 constantesLanç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 IDsO 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 testesOpcional incluir
2001:db8::1para 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 testesAo 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 dejest.spyOnsobre funções já mockadasOs métodos do repositório já são
jest.fn()via factory; pode-se assertar direto, evitandospyOn.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 corretamenteO teste chama
service.executeduas vezes. Além disso, para mensagens deHttpException, é 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 empresaConsolidar 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 senhaSe 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
companySiteestá alinhado com a entidade/mapper.publicCompanyMockomitepassworderecoverPasswordToken. Ótimo.Pequeno cuidado: este fallback não evita um eventual throw de import no próprio
test-constants.tsse 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.tse 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
📒 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
moduleNameMappercobresrc/...aqui; verifique se otest/jest-e2e.jsontem o mesmo mapeamento para evitar falhas de import nos testes E2E.src/database/data-source.ts (5)
1-1: Carga antecipada de env viadotenv/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➜\nevita 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: trueesynchronizedefaulttruequando indefinido deixam os testes rápidos e determinísticos.
126-135: Config Postgres — ok com SSL condicionado.Uso de
getEntityPaths/getMigrationPathsecreateSSLConfigdeixa 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 porerror.codede cada driver ou useinstanceofquando disponível no client. Alternativamente, para serviços Nest, lançar umaConflictExceptionquando detectarUniqueViolation.test/shared/repository-mocks.ts (1)
5-20: Mocks de repositório estão consistentes e úteis para DI em testesBoa 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á okA quebra em múltiplas linhas melhora leitura sem alterar comportamento.
117-119: Formatação okSem mudanças funcionais.
src/modules/user/services/create-user.service.ts (1)
21-37: Ótima inclusão: normalização de e-mail +ConflictExceptionAjuste 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 valoresissuer: 'vagas-api'eaudience: 'vagas-api-users'emjwt.strategy.tscoincidem exatamente com os definidos emJwtModule.registerAsyncemauth.module.ts.
There was a problem hiding this comment.
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 testesCapture 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árioEvita 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 empresaEspelha 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 mensagemMensagens 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-constantsManté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ãoA ordem atual pode retornar
HASHED(“hashedPassword”), quebrando comparaçõesbcrypt.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 comoDateem vez de stringOutros 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 paraTEST_IDS.UNCONFIRMED_COMPANY_IDCentraliza 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 restringirpublicCompanyMockpara 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 paracompanyEntityMock(ou similar) para refletir corretamente seu uso.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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)
There was a problem hiding this comment.
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_UNAUTHORIZEDno.env.examplee 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 pararejectUnauthorized: falseestá presente.
97-99: SimplificarisDevelopmente tornarisTestEnvironmentmais resiliente.
NODE_ENV === 'development' || NODE_ENV !== 'production'≡NODE_ENV !== 'production'.- Considere aceitar
NODE_ENV === 'test'como gatilho de teste para não depender apenas deDB_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:deve 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 SwaggerA 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 403Troque
ApiResponsegenérico porApiUnauthorizedResponse/ApiNotFoundResponsee incluaApiForbiddenResponsese 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 factoryPequeno 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çãoO 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 erroMensagens 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:poralgorithms: ['HS256'], issuer: 'vagas-api', audience: 'vagas-api-users',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 objetosignOptions), substituir:poralgorithm: 'HS256', issuer: 'vagas-api', audience: 'vagas-api-users',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 evitarany.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: Evitarsynchronizeem produção por segurança.Mesmo controlado por ENV, é fácil um deslize habilitar
synchronizeem prod. Sugiro forçarfalsequandoNODE_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
📒 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çãoResumo 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,Swaggervsswaggerquebra a resolução.src/modules/auth/jwt/jwt.strategy.ts (2)
31-48: Validação defensiva do payload: ótimoType-guards + checagem de campos obrigatórios resolveram os riscos anteriores com
.trim()e tipos não-string.
90-95: Tratamento de erros consistenteRethrow 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
\\nem 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-sourcerecebam as variáveis necessárias, para não disparar estethrowem ambientes locais/CI.
116-124: Config de teste (SQLite in-memory) está consistente.
synchronizedefaulta paratrueedropSchemaativo — 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.
|
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/database/data-source.ts (3)
47-66: Exigir e documentar envs obrigatórias do PostgresA validação está boa. Garanta que essas chaves estejam no
.env.examplee 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
rejectUnauthorizedpor padrão emtruee aviso quando desativado.- CA normalizada via
normalizeCACert.- Fallback para TLS mesmo sem CA (objeto com
rejectUnauthorized).
97-99: Confirme que os testes setamDB_TYPE=sqliteeDB_DATABASE=:memory:(ou adoteNODE_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 docreateSSLConfig(evitarany)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: SimplifiqueisDevelopmente torne o gatilho de teste mais resiliente
NODE_ENV === 'development' || NODE_ENV !== 'production'é redundante; equivale aNODE_ENV !== 'production'.- Considere reconhecer
NODE_ENV === 'test'como modo de teste, reduzindo dependência deDB_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
📒 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 corretamenteSubstituição de
\\npor quebras reais e retornoundefinedpara input vazio estão corretos.
24-45: Validação de porta robustaChecagens de formato e faixa [1–65535] com mensagens claras — perfeito.
100-110: Pathssrcvsdist: atenção para ambientes “staging/homolog”Como a escolha usa
NODE_ENV !== 'production', qualquerNODE_ENVdiferente de'production'apontará parasrc/*.ts. Se seu “staging” roda build compilado, garanta queNODE_ENVseja'production'lá, ou ajuste a lógica para refletir o runtime real.
116-124: Branch de testes bem configuradoUso de SQLite em memória,
dropSchema: trueesynchronizedefaulttruequando não definido favorecem testes rápidos e isolados.


📋 Resumo das Alterações
✨ Implementação de Testes de Autenticação e JWT
🔐 Testes de Auth Implementados:
AuthController tests (
auth.controller.spec.ts):userLoggedAuthLoginService tests (
auth-login.service.spec.ts):JwtStrategy tests (
jwt.strategy.spec.ts):Auth E2E tests (
auth.e2e-spec.ts):/auth/login🎭 Mocks Criados:
🏗️ Estrutura de Auth Implementada:
🔧 Correções de Estrutura e Prettier
📁 Reorganização de Arquivos:
company-repository.ts→company.repository.tsjwt.strategy.tsdejtw/→jwt/(correção de typo)📦 Correções de Importações:
auth.module.ts,company.module.ts,jobs.module.ts,user.module.tsactivate-company.service.ts,create-company.service.ts, etc.⚙️ Configurações Atualizadas:
SECRET_KEY→JWT_SECRET🎨 Formatação Prettier:
🧪 Correção de Testes Falhando
🔧 Testes Corrigidos:
🗑️ Limpeza:
delete-user.service.spec.ts,find-one-user.service.spec.ts📊 Resultados dos Testes
🔍 Commits Organizados
🎯 Benefícios
Summary by CodeRabbit
New Features
Changes
Bug Fixes
Database
Documentation
Tests