implementação do logger

This commit is contained in:
unknown
2025-03-28 14:42:53 -03:00
parent 9c77880d89
commit 234704c9ba
23 changed files with 7736 additions and 4005 deletions

6
src/Log/ILogger.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface ILogger {
log(message: string): void;
warn(message: string): void;
error(message: string, trace?: string): void;
}

View File

@@ -0,0 +1,26 @@
import { Logger } from '@nestjs/common';
import { ILogger } from './ILogger';
export class NestLoggerAdapter implements ILogger {
private readonly logger: Logger;
constructor(private readonly context: string) {
this.logger = new Logger(context);
}
log(message: string, meta?: Record<string, any>): void {
this.logger.log(this.formatMessage(message, meta));
}
warn(message: string, meta?: Record<string, any>): void {
this.logger.warn(this.formatMessage(message, meta));
}
error(message: string, trace?: string, meta?: Record<string, any>): void {
this.logger.error(this.formatMessage(message, meta), trace);
}
private formatMessage(message: string, meta?: Record<string, any>): string {
return meta ? `${message} | ${JSON.stringify(meta)}` : message;
}
}

View File

@@ -0,0 +1,22 @@
import { ILogger } from './ILogger';
export function LogExecution(label?: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = async function (...args: any[]) {
const logger: ILogger = this.logger;
const context = label || `${target.constructor.name}.${propertyKey}`;
const start = Date.now();
logger.log(`Iniciando: ${context} | ${JSON.stringify({ args })}`);
const result = await original.apply(this, args);
const duration = Date.now() - start;
logger.log(`Finalizado: ${context} em ${duration}ms`);
return result;
};
return descriptor;
};
}

14
src/Log/logger.module.ts Normal file
View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { NestLoggerAdapter } from './NestLoggerAdapter';
import { ILogger } from './ILogger';
@Module({
providers: [
{
provide: 'LoggerService',
useFactory: () => new NestLoggerAdapter('DataConsultService'),
},
],
exports: ['LoggerService'],
})
export class LoggerModule {}

View File

@@ -18,7 +18,9 @@ import { ReasonTableModule } from './crm/reason-table/reason-table.module';
import { NegotiationsModule } from './crm/negotiations/negotiations.module';
import { HttpModule } from '@nestjs/axios';
import { LogisticController } from './logistic/logistic.controller';
import { CacheModule } from './core/configs/cache/redis.module';
import { LogisticService } from './logistic/logistic.service';
import { LoggerModule } from './Log/logger.module';
@Module({
imports: [
@@ -29,12 +31,15 @@ import { LogisticService } from './logistic/logistic.service';
NegotiationsModule,
OccurrencesModule,
ReasonTableModule,
LoggerModule,
DataConsultModule,
ProductsModule,
AuthModule,
OrdersModule,
TypeOrmModule.forRoot(typeOrmConfig),
TypeOrmModule.forRoot(typeOrmPgConfig),
CacheModule,
],
controllers: [
OcorrencesController, LogisticController,],

View File

@@ -0,0 +1,6 @@
export interface IRedisClient {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
del(key: string): Promise<void>;
}

View File

@@ -0,0 +1,8 @@
import { RedisClientAdapter } from './redis-client.adapter';
export const RedisClientToken = 'RedisClientInterface';
export const RedisClientAdapterProvider = {
provide: RedisClientToken,
useClass: RedisClientAdapter,
};

View File

@@ -0,0 +1,24 @@
import { Inject, Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';
import { IRedisClient } from './IRedisClient';
@Injectable()
export class RedisClientAdapter implements IRedisClient {
constructor(
@Inject('REDIS_CLIENT')
private readonly redis: Redis
) {}
async get<T>(key: string): Promise<T | null> {
const data = await this.redis.get(key);
return data ? JSON.parse(data) : null;
}
async set<T>(key: string, value: T, ttlSeconds = 300): Promise<void> {
await this.redis.set(key, JSON.stringify(value), 'EX', ttlSeconds);
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
}

12
src/core/configs/cache/redis.module.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
import { Module, Global } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RedisProvider } from './redis.provider';
import { RedisClientAdapterProvider } from './redis-client.adapter.provider';
@Global()
@Module({
imports: [ConfigModule],
providers: [RedisProvider, RedisClientAdapterProvider],
exports: [RedisProvider, RedisClientAdapterProvider],
})
export class CacheModule {}

View File

@@ -0,0 +1,21 @@
import { Provider } from '@nestjs/common';
import Redis from 'ioredis';
import { ConfigService } from '@nestjs/config';
export const RedisProvider: Provider = {
provide: 'REDIS_CLIENT',
useFactory: (configService: ConfigService) => {
const redis = new Redis({
host: configService.get<string>('REDIS_HOST', '10.1.1.109'),
port: configService.get<number>('REDIS_PORT', 6379),
// password: configService.get<string>('REDIS_PASSWORD', ''),
});
redis.on('error', (err) => {
console.error('Erro ao conectar ao Redis:', err);
});
return redis;
},
inject: [ConfigService],
};

View File

@@ -1,44 +1,43 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
https://docs.nestjs.com/controllers#controllers
*/
import { Controller, Get, Param } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
import { DataConsultService } from './data-consult.service';
@ApiTags('DataConsult')
@Controller('api/v1/data-consult')
export class DataConsultController {
export class DataConsultController {
constructor(private readonly dataConsultService: DataConsultService) {}
constructor(private readonly dataConsultService: DataConsultService) {}
@Get('stores')
async stores() {
return this.dataConsultService.stores();
}
@Get('stores')
@ApiOperation({ summary: 'Lista todas as lojas' })
async stores() {
return this.dataConsultService.stores();
}
@Get('sellers')
async sellers() {
return this.dataConsultService.sellers();
}
@Get('sellers')
@ApiOperation({ summary: 'Lista todos os vendedores' })
async sellers() {
return this.dataConsultService.sellers();
}
@Get('billings')
async billings() {
return this.dataConsultService.billings();
}
@Get('billings')
@ApiOperation({ summary: 'Retorna informações de faturamento' })
async billings() {
return this.dataConsultService.billings();
}
@Get('customers/:filter')
async customer(@Param('filter') filter: string) {
return this.dataConsultService.customers(filter);
}
@Get('products/:filter')
async products(@Param('filter') filter: string) {
return this.dataConsultService.products(filter);
}
@Get('customers/:filter')
@ApiOperation({ summary: 'Filtra clientes pelo parâmetro fornecido' })
@ApiParam({ name: 'filter', description: 'Filtro de busca para clientes' })
async customer(@Param('filter') filter: string) {
return this.dataConsultService.customers(filter);
}
@Get('products/:filter')
@ApiOperation({ summary: 'Filtra produtos pelo parâmetro fornecido' })
@ApiParam({ name: 'filter', description: 'Filtro de busca para produtos' })
async products(@Param('filter') filter: string) {
return this.dataConsultService.products(filter);
}
}

View File

@@ -1,19 +1,16 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Module } from '@nestjs/common';
import { DataConsultService } from './data-consult.service';
import { DataConsultController } from './data-consult.controller';
/*
https://docs.nestjs.com/modules
*/
import { Module } from '@nestjs/common';
import { DataConsultRepository } from './data-consult.repository';
import { CacheModule } from '../core/configs/cache/redis.module';
import { LoggerModule } from 'src/Log/logger.module';
@Module({
imports: [],
controllers: [
DataConsultController,],
providers: [
DataConsultService,],
imports: [CacheModule,LoggerModule],
controllers: [DataConsultController],
providers: [
DataConsultService,
DataConsultRepository,
],
})
export class DataConsultModule { }
export class DataConsultModule {}

View File

@@ -0,0 +1,158 @@
import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { typeOrmConfig } from '../core/configs/typeorm.config';
import { StoreDto } from './dto/store.dto';
import { SellerDto } from './dto/seller.dto';
import { BillingDto } from './dto/billing.dto';
import { CustomerDto } from './dto/customer.dto';
import { ProductDto } from './dto/product.dto';
@Injectable()
export class DataConsultRepository {
private readonly dataSource: DataSource;
constructor() {
this.dataSource = new DataSource(typeOrmConfig);
this.dataSource.initialize();
}
private async executeQuery<T>(sql: string, params: any[] = []): Promise<T> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
try {
const result = await queryRunner.query(sql, params);
return result as T;
} finally {
await queryRunner.release();
}
}
async findStores(): Promise<StoreDto[]> {
const sql = `
SELECT PCFILIAL.CODIGO as "id",
PCFILIAL.RAZAOSOCIAL as "name",
PCFILIAL.CODIGO || ' - ' || PCFILIAL.FANTASIA as "store"
FROM PCFILIAL
WHERE PCFILIAL.CODIGO NOT IN ('99', '69')
ORDER BY TO_NUMBER(PCFILIAL.CODIGO)
`;
return this.executeQuery<StoreDto[]>(sql);
}
async findSellers(): Promise<SellerDto[]> {
const sql = `
SELECT PCUSUARI.CODUSUR as "id",
PCUSUARI.NOME as "name"
FROM PCUSUARI
WHERE PCUSUARI.DTTERMINO IS NULL
AND PCUSUARI.TIPOVEND NOT IN ('P')
ORDER BY PCUSUARI.NOME
`;
return this.executeQuery<SellerDto[]>(sql);
}
async findBillings(): Promise<BillingDto[]> {
const sql = `
SELECT PCCOB.CODCOB as "id",
PCCOB.CODCOB || ' - ' || PCCOB.COBRANCA as "description"
FROM PCCOB
WHERE PCCOB.CODCOB NOT IN ('DEVP', 'DEVT', 'DESD')
ORDER BY PCCOB.COBRANCA
`;
return this.executeQuery<BillingDto[]>(sql);
}
async findCustomers(filter: string): Promise<CustomerDto[]> {
const queries = [
{
sql: `
SELECT PCCLIENT.CODCLI as "id",
PCCLIENT.CODCLI || ' - ' || PCCLIENT.CLIENTE ||
' ( ' || REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') || ' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CODCLI = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE
`,
params: [filter],
},
{
sql: `
SELECT PCCLIENT.CODCLI as "id",
PCCLIENT.CODCLI || ' - ' || PCCLIENT.CLIENTE ||
' ( ' || REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') || ' )' as "name"
FROM PCCLIENT
WHERE REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE
`,
params: [filter],
},
{
sql: `
SELECT PCCLIENT.CODCLI as "id",
PCCLIENT.CODCLI || ' - ' || PCCLIENT.CLIENTE ||
' ( ' || REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') || ' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CLIENTE LIKE ?
ORDER BY PCCLIENT.CLIENTE
`,
params: [filter.toUpperCase().replace('@', '%') + '%'],
},
];
for (const { sql, params } of queries) {
const result = await this.executeQuery<CustomerDto[]>(sql, params);
if (result.length > 0) {
return result;
}
}
return [];
}
async findProducts(filter: string): Promise<ProductDto[]> {
const queries = [
{
sql: `
SELECT PCPRODUT.CODPROD as "id",
PCPRODUT.CODPROD || ' - ' || PCPRODUT.DESCRICAO ||
' ( ' || REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '') || ' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODPROD = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO
`,
params: [filter],
},
{
sql: `
SELECT PCPRODUT.CODPROD as "id",
PCPRODUT.CODPROD || ' - ' || PCPRODUT.DESCRICAO ||
' ( ' || REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '') || ' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODAUXILIAR = REGEXP_REPLACE(?, '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO
`,
params: [filter],
},
{
sql: `
SELECT PCPRODUT.CODPROD as "id",
PCPRODUT.CODPROD || ' - ' || PCPRODUT.DESCRICAO ||
' ( ' || REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '') || ' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.DESCRICAO LIKE ?
ORDER BY PCPRODUT.DESCRICAO
`,
params: [filter + '%'],
},
];
for (const { sql, params } of queries) {
const result = await this.executeQuery<ProductDto[]>(sql, params);
if (result.length > 0) {
return result;
}
}
return [];
}
}

View File

@@ -1,170 +1,127 @@
/* eslint-disable prettier/prettier */
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
import { Inject, Injectable } from '@nestjs/common';
import { IRedisClient } from '../core/configs/cache/IRedisClient';
import { RedisClientToken } from '../core/configs/cache/redis-client.adapter.provider';
import { DataConsultRepository } from './data-consult.repository';
import { StoreDto } from './dto/store.dto';
import { SellerDto } from './dto/seller.dto';
import { BillingDto } from './dto/billing.dto';
import { CustomerDto } from './dto/customer.dto';
import { ProductDto } from './dto/product.dto';
import { ILogger } from '../Log/ILogger';
https://docs.nestjs.com/providers#services
*/
import { Injectable } from '@nestjs/common';
import { typeOrmConfig } from '../core/configs/typeorm.config';
import { DataSource } from 'typeorm';
const DEFAULT_CACHE_TTL = 100;
@Injectable()
export class DataConsultService {
constructor(
private readonly repository: DataConsultRepository,
@Inject(RedisClientToken)
private readonly redisClient: IRedisClient,
@Inject('LoggerService')
private readonly logger: ILogger
) {}
/**
* Método genérico para lidar com lógica de cache
* @param cacheKey - A chave a ser usada para cache
* @param fetchFn - Função para buscar dados se não estiverem no cache
* @param ttl - Tempo de vida em segundos para o cache
* @returns Os dados em cache ou recentemente buscados
*/
private async getCachedData<T>(
cacheKey: string,
fetchFn: () => Promise<T>,
ttl: number = DEFAULT_CACHE_TTL
): Promise<T> {
try {
this.logger.log(`Tentando obter dados em cache para a chave: ${cacheKey}`);
const cached = await this.redisClient.get<T>(cacheKey);
async stores() {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT PCFILIAL.CODIGO as "id"
,PCFILIAL.RAZAOSOCIAL as "name"
,PCFILIAL.CODIGO || ' - ' || PCFILIAL.FANTASIA as "store"
FROM PCFILIAL
WHERE PCFILIAL.CODIGO NOT IN ('99', '69')
ORDER BY TO_NUMBER(PCFILIAL.CODIGO)`;
if (cached) {
this.logger.log(`Cache encontrado para a chave: ${cacheKey}`);
return cached;
}
this.logger.log(`Cache não encontrado para a chave: ${cacheKey}, buscando na origem`);
const result = await fetchFn();
const stores = await queryRunner.manager.query(sql);
try {
await this.redisClient.set<T>(cacheKey, result, ttl);
this.logger.log(`Dados armazenados em cache com sucesso para a chave: ${cacheKey}`);
} catch (cacheError) {
this.logger.warn(`Falha ao armazenar dados em cache para a chave: ${cacheKey}`);
this.logger.error('Detalhes do erro de cache:', cacheError instanceof Error ? cacheError.stack : '');
}
return stores;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
return result;
} catch (error) {
this.logger.error(
`Erro no método getCachedData para a chave ${cacheKey}:`,
error instanceof Error ? error.stack : ''
);
return fetchFn();
}
}
/**
* Obter todas as lojas com cache
* @returns Array de StoreDto
*/
async stores(): Promise<StoreDto[]> {
this.logger.log('Buscando todas as lojas');
return this.getCachedData<StoreDto[]>(
'data-consult:stores',
() => this.repository.findStores()
);
}
async sellers() {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT PCUSUARI.CODUSUR as "id"
,PCUSUARI.NOME as "name"
FROM PCUSUARI
WHERE PCUSUARI.DTTERMINO IS NULL
AND PCUSUARI.TIPOVEND NOT IN ('P')
ORDER BY PCUSUARI.NOME`;
/**
* Obter todos os vendedores com cache
* @returns Array de SellerDto
*/
async sellers(): Promise<SellerDto[]> {
this.logger.log('Buscando todos os vendedores');
return this.getCachedData<SellerDto[]>(
'data-consult:sellers',
() => this.repository.findSellers()
);
}
/**
* Obter todos os faturamentos com cache
* @returns Array de BillingDto
*/
async billings(): Promise<BillingDto[]> {
this.logger.log('Buscando todos os faturamentos');
return this.getCachedData<BillingDto[]>(
'data-consult:billings',
() => this.repository.findBillings()
);
}
const sellers = await queryRunner.manager.query(sql);
return sellers;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
async billings() {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
const sql = `SELECT PCCOB.CODCOB as "id"
,PCCOB.CODCOB||' - '||PCCOB.COBRANCA as "description"
FROM PCCOB
WHERE PCCOB.CODCOB NOT IN ('DEVP', 'DEVT', 'DESD')
ORDER BY PCCOB.COBRANCA`;
const sellers = await queryRunner.manager.query(sql);
return sellers;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
async customers(filter: string) {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
let sql = `SELECT PCCLIENT.CODCLI as "id"
,PCCLIENT.CODCLI || ' - '|| PCCLIENT.CLIENTE||
' ( '||REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '')||' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CODCLI = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE`;
let customers = await queryRunner.manager.query(sql);
if (customers.length == 0) {
sql = `SELECT PCCLIENT.CODCLI as "id"
,PCCLIENT.CODCLI || ' - '|| PCCLIENT.CLIENTE||
' ( '||REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '')||' )' as "name"
FROM PCCLIENT
WHERE REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '') = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCCLIENT.CLIENTE`;
customers = await queryRunner.manager.query(sql);
}
if (customers.length == 0) {
sql = `SELECT PCCLIENT.CODCLI as "id"
,PCCLIENT.CODCLI || ' - '|| PCCLIENT.CLIENTE||
' ( '||REGEXP_REPLACE(PCCLIENT.CGCENT, '[^0-9]', '')||' )' as "name"
FROM PCCLIENT
WHERE PCCLIENT.CLIENTE LIKE '${filter.toUpperCase().replace('@', '%')}%'
ORDER BY PCCLIENT.CLIENTE`;
customers = await queryRunner.manager.query(sql);
}
return customers;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
async products(filter: string) {
const dataSource = new DataSource(typeOrmConfig);
await dataSource.initialize();
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
try {
let sql = `SELECT PCPRODUT.CODPROD as "id"
,PCPRODUT.CODPROD || ' - '|| PCPRODUT.DESCRICAO||
' ( '||REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '')||' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODPROD = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO`;
let products = await queryRunner.manager.query(sql);
if (products.length == 0) {
sql = `SELECT PCPRODUT.CODPROD as "id"
,PCPRODUT.CODPROD || ' - '|| PCPRODUT.DESCRICAO||
' ( '||REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '')||' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.CODAUXILIAR = REGEXP_REPLACE('${filter}', '[^0-9]', '')
ORDER BY PCPRODUT.DESCRICAO`;
products = await queryRunner.manager.query(sql);
}
if (products.length == 0) {
sql = `SELECT PCPRODUT.CODPROD as "id"
,PCPRODUT.CODPROD || ' - '|| PCPRODUT.DESCRICAO||
' ( '||REGEXP_REPLACE(PCPRODUT.CODAUXILIAR, '[^0-9]', '')||' )' as "description"
FROM PCPRODUT
WHERE PCPRODUT.DESCRICAO LIKE '${filter}%'
ORDER BY PCPRODUT.DESCRICAO`;
products = await queryRunner.manager.query(sql);
}
return products;
} finally {
await queryRunner.release();
await dataSource.destroy();
}
}
/**
* Obter clientes filtrados por termo de pesquisa com cache
* @param filter - Termo de pesquisa para filtrar clientes
* @returns Array de CustomerDto
*/
async customers(filter: string): Promise<CustomerDto[]> {
this.logger.log(`Buscando clientes com filtro: ${filter}`);
return this.getCachedData<CustomerDto[]>(
`data-consult:customers:${filter}`,
() => this.repository.findCustomers(filter)
);
}
/**
* Obter produtos filtrados por termo de pesquisa com cache
* @param filter - Termo de pesquisa para filtrar produtos
* @returns Array de ProductDto
*/
async products(filter: string): Promise<ProductDto[]> {
this.logger.log(`Buscando produtos com filtro: ${filter}`);
return this.getCachedData<ProductDto[]>(
`data-consult:products:${filter}`,
() => this.repository.findProducts(filter)
);
}
}

View File

@@ -0,0 +1,90 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DataConsultService } from './data-consult.service';
import { DataConsultRepository } from './data-consult.repository';
describe('DataConsultService', () => {
let service: DataConsultService;
let repository: DataConsultRepository;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
DataConsultService,
{
provide: DataConsultRepository,
useValue: {
findStores: jest.fn(),
findSellers: jest.fn(),
findBillings: jest.fn(),
findCustomers: jest.fn(),
findProducts: jest.fn(),
},
},
],
}).compile();
service = module.get<DataConsultService>(DataConsultService);
repository = module.get<DataConsultRepository>(DataConsultRepository);
});
it('deve estar definido', () => {
expect(service).toBeDefined();
});
describe('stores', () => {
it('deve retornar uma lista de lojas', async () => {
const mockStores = [{ id: '1', name: 'Loja 1', store: '1 - Loja 1' }];
jest.spyOn(repository, 'findStores').mockResolvedValue(mockStores);
const result = await service.stores();
expect(result).toEqual(mockStores);
expect(repository.findStores).toHaveBeenCalled();
});
});
describe('sellers', () => {
it('deve retornar uma lista de vendedores', async () => {
const mockSellers = [{ id: '1', name: 'Vendedor 1' }];
jest.spyOn(repository, 'findSellers').mockResolvedValue(mockSellers);
const result = await service.sellers();
expect(result).toEqual(mockSellers);
expect(repository.findSellers).toHaveBeenCalled();
});
});
describe('billings', () => {
it('deve retornar informações de faturamento', async () => {
const mockBillings = [{ id: '1', description: 'Faturamento 1' }];
jest.spyOn(repository, 'findBillings').mockResolvedValue(mockBillings);
const result = await service.billings();
expect(result).toEqual(mockBillings);
expect(repository.findBillings).toHaveBeenCalled();
});
});
describe('customers', () => {
it('deve retornar clientes de acordo com o filtro fornecido', async () => {
const filter = '123';
const mockCustomers = [{ id: '123', name: 'Cliente 123' }];
jest.spyOn(repository, 'findCustomers').mockResolvedValue(mockCustomers);
const result = await service.customers(filter);
expect(result).toEqual(mockCustomers);
expect(repository.findCustomers).toHaveBeenCalledWith(filter);
});
});
describe('products', () => {
it('deve retornar produtos de acordo com o filtro fornecido', async () => {
const filter = '456';
const mockProducts = [{ id: '456', description: 'Produto 456' }];
jest.spyOn(repository, 'findProducts').mockResolvedValue(mockProducts);
const result = await service.products(filter);
expect(result).toEqual(mockProducts);
expect(repository.findProducts).toHaveBeenCalledWith(filter);
});
});
});

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class BillingDto {
@ApiProperty({ description: 'Identificador do faturamento' })
id: string;
@ApiProperty({ description: 'Descrição do faturamento' })
description: string;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class CustomerDto {
@ApiProperty({ description: 'Identificador do cliente' })
id: string;
@ApiProperty({ description: 'Nome e identificação do cliente' })
name: string;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class ProductDto {
@ApiProperty({ description: 'Identificador do produto' })
id: string;
@ApiProperty({ description: 'Descrição do produto' })
description: string;
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class SellerDto {
@ApiProperty({ description: 'Identificador do vendedor' })
id: string;
@ApiProperty({ description: 'Nome do vendedor' })
name: string;
}

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
export class StoreDto {
@ApiProperty({ description: 'Identificador da loja' })
id: string;
@ApiProperty({ description: 'Nome da loja' })
name: string;
@ApiProperty({ description: 'Representação da loja (código e fantasia)' })
store: string;
}

View File

@@ -23,6 +23,6 @@ async function bootstrap() {
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(9000);
await app.listen(9002);
}
bootstrap();