Adiciona testes para RefreshTokenService e TokenBlacklistService

- Cria testes completos para RefreshTokenService (14 testes)
- Cria testes completos para TokenBlacklistService (11 testes)
- Remove JSDoc do DebService
- Adiciona testes para DebService (6 testes)
- Corrige query SQL no DebRepository para usar SQL raw em vez de QueryBuilder
- Adiciona documentação de cobertura de testes
This commit is contained in:
joelson brito
2025-11-10 16:24:02 -03:00
parent e3acf34510
commit c07df023dd
9 changed files with 1315 additions and 41 deletions

View File

@@ -0,0 +1,64 @@
import { Test, TestingModule } from '@nestjs/testing';
import { RefreshTokenService } from '../refresh-token.service';
import { IRedisClient } from '../../../core/configs/cache/IRedisClient';
import { RedisClientToken } from '../../../core/configs/cache/redis-client.adapter.provider';
import { JwtService } from '@nestjs/jwt';
export const createMockRedisClient = () =>
({
get: jest.fn(),
set: jest.fn(),
del: jest.fn(),
keys: jest.fn(),
} as any);
export const createMockJwtService = () =>
({
sign: jest.fn(),
verify: jest.fn(),
decode: jest.fn(),
} as any);
export interface RefreshTokenServiceTestContext {
service: RefreshTokenService;
mockRedisClient: jest.Mocked<IRedisClient>;
mockJwtService: jest.Mocked<JwtService>;
}
export async function createRefreshTokenServiceTestModule(
redisClientMethods: Partial<IRedisClient> = {},
jwtServiceMethods: Partial<JwtService> = {},
): Promise<RefreshTokenServiceTestContext> {
const mockRedisClient = {
...createMockRedisClient(),
...redisClientMethods,
} as any;
const mockJwtService = {
...createMockJwtService(),
...jwtServiceMethods,
} as any;
const module: TestingModule = await Test.createTestingModule({
providers: [
RefreshTokenService,
{
provide: RedisClientToken,
useValue: mockRedisClient,
},
{
provide: JwtService,
useValue: mockJwtService,
},
],
}).compile();
const service = module.get<RefreshTokenService>(RefreshTokenService);
return {
service,
mockRedisClient,
mockJwtService,
};
}

View File

@@ -0,0 +1,392 @@
import { UnauthorizedException } from '@nestjs/common';
import { createRefreshTokenServiceTestModule } from './refresh-token.service.spec.helper';
import { RefreshTokenData } from '../refresh-token.service';
describe('RefreshTokenService', () => {
describe('generateRefreshToken', () => {
let context: Awaited<
ReturnType<typeof createRefreshTokenServiceTestModule>
>;
beforeEach(async () => {
context = await createRefreshTokenServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve gerar refresh token com sucesso', async () => {
const userId = 123;
const sessionId = 'session-123';
const mockToken = 'mock.refresh.token';
const mockTokenId = 'token-id-123';
jest.spyOn(require('crypto'), 'randomBytes').mockReturnValue({
toString: () => mockTokenId,
});
context.mockJwtService.sign.mockReturnValue(mockToken);
context.mockRedisClient.set.mockResolvedValue(undefined);
context.mockRedisClient.keys.mockResolvedValue([]);
const result = await context.service.generateRefreshToken(
userId,
sessionId,
);
expect(result).toBe(mockToken);
expect(context.mockJwtService.sign).toHaveBeenCalledWith(
{
userId,
tokenId: mockTokenId,
sessionId,
type: 'refresh',
},
{ expiresIn: '7d' },
);
expect(context.mockRedisClient.set).toHaveBeenCalled();
});
it('deve gerar refresh token sem sessionId', async () => {
const userId = 123;
const mockToken = 'mock.refresh.token';
const mockTokenId = 'token-id-123';
jest.spyOn(require('crypto'), 'randomBytes').mockReturnValue({
toString: () => mockTokenId,
});
context.mockJwtService.sign.mockReturnValue(mockToken);
context.mockRedisClient.set.mockResolvedValue(undefined);
context.mockRedisClient.keys.mockResolvedValue([]);
const result = await context.service.generateRefreshToken(userId);
expect(result).toBe(mockToken);
expect(context.mockJwtService.sign).toHaveBeenCalledWith(
{
userId,
tokenId: mockTokenId,
sessionId: undefined,
type: 'refresh',
},
{ expiresIn: '7d' },
);
});
it('deve limitar número de refresh tokens por usuário', async () => {
const userId = 123;
const mockToken = 'mock.refresh.token';
const mockTokenId = 'token-id-123';
jest.spyOn(require('crypto'), 'randomBytes').mockReturnValue({
toString: () => mockTokenId,
});
context.mockJwtService.sign.mockReturnValue(mockToken);
context.mockRedisClient.set.mockResolvedValue(undefined);
const existingTokens: RefreshTokenData[] = Array.from(
{ length: 6 },
(_, i) => ({
userId,
tokenId: `token-${i}`,
expiresAt: Date.now() + 1000000,
createdAt: Date.now(),
}),
);
context.mockRedisClient.keys.mockResolvedValue([
'auth:refresh_tokens:123:token-0',
'auth:refresh_tokens:123:token-1',
'auth:refresh_tokens:123:token-2',
'auth:refresh_tokens:123:token-3',
'auth:refresh_tokens:123:token-4',
'auth:refresh_tokens:123:token-5',
]);
context.mockRedisClient.get
.mockResolvedValueOnce(existingTokens[0])
.mockResolvedValueOnce(existingTokens[1])
.mockResolvedValueOnce(existingTokens[2])
.mockResolvedValueOnce(existingTokens[3])
.mockResolvedValueOnce(existingTokens[4])
.mockResolvedValueOnce(existingTokens[5]);
await context.service.generateRefreshToken(userId);
expect(context.mockRedisClient.del).toHaveBeenCalled();
});
});
describe('validateRefreshToken', () => {
let context: Awaited<
ReturnType<typeof createRefreshTokenServiceTestModule>
>;
beforeEach(async () => {
context = await createRefreshTokenServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve validar refresh token com sucesso', async () => {
const mockDecoded = {
userId: 123,
tokenId: 'token-id-123',
sessionId: 'session-123',
type: 'refresh',
};
const mockTokenData: RefreshTokenData = {
userId: 123,
tokenId: 'token-id-123',
sessionId: 'session-123',
expiresAt: Date.now() + 1000000,
createdAt: Date.now(),
};
context.mockJwtService.verify.mockReturnValue(mockDecoded);
context.mockRedisClient.get.mockResolvedValue(mockTokenData);
const result = await context.service.validateRefreshToken(
'valid.refresh.token',
);
expect(result.id).toBe(123);
expect((result as any).tokenId).toBe('token-id-123');
expect(result.sessionId).toBe('session-123');
});
it('deve lançar exceção quando token não é do tipo refresh', async () => {
const mockDecoded = {
userId: 123,
tokenId: 'token-id-123',
type: 'access',
};
context.mockJwtService.verify.mockReturnValue(mockDecoded);
await expect(
context.service.validateRefreshToken('invalid.token'),
).rejects.toThrow(UnauthorizedException);
});
it('deve lançar exceção quando token não existe no Redis', async () => {
const mockDecoded = {
userId: 123,
tokenId: 'token-id-123',
sessionId: 'session-123',
type: 'refresh',
};
context.mockJwtService.verify.mockReturnValue(mockDecoded);
context.mockRedisClient.get.mockResolvedValue(null);
await expect(
context.service.validateRefreshToken('expired.token'),
).rejects.toThrow(UnauthorizedException);
});
it('deve lançar exceção quando token está expirado', async () => {
const mockDecoded = {
userId: 123,
tokenId: 'token-id-123',
sessionId: 'session-123',
type: 'refresh',
};
const mockTokenData: RefreshTokenData = {
userId: 123,
tokenId: 'token-id-123',
sessionId: 'session-123',
expiresAt: Date.now() - 1000,
createdAt: Date.now() - 1000000,
};
context.mockJwtService.verify.mockReturnValue(mockDecoded);
context.mockRedisClient.get.mockResolvedValue(mockTokenData);
context.mockRedisClient.del.mockResolvedValue(undefined);
await expect(
context.service.validateRefreshToken('expired.token'),
).rejects.toThrow(UnauthorizedException);
expect(context.mockRedisClient.del).toHaveBeenCalled();
});
it('deve lançar exceção quando verificação do JWT falha', async () => {
context.mockJwtService.verify.mockImplementation(() => {
throw new Error('Token inválido');
});
await expect(
context.service.validateRefreshToken('invalid.token'),
).rejects.toThrow(UnauthorizedException);
});
});
describe('revokeRefreshToken', () => {
let context: Awaited<
ReturnType<typeof createRefreshTokenServiceTestModule>
>;
beforeEach(async () => {
context = await createRefreshTokenServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve revogar refresh token com sucesso', async () => {
const userId = 123;
const tokenId = 'token-id-123';
context.mockRedisClient.del.mockResolvedValue(undefined);
await context.service.revokeRefreshToken(userId, tokenId);
expect(context.mockRedisClient.del).toHaveBeenCalledWith(
`auth:refresh_tokens:${userId}:${tokenId}`,
);
});
});
describe('revokeAllRefreshTokens', () => {
let context: Awaited<
ReturnType<typeof createRefreshTokenServiceTestModule>
>;
beforeEach(async () => {
context = await createRefreshTokenServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve revogar todos os refresh tokens do usuário', async () => {
const userId = 123;
const mockKeys = [
'auth:refresh_tokens:123:token-1',
'auth:refresh_tokens:123:token-2',
'auth:refresh_tokens:123:token-3',
];
context.mockRedisClient.keys.mockResolvedValue(mockKeys);
context.mockRedisClient.del.mockResolvedValue(undefined);
await context.service.revokeAllRefreshTokens(userId);
expect(context.mockRedisClient.keys).toHaveBeenCalledWith(
`auth:refresh_tokens:${userId}:*`,
);
expect(context.mockRedisClient.del).toHaveBeenCalledWith(...mockKeys);
});
it('deve retornar sem erro quando não há tokens para revogar', async () => {
const userId = 123;
context.mockRedisClient.keys.mockResolvedValue([]);
await context.service.revokeAllRefreshTokens(userId);
expect(context.mockRedisClient.del).not.toHaveBeenCalled();
});
});
describe('getActiveRefreshTokens', () => {
let context: Awaited<
ReturnType<typeof createRefreshTokenServiceTestModule>
>;
beforeEach(async () => {
context = await createRefreshTokenServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve retornar tokens ativos ordenados por data de criação', async () => {
const userId = 123;
const mockKeys = [
'auth:refresh_tokens:123:token-1',
'auth:refresh_tokens:123:token-2',
];
const now = Date.now();
const token1: RefreshTokenData = {
userId: 123,
tokenId: 'token-1',
expiresAt: now + 1000000,
createdAt: now - 2000,
};
const token2: RefreshTokenData = {
userId: 123,
tokenId: 'token-2',
expiresAt: now + 1000000,
createdAt: now - 1000,
};
context.mockRedisClient.keys.mockResolvedValue(mockKeys);
context.mockRedisClient.get
.mockResolvedValueOnce(token1)
.mockResolvedValueOnce(token2);
const result = await context.service.getActiveRefreshTokens(userId);
expect(result).toHaveLength(2);
expect(result[0].tokenId).toBe('token-2');
expect(result[1].tokenId).toBe('token-1');
});
it('deve filtrar tokens expirados', async () => {
const userId = 123;
const mockKeys = [
'auth:refresh_tokens:123:token-1',
'auth:refresh_tokens:123:token-2',
];
const now = Date.now();
const token1: RefreshTokenData = {
userId: 123,
tokenId: 'token-1',
expiresAt: now - 1000,
createdAt: now - 2000,
};
const token2: RefreshTokenData = {
userId: 123,
tokenId: 'token-2',
expiresAt: now + 1000000,
createdAt: now - 1000,
};
context.mockRedisClient.keys.mockResolvedValue(mockKeys);
context.mockRedisClient.get
.mockResolvedValueOnce(token1)
.mockResolvedValueOnce(token2);
const result = await context.service.getActiveRefreshTokens(userId);
expect(result).toHaveLength(1);
expect(result[0].tokenId).toBe('token-2');
});
it('deve retornar array vazio quando não há tokens', async () => {
const userId = 123;
context.mockRedisClient.keys.mockResolvedValue([]);
const result = await context.service.getActiveRefreshTokens(userId);
expect(result).toHaveLength(0);
});
});
});

View File

@@ -0,0 +1,62 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TokenBlacklistService } from '../token-blacklist.service';
import { IRedisClient } from '../../../core/configs/cache/IRedisClient';
import { RedisClientToken } from '../../../core/configs/cache/redis-client.adapter.provider';
import { JwtService } from '@nestjs/jwt';
export const createMockRedisClient = () =>
({
get: jest.fn(),
set: jest.fn(),
del: jest.fn(),
keys: jest.fn(),
} as any);
export const createMockJwtService = () =>
({
decode: jest.fn(),
} as any);
export interface TokenBlacklistServiceTestContext {
service: TokenBlacklistService;
mockRedisClient: jest.Mocked<IRedisClient>;
mockJwtService: jest.Mocked<JwtService>;
}
export async function createTokenBlacklistServiceTestModule(
redisClientMethods: Partial<IRedisClient> = {},
jwtServiceMethods: Partial<JwtService> = {},
): Promise<TokenBlacklistServiceTestContext> {
const mockRedisClient = {
...createMockRedisClient(),
...redisClientMethods,
} as any;
const mockJwtService = {
...createMockJwtService(),
...jwtServiceMethods,
} as any;
const module: TestingModule = await Test.createTestingModule({
providers: [
TokenBlacklistService,
{
provide: RedisClientToken,
useValue: mockRedisClient,
},
{
provide: JwtService,
useValue: mockJwtService,
},
],
}).compile();
const service = module.get<TokenBlacklistService>(TokenBlacklistService);
return {
service,
mockRedisClient,
mockJwtService,
};
}

View File

@@ -0,0 +1,257 @@
import { createTokenBlacklistServiceTestModule } from './token-blacklist.service.spec.helper';
import { JwtPayload } from '../../models/jwt-payload.model';
describe('TokenBlacklistService', () => {
describe('addToBlacklist', () => {
let context: Awaited<
ReturnType<typeof createTokenBlacklistServiceTestModule>
>;
beforeEach(async () => {
context = await createTokenBlacklistServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve adicionar token à blacklist com sucesso', async () => {
const mockToken = 'valid.jwt.token';
const mockPayload: JwtPayload = {
id: 123,
sellerId: 1,
storeId: '1',
username: 'user',
email: 'user@example.com',
exp: Math.floor(Date.now() / 1000) + 3600,
};
context.mockJwtService.decode.mockReturnValue(mockPayload);
context.mockRedisClient.set.mockResolvedValue(undefined);
await context.service.addToBlacklist(mockToken);
expect(context.mockJwtService.decode).toHaveBeenCalledWith(mockToken);
expect(context.mockRedisClient.set).toHaveBeenCalled();
});
it('deve adicionar token à blacklist com TTL customizado', async () => {
const mockToken = 'valid.jwt.token';
const mockPayload: JwtPayload = {
id: 123,
sellerId: 1,
storeId: '1',
username: 'user',
email: 'user@example.com',
exp: Math.floor(Date.now() / 1000) + 3600,
};
const customTTL = 7200;
context.mockJwtService.decode.mockReturnValue(mockPayload);
context.mockRedisClient.set.mockResolvedValue(undefined);
await context.service.addToBlacklist(mockToken, customTTL);
expect(context.mockRedisClient.set).toHaveBeenCalledWith(
expect.any(String),
'blacklisted',
customTTL,
);
});
it('deve calcular TTL automaticamente quando não informado', async () => {
const mockToken = 'valid.jwt.token';
const now = Math.floor(Date.now() / 1000);
const exp = now + 3600;
const mockPayload: JwtPayload = {
id: 123,
sellerId: 1,
storeId: '1',
username: 'user',
email: 'user@example.com',
exp,
};
context.mockJwtService.decode.mockReturnValue(mockPayload);
context.mockRedisClient.set.mockResolvedValue(undefined);
await context.service.addToBlacklist(mockToken);
expect(context.mockRedisClient.set).toHaveBeenCalledWith(
expect.any(String),
'blacklisted',
expect.any(Number),
);
});
it('deve lançar erro quando token é inválido', async () => {
const mockToken = 'invalid.token';
context.mockJwtService.decode.mockReturnValue(null);
await expect(
context.service.addToBlacklist(mockToken),
).rejects.toThrow('Token inválido');
});
it('deve lançar erro quando decode falha', async () => {
const mockToken = 'invalid.token';
context.mockJwtService.decode.mockImplementation(() => {
throw new Error('Token malformado');
});
await expect(
context.service.addToBlacklist(mockToken),
).rejects.toThrow('Erro ao adicionar token à blacklist');
});
});
describe('isBlacklisted', () => {
let context: Awaited<
ReturnType<typeof createTokenBlacklistServiceTestModule>
>;
beforeEach(async () => {
context = await createTokenBlacklistServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve retornar true quando token está na blacklist', async () => {
const mockToken = 'blacklisted.token';
const mockPayload: JwtPayload = {
id: 123,
sellerId: 1,
storeId: '1',
username: 'user',
email: 'user@example.com',
};
context.mockJwtService.decode.mockReturnValue(mockPayload);
context.mockRedisClient.get.mockResolvedValue('blacklisted');
const result = await context.service.isBlacklisted(mockToken);
expect(result).toBe(true);
expect(context.mockRedisClient.get).toHaveBeenCalled();
});
it('deve retornar false quando token não está na blacklist', async () => {
const mockToken = 'valid.token';
const mockPayload: JwtPayload = {
id: 123,
sellerId: 1,
storeId: '1',
username: 'user',
email: 'user@example.com',
};
context.mockJwtService.decode.mockReturnValue(mockPayload);
context.mockRedisClient.get.mockResolvedValue(null);
const result = await context.service.isBlacklisted(mockToken);
expect(result).toBe(false);
});
it('deve retornar false quando ocorre erro', async () => {
const mockToken = 'error.token';
const mockPayload: JwtPayload = {
id: 123,
sellerId: 1,
storeId: '1',
username: 'user',
email: 'user@example.com',
};
context.mockJwtService.decode.mockReturnValue(mockPayload);
context.mockRedisClient.get.mockRejectedValue(
new Error('Redis error'),
);
const result = await context.service.isBlacklisted(mockToken);
expect(result).toBe(false);
});
});
describe('removeFromBlacklist', () => {
let context: Awaited<
ReturnType<typeof createTokenBlacklistServiceTestModule>
>;
beforeEach(async () => {
context = await createTokenBlacklistServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve remover token da blacklist com sucesso', async () => {
const mockToken = 'token.to.remove';
const mockPayload: JwtPayload = {
id: 123,
sellerId: 1,
storeId: '1',
username: 'user',
email: 'user@example.com',
};
context.mockJwtService.decode.mockReturnValue(mockPayload);
context.mockRedisClient.del.mockResolvedValue(undefined);
await context.service.removeFromBlacklist(mockToken);
expect(context.mockRedisClient.del).toHaveBeenCalled();
});
});
describe('clearUserBlacklist', () => {
let context: Awaited<
ReturnType<typeof createTokenBlacklistServiceTestModule>
>;
beforeEach(async () => {
context = await createTokenBlacklistServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve limpar todos os tokens do usuário da blacklist', async () => {
const userId = 123;
const mockKeys = [
'auth:blacklist:123:hash1',
'auth:blacklist:123:hash2',
'auth:blacklist:123:hash3',
];
context.mockRedisClient.keys.mockResolvedValue(mockKeys);
context.mockRedisClient.del.mockResolvedValue(undefined);
await context.service.clearUserBlacklist(userId);
expect(context.mockRedisClient.keys).toHaveBeenCalledWith(
`auth:blacklist:${userId}:*`,
);
expect(context.mockRedisClient.del).toHaveBeenCalledWith(...mockKeys);
});
it('deve retornar sem erro quando não há tokens para limpar', async () => {
const userId = 123;
context.mockRedisClient.keys.mockResolvedValue([]);
await context.service.clearUserBlacklist(userId);
expect(context.mockRedisClient.del).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,40 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DebService } from '../deb.service';
import { DebRepository } from '../../repositories/deb.repository';
export const createMockRepository = (
methods: Partial<DebRepository> = {},
) =>
({
findByCpfCgcent: jest.fn(),
...methods,
} as any);
export interface DebServiceTestContext {
service: DebService;
mockRepository: jest.Mocked<DebRepository>;
}
export async function createDebServiceTestModule(
repositoryMethods: Partial<DebRepository> = {},
): Promise<DebServiceTestContext> {
const mockRepository = createMockRepository(repositoryMethods);
const module: TestingModule = await Test.createTestingModule({
providers: [
DebService,
{
provide: DebRepository,
useValue: mockRepository,
},
],
}).compile();
const service = module.get<DebService>(DebService);
return {
service,
mockRepository,
};
}

View File

@@ -0,0 +1,191 @@
import { createDebServiceTestModule } from './deb.service.spec.helper';
import { DebDto } from '../../dto/DebDto';
describe('DebService', () => {
describe('findByCpfCgcent', () => {
let context: Awaited<ReturnType<typeof createDebServiceTestModule>>;
beforeEach(async () => {
context = await createDebServiceTestModule();
});
afterEach(() => {
jest.clearAllMocks();
});
it('deve buscar débitos por CPF/CGCENT com sucesso', async () => {
const mockDebs: DebDto[] = [
{
dtemissao: new Date('2024-01-15'),
codfilial: '1',
duplic: '12345',
prest: '1',
codcli: 1000,
cliente: 'JOÃO DA SILVA',
codcob: 'BL',
cobranca: 'BOLETO',
dtvenc: new Date('2024-02-15'),
dtpag: null,
valor: 150.5,
situacao: 'A VENCER',
},
{
dtemissao: new Date('2024-01-20'),
codfilial: '1',
duplic: '12346',
prest: '2',
codcli: 1000,
cliente: 'JOÃO DA SILVA',
codcob: 'BL',
cobranca: 'BOLETO',
dtvenc: new Date('2024-02-20'),
dtpag: new Date('2024-02-10'),
valor: 200.0,
situacao: 'PAGO',
},
];
context.mockRepository.findByCpfCgcent.mockResolvedValue(mockDebs);
const result = await context.service.findByCpfCgcent('12345678900');
expect(result).toHaveLength(2);
expect(result[0].codcli).toBe(1000);
expect(result[0].cliente).toBe('JOÃO DA SILVA');
expect(result[0].situacao).toBe('A VENCER');
expect(result[1].situacao).toBe('PAGO');
expect(context.mockRepository.findByCpfCgcent).toHaveBeenCalledWith(
'12345678900',
undefined,
undefined,
);
});
it('deve buscar débitos com matricula informada', async () => {
const mockDebs: DebDto[] = [
{
dtemissao: new Date('2024-01-15'),
codfilial: '1',
duplic: '12345',
prest: '1',
codcli: 1000,
cliente: 'JOÃO DA SILVA',
codcob: 'BL',
cobranca: 'BOLETO',
dtvenc: new Date('2024-02-15'),
dtpag: null,
valor: 150.5,
situacao: 'A VENCER',
},
];
context.mockRepository.findByCpfCgcent.mockResolvedValue(mockDebs);
const result = await context.service.findByCpfCgcent(
'12345678900',
1498,
);
expect(result).toHaveLength(1);
expect(context.mockRepository.findByCpfCgcent).toHaveBeenCalledWith(
'12345678900',
1498,
undefined,
);
});
it('deve buscar débitos com cobranca informada', async () => {
const mockDebs: DebDto[] = [
{
dtemissao: new Date('2024-01-15'),
codfilial: '1',
duplic: '12345',
prest: '1',
codcli: 1000,
cliente: 'JOÃO DA SILVA',
codcob: 'BL',
cobranca: 'BOLETO',
dtvenc: new Date('2024-02-15'),
dtpag: null,
valor: 150.5,
situacao: 'A VENCER',
},
];
context.mockRepository.findByCpfCgcent.mockResolvedValue(mockDebs);
const result = await context.service.findByCpfCgcent(
'12345678900',
undefined,
'BL',
);
expect(result).toHaveLength(1);
expect(context.mockRepository.findByCpfCgcent).toHaveBeenCalledWith(
'12345678900',
undefined,
'BL',
);
});
it('deve buscar débitos com matricula e cobranca informadas', async () => {
const mockDebs: DebDto[] = [
{
dtemissao: new Date('2024-01-15'),
codfilial: '1',
duplic: '12345',
prest: '1',
codcli: 1000,
cliente: 'JOÃO DA SILVA',
codcob: 'BL',
cobranca: 'BOLETO',
dtvenc: new Date('2024-02-15'),
dtpag: null,
valor: 150.5,
situacao: 'A VENCER',
},
];
context.mockRepository.findByCpfCgcent.mockResolvedValue(mockDebs);
const result = await context.service.findByCpfCgcent(
'12345678900',
1498,
'BL',
);
expect(result).toHaveLength(1);
expect(context.mockRepository.findByCpfCgcent).toHaveBeenCalledWith(
'12345678900',
1498,
'BL',
);
});
it('deve retornar array vazio quando nenhum débito é encontrado', async () => {
context.mockRepository.findByCpfCgcent.mockResolvedValue([]);
const result = await context.service.findByCpfCgcent('99999999999');
expect(result).toHaveLength(0);
expect(Array.isArray(result)).toBe(true);
expect(context.mockRepository.findByCpfCgcent).toHaveBeenCalledWith(
'99999999999',
undefined,
undefined,
);
});
it('deve propagar erro do repositório', async () => {
const repositoryError = new Error('Database connection failed');
context.mockRepository.findByCpfCgcent.mockRejectedValue(
repositoryError,
);
await expect(
context.service.findByCpfCgcent('12345678900'),
).rejects.toThrow('Database connection failed');
});
});
});

View File

@@ -6,14 +6,6 @@ import { DebDto } from '../dto/DebDto';
export class DebService {
constructor(private readonly debRepository: DebRepository) {}
/**
* Busca débitos por CPF ou CGCENT
* @param cpfCgcent - CPF ou CGCENT do cliente (validado pelo DTO)
* @param matricula - Matrícula do funcionário (opcional)
* @param cobranca - Código de cobrança (opcional)
* @returns Lista de débitos do cliente
* @throws {Error} Erro ao buscar débitos no banco de dados
*/
async findByCpfCgcent(
cpfCgcent: string,
matricula?: number,

View File

@@ -25,47 +25,50 @@ export class DebRepository {
const queryRunner = this.oracleDataSource.createQueryRunner();
await queryRunner.connect();
try {
const queryBuilder = queryRunner.manager
.createQueryBuilder()
.select([
'p.dtemissao AS "dtemissao"',
'p.codfilial AS "codfilial"',
'p.duplic AS "duplic"',
'p.prest AS "prest"',
'p.codcli AS "codcli"',
'c.cliente AS "cliente"',
'p.codcob AS "codcob"',
'cb.cobranca AS "cobranca"',
'p.dtvenc AS "dtvenc"',
'p.dtpag AS "dtpag"',
'p.valor AS "valor"',
`CASE
WHEN p.dtpag IS NOT NULL THEN 'PAGO'
WHEN p.dtvenc < TRUNC(SYSDATE) THEN 'EM ATRASO'
WHEN p.dtvenc >= TRUNC(SYSDATE) THEN 'A VENCER'
ELSE 'NENHUM'
END AS "situacao"`,
])
.from('pcprest', 'p')
.innerJoin('pcclient', 'c', 'p.codcli = c.codcli')
.innerJoin('pccob', 'cb', 'p.codcob = cb.codcob')
.innerJoin('pcempr', 'e', 'c.cgcent = e.cpf')
.where('p.codcob NOT IN (:...excludedCob)', {
excludedCob: ['DESD', 'CANC'],
})
.andWhere('c.cgcent = :cpfCgcent', { cpfCgcent });
let sql = `
SELECT p.dtemissao AS "dtemissao",
p.codfilial AS "codfilial",
p.duplic AS "duplic",
p.prest AS "prest",
p.codcli AS "codcli",
c.cliente AS "cliente",
p.codcob AS "codcob",
cb.cobranca AS "cobranca",
p.dtvenc AS "dtvenc",
p.dtpag AS "dtpag",
p.valor AS "valor",
CASE
WHEN p.dtpag IS NOT NULL THEN 'PAGO'
WHEN p.dtvenc < TRUNC(SYSDATE) THEN 'EM ATRASO'
WHEN p.dtvenc >= TRUNC(SYSDATE) THEN 'A VENCER'
ELSE 'NENHUM'
END AS "situacao"
FROM PCPREST p
INNER JOIN PCCLIENT c ON p.codcli = c.codcli
INNER JOIN PCCOB cb ON p.codcob = cb.codcob
INNER JOIN PCEMPR e ON c.cgcent = e.cpf
WHERE p.codcob NOT IN (:0, :1)
AND c.cgcent = :2
`;
const params: any[] = ['DESD', 'CANC', cpfCgcent];
let paramIndex = 3;
if (matricula) {
queryBuilder.andWhere('e.matricula = :matricula', { matricula });
sql += ` AND e.matricula = :${paramIndex}`;
params.push(matricula);
paramIndex++;
}
if (cobranca) {
queryBuilder.andWhere('p.codcob = :cobranca', { cobranca });
sql += ` AND p.codcob = :${paramIndex}`;
params.push(cobranca);
paramIndex++;
}
queryBuilder.orderBy('p.dtvenc', 'ASC');
sql += ` ORDER BY p.dtvenc ASC`;
const result = await queryBuilder.getRawMany();
const result = await queryRunner.query(sql, params);
return result;
} finally {
await queryRunner.release();