- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8 - Actualizaciones de configuracion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
373 lines
17 KiB
JavaScript
373 lines
17 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const testing_1 = require("@nestjs/testing");
|
|
const notifications_gateway_1 = require("../gateways/notifications.gateway");
|
|
describe('NotificationsGateway', () => {
|
|
let gateway;
|
|
let mockServer;
|
|
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
|
|
const mockUserId = '550e8400-e29b-41d4-a716-446655440002';
|
|
const mockSocketId = 'socket-123';
|
|
const createMockSocket = (overrides = {}) => ({
|
|
id: mockSocketId,
|
|
disconnect: jest.fn(),
|
|
join: jest.fn(),
|
|
emit: jest.fn(),
|
|
handshake: {
|
|
auth: { userId: mockUserId, tenantId: mockTenantId },
|
|
query: {},
|
|
},
|
|
...overrides,
|
|
});
|
|
const mockNotification = {
|
|
id: 'notif-001',
|
|
tenant_id: mockTenantId,
|
|
user_id: mockUserId,
|
|
type: 'info',
|
|
channel: 'in_app',
|
|
title: 'Test Notification',
|
|
message: 'This is a test notification',
|
|
is_read: false,
|
|
delivery_status: 'delivered',
|
|
created_at: new Date(),
|
|
};
|
|
beforeEach(async () => {
|
|
const module = await testing_1.Test.createTestingModule({
|
|
providers: [notifications_gateway_1.NotificationsGateway],
|
|
}).compile();
|
|
gateway = module.get(notifications_gateway_1.NotificationsGateway);
|
|
mockServer = {
|
|
to: jest.fn().mockReturnThis(),
|
|
emit: jest.fn(),
|
|
};
|
|
gateway.server = mockServer;
|
|
});
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
describe('handleConnection', () => {
|
|
it('should accept connection with valid auth in handshake.auth', () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
expect(mockSocket.userId).toBe(mockUserId);
|
|
expect(mockSocket.tenantId).toBe(mockTenantId);
|
|
expect(mockSocket.join).toHaveBeenCalledWith(`tenant:${mockTenantId}`);
|
|
expect(mockSocket.join).toHaveBeenCalledWith(`user:${mockUserId}`);
|
|
expect(mockSocket.emit).toHaveBeenCalledWith('connected', {
|
|
socketId: mockSocketId,
|
|
userId: mockUserId,
|
|
tenantId: mockTenantId,
|
|
});
|
|
});
|
|
it('should accept connection with valid auth in handshake.query', () => {
|
|
const mockSocket = createMockSocket({
|
|
handshake: {
|
|
auth: {},
|
|
query: { userId: mockUserId, tenantId: mockTenantId },
|
|
},
|
|
});
|
|
gateway.handleConnection(mockSocket);
|
|
expect(mockSocket.userId).toBe(mockUserId);
|
|
expect(mockSocket.tenantId).toBe(mockTenantId);
|
|
expect(mockSocket.disconnect).not.toHaveBeenCalled();
|
|
});
|
|
it('should disconnect client without userId', () => {
|
|
const mockSocket = createMockSocket({
|
|
handshake: {
|
|
auth: { tenantId: mockTenantId },
|
|
query: {},
|
|
},
|
|
});
|
|
gateway.handleConnection(mockSocket);
|
|
expect(mockSocket.disconnect).toHaveBeenCalled();
|
|
});
|
|
it('should disconnect client without tenantId', () => {
|
|
const mockSocket = createMockSocket({
|
|
handshake: {
|
|
auth: { userId: mockUserId },
|
|
query: {},
|
|
},
|
|
});
|
|
gateway.handleConnection(mockSocket);
|
|
expect(mockSocket.disconnect).toHaveBeenCalled();
|
|
});
|
|
it('should disconnect client with no auth', () => {
|
|
const mockSocket = createMockSocket({
|
|
handshake: {
|
|
auth: {},
|
|
query: {},
|
|
},
|
|
});
|
|
gateway.handleConnection(mockSocket);
|
|
expect(mockSocket.disconnect).toHaveBeenCalled();
|
|
});
|
|
it('should track multiple sockets for same user', () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({ id: 'socket-2' });
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
expect(gateway.getTotalConnections()).toBe(2);
|
|
expect(gateway.getConnectedUsersCount()).toBe(1);
|
|
});
|
|
it('should handle connection error gracefully', () => {
|
|
const mockSocket = createMockSocket();
|
|
mockSocket.join = jest.fn().mockImplementation(() => {
|
|
throw new Error('Join failed');
|
|
});
|
|
gateway.handleConnection(mockSocket);
|
|
expect(mockSocket.disconnect).toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('handleDisconnect', () => {
|
|
it('should remove socket from tracking maps', () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
expect(gateway.getTotalConnections()).toBe(1);
|
|
gateway.handleDisconnect(mockSocket);
|
|
expect(gateway.getTotalConnections()).toBe(0);
|
|
expect(gateway.getConnectedUsersCount()).toBe(0);
|
|
});
|
|
it('should keep user in map if other sockets remain', () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({ id: 'socket-2' });
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
expect(gateway.getTotalConnections()).toBe(2);
|
|
gateway.handleDisconnect(socket1);
|
|
expect(gateway.getTotalConnections()).toBe(1);
|
|
expect(gateway.getConnectedUsersCount()).toBe(1);
|
|
});
|
|
it('should handle disconnect of unknown socket gracefully', () => {
|
|
const mockSocket = createMockSocket();
|
|
expect(() => gateway.handleDisconnect(mockSocket)).not.toThrow();
|
|
});
|
|
});
|
|
describe('emitToUser', () => {
|
|
it('should emit notification to all user sockets', async () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({ id: 'socket-2' });
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
const result = await gateway.emitToUser(mockTenantId, mockUserId, mockNotification);
|
|
expect(result).toBe(2);
|
|
expect(mockServer.to).toHaveBeenCalledWith('socket-1');
|
|
expect(mockServer.to).toHaveBeenCalledWith('socket-2');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('notification:created', mockNotification);
|
|
});
|
|
it('should return 0 if user has no active sockets', async () => {
|
|
const result = await gateway.emitToUser(mockTenantId, mockUserId, mockNotification);
|
|
expect(result).toBe(0);
|
|
expect(mockServer.to).not.toHaveBeenCalled();
|
|
});
|
|
it('should return 0 if user key does not exist', async () => {
|
|
const result = await gateway.emitToUser('different-tenant', 'different-user', mockNotification);
|
|
expect(result).toBe(0);
|
|
});
|
|
});
|
|
describe('emitToTenant', () => {
|
|
it('should broadcast event to tenant room', async () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
await gateway.emitToTenant(mockTenantId, 'announcement', {
|
|
message: 'Hello everyone!',
|
|
});
|
|
expect(mockServer.to).toHaveBeenCalledWith(`tenant:${mockTenantId}`);
|
|
expect(mockServer.emit).toHaveBeenCalledWith('announcement', {
|
|
message: 'Hello everyone!',
|
|
});
|
|
});
|
|
it('should emit custom events to tenant', async () => {
|
|
await gateway.emitToTenant(mockTenantId, 'system:maintenance', {
|
|
startTime: '2024-01-01T00:00:00Z',
|
|
});
|
|
expect(mockServer.to).toHaveBeenCalledWith(`tenant:${mockTenantId}`);
|
|
expect(mockServer.emit).toHaveBeenCalledWith('system:maintenance', {
|
|
startTime: '2024-01-01T00:00:00Z',
|
|
});
|
|
});
|
|
});
|
|
describe('handleMarkAsRead', () => {
|
|
it('should broadcast read event to other user sockets', () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({ id: 'socket-2' });
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
const payload = { notificationId: 'notif-001' };
|
|
socket1.userId = mockUserId;
|
|
socket1.tenantId = mockTenantId;
|
|
gateway.handleMarkAsRead(socket1, payload);
|
|
expect(mockServer.to).toHaveBeenCalledWith('socket-2');
|
|
expect(mockServer.to).not.toHaveBeenCalledWith('socket-1');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('notification:read', payload);
|
|
});
|
|
it('should do nothing if client is not authenticated', () => {
|
|
const mockSocket = createMockSocket();
|
|
delete mockSocket.userId;
|
|
delete mockSocket.tenantId;
|
|
gateway.handleMarkAsRead(mockSocket, { notificationId: 'notif-001' });
|
|
expect(mockServer.to).not.toHaveBeenCalled();
|
|
});
|
|
it('should handle single socket user', () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
mockSocket.userId = mockUserId;
|
|
mockSocket.tenantId = mockTenantId;
|
|
gateway.handleMarkAsRead(mockSocket, { notificationId: 'notif-001' });
|
|
expect(mockServer.to).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('handleMarkAllAsRead', () => {
|
|
it('should broadcast read-all event to other user sockets', () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({ id: 'socket-2' });
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
socket1.userId = mockUserId;
|
|
socket1.tenantId = mockTenantId;
|
|
gateway.handleMarkAllAsRead(socket1);
|
|
expect(mockServer.to).toHaveBeenCalledWith('socket-2');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('notification:read-all', {});
|
|
});
|
|
it('should do nothing if client is not authenticated', () => {
|
|
const mockSocket = createMockSocket();
|
|
delete mockSocket.userId;
|
|
delete mockSocket.tenantId;
|
|
gateway.handleMarkAllAsRead(mockSocket);
|
|
expect(mockServer.to).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('handleGetUnreadCount', () => {
|
|
it('should return acknowledgement event', () => {
|
|
const mockSocket = createMockSocket();
|
|
mockSocket.userId = mockUserId;
|
|
mockSocket.tenantId = mockTenantId;
|
|
const result = gateway.handleGetUnreadCount(mockSocket);
|
|
expect(result).toEqual({ event: 'notification:unread-count-requested' });
|
|
});
|
|
});
|
|
describe('emitUnreadCount', () => {
|
|
it('should emit unread count to all user sockets', async () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({ id: 'socket-2' });
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
await gateway.emitUnreadCount(mockTenantId, mockUserId, 5);
|
|
expect(mockServer.to).toHaveBeenCalledWith('socket-1');
|
|
expect(mockServer.to).toHaveBeenCalledWith('socket-2');
|
|
expect(mockServer.emit).toHaveBeenCalledWith('notification:unread-count', {
|
|
count: 5,
|
|
});
|
|
});
|
|
it('should do nothing if user has no sockets', async () => {
|
|
await gateway.emitUnreadCount(mockTenantId, 'unknown-user', 5);
|
|
expect(mockServer.to).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('emitNotificationDeleted', () => {
|
|
it('should emit deleted event to all user sockets', async () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
await gateway.emitNotificationDeleted(mockTenantId, mockUserId, 'notif-001');
|
|
expect(mockServer.to).toHaveBeenCalledWith(mockSocketId);
|
|
expect(mockServer.emit).toHaveBeenCalledWith('notification:deleted', {
|
|
notificationId: 'notif-001',
|
|
});
|
|
});
|
|
it('should do nothing if user has no sockets', async () => {
|
|
await gateway.emitNotificationDeleted(mockTenantId, 'unknown-user', 'notif-001');
|
|
expect(mockServer.to).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('getConnectedUsersCount', () => {
|
|
it('should return count of unique connected users', () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({
|
|
id: 'socket-2',
|
|
handshake: {
|
|
auth: { userId: 'user-2', tenantId: mockTenantId },
|
|
query: {},
|
|
},
|
|
});
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
expect(gateway.getConnectedUsersCount()).toBe(2);
|
|
});
|
|
it('should return 0 when no users connected', () => {
|
|
expect(gateway.getConnectedUsersCount()).toBe(0);
|
|
});
|
|
});
|
|
describe('getTotalConnections', () => {
|
|
it('should return total socket connections', () => {
|
|
const socket1 = createMockSocket({ id: 'socket-1' });
|
|
const socket2 = createMockSocket({ id: 'socket-2' });
|
|
const socket3 = createMockSocket({ id: 'socket-3' });
|
|
gateway.handleConnection(socket1);
|
|
gateway.handleConnection(socket2);
|
|
gateway.handleConnection(socket3);
|
|
expect(gateway.getTotalConnections()).toBe(3);
|
|
});
|
|
it('should return 0 when no connections', () => {
|
|
expect(gateway.getTotalConnections()).toBe(0);
|
|
});
|
|
});
|
|
describe('isUserOnline', () => {
|
|
it('should return true if user has active sockets', () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
expect(gateway.isUserOnline(mockTenantId, mockUserId)).toBe(true);
|
|
});
|
|
it('should return false if user has no sockets', () => {
|
|
expect(gateway.isUserOnline(mockTenantId, mockUserId)).toBe(false);
|
|
});
|
|
it('should return false after user disconnects', () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
gateway.handleDisconnect(mockSocket);
|
|
expect(gateway.isUserOnline(mockTenantId, mockUserId)).toBe(false);
|
|
});
|
|
it('should check correct tenant and user combination', () => {
|
|
const mockSocket = createMockSocket();
|
|
gateway.handleConnection(mockSocket);
|
|
expect(gateway.isUserOnline(mockTenantId, mockUserId)).toBe(true);
|
|
expect(gateway.isUserOnline('other-tenant', mockUserId)).toBe(false);
|
|
expect(gateway.isUserOnline(mockTenantId, 'other-user')).toBe(false);
|
|
});
|
|
});
|
|
describe('multi-tenant isolation', () => {
|
|
it('should isolate users by tenant', async () => {
|
|
const tenant1User = createMockSocket({
|
|
id: 'socket-t1',
|
|
handshake: {
|
|
auth: { userId: 'user-1', tenantId: 'tenant-1' },
|
|
query: {},
|
|
},
|
|
});
|
|
const tenant2User = createMockSocket({
|
|
id: 'socket-t2',
|
|
handshake: {
|
|
auth: { userId: 'user-1', tenantId: 'tenant-2' },
|
|
query: {},
|
|
},
|
|
});
|
|
gateway.handleConnection(tenant1User);
|
|
gateway.handleConnection(tenant2User);
|
|
const result1 = await gateway.emitToUser('tenant-1', 'user-1', mockNotification);
|
|
expect(result1).toBe(1);
|
|
expect(mockServer.to).toHaveBeenCalledWith('socket-t1');
|
|
expect(mockServer.to).not.toHaveBeenCalledWith('socket-t2');
|
|
});
|
|
it('should broadcast to correct tenant only', async () => {
|
|
const tenant1User = createMockSocket({
|
|
id: 'socket-t1',
|
|
handshake: {
|
|
auth: { userId: 'user-1', tenantId: 'tenant-1' },
|
|
query: {},
|
|
},
|
|
});
|
|
gateway.handleConnection(tenant1User);
|
|
await gateway.emitToTenant('tenant-1', 'test-event', { data: 'test' });
|
|
expect(mockServer.to).toHaveBeenCalledWith('tenant:tenant-1');
|
|
});
|
|
});
|
|
});
|
|
//# sourceMappingURL=notifications.gateway.spec.js.map
|