template-saas/apps/backend/dist/modules/audit/__tests__/audit.service.spec.js
rckrdmrd 50a821a415
Some checks failed
CI / Backend CI (push) Has been cancelled
CI / Frontend CI (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / CI Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones de configuracion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:08 -06:00

427 lines
20 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const testing_1 = require("@nestjs/testing");
const typeorm_1 = require("@nestjs/typeorm");
const audit_service_1 = require("../services/audit.service");
const audit_log_entity_1 = require("../entities/audit-log.entity");
const activity_log_entity_1 = require("../entities/activity-log.entity");
describe('AuditService', () => {
let service;
let auditLogRepo;
let activityLogRepo;
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
const mockAuditLog = {
id: 'audit-001',
tenant_id: mockTenantId,
user_id: mockUserId,
action: audit_log_entity_1.AuditAction.CREATE,
entity_type: 'user',
entity_id: 'user-001',
new_values: { email: 'test@example.com' },
changed_fields: ['email'],
ip_address: '192.168.1.1',
created_at: new Date(),
};
const mockActivityLog = {
id: 'activity-001',
tenant_id: mockTenantId,
user_id: mockUserId,
activity_type: activity_log_entity_1.ActivityType.PAGE_VIEW,
resource_type: 'dashboard',
description: 'Viewed dashboard',
ip_address: '192.168.1.1',
created_at: new Date(),
};
beforeEach(async () => {
const mockAuditLogRepo = {
create: jest.fn(),
save: jest.fn(),
findOne: jest.fn(),
find: jest.fn(),
count: jest.fn(),
createQueryBuilder: jest.fn(),
};
const mockActivityLogRepo = {
create: jest.fn(),
save: jest.fn(),
findOne: jest.fn(),
find: jest.fn(),
createQueryBuilder: jest.fn(),
};
const module = await testing_1.Test.createTestingModule({
providers: [
audit_service_1.AuditService,
{ provide: (0, typeorm_1.getRepositoryToken)(audit_log_entity_1.AuditLog), useValue: mockAuditLogRepo },
{ provide: (0, typeorm_1.getRepositoryToken)(activity_log_entity_1.ActivityLog), useValue: mockActivityLogRepo },
],
}).compile();
service = module.get(audit_service_1.AuditService);
auditLogRepo = module.get((0, typeorm_1.getRepositoryToken)(audit_log_entity_1.AuditLog));
activityLogRepo = module.get((0, typeorm_1.getRepositoryToken)(activity_log_entity_1.ActivityLog));
});
afterEach(() => {
jest.clearAllMocks();
});
describe('createAuditLog', () => {
it('should create audit log successfully', async () => {
auditLogRepo.create.mockReturnValue(mockAuditLog);
auditLogRepo.save.mockResolvedValue(mockAuditLog);
const result = await service.createAuditLog({
tenant_id: mockTenantId,
user_id: mockUserId,
action: audit_log_entity_1.AuditAction.CREATE,
entity_type: 'user',
entity_id: 'user-001',
new_values: { email: 'test@example.com' },
});
expect(result).toEqual(mockAuditLog);
expect(auditLogRepo.create).toHaveBeenCalled();
expect(auditLogRepo.save).toHaveBeenCalled();
});
it('should detect changed fields', async () => {
auditLogRepo.create.mockReturnValue(mockAuditLog);
auditLogRepo.save.mockResolvedValue(mockAuditLog);
await service.createAuditLog({
tenant_id: mockTenantId,
user_id: mockUserId,
action: audit_log_entity_1.AuditAction.UPDATE,
entity_type: 'user',
entity_id: 'user-001',
old_values: { email: 'old@example.com', name: 'Old Name' },
new_values: { email: 'new@example.com', name: 'Old Name' },
});
expect(auditLogRepo.create).toHaveBeenCalledWith(expect.objectContaining({
changed_fields: ['email'],
}));
});
it('should handle null old/new values', async () => {
auditLogRepo.create.mockReturnValue(mockAuditLog);
auditLogRepo.save.mockResolvedValue(mockAuditLog);
await service.createAuditLog({
tenant_id: mockTenantId,
action: audit_log_entity_1.AuditAction.DELETE,
entity_type: 'user',
entity_id: 'user-001',
});
expect(auditLogRepo.create).toHaveBeenCalledWith(expect.objectContaining({
changed_fields: [],
}));
});
it('should include request metadata', async () => {
auditLogRepo.create.mockReturnValue(mockAuditLog);
auditLogRepo.save.mockResolvedValue(mockAuditLog);
await service.createAuditLog({
tenant_id: mockTenantId,
user_id: mockUserId,
action: audit_log_entity_1.AuditAction.READ,
entity_type: 'document',
ip_address: '192.168.1.1',
user_agent: 'Mozilla/5.0',
endpoint: '/api/documents/1',
http_method: 'GET',
response_status: 200,
duration_ms: 150,
});
expect(auditLogRepo.create).toHaveBeenCalledWith(expect.objectContaining({
ip_address: '192.168.1.1',
endpoint: '/api/documents/1',
duration_ms: 150,
}));
});
});
describe('queryAuditLogs', () => {
it('should return paginated audit logs', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[mockAuditLog], 1]),
};
auditLogRepo.createQueryBuilder.mockReturnValue(qb);
const result = await service.queryAuditLogs(mockTenantId, {});
expect(result.data).toHaveLength(1);
expect(result.total).toBe(1);
expect(result.page).toBe(1);
expect(result.limit).toBe(20);
});
it('should filter by user_id', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
};
auditLogRepo.createQueryBuilder.mockReturnValue(qb);
await service.queryAuditLogs(mockTenantId, { user_id: mockUserId });
expect(qb.andWhere).toHaveBeenCalledWith('audit.user_id = :user_id', {
user_id: mockUserId,
});
});
it('should filter by action', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
};
auditLogRepo.createQueryBuilder.mockReturnValue(qb);
await service.queryAuditLogs(mockTenantId, { action: audit_log_entity_1.AuditAction.CREATE });
expect(qb.andWhere).toHaveBeenCalledWith('audit.action = :action', {
action: audit_log_entity_1.AuditAction.CREATE,
});
});
it('should filter by entity_type', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
};
auditLogRepo.createQueryBuilder.mockReturnValue(qb);
await service.queryAuditLogs(mockTenantId, { entity_type: 'user' });
expect(qb.andWhere).toHaveBeenCalledWith('audit.entity_type = :entity_type', {
entity_type: 'user',
});
});
it('should filter by date range', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
};
auditLogRepo.createQueryBuilder.mockReturnValue(qb);
const from_date = '2026-01-01';
const to_date = '2026-01-31';
await service.queryAuditLogs(mockTenantId, { from_date, to_date });
expect(qb.andWhere).toHaveBeenCalledWith('audit.created_at BETWEEN :from_date AND :to_date', { from_date, to_date });
});
it('should handle pagination correctly', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 100]),
};
auditLogRepo.createQueryBuilder.mockReturnValue(qb);
const result = await service.queryAuditLogs(mockTenantId, {
page: 3,
limit: 10,
});
expect(qb.skip).toHaveBeenCalledWith(20);
expect(qb.take).toHaveBeenCalledWith(10);
expect(result.totalPages).toBe(10);
});
});
describe('getAuditLogById', () => {
it('should return audit log by id', async () => {
auditLogRepo.findOne.mockResolvedValue(mockAuditLog);
const result = await service.getAuditLogById(mockTenantId, 'audit-001');
expect(result).toEqual(mockAuditLog);
expect(auditLogRepo.findOne).toHaveBeenCalledWith({
where: { id: 'audit-001', tenant_id: mockTenantId },
});
});
it('should return null when not found', async () => {
auditLogRepo.findOne.mockResolvedValue(null);
const result = await service.getAuditLogById(mockTenantId, 'invalid');
expect(result).toBeNull();
});
});
describe('getEntityAuditHistory', () => {
it('should return audit history for entity', async () => {
auditLogRepo.find.mockResolvedValue([mockAuditLog]);
const result = await service.getEntityAuditHistory(mockTenantId, 'user', 'user-001');
expect(result).toHaveLength(1);
expect(auditLogRepo.find).toHaveBeenCalledWith({
where: {
tenant_id: mockTenantId,
entity_type: 'user',
entity_id: 'user-001',
},
order: { created_at: 'DESC' },
});
});
it('should return empty array for no history', async () => {
auditLogRepo.find.mockResolvedValue([]);
const result = await service.getEntityAuditHistory(mockTenantId, 'document', 'doc-999');
expect(result).toHaveLength(0);
});
});
describe('createActivityLog', () => {
it('should create activity log successfully', async () => {
activityLogRepo.create.mockReturnValue(mockActivityLog);
activityLogRepo.save.mockResolvedValue(mockActivityLog);
const result = await service.createActivityLog(mockTenantId, mockUserId, {
activity_type: activity_log_entity_1.ActivityType.PAGE_VIEW,
resource_type: 'dashboard',
description: 'Viewed dashboard',
}, { ip_address: '192.168.1.1' });
expect(result).toEqual(mockActivityLog);
expect(activityLogRepo.create).toHaveBeenCalled();
});
it('should include session context', async () => {
activityLogRepo.create.mockReturnValue(mockActivityLog);
activityLogRepo.save.mockResolvedValue(mockActivityLog);
await service.createActivityLog(mockTenantId, mockUserId, {
activity_type: activity_log_entity_1.ActivityType.FEATURE_USE,
description: 'Used export feature',
}, {
ip_address: '192.168.1.1',
user_agent: 'Mozilla/5.0',
session_id: 'session-001',
});
expect(activityLogRepo.create).toHaveBeenCalledWith(expect.objectContaining({
session_id: 'session-001',
}));
});
});
describe('queryActivityLogs', () => {
it('should return paginated activity logs', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest
.fn()
.mockResolvedValue([[mockActivityLog], 1]),
};
activityLogRepo.createQueryBuilder.mockReturnValue(qb);
const result = await service.queryActivityLogs(mockTenantId, {});
expect(result.data).toHaveLength(1);
expect(result.total).toBe(1);
});
it('should filter by activity_type', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
};
activityLogRepo.createQueryBuilder.mockReturnValue(qb);
await service.queryActivityLogs(mockTenantId, {
activity_type: activity_log_entity_1.ActivityType.PAGE_VIEW,
});
expect(qb.andWhere).toHaveBeenCalledWith('activity.activity_type = :activity_type', { activity_type: activity_log_entity_1.ActivityType.PAGE_VIEW });
});
it('should filter by resource_type', async () => {
const qb = {
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
};
activityLogRepo.createQueryBuilder.mockReturnValue(qb);
await service.queryActivityLogs(mockTenantId, { resource_type: 'document' });
expect(qb.andWhere).toHaveBeenCalledWith('activity.resource_type = :resource_type', { resource_type: 'document' });
});
});
describe('getUserActivitySummary', () => {
it('should return activity summary by type', async () => {
const qb = {
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
groupBy: jest.fn().mockReturnThis(),
getRawMany: jest.fn().mockResolvedValue([
{ activity_type: activity_log_entity_1.ActivityType.PAGE_VIEW, count: '50' },
{ activity_type: activity_log_entity_1.ActivityType.FEATURE_USE, count: '25' },
]),
};
activityLogRepo.createQueryBuilder.mockReturnValue(qb);
const result = await service.getUserActivitySummary(mockTenantId, mockUserId, 30);
expect(result).toHaveLength(2);
expect(result[0]).toEqual({
activity_type: activity_log_entity_1.ActivityType.PAGE_VIEW,
count: 50,
});
});
it('should use default 30 days', async () => {
const qb = {
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
groupBy: jest.fn().mockReturnThis(),
getRawMany: jest.fn().mockResolvedValue([]),
};
activityLogRepo.createQueryBuilder.mockReturnValue(qb);
await service.getUserActivitySummary(mockTenantId, mockUserId);
expect(qb.andWhere).toHaveBeenCalledWith('activity.created_at >= :fromDate', expect.any(Object));
});
});
describe('getAuditStats', () => {
it('should return audit statistics', async () => {
auditLogRepo.count.mockResolvedValue(100);
const actionsByTypeQb = {
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
groupBy: jest.fn().mockReturnThis(),
getRawMany: jest.fn().mockResolvedValue([
{ action: audit_log_entity_1.AuditAction.CREATE, count: '30' },
{ action: audit_log_entity_1.AuditAction.UPDATE, count: '50' },
{ action: audit_log_entity_1.AuditAction.DELETE, count: '20' },
]),
};
const topUsersQb = {
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
groupBy: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
limit: jest.fn().mockReturnThis(),
getRawMany: jest.fn().mockResolvedValue([
{ user_id: mockUserId, count: '45' },
{ user_id: 'user-002', count: '30' },
]),
};
auditLogRepo.createQueryBuilder
.mockReturnValueOnce(actionsByTypeQb)
.mockReturnValueOnce(topUsersQb);
const result = await service.getAuditStats(mockTenantId, 7);
expect(result.total_actions).toBe(100);
expect(result.actions_by_type).toHaveLength(3);
expect(result.top_users).toHaveLength(2);
});
it('should use default 7 days', async () => {
auditLogRepo.count.mockResolvedValue(0);
const qb = {
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
groupBy: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
limit: jest.fn().mockReturnThis(),
getRawMany: jest.fn().mockResolvedValue([]),
};
auditLogRepo.createQueryBuilder.mockReturnValue(qb);
await service.getAuditStats(mockTenantId);
expect(auditLogRepo.count).toHaveBeenCalled();
});
});
});
//# sourceMappingURL=audit.service.spec.js.map