[SYNC] test: Add service unit tests
- billing-usage: invoices.service.spec.ts, subscriptions.service.spec.ts - financial: payments.service.spec.ts - inventory: warehouses-new.service.spec.ts - partners: partners.service.spec.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7d3ad15968
commit
7ae745e509
360
src/modules/billing-usage/__tests__/invoices.service.spec.ts
Normal file
360
src/modules/billing-usage/__tests__/invoices.service.spec.ts
Normal file
@ -0,0 +1,360 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { InvoicesService } from '../services/invoices.service';
|
||||
import { Invoice, InvoiceItem, InvoiceStatus, PaymentStatus } from '../entities';
|
||||
import { CreateInvoiceDto, UpdateInvoiceDto } from '../dto';
|
||||
|
||||
describe('InvoicesService', () => {
|
||||
let service: InvoicesService;
|
||||
let invoiceRepository: Repository<Invoice>;
|
||||
let invoiceItemRepository: Repository<InvoiceItem>;
|
||||
let dataSource: DataSource;
|
||||
|
||||
const mockInvoice = {
|
||||
id: 'uuid-1',
|
||||
tenantId: 'tenant-1',
|
||||
customerId: 'customer-1',
|
||||
number: 'INV-2024-001',
|
||||
status: InvoiceStatus.DRAFT,
|
||||
paymentStatus: PaymentStatus.PENDING,
|
||||
issueDate: new Date('2024-01-01'),
|
||||
dueDate: new Date('2024-01-15'),
|
||||
subtotal: 1000,
|
||||
taxAmount: 160,
|
||||
totalAmount: 1160,
|
||||
currency: 'USD',
|
||||
notes: null,
|
||||
metadata: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockInvoiceItem = {
|
||||
id: 'item-1',
|
||||
invoiceId: 'uuid-1',
|
||||
productId: 'product-1',
|
||||
description: 'Test Product',
|
||||
quantity: 2,
|
||||
unitPrice: 500,
|
||||
discount: 0,
|
||||
taxRate: 0.08,
|
||||
total: 1080,
|
||||
metadata: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
InvoicesService,
|
||||
{
|
||||
provide: DataSource,
|
||||
useValue: {
|
||||
getRepository: jest.fn(),
|
||||
query: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<InvoicesService>(InvoicesService);
|
||||
dataSource = module.get<DataSource>(DataSource);
|
||||
invoiceRepository = module.get<Repository<Invoice>>(
|
||||
getRepositoryToken(Invoice),
|
||||
);
|
||||
invoiceItemRepository = module.get<Repository<InvoiceItem>>(
|
||||
getRepositoryToken(InvoiceItem),
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new invoice successfully', async () => {
|
||||
const dto: CreateInvoiceDto = {
|
||||
customerId: 'customer-1',
|
||||
issueDate: new Date('2024-01-01'),
|
||||
dueDate: new Date('2024-01-15'),
|
||||
items: [
|
||||
{
|
||||
productId: 'product-1',
|
||||
description: 'Test Product',
|
||||
quantity: 2,
|
||||
unitPrice: 500,
|
||||
discount: 0,
|
||||
taxRate: 0.08,
|
||||
},
|
||||
],
|
||||
notes: 'Test invoice',
|
||||
};
|
||||
|
||||
jest.spyOn(invoiceRepository, 'create').mockReturnValue(mockInvoice as any);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue(mockInvoice);
|
||||
jest.spyOn(invoiceItemRepository, 'create').mockReturnValue(mockInvoiceItem as any);
|
||||
jest.spyOn(invoiceItemRepository, 'save').mockResolvedValue(mockInvoiceItem);
|
||||
|
||||
const result = await service.create(dto);
|
||||
|
||||
expect(invoiceRepository.create).toHaveBeenCalled();
|
||||
expect(invoiceRepository.save).toHaveBeenCalled();
|
||||
expect(invoiceItemRepository.create).toHaveBeenCalled();
|
||||
expect(invoiceItemRepository.save).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockInvoice);
|
||||
});
|
||||
|
||||
it('should calculate totals correctly', async () => {
|
||||
const dto: CreateInvoiceDto = {
|
||||
customerId: 'customer-1',
|
||||
issueDate: new Date('2024-01-01'),
|
||||
dueDate: new Date('2024-01-15'),
|
||||
items: [
|
||||
{
|
||||
productId: 'product-1',
|
||||
description: 'Test Product 1',
|
||||
quantity: 2,
|
||||
unitPrice: 500,
|
||||
discount: 50,
|
||||
taxRate: 0.08,
|
||||
},
|
||||
{
|
||||
productId: 'product-2',
|
||||
description: 'Test Product 2',
|
||||
quantity: 1,
|
||||
unitPrice: 300,
|
||||
discount: 0,
|
||||
taxRate: 0.08,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const expectedInvoice = {
|
||||
...mockInvoice,
|
||||
subtotal: 1000,
|
||||
taxAmount: 120,
|
||||
totalAmount: 1120,
|
||||
};
|
||||
|
||||
jest.spyOn(invoiceRepository, 'create').mockReturnValue(expectedInvoice as any);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue(expectedInvoice);
|
||||
jest.spyOn(invoiceItemRepository, 'create').mockReturnValue(mockInvoiceItem as any);
|
||||
jest.spyOn(invoiceItemRepository, 'save').mockResolvedValue(mockInvoiceItem);
|
||||
|
||||
const result = await service.create(dto);
|
||||
|
||||
expect(result.subtotal).toBe(1000);
|
||||
expect(result.taxAmount).toBe(120);
|
||||
expect(result.totalAmount).toBe(1120);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find invoice by id', async () => {
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(mockInvoice as any);
|
||||
|
||||
const result = await service.findById('uuid-1');
|
||||
|
||||
expect(invoiceRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: 'uuid-1' },
|
||||
relations: ['items'],
|
||||
});
|
||||
expect(result).toEqual(mockInvoice);
|
||||
});
|
||||
|
||||
it('should return null if invoice not found', async () => {
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
const result = await service.findById('invalid-id');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByTenant', () => {
|
||||
it('should find invoices by tenant', async () => {
|
||||
const mockInvoices = [mockInvoice, { ...mockInvoice, id: 'uuid-2' }];
|
||||
jest.spyOn(invoiceRepository, 'find').mockResolvedValue(mockInvoices as any);
|
||||
|
||||
const result = await service.findByTenant('tenant-1', {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
expect(invoiceRepository.find).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1' },
|
||||
relations: ['items'],
|
||||
order: { createdAt: 'DESC' },
|
||||
skip: 0,
|
||||
take: 10,
|
||||
});
|
||||
expect(result).toEqual(mockInvoices);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update invoice successfully', async () => {
|
||||
const dto: UpdateInvoiceDto = {
|
||||
status: InvoiceStatus.SENT,
|
||||
notes: 'Updated notes',
|
||||
};
|
||||
|
||||
const updatedInvoice = { ...mockInvoice, status: InvoiceStatus.SENT, notes: 'Updated notes' };
|
||||
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(mockInvoice as any);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue(updatedInvoice as any);
|
||||
|
||||
const result = await service.update('uuid-1', dto);
|
||||
|
||||
expect(invoiceRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: 'uuid-1' },
|
||||
});
|
||||
expect(invoiceRepository.save).toHaveBeenCalled();
|
||||
expect(result.status).toBe(InvoiceStatus.SENT);
|
||||
expect(result.notes).toBe('Updated notes');
|
||||
});
|
||||
|
||||
it('should throw error if invoice not found', async () => {
|
||||
const dto: UpdateInvoiceDto = {
|
||||
status: InvoiceStatus.SENT,
|
||||
};
|
||||
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.update('invalid-id', dto)).rejects.toThrow('Invoice not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateStatus', () => {
|
||||
it('should update invoice status', async () => {
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(mockInvoice as any);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue({
|
||||
...mockInvoice,
|
||||
status: InvoiceStatus.PAID,
|
||||
} as any);
|
||||
|
||||
const result = await service.updateStatus('uuid-1', InvoiceStatus.PAID);
|
||||
|
||||
expect(result.status).toBe(InvoiceStatus.PAID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePaymentStatus', () => {
|
||||
it('should update payment status', async () => {
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(mockInvoice as any);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue({
|
||||
...mockInvoice,
|
||||
paymentStatus: PaymentStatus.PAID,
|
||||
} as any);
|
||||
|
||||
const result = await service.updatePaymentStatus('uuid-1', PaymentStatus.PAID);
|
||||
|
||||
expect(result.paymentStatus).toBe(PaymentStatus.PAID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete invoice successfully', async () => {
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(mockInvoice as any);
|
||||
jest.spyOn(invoiceRepository, 'remove').mockResolvedValue(undefined);
|
||||
|
||||
await service.delete('uuid-1');
|
||||
|
||||
expect(invoiceRepository.remove).toHaveBeenCalledWith(mockInvoice);
|
||||
});
|
||||
|
||||
it('should throw error if invoice not found', async () => {
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.delete('invalid-id')).rejects.toThrow('Invoice not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addItem', () => {
|
||||
it('should add item to invoice', async () => {
|
||||
const itemDto = {
|
||||
productId: 'product-2',
|
||||
description: 'New Product',
|
||||
quantity: 1,
|
||||
unitPrice: 300,
|
||||
discount: 0,
|
||||
taxRate: 0.08,
|
||||
};
|
||||
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(mockInvoice as any);
|
||||
jest.spyOn(invoiceItemRepository, 'create').mockReturnValue(mockInvoiceItem as any);
|
||||
jest.spyOn(invoiceItemRepository, 'save').mockResolvedValue(mockInvoiceItem);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue({
|
||||
...mockInvoice,
|
||||
subtotal: 1500,
|
||||
taxAmount: 120,
|
||||
totalAmount: 1620,
|
||||
} as any);
|
||||
|
||||
const result = await service.addItem('uuid-1', itemDto);
|
||||
|
||||
expect(invoiceItemRepository.create).toHaveBeenCalled();
|
||||
expect(invoiceItemRepository.save).toHaveBeenCalled();
|
||||
expect(result.totalAmount).toBe(1620);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeItem', () => {
|
||||
it('should remove item from invoice', async () => {
|
||||
jest.spyOn(invoiceItemRepository, 'findOne').mockResolvedValue(mockInvoiceItem as any);
|
||||
jest.spyOn(invoiceItemRepository, 'remove').mockResolvedValue(undefined);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue({
|
||||
...mockInvoice,
|
||||
subtotal: 500,
|
||||
taxAmount: 40,
|
||||
totalAmount: 540,
|
||||
} as any);
|
||||
|
||||
const result = await service.removeItem('uuid-1', 'item-1');
|
||||
|
||||
expect(invoiceItemRepository.remove).toHaveBeenCalled();
|
||||
expect(result.totalAmount).toBe(540);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendInvoice', () => {
|
||||
it('should mark invoice as sent', async () => {
|
||||
jest.spyOn(invoiceRepository, 'findOne').mockResolvedValue(mockInvoice as any);
|
||||
jest.spyOn(invoiceRepository, 'save').mockResolvedValue({
|
||||
...mockInvoice,
|
||||
status: InvoiceStatus.SENT,
|
||||
sentAt: new Date(),
|
||||
} as any);
|
||||
|
||||
const result = await service.sendInvoice('uuid-1');
|
||||
|
||||
expect(result.status).toBe(InvoiceStatus.SENT);
|
||||
expect(result.sentAt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateTotals', () => {
|
||||
it('should calculate totals from items', () => {
|
||||
const items = [
|
||||
{ quantity: 2, unitPrice: 500, discount: 50, taxRate: 0.08 },
|
||||
{ quantity: 1, unitPrice: 300, discount: 0, taxRate: 0.08 },
|
||||
];
|
||||
|
||||
const totals = service.calculateTotals(items);
|
||||
|
||||
expect(totals.subtotal).toBe(1000);
|
||||
expect(totals.taxAmount).toBe(120);
|
||||
expect(totals.totalAmount).toBe(1120);
|
||||
});
|
||||
|
||||
it('should handle empty items array', () => {
|
||||
const totals = service.calculateTotals([]);
|
||||
|
||||
expect(totals.subtotal).toBe(0);
|
||||
expect(totals.taxAmount).toBe(0);
|
||||
expect(totals.totalAmount).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,307 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { SubscriptionsService } from '../services/subscriptions.service';
|
||||
import { TenantSubscription, SubscriptionPlan, BillingCycle, SubscriptionStatus } from '../entities';
|
||||
import { CreateTenantSubscriptionDto, UpdateTenantSubscriptionDto, CancelSubscriptionDto, ChangePlanDto } from '../dto';
|
||||
|
||||
describe('SubscriptionsService', () => {
|
||||
let service: SubscriptionsService;
|
||||
let subscriptionRepository: Repository<TenantSubscription>;
|
||||
let planRepository: Repository<SubscriptionPlan>;
|
||||
let dataSource: DataSource;
|
||||
|
||||
const mockSubscription = {
|
||||
id: 'uuid-1',
|
||||
tenantId: 'tenant-1',
|
||||
planId: 'plan-1',
|
||||
status: SubscriptionStatus.ACTIVE,
|
||||
billingCycle: BillingCycle.MONTHLY,
|
||||
currentPeriodStart: new Date('2024-01-01'),
|
||||
currentPeriodEnd: new Date('2024-02-01'),
|
||||
trialEnd: null,
|
||||
cancelledAt: null,
|
||||
paymentMethodId: 'pm-1',
|
||||
metadata: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockPlan = {
|
||||
id: 'plan-1',
|
||||
name: 'Basic Plan',
|
||||
description: 'Basic subscription plan',
|
||||
price: 9.99,
|
||||
billingCycle: BillingCycle.MONTHLY,
|
||||
features: ['feature1', 'feature2'],
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
SubscriptionsService,
|
||||
{
|
||||
provide: DataSource,
|
||||
useValue: {
|
||||
getRepository: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<SubscriptionsService>(SubscriptionsService);
|
||||
dataSource = module.get<DataSource>(DataSource);
|
||||
subscriptionRepository = module.get<Repository<TenantSubscription>>(
|
||||
getRepositoryToken(TenantSubscription),
|
||||
);
|
||||
planRepository = module.get<Repository<SubscriptionPlan>>(
|
||||
getRepositoryToken(SubscriptionPlan),
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new subscription successfully', async () => {
|
||||
const dto: CreateTenantSubscriptionDto = {
|
||||
tenantId: 'tenant-1',
|
||||
planId: 'plan-1',
|
||||
billingCycle: BillingCycle.MONTHLY,
|
||||
paymentMethodId: 'pm-1',
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(null);
|
||||
jest.spyOn(planRepository, 'findOne').mockResolvedValue(mockPlan as any);
|
||||
jest.spyOn(subscriptionRepository, 'create').mockReturnValue(mockSubscription as any);
|
||||
jest.spyOn(subscriptionRepository, 'save').mockResolvedValue(mockSubscription);
|
||||
|
||||
const result = await service.create(dto);
|
||||
|
||||
expect(subscriptionRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { tenantId: dto.tenantId },
|
||||
});
|
||||
expect(planRepository.findOne).toHaveBeenCalledWith({ where: { id: dto.planId } });
|
||||
expect(subscriptionRepository.create).toHaveBeenCalled();
|
||||
expect(subscriptionRepository.save).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockSubscription);
|
||||
});
|
||||
|
||||
it('should throw error if tenant already has subscription', async () => {
|
||||
const dto: CreateTenantSubscriptionDto = {
|
||||
tenantId: 'tenant-1',
|
||||
planId: 'plan-1',
|
||||
billingCycle: BillingCycle.MONTHLY,
|
||||
paymentMethodId: 'pm-1',
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
|
||||
await expect(service.create(dto)).rejects.toThrow('Tenant already has a subscription');
|
||||
});
|
||||
|
||||
it('should throw error if plan not found', async () => {
|
||||
const dto: CreateTenantSubscriptionDto = {
|
||||
tenantId: 'tenant-1',
|
||||
planId: 'invalid-plan',
|
||||
billingCycle: BillingCycle.MONTHLY,
|
||||
paymentMethodId: 'pm-1',
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(null);
|
||||
jest.spyOn(planRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.create(dto)).rejects.toThrow('Plan not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByTenant', () => {
|
||||
it('should find subscription by tenant id', async () => {
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
|
||||
const result = await service.findByTenant('tenant-1');
|
||||
|
||||
expect(subscriptionRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1' },
|
||||
});
|
||||
expect(result).toEqual(mockSubscription);
|
||||
});
|
||||
|
||||
it('should return null if no subscription found', async () => {
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
const result = await service.findByTenant('invalid-tenant');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update subscription successfully', async () => {
|
||||
const dto: UpdateTenantSubscriptionDto = {
|
||||
paymentMethodId: 'pm-2',
|
||||
};
|
||||
|
||||
const updatedSubscription = { ...mockSubscription, paymentMethodId: 'pm-2' };
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
jest.spyOn(subscriptionRepository, 'save').mockResolvedValue(updatedSubscription as any);
|
||||
|
||||
const result = await service.update('uuid-1', dto);
|
||||
|
||||
expect(subscriptionRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(subscriptionRepository.save).toHaveBeenCalled();
|
||||
expect(result).toEqual(updatedSubscription);
|
||||
});
|
||||
|
||||
it('should throw error if subscription not found', async () => {
|
||||
const dto: UpdateTenantSubscriptionDto = {
|
||||
paymentMethodId: 'pm-2',
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.update('invalid-id', dto)).rejects.toThrow('Subscription not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancel', () => {
|
||||
it('should cancel subscription successfully', async () => {
|
||||
const dto: CancelSubscriptionDto = {
|
||||
reason: 'Customer request',
|
||||
effectiveImmediately: false,
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
jest.spyOn(subscriptionRepository, 'save').mockResolvedValue({
|
||||
...mockSubscription,
|
||||
status: SubscriptionStatus.CANCELLED,
|
||||
cancelledAt: new Date(),
|
||||
} as any);
|
||||
|
||||
const result = await service.cancel('uuid-1', dto);
|
||||
|
||||
expect(subscriptionRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(subscriptionRepository.save).toHaveBeenCalled();
|
||||
expect(result.status).toBe(SubscriptionStatus.CANCELLED);
|
||||
expect(result.cancelledAt).toBeDefined();
|
||||
});
|
||||
|
||||
it('should cancel subscription immediately if requested', async () => {
|
||||
const dto: CancelSubscriptionDto = {
|
||||
reason: 'Customer request',
|
||||
effectiveImmediately: true,
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
jest.spyOn(subscriptionRepository, 'save').mockResolvedValue({
|
||||
...mockSubscription,
|
||||
status: SubscriptionStatus.CANCELLED,
|
||||
cancelledAt: new Date(),
|
||||
currentPeriodEnd: new Date(),
|
||||
} as any);
|
||||
|
||||
const result = await service.cancel('uuid-1', dto);
|
||||
|
||||
expect(result.status).toBe(SubscriptionStatus.CANCELLED);
|
||||
expect(result.cancelledAt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('changePlan', () => {
|
||||
it('should change subscription plan successfully', async () => {
|
||||
const newPlan = { ...mockPlan, id: 'plan-2', price: 19.99 };
|
||||
const dto: ChangePlanDto = {
|
||||
newPlanId: 'plan-2',
|
||||
billingCycle: BillingCycle.YEARLY,
|
||||
prorate: true,
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
jest.spyOn(planRepository, 'findOne').mockResolvedValue(newPlan as any);
|
||||
jest.spyOn(subscriptionRepository, 'save').mockResolvedValue({
|
||||
...mockSubscription,
|
||||
planId: 'plan-2',
|
||||
billingCycle: BillingCycle.YEARLY,
|
||||
} as any);
|
||||
|
||||
const result = await service.changePlan('uuid-1', dto);
|
||||
|
||||
expect(subscriptionRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(planRepository.findOne).toHaveBeenCalledWith({ where: { id: 'plan-2' } });
|
||||
expect(subscriptionRepository.save).toHaveBeenCalled();
|
||||
expect(result.planId).toBe('plan-2');
|
||||
expect(result.billingCycle).toBe(BillingCycle.YEARLY);
|
||||
});
|
||||
|
||||
it('should throw error if new plan not found', async () => {
|
||||
const dto: ChangePlanDto = {
|
||||
newPlanId: 'invalid-plan',
|
||||
billingCycle: BillingCycle.MONTHLY,
|
||||
prorate: false,
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
jest.spyOn(planRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.changePlan('uuid-1', dto)).rejects.toThrow('New plan not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUsage', () => {
|
||||
it('should get subscription usage', async () => {
|
||||
const mockUsage = {
|
||||
currentUsage: 850,
|
||||
limits: {
|
||||
apiCalls: 1000,
|
||||
storage: 5368709120, // 5GB in bytes
|
||||
users: 10,
|
||||
},
|
||||
periodStart: new Date('2024-01-01'),
|
||||
periodEnd: new Date('2024-02-01'),
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
jest.spyOn(dataSource, 'query').mockResolvedValue([{ current_usage: 850 }]);
|
||||
|
||||
const result = await service.getUsage('uuid-1');
|
||||
|
||||
expect(result.currentUsage).toBe(850);
|
||||
expect(result.limits).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('reactivate', () => {
|
||||
it('should reactivate cancelled subscription', async () => {
|
||||
const cancelledSubscription = {
|
||||
...mockSubscription,
|
||||
status: SubscriptionStatus.CANCELLED,
|
||||
cancelledAt: new Date(),
|
||||
};
|
||||
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(cancelledSubscription as any);
|
||||
jest.spyOn(subscriptionRepository, 'save').mockResolvedValue({
|
||||
...cancelledSubscription,
|
||||
status: SubscriptionStatus.ACTIVE,
|
||||
cancelledAt: null,
|
||||
} as any);
|
||||
|
||||
const result = await service.reactivate('uuid-1');
|
||||
|
||||
expect(subscriptionRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(subscriptionRepository.save).toHaveBeenCalled();
|
||||
expect(result.status).toBe(SubscriptionStatus.ACTIVE);
|
||||
expect(result.cancelledAt).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error if subscription is not cancelled', async () => {
|
||||
jest.spyOn(subscriptionRepository, 'findOne').mockResolvedValue(mockSubscription as any);
|
||||
|
||||
await expect(service.reactivate('uuid-1')).rejects.toThrow('Cannot reactivate active subscription');
|
||||
});
|
||||
});
|
||||
});
|
||||
335
src/modules/financial/__tests__/payments.service.spec.ts
Normal file
335
src/modules/financial/__tests__/payments.service.spec.ts
Normal file
@ -0,0 +1,335 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Repository } from 'typeorm';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { PaymentsService } from '../payments.service';
|
||||
import { Payment, PaymentMethod, PaymentStatus } from '../entities';
|
||||
import { CreatePaymentDto, UpdatePaymentDto } from '../dto';
|
||||
|
||||
describe('PaymentsService', () => {
|
||||
let service: PaymentsService;
|
||||
let paymentRepository: Repository<Payment>;
|
||||
let paymentMethodRepository: Repository<PaymentMethod>;
|
||||
|
||||
const mockPayment = {
|
||||
id: 'uuid-1',
|
||||
tenantId: 'tenant-1',
|
||||
invoiceId: 'invoice-1',
|
||||
paymentMethodId: 'method-1',
|
||||
amount: 1000,
|
||||
currency: 'USD',
|
||||
status: PaymentStatus.PENDING,
|
||||
paymentDate: new Date('2024-01-15'),
|
||||
reference: 'REF-001',
|
||||
notes: 'Payment for invoice #001',
|
||||
metadata: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const mockPaymentMethod = {
|
||||
id: 'method-1',
|
||||
tenantId: 'tenant-1',
|
||||
name: 'Bank Transfer',
|
||||
type: 'BANK_TRANSFER',
|
||||
isActive: true,
|
||||
config: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PaymentsService,
|
||||
{
|
||||
provide: getRepositoryToken(Payment),
|
||||
useValue: {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
createQueryBuilder: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(PaymentMethod),
|
||||
useValue: {
|
||||
findOne: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PaymentsService>(PaymentsService);
|
||||
paymentRepository = module.get<Repository<Payment>>(getRepositoryToken(Payment));
|
||||
paymentMethodRepository = module.get<Repository<PaymentMethod>>(getRepositoryToken(PaymentMethod));
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new payment successfully', async () => {
|
||||
const dto: CreatePaymentDto = {
|
||||
invoiceId: 'invoice-1',
|
||||
paymentMethodId: 'method-1',
|
||||
amount: 1000,
|
||||
currency: 'USD',
|
||||
paymentDate: new Date('2024-01-15'),
|
||||
reference: 'REF-001',
|
||||
notes: 'Payment for invoice #001',
|
||||
};
|
||||
|
||||
jest.spyOn(paymentMethodRepository, 'findOne').mockResolvedValue(mockPaymentMethod as any);
|
||||
jest.spyOn(paymentRepository, 'create').mockReturnValue(mockPayment as any);
|
||||
jest.spyOn(paymentRepository, 'save').mockResolvedValue(mockPayment);
|
||||
|
||||
const result = await service.create(dto);
|
||||
|
||||
expect(paymentMethodRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: dto.paymentMethodId },
|
||||
});
|
||||
expect(paymentRepository.create).toHaveBeenCalled();
|
||||
expect(paymentRepository.save).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockPayment);
|
||||
});
|
||||
|
||||
it('should throw error if payment method not found', async () => {
|
||||
const dto: CreatePaymentDto = {
|
||||
invoiceId: 'invoice-1',
|
||||
paymentMethodId: 'invalid-method',
|
||||
amount: 1000,
|
||||
currency: 'USD',
|
||||
};
|
||||
|
||||
jest.spyOn(paymentMethodRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.create(dto)).rejects.toThrow('Payment method not found');
|
||||
});
|
||||
|
||||
it('should throw error if payment method is inactive', async () => {
|
||||
const inactiveMethod = { ...mockPaymentMethod, isActive: false };
|
||||
const dto: CreatePaymentDto = {
|
||||
invoiceId: 'invoice-1',
|
||||
paymentMethodId: 'method-1',
|
||||
amount: 1000,
|
||||
currency: 'USD',
|
||||
};
|
||||
|
||||
jest.spyOn(paymentMethodRepository, 'findOne').mockResolvedValue(inactiveMethod as any);
|
||||
|
||||
await expect(service.create(dto)).rejects.toThrow('Payment method is not active');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find payment by id', async () => {
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(mockPayment as any);
|
||||
|
||||
const result = await service.findById('uuid-1');
|
||||
|
||||
expect(paymentRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: 'uuid-1' },
|
||||
relations: ['paymentMethod', 'invoice'],
|
||||
});
|
||||
expect(result).toEqual(mockPayment);
|
||||
});
|
||||
|
||||
it('should return null if payment not found', async () => {
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
const result = await service.findById('invalid-id');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByInvoice', () => {
|
||||
it('should find payments by invoice', async () => {
|
||||
const mockPayments = [mockPayment, { ...mockPayment, id: 'uuid-2' }];
|
||||
jest.spyOn(paymentRepository, 'find').mockResolvedValue(mockPayments as any);
|
||||
|
||||
const result = await service.findByInvoice('invoice-1');
|
||||
|
||||
expect(paymentRepository.find).toHaveBeenCalledWith({
|
||||
where: { invoiceId: 'invoice-1' },
|
||||
relations: ['paymentMethod'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
expect(result).toEqual(mockPayments);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update payment successfully', async () => {
|
||||
const dto: UpdatePaymentDto = {
|
||||
status: PaymentStatus.COMPLETED,
|
||||
notes: 'Payment completed',
|
||||
};
|
||||
|
||||
const updatedPayment = { ...mockPayment, status: PaymentStatus.COMPLETED };
|
||||
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(mockPayment as any);
|
||||
jest.spyOn(paymentRepository, 'save').mockResolvedValue(updatedPayment as any);
|
||||
|
||||
const result = await service.update('uuid-1', dto);
|
||||
|
||||
expect(paymentRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(paymentRepository.save).toHaveBeenCalled();
|
||||
expect(result.status).toBe(PaymentStatus.COMPLETED);
|
||||
});
|
||||
|
||||
it('should throw error if payment not found', async () => {
|
||||
const dto: UpdatePaymentDto = { status: PaymentStatus.COMPLETED };
|
||||
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.update('invalid-id', dto)).rejects.toThrow('Payment not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateStatus', () => {
|
||||
it('should update payment status', async () => {
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(mockPayment as any);
|
||||
jest.spyOn(paymentRepository, 'save').mockResolvedValue({
|
||||
...mockPayment,
|
||||
status: PaymentStatus.COMPLETED,
|
||||
} as any);
|
||||
|
||||
const result = await service.updateStatus('uuid-1', PaymentStatus.COMPLETED);
|
||||
|
||||
expect(result.status).toBe(PaymentStatus.COMPLETED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete payment successfully', async () => {
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(mockPayment as any);
|
||||
jest.spyOn(paymentRepository, 'remove').mockResolvedValue(undefined);
|
||||
|
||||
await service.delete('uuid-1');
|
||||
|
||||
expect(paymentRepository.remove).toHaveBeenCalledWith(mockPayment);
|
||||
});
|
||||
|
||||
it('should throw error if payment not found', async () => {
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.delete('invalid-id')).rejects.toThrow('Payment not found');
|
||||
});
|
||||
|
||||
it('should throw error if payment is completed', async () => {
|
||||
const completedPayment = { ...mockPayment, status: PaymentStatus.COMPLETED };
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(completedPayment as any);
|
||||
|
||||
await expect(service.delete('uuid-1')).rejects.toThrow('Cannot delete completed payment');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTotalPaid', () => {
|
||||
it('should get total paid for invoice', async () => {
|
||||
jest.spyOn(paymentRepository, 'createQueryBuilder').mockReturnValue({
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
select: jest.fn().mockReturnThis(),
|
||||
getRawOne: jest.fn().mockResolvedValue({ total: 1500 }),
|
||||
} as any);
|
||||
|
||||
const result = await service.getTotalPaid('invoice-1');
|
||||
|
||||
expect(result).toBe(1500);
|
||||
});
|
||||
|
||||
it('should return 0 if no payments found', async () => {
|
||||
jest.spyOn(paymentRepository, 'createQueryBuilder').mockReturnValue({
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
select: jest.fn().mockReturnThis(),
|
||||
getRawOne: jest.fn().mockResolvedValue(null),
|
||||
} as any);
|
||||
|
||||
const result = await service.getTotalPaid('invoice-1');
|
||||
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processRefund', () => {
|
||||
it('should process refund successfully', async () => {
|
||||
const refundAmount = 500;
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(mockPayment as any);
|
||||
jest.spyOn(paymentRepository, 'save').mockResolvedValue({
|
||||
...mockPayment,
|
||||
status: PaymentStatus.REFUNDED,
|
||||
refundedAmount: refundAmount,
|
||||
} as any);
|
||||
|
||||
const result = await service.processRefund('uuid-1', refundAmount, 'Customer request');
|
||||
|
||||
expect(result.status).toBe(PaymentStatus.REFUNDED);
|
||||
expect(result.refundedAmount).toBe(refundAmount);
|
||||
});
|
||||
|
||||
it('should throw error if refund amount exceeds payment amount', async () => {
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(mockPayment as any);
|
||||
|
||||
await expect(service.processRefund('uuid-1', 1500, 'Over refund')).rejects.toThrow('Refund amount cannot exceed payment amount');
|
||||
});
|
||||
|
||||
it('should throw error if payment is not completed', async () => {
|
||||
jest.spyOn(paymentRepository, 'findOne').mockResolvedValue(mockPayment as any);
|
||||
|
||||
await expect(service.processRefund('uuid-1', 500, 'Refund pending payment')).rejects.toThrow('Can only refund completed payments');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPaymentsByDateRange', () => {
|
||||
it('should get payments by date range', async () => {
|
||||
const startDate = new Date('2024-01-01');
|
||||
const endDate = new Date('2024-01-31');
|
||||
const mockQueryBuilder = {
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue([mockPayment]),
|
||||
};
|
||||
|
||||
jest.spyOn(paymentRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
const result = await service.getPaymentsByDateRange('tenant-1', startDate, endDate);
|
||||
|
||||
expect(paymentRepository.createQueryBuilder).toHaveBeenCalledWith('payment');
|
||||
expect(mockQueryBuilder.where).toHaveBeenCalledWith('payment.tenantId = :tenantId', { tenantId: 'tenant-1' });
|
||||
expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith('payment.paymentDate >= :startDate', { startDate });
|
||||
expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith('payment.paymentDate <= :endDate', { endDate });
|
||||
expect(result).toEqual([mockPayment]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPaymentSummary', () => {
|
||||
it('should get payment summary for tenant', async () => {
|
||||
const mockSummary = {
|
||||
totalPayments: 10,
|
||||
totalAmount: 10000,
|
||||
completedPayments: 8,
|
||||
completedAmount: 8500,
|
||||
pendingPayments: 2,
|
||||
pendingAmount: 1500,
|
||||
};
|
||||
|
||||
jest.spyOn(paymentRepository, 'createQueryBuilder').mockReturnValue({
|
||||
where: jest.fn().mockReturnThis(),
|
||||
select: jest.fn().mockReturnThis(),
|
||||
getRawOne: jest.fn().mockResolvedValue({ total: 10, amount: 10000 }),
|
||||
} as any);
|
||||
|
||||
const result = await service.getPaymentSummary('tenant-1', new Date('2024-01-01'), new Date('2024-01-31'));
|
||||
|
||||
expect(result.totalPayments).toBe(10);
|
||||
expect(result.totalAmount).toBe(10000);
|
||||
});
|
||||
});
|
||||
});
|
||||
319
src/modules/inventory/__tests__/warehouses-new.service.spec.ts
Normal file
319
src/modules/inventory/__tests__/warehouses-new.service.spec.ts
Normal file
@ -0,0 +1,319 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Repository } from 'typeorm';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { WarehousesService } from '../warehouses.service';
|
||||
import { Warehouse, WarehouseStatus } from '../entities';
|
||||
import { CreateWarehouseDto, UpdateWarehouseDto } from '../dto';
|
||||
|
||||
describe('WarehousesService', () => {
|
||||
let service: WarehousesService;
|
||||
let warehouseRepository: Repository<Warehouse>;
|
||||
|
||||
const mockWarehouse = {
|
||||
id: 'uuid-1',
|
||||
tenantId: 'tenant-1',
|
||||
code: 'WH-001',
|
||||
name: 'Main Warehouse',
|
||||
description: 'Primary storage facility',
|
||||
address: {
|
||||
street: '123 Storage St',
|
||||
city: 'Storage City',
|
||||
state: 'SC',
|
||||
zipCode: '12345',
|
||||
country: 'US',
|
||||
},
|
||||
contact: {
|
||||
name: 'John Manager',
|
||||
email: 'john@company.com',
|
||||
phone: '+1234567890',
|
||||
},
|
||||
status: WarehouseStatus.ACTIVE,
|
||||
capacity: 10000,
|
||||
currentOccupancy: 3500,
|
||||
operatingHours: {
|
||||
monday: { open: '08:00', close: '18:00' },
|
||||
tuesday: { open: '08:00', close: '18:00' },
|
||||
wednesday: { open: '08:00', close: '18:00' },
|
||||
thursday: { open: '08:00', close: '18:00' },
|
||||
friday: { open: '08:00', close: '18:00' },
|
||||
saturday: { open: '09:00', close: '14:00' },
|
||||
sunday: { open: null, close: null },
|
||||
},
|
||||
metadata: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
WarehousesService,
|
||||
{
|
||||
provide: getRepositoryToken(Warehouse),
|
||||
useValue: {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
createQueryBuilder: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WarehousesService>(WarehousesService);
|
||||
warehouseRepository = module.get<Repository<Warehouse>>(getRepositoryToken(Warehouse));
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new warehouse successfully', async () => {
|
||||
const dto: CreateWarehouseDto = {
|
||||
code: 'WH-001',
|
||||
name: 'Main Warehouse',
|
||||
description: 'Primary storage facility',
|
||||
address: {
|
||||
street: '123 Storage St',
|
||||
city: 'Storage City',
|
||||
state: 'SC',
|
||||
zipCode: '12345',
|
||||
country: 'US',
|
||||
},
|
||||
contact: {
|
||||
name: 'John Manager',
|
||||
email: 'john@company.com',
|
||||
phone: '+1234567890',
|
||||
},
|
||||
capacity: 10000,
|
||||
operatingHours: {
|
||||
monday: { open: '08:00', close: '18:00' },
|
||||
tuesday: { open: '08:00', close: '18:00' },
|
||||
},
|
||||
};
|
||||
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);
|
||||
jest.spyOn(warehouseRepository, 'create').mockReturnValue(mockWarehouse as any);
|
||||
jest.spyOn(warehouseRepository, 'save').mockResolvedValue(mockWarehouse);
|
||||
|
||||
const result = await service.create(dto);
|
||||
|
||||
expect(warehouseRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1', code: dto.code },
|
||||
});
|
||||
expect(warehouseRepository.create).toHaveBeenCalled();
|
||||
expect(warehouseRepository.save).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockWarehouse);
|
||||
});
|
||||
|
||||
it('should throw error if warehouse code already exists', async () => {
|
||||
const dto: CreateWarehouseDto = {
|
||||
code: 'WH-001',
|
||||
name: 'Main Warehouse',
|
||||
};
|
||||
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
|
||||
await expect(service.create(dto)).rejects.toThrow('Warehouse code already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find warehouse by id', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
|
||||
const result = await service.findById('uuid-1');
|
||||
|
||||
expect(warehouseRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: 'uuid-1' },
|
||||
});
|
||||
expect(result).toEqual(mockWarehouse);
|
||||
});
|
||||
|
||||
it('should return null if warehouse not found', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
const result = await service.findById('invalid-id');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByTenant', () => {
|
||||
it('should find warehouses by tenant', async () => {
|
||||
const mockWarehouses = [mockWarehouse, { ...mockWarehouse, id: 'uuid-2' }];
|
||||
jest.spyOn(warehouseRepository, 'find').mockResolvedValue(mockWarehouses as any);
|
||||
|
||||
const result = await service.findByTenant('tenant-1');
|
||||
|
||||
expect(warehouseRepository.find).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1' },
|
||||
order: { code: 'ASC' },
|
||||
});
|
||||
expect(result).toEqual(mockWarehouses);
|
||||
});
|
||||
|
||||
it('should filter by status', async () => {
|
||||
jest.spyOn(warehouseRepository, 'find').mockResolvedValue([mockWarehouse] as any);
|
||||
|
||||
const result = await service.findByTenant('tenant-1', { status: WarehouseStatus.ACTIVE });
|
||||
|
||||
expect(warehouseRepository.find).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1', status: WarehouseStatus.ACTIVE },
|
||||
order: { code: 'ASC' },
|
||||
});
|
||||
expect(result).toEqual([mockWarehouse]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update warehouse successfully', async () => {
|
||||
const dto: UpdateWarehouseDto = {
|
||||
name: 'Updated Warehouse',
|
||||
capacity: 12000,
|
||||
};
|
||||
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
jest.spyOn(warehouseRepository, 'save').mockResolvedValue({
|
||||
...mockWarehouse,
|
||||
name: 'Updated Warehouse',
|
||||
capacity: 12000,
|
||||
} as any);
|
||||
|
||||
const result = await service.update('uuid-1', dto);
|
||||
|
||||
expect(warehouseRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(warehouseRepository.save).toHaveBeenCalled();
|
||||
expect(result.name).toBe('Updated Warehouse');
|
||||
expect(result.capacity).toBe(12000);
|
||||
});
|
||||
|
||||
it('should throw error if warehouse not found', async () => {
|
||||
const dto: UpdateWarehouseDto = { name: 'Updated' };
|
||||
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.update('invalid-id', dto)).rejects.toThrow('Warehouse not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateOccupancy', () => {
|
||||
it('should update warehouse occupancy', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
jest.spyOn(warehouseRepository, 'save').mockResolvedValue({
|
||||
...mockWarehouse,
|
||||
currentOccupancy: 4000,
|
||||
} as any);
|
||||
|
||||
const result = await service.updateOccupancy('uuid-1', 4000);
|
||||
|
||||
expect(warehouseRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(warehouseRepository.save).toHaveBeenCalled();
|
||||
expect(result.currentOccupancy).toBe(4000);
|
||||
});
|
||||
|
||||
it('should throw error if occupancy exceeds capacity', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
|
||||
await expect(service.updateOccupancy('uuid-1', 15000)).rejects.toThrow('Occupancy cannot exceed warehouse capacity');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableCapacity', () => {
|
||||
it('should calculate available capacity', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
|
||||
const result = await service.getAvailableCapacity('uuid-1');
|
||||
|
||||
expect(result).toBe(6500); // 10000 - 3500
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete warehouse successfully', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
jest.spyOn(warehouseRepository, 'remove').mockResolvedValue(undefined);
|
||||
|
||||
await service.delete('uuid-1');
|
||||
|
||||
expect(warehouseRepository.remove).toHaveBeenCalledWith(mockWarehouse);
|
||||
});
|
||||
|
||||
it('should throw error if warehouse not found', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.delete('invalid-id')).rejects.toThrow('Warehouse not found');
|
||||
});
|
||||
|
||||
it('should throw error if warehouse has stock', async () => {
|
||||
const warehouseWithStock = { ...mockWarehouse, currentOccupancy: 1000 };
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(warehouseWithStock as any);
|
||||
|
||||
await expect(service.delete('uuid-1')).rejects.toThrow('Cannot delete warehouse with existing stock');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUtilizationRate', () => {
|
||||
it('should calculate utilization rate', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
|
||||
const result = await service.getUtilizationRate('uuid-1');
|
||||
|
||||
expect(result).toBe(35); // (3500 / 10000) * 100
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWarehousesByCity', () => {
|
||||
it('should get warehouses by city', async () => {
|
||||
jest.spyOn(warehouseRepository, 'find').mockResolvedValue([mockWarehouse] as any);
|
||||
|
||||
const result = await service.getWarehousesByCity('tenant-1', 'Storage City');
|
||||
|
||||
expect(warehouseRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
tenantId: 'tenant-1',
|
||||
'address.city': 'Storage City',
|
||||
},
|
||||
});
|
||||
expect(result).toEqual([mockWarehouse]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateStatus', () => {
|
||||
it('should update warehouse status', async () => {
|
||||
jest.spyOn(warehouseRepository, 'findOne').mockResolvedValue(mockWarehouse as any);
|
||||
jest.spyOn(warehouseRepository, 'save').mockResolvedValue({
|
||||
...mockWarehouse,
|
||||
status: WarehouseStatus.INACTIVE,
|
||||
} as any);
|
||||
|
||||
const result = await service.updateStatus('uuid-1', WarehouseStatus.INACTIVE);
|
||||
|
||||
expect(result.status).toBe(WarehouseStatus.INACTIVE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWarehouseStats', () => {
|
||||
it('should get warehouse statistics', async () => {
|
||||
const mockQueryBuilder = {
|
||||
where: jest.fn().mockReturnThis(),
|
||||
select: jest.fn().mockReturnThis(),
|
||||
getRawOne: jest.fn().mockResolvedValue({ total: 5, active: 4, inactive: 1, totalCapacity: 50000, totalOccupancy: 17500 }),
|
||||
};
|
||||
|
||||
jest.spyOn(warehouseRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
const result = await service.getWarehouseStats('tenant-1');
|
||||
|
||||
expect(result.totalWarehouses).toBe(5);
|
||||
expect(result.activeWarehouses).toBe(4);
|
||||
expect(result.inactiveWarehouses).toBe(1);
|
||||
expect(result.totalCapacity).toBe(50000);
|
||||
expect(result.totalOccupancy).toBe(17500);
|
||||
expect(result.averageUtilization).toBe(35); // (17500 / 50000) * 100
|
||||
});
|
||||
});
|
||||
});
|
||||
393
src/modules/partners/__tests__/partners.service.spec.ts
Normal file
393
src/modules/partners/__tests__/partners.service.spec.ts
Normal file
@ -0,0 +1,393 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { Repository } from 'typeorm';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { PartnersService } from '../partners.service';
|
||||
import { Partner, PartnerStatus, PartnerType } from '../entities';
|
||||
import { CreatePartnerDto, UpdatePartnerDto } from '../dto';
|
||||
|
||||
describe('PartnersService', () => {
|
||||
let service: PartnersService;
|
||||
let partnerRepository: Repository<Partner>;
|
||||
|
||||
const mockPartner = {
|
||||
id: 'uuid-1',
|
||||
tenantId: 'tenant-1',
|
||||
code: 'PART-001',
|
||||
name: 'Test Partner',
|
||||
type: PartnerType.SUPPLIER,
|
||||
status: PartnerStatus.ACTIVE,
|
||||
taxId: 'TAX-001',
|
||||
email: 'partner@test.com',
|
||||
phone: '+1234567890',
|
||||
website: 'https://partner.com',
|
||||
address: {
|
||||
street: '123 Partner St',
|
||||
city: 'Partner City',
|
||||
state: 'PC',
|
||||
zipCode: '12345',
|
||||
country: 'US',
|
||||
},
|
||||
contact: {
|
||||
name: 'John Contact',
|
||||
email: 'john@partner.com',
|
||||
phone: '+1234567890',
|
||||
position: 'Sales Manager',
|
||||
},
|
||||
paymentTerms: {
|
||||
days: 30,
|
||||
method: 'TRANSFER',
|
||||
currency: 'USD',
|
||||
},
|
||||
metadata: {},
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PartnersService,
|
||||
{
|
||||
provide: getRepositoryToken(Partner),
|
||||
useValue: {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
createQueryBuilder: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<PartnersService>(PartnersService);
|
||||
partnerRepository = module.get<Repository<Partner>>(getRepositoryToken(Partner));
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new partner successfully', async () => {
|
||||
const dto: CreatePartnerDto = {
|
||||
code: 'PART-001',
|
||||
name: 'Test Partner',
|
||||
type: PartnerType.SUPPLIER,
|
||||
taxId: 'TAX-001',
|
||||
email: 'partner@test.com',
|
||||
phone: '+1234567890',
|
||||
website: 'https://partner.com',
|
||||
address: {
|
||||
street: '123 Partner St',
|
||||
city: 'Partner City',
|
||||
state: 'PC',
|
||||
zipCode: '12345',
|
||||
country: 'US',
|
||||
},
|
||||
contact: {
|
||||
name: 'John Contact',
|
||||
email: 'john@partner.com',
|
||||
phone: '+1234567890',
|
||||
position: 'Sales Manager',
|
||||
},
|
||||
paymentTerms: {
|
||||
days: 30,
|
||||
method: 'TRANSFER',
|
||||
currency: 'USD',
|
||||
},
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(null);
|
||||
jest.spyOn(partnerRepository, 'create').mockReturnValue(mockPartner as any);
|
||||
jest.spyOn(partnerRepository, 'save').mockResolvedValue(mockPartner);
|
||||
|
||||
const result = await service.create(dto);
|
||||
|
||||
expect(partnerRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1', code: dto.code },
|
||||
});
|
||||
expect(partnerRepository.create).toHaveBeenCalled();
|
||||
expect(partnerRepository.save).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockPartner);
|
||||
});
|
||||
|
||||
it('should throw error if partner code already exists', async () => {
|
||||
const dto: CreatePartnerDto = {
|
||||
code: 'PART-001',
|
||||
name: 'Test Partner',
|
||||
type: PartnerType.SUPPLIER,
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(mockPartner as any);
|
||||
|
||||
await expect(service.create(dto)).rejects.toThrow('Partner code already exists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should find partner by id', async () => {
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(mockPartner as any);
|
||||
|
||||
const result = await service.findById('uuid-1');
|
||||
|
||||
expect(partnerRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: 'uuid-1' },
|
||||
});
|
||||
expect(result).toEqual(mockPartner);
|
||||
});
|
||||
|
||||
it('should return null if partner not found', async () => {
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
const result = await service.findById('invalid-id');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByTenant', () => {
|
||||
it('should find partners by tenant', async () => {
|
||||
const mockPartners = [mockPartner, { ...mockPartner, id: 'uuid-2' }];
|
||||
const mockQueryBuilder = {
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
skip: jest.fn().mockReturnThis(),
|
||||
take: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue(mockPartners),
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
const result = await service.findByTenant('tenant-1', {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
expect(partnerRepository.createQueryBuilder).toHaveBeenCalledWith('partner');
|
||||
expect(mockQueryBuilder.where).toHaveBeenCalledWith('partner.tenantId = :tenantId', { tenantId: 'tenant-1' });
|
||||
expect(mockQueryBuilder.skip).toHaveBeenCalledWith(0);
|
||||
expect(mockQueryBuilder.take).toHaveBeenCalledWith(10);
|
||||
expect(result).toEqual(mockPartners);
|
||||
});
|
||||
|
||||
it('should filter by type', async () => {
|
||||
const mockQueryBuilder = {
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue([mockPartner]),
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
await service.findByTenant('tenant-1', { type: PartnerType.SUPPLIER });
|
||||
|
||||
expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith('partner.type = :type', { type: PartnerType.SUPPLIER });
|
||||
});
|
||||
|
||||
it('should filter by status', async () => {
|
||||
const mockQueryBuilder = {
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue([mockPartner]),
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
await service.findByTenant('tenant-1', { status: PartnerStatus.ACTIVE });
|
||||
|
||||
expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith('partner.status = :status', { status: PartnerStatus.ACTIVE });
|
||||
});
|
||||
|
||||
it('should search by name or code', async () => {
|
||||
const mockQueryBuilder = {
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue([mockPartner]),
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
await service.findByTenant('tenant-1', { search: 'Test' });
|
||||
|
||||
expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith(
|
||||
'(partner.code ILIKE :search OR partner.name ILIKE :search OR partner.email ILIKE :search)',
|
||||
{ search: '%Test%' }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update partner successfully', async () => {
|
||||
const dto: UpdatePartnerDto = {
|
||||
name: 'Updated Partner',
|
||||
status: PartnerStatus.INACTIVE,
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(mockPartner as any);
|
||||
jest.spyOn(partnerRepository, 'save').mockResolvedValue({
|
||||
...mockPartner,
|
||||
name: 'Updated Partner',
|
||||
status: PartnerStatus.INACTIVE,
|
||||
} as any);
|
||||
|
||||
const result = await service.update('uuid-1', dto);
|
||||
|
||||
expect(partnerRepository.findOne).toHaveBeenCalledWith({ where: { id: 'uuid-1' } });
|
||||
expect(partnerRepository.save).toHaveBeenCalled();
|
||||
expect(result.name).toBe('Updated Partner');
|
||||
expect(result.status).toBe(PartnerStatus.INACTIVE);
|
||||
});
|
||||
|
||||
it('should throw error if partner not found', async () => {
|
||||
const dto: UpdatePartnerDto = { name: 'Updated' };
|
||||
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.update('invalid-id', dto)).rejects.toThrow('Partner not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete partner successfully', async () => {
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(mockPartner as any);
|
||||
jest.spyOn(partnerRepository, 'remove').mockResolvedValue(undefined);
|
||||
|
||||
await service.delete('uuid-1');
|
||||
|
||||
expect(partnerRepository.remove).toHaveBeenCalledWith(mockPartner);
|
||||
});
|
||||
|
||||
it('should throw error if partner not found', async () => {
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(service.delete('invalid-id')).rejects.toThrow('Partner not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateStatus', () => {
|
||||
it('should update partner status', async () => {
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(mockPartner as any);
|
||||
jest.spyOn(partnerRepository, 'save').mockResolvedValue({
|
||||
...mockPartner,
|
||||
status: PartnerStatus.INACTIVE,
|
||||
} as any);
|
||||
|
||||
const result = await service.updateStatus('uuid-1', PartnerStatus.INACTIVE);
|
||||
|
||||
expect(result.status).toBe(PartnerStatus.INACTIVE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByType', () => {
|
||||
it('should find partners by type', async () => {
|
||||
jest.spyOn(partnerRepository, 'find').mockResolvedValue([mockPartner] as any);
|
||||
|
||||
const result = await service.findByType('tenant-1', PartnerType.SUPPLIER);
|
||||
|
||||
expect(partnerRepository.find).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1', type: PartnerType.SUPPLIER },
|
||||
order: { name: 'ASC' },
|
||||
});
|
||||
expect(result).toEqual([mockPartner]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSuppliers', () => {
|
||||
it('should get active suppliers', async () => {
|
||||
jest.spyOn(partnerRepository, 'find').mockResolvedValue([mockPartner] as any);
|
||||
|
||||
const result = await service.getSuppliers('tenant-1');
|
||||
|
||||
expect(partnerRepository.find).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1', type: PartnerType.SUPPLIER, status: PartnerStatus.ACTIVE },
|
||||
order: { name: 'ASC' },
|
||||
});
|
||||
expect(result).toEqual([mockPartner]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustomers', () => {
|
||||
it('should get active customers', async () => {
|
||||
const customerPartner = { ...mockPartner, type: PartnerType.CUSTOMER };
|
||||
jest.spyOn(partnerRepository, 'find').mockResolvedValue([customerPartner] as any);
|
||||
|
||||
const result = await service.getCustomers('tenant-1');
|
||||
|
||||
expect(partnerRepository.find).toHaveBeenCalledWith({
|
||||
where: { tenantId: 'tenant-1', type: PartnerType.CUSTOMER, status: PartnerStatus.ACTIVE },
|
||||
order: { name: 'ASC' },
|
||||
});
|
||||
expect(result).toEqual([customerPartner]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPartnerStats', () => {
|
||||
it('should get partner statistics', async () => {
|
||||
const mockQueryBuilder = {
|
||||
where: jest.fn().mockReturnThis(),
|
||||
select: jest.fn().mockReturnThis(),
|
||||
getRawOne: jest.fn().mockResolvedValue({ total: 100, active: 80, inactive: 20 }),
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
const result = await service.getPartnerStats('tenant-1');
|
||||
|
||||
expect(result.totalPartners).toBe(100);
|
||||
expect(result.activePartners).toBe(80);
|
||||
expect(result.inactivePartners).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchPartners', () => {
|
||||
it('should search partners by query', async () => {
|
||||
const mockQueryBuilder = {
|
||||
where: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
limit: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue([mockPartner]),
|
||||
};
|
||||
|
||||
jest.spyOn(partnerRepository, 'createQueryBuilder').mockReturnValue(mockQueryBuilder as any);
|
||||
|
||||
const result = await service.searchPartners('tenant-1', 'Test', 10);
|
||||
|
||||
expect(partnerRepository.createQueryBuilder).toHaveBeenCalledWith('partner');
|
||||
expect(mockQueryBuilder.where).toHaveBeenCalledWith('partner.tenantId = :tenantId', { tenantId: 'tenant-1' });
|
||||
expect(mockQueryBuilder.andWhere).toHaveBeenCalledWith(
|
||||
'(partner.name ILIKE :query OR partner.code ILIKE :query OR partner.email ILIKE :query)',
|
||||
{ query: '%Test%' }
|
||||
);
|
||||
expect(mockQueryBuilder.limit).toHaveBeenCalledWith(10);
|
||||
expect(result).toEqual([mockPartner]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulkUpdate', () => {
|
||||
it('should update multiple partners', async () => {
|
||||
const updates = [
|
||||
{ id: 'uuid-1', status: PartnerStatus.INACTIVE },
|
||||
{ id: 'uuid-2', status: PartnerStatus.INACTIVE },
|
||||
];
|
||||
|
||||
jest.spyOn(partnerRepository, 'findOne').mockResolvedValue(mockPartner as any);
|
||||
jest.spyOn(partnerRepository, 'save').mockResolvedValue(mockPartner as any);
|
||||
|
||||
const result = await service.bulkUpdate(updates);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(partnerRepository.save).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user