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:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
392
src/auth/services/__tests__/refresh-token.service.spec.ts
Normal file
392
src/auth/services/__tests__/refresh-token.service.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
257
src/auth/services/__tests__/token-blacklist.service.spec.ts
Normal file
257
src/auth/services/__tests__/token-blacklist.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
40
src/orders/application/__tests__/deb.service.spec.helper.ts
Normal file
40
src/orders/application/__tests__/deb.service.spec.helper.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
||||
191
src/orders/application/__tests__/deb.service.spec.ts
Normal file
191
src/orders/application/__tests__/deb.service.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user