- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8 - Actualizaciones de configuracion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
302 lines
14 KiB
JavaScript
302 lines
14 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const testing_1 = require("@nestjs/testing");
|
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
const common_1 = require("@nestjs/common");
|
|
const superadmin_service_1 = require("../superadmin.service");
|
|
const tenant_entity_1 = require("../../tenants/entities/tenant.entity");
|
|
const user_entity_1 = require("../../auth/entities/user.entity");
|
|
const subscription_entity_1 = require("../../billing/entities/subscription.entity");
|
|
describe('SuperadminService', () => {
|
|
let service;
|
|
let tenantRepository;
|
|
let userRepository;
|
|
let subscriptionRepository;
|
|
const mockTenant = {
|
|
id: 'tenant-123',
|
|
name: 'Test Company',
|
|
slug: 'test-company',
|
|
domain: 'test.example.com',
|
|
logo_url: 'https://example.com/logo.png',
|
|
status: 'active',
|
|
plan_id: 'plan-123',
|
|
trial_ends_at: new Date('2026-02-01'),
|
|
settings: { theme: 'dark' },
|
|
metadata: {},
|
|
created_at: new Date('2026-01-01'),
|
|
updated_at: new Date('2026-01-01'),
|
|
};
|
|
const mockUser = {
|
|
id: 'user-123',
|
|
tenant_id: 'tenant-123',
|
|
email: 'test@example.com',
|
|
first_name: 'John',
|
|
last_name: 'Doe',
|
|
status: 'active',
|
|
created_at: new Date('2026-01-01'),
|
|
updated_at: new Date('2026-01-01'),
|
|
};
|
|
const mockSubscription = {
|
|
id: 'sub-123',
|
|
tenant_id: 'tenant-123',
|
|
plan_id: 'plan-123',
|
|
status: 'active',
|
|
created_at: new Date('2026-01-01'),
|
|
updated_at: new Date('2026-01-01'),
|
|
};
|
|
const mockQueryBuilder = {
|
|
where: jest.fn().mockReturnThis(),
|
|
andWhere: jest.fn().mockReturnThis(),
|
|
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
getCount: jest.fn().mockResolvedValue(5),
|
|
getMany: jest.fn().mockResolvedValue([]),
|
|
};
|
|
beforeEach(async () => {
|
|
const module = await testing_1.Test.createTestingModule({
|
|
providers: [
|
|
superadmin_service_1.SuperadminService,
|
|
{
|
|
provide: (0, typeorm_1.getRepositoryToken)(tenant_entity_1.Tenant),
|
|
useValue: {
|
|
findOne: jest.fn(),
|
|
find: jest.fn(),
|
|
findAndCount: jest.fn(),
|
|
save: jest.fn(),
|
|
create: jest.fn(),
|
|
remove: jest.fn(),
|
|
count: jest.fn(),
|
|
createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder),
|
|
},
|
|
},
|
|
{
|
|
provide: (0, typeorm_1.getRepositoryToken)(user_entity_1.User),
|
|
useValue: {
|
|
findOne: jest.fn(),
|
|
find: jest.fn(),
|
|
findAndCount: jest.fn(),
|
|
count: jest.fn(),
|
|
createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder),
|
|
},
|
|
},
|
|
{
|
|
provide: (0, typeorm_1.getRepositoryToken)(subscription_entity_1.Subscription),
|
|
useValue: {
|
|
findOne: jest.fn(),
|
|
find: jest.fn(),
|
|
createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder),
|
|
},
|
|
},
|
|
],
|
|
}).compile();
|
|
service = module.get(superadmin_service_1.SuperadminService);
|
|
tenantRepository = module.get((0, typeorm_1.getRepositoryToken)(tenant_entity_1.Tenant));
|
|
userRepository = module.get((0, typeorm_1.getRepositoryToken)(user_entity_1.User));
|
|
subscriptionRepository = module.get((0, typeorm_1.getRepositoryToken)(subscription_entity_1.Subscription));
|
|
});
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
describe('listTenants', () => {
|
|
it('should return paginated tenants with stats', async () => {
|
|
tenantRepository.findAndCount.mockResolvedValue([[mockTenant], 1]);
|
|
userRepository.count.mockResolvedValue(5);
|
|
subscriptionRepository.findOne.mockResolvedValue(mockSubscription);
|
|
const result = await service.listTenants({ page: 1, limit: 10 });
|
|
expect(result.data).toHaveLength(1);
|
|
expect(result.total).toBe(1);
|
|
expect(result.page).toBe(1);
|
|
expect(result.limit).toBe(10);
|
|
expect(result.totalPages).toBe(1);
|
|
expect(result.data[0].userCount).toBe(5);
|
|
});
|
|
it('should handle search parameter', async () => {
|
|
tenantRepository.findAndCount.mockResolvedValue([[], 0]);
|
|
await service.listTenants({ page: 1, limit: 10, search: 'test' });
|
|
expect(tenantRepository.findAndCount).toHaveBeenCalledWith(expect.objectContaining({
|
|
where: expect.objectContaining({
|
|
name: expect.anything(),
|
|
}),
|
|
}));
|
|
});
|
|
it('should handle status filter', async () => {
|
|
tenantRepository.findAndCount.mockResolvedValue([[], 0]);
|
|
await service.listTenants({ page: 1, limit: 10, status: 'active' });
|
|
expect(tenantRepository.findAndCount).toHaveBeenCalledWith(expect.objectContaining({
|
|
where: expect.objectContaining({
|
|
status: 'active',
|
|
}),
|
|
}));
|
|
});
|
|
});
|
|
describe('getTenant', () => {
|
|
it('should return tenant with stats', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(mockTenant);
|
|
userRepository.count.mockResolvedValue(5);
|
|
subscriptionRepository.findOne.mockResolvedValue(mockSubscription);
|
|
const result = await service.getTenant('tenant-123');
|
|
expect(result.id).toBe('tenant-123');
|
|
expect(result.userCount).toBe(5);
|
|
expect(result.subscription).toBeDefined();
|
|
});
|
|
it('should throw NotFoundException if tenant not found', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.getTenant('non-existent')).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('createTenant', () => {
|
|
const createDto = {
|
|
name: 'New Company',
|
|
slug: 'new-company',
|
|
domain: 'new.example.com',
|
|
status: 'trial',
|
|
};
|
|
it('should create a new tenant', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(null);
|
|
tenantRepository.create.mockReturnValue({ ...mockTenant, ...createDto });
|
|
tenantRepository.save.mockResolvedValue({ ...mockTenant, ...createDto });
|
|
const result = await service.createTenant(createDto);
|
|
expect(result.name).toBe('New Company');
|
|
expect(tenantRepository.create).toHaveBeenCalled();
|
|
expect(tenantRepository.save).toHaveBeenCalled();
|
|
});
|
|
it('should throw ConflictException if slug exists', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(mockTenant);
|
|
await expect(service.createTenant(createDto)).rejects.toThrow(common_1.ConflictException);
|
|
});
|
|
});
|
|
describe('updateTenant', () => {
|
|
const updateDto = { name: 'Updated Company' };
|
|
it('should update a tenant', async () => {
|
|
const tenantCopy = { ...mockTenant };
|
|
tenantRepository.findOne.mockResolvedValue(tenantCopy);
|
|
tenantRepository.save.mockResolvedValue({ ...tenantCopy, ...updateDto });
|
|
const result = await service.updateTenant('tenant-123', updateDto);
|
|
expect(result.name).toBe('Updated Company');
|
|
expect(tenantRepository.save).toHaveBeenCalled();
|
|
});
|
|
it('should throw NotFoundException if tenant not found', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.updateTenant('non-existent', updateDto)).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('updateTenantStatus', () => {
|
|
it('should update tenant status', async () => {
|
|
const tenantCopy = { ...mockTenant };
|
|
tenantRepository.findOne.mockResolvedValue(tenantCopy);
|
|
tenantRepository.save.mockResolvedValue({ ...tenantCopy, status: 'suspended' });
|
|
const result = await service.updateTenantStatus('tenant-123', {
|
|
status: 'suspended',
|
|
reason: 'Non-payment',
|
|
});
|
|
expect(tenantRepository.save).toHaveBeenCalled();
|
|
});
|
|
it('should throw NotFoundException if tenant not found', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.updateTenantStatus('non-existent', { status: 'suspended' })).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
it('should store reason in metadata', async () => {
|
|
const tenantCopy = { ...mockTenant, metadata: {} };
|
|
tenantRepository.findOne.mockResolvedValue(tenantCopy);
|
|
tenantRepository.save.mockImplementation(async (t) => t);
|
|
await service.updateTenantStatus('tenant-123', {
|
|
status: 'suspended',
|
|
reason: 'Policy violation',
|
|
});
|
|
const savedTenant = tenantRepository.save.mock.calls[0][0];
|
|
expect(savedTenant.metadata?.statusChangeReason).toBe('Policy violation');
|
|
});
|
|
});
|
|
describe('deleteTenant', () => {
|
|
it('should delete a tenant without users', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(mockTenant);
|
|
userRepository.count.mockResolvedValue(0);
|
|
tenantRepository.remove.mockResolvedValue(mockTenant);
|
|
await service.deleteTenant('tenant-123');
|
|
expect(tenantRepository.remove).toHaveBeenCalledWith(mockTenant);
|
|
});
|
|
it('should throw NotFoundException if tenant not found', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.deleteTenant('non-existent')).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
it('should throw BadRequestException if tenant has users', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(mockTenant);
|
|
userRepository.count.mockResolvedValue(5);
|
|
await expect(service.deleteTenant('tenant-123')).rejects.toThrow(common_1.BadRequestException);
|
|
});
|
|
});
|
|
describe('getTenantUsers', () => {
|
|
it('should return paginated users for a tenant', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(mockTenant);
|
|
userRepository.findAndCount.mockResolvedValue([[mockUser], 1]);
|
|
const result = await service.getTenantUsers('tenant-123', 1, 10);
|
|
expect(result.data).toHaveLength(1);
|
|
expect(result.total).toBe(1);
|
|
expect(result.page).toBe(1);
|
|
});
|
|
it('should throw NotFoundException if tenant not found', async () => {
|
|
tenantRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.getTenantUsers('non-existent')).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('getDashboardStats', () => {
|
|
it('should return dashboard statistics', async () => {
|
|
tenantRepository.count
|
|
.mockResolvedValueOnce(100)
|
|
.mockResolvedValueOnce(80)
|
|
.mockResolvedValueOnce(15)
|
|
.mockResolvedValueOnce(5);
|
|
userRepository.count.mockResolvedValue(500);
|
|
mockQueryBuilder.getCount.mockResolvedValue(10);
|
|
const result = await service.getDashboardStats();
|
|
expect(result.totalTenants).toBe(100);
|
|
expect(result.activeTenants).toBe(80);
|
|
expect(result.trialTenants).toBe(15);
|
|
expect(result.suspendedTenants).toBe(5);
|
|
expect(result.totalUsers).toBe(500);
|
|
expect(result.newTenantsThisMonth).toBe(10);
|
|
});
|
|
});
|
|
describe('getStatusDistribution', () => {
|
|
it('should return status distribution', async () => {
|
|
tenantRepository.count
|
|
.mockResolvedValueOnce(100)
|
|
.mockResolvedValueOnce(60)
|
|
.mockResolvedValueOnce(20)
|
|
.mockResolvedValueOnce(15)
|
|
.mockResolvedValueOnce(5);
|
|
const result = await service.getStatusDistribution();
|
|
expect(result).toHaveLength(4);
|
|
expect(result.find(s => s.status === 'Active')?.count).toBe(60);
|
|
});
|
|
it('should calculate percentages correctly', async () => {
|
|
tenantRepository.count
|
|
.mockResolvedValueOnce(100)
|
|
.mockResolvedValueOnce(50)
|
|
.mockResolvedValueOnce(30)
|
|
.mockResolvedValueOnce(15)
|
|
.mockResolvedValueOnce(5);
|
|
const result = await service.getStatusDistribution();
|
|
expect(result.find(s => s.status === 'Active')?.percentage).toBe(50);
|
|
expect(result.find(s => s.status === 'Trial')?.percentage).toBe(30);
|
|
});
|
|
});
|
|
describe('getTenantGrowth', () => {
|
|
it('should return growth data for specified months', async () => {
|
|
mockQueryBuilder.getCount.mockResolvedValue(10);
|
|
const result = await service.getTenantGrowth(6);
|
|
expect(result).toHaveLength(6);
|
|
expect(result[0]).toHaveProperty('month');
|
|
expect(result[0]).toHaveProperty('count');
|
|
});
|
|
});
|
|
describe('getUserGrowth', () => {
|
|
it('should return user growth data', async () => {
|
|
mockQueryBuilder.getCount.mockResolvedValue(20);
|
|
const result = await service.getUserGrowth(3);
|
|
expect(result).toHaveLength(3);
|
|
expect(result[0]).toHaveProperty('month');
|
|
expect(result[0]).toHaveProperty('count');
|
|
});
|
|
});
|
|
});
|
|
//# sourceMappingURL=superadmin.service.spec.js.map
|