Integrate Oudelaa backend features, security, tests, and deployment updates

This commit is contained in:
boutmoun123
2026-07-26 16:58:53 +03:00
parent 1b24ca4294
commit 2fd5322ef7
178 changed files with 19068 additions and 2592 deletions
+100
View File
@@ -0,0 +1,100 @@
import { AppLoggerService } from '../../infrastructure/logging/app-logger.service';
import { AppQueueService } from '../../infrastructure/queue/app-queue.service';
import { NotificationsService } from '../notifications/notifications.service';
import { OutboxService } from './outbox.service';
describe('OutboxService', () => {
const model = { create: jest.fn(), findById: jest.fn() };
const notifications = {
createFollowNotification: jest.fn(),
createFollowRequestApprovedNotification: jest.fn(),
} as unknown as NotificationsService;
const queue = { registerProcessor: jest.fn(), enqueue: jest.fn() } as unknown as AppQueueService;
const logger = { warn: jest.fn() } as unknown as AppLoggerService;
const service = new OutboxService(model as never, notifications, queue, logger);
beforeEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
});
it('registers its processor and enqueues persisted follow events', async () => {
service.onModuleInit();
expect(queue.registerProcessor).toHaveBeenCalledWith('process_outbox_event', expect.any(Function));
const processor = (queue.registerProcessor as jest.Mock).mock.calls[0][1] as (value: unknown) => Promise<void>;
const processSpy = jest.spyOn(service, 'processEvent').mockResolvedValue();
await processor({ eventId: 9 });
expect(processSpy).toHaveBeenCalledWith('9');
await processor({});
expect(processSpy).toHaveBeenLastCalledWith('');
model.create.mockResolvedValue({ id: 'event-1' });
await service.enqueueFollowNotification('actor', 'recipient');
expect(model.create).toHaveBeenCalledWith({
eventType: 'follow_notification',
payload: { actorId: 'actor', recipientId: 'recipient', referenceId: '' },
status: 'pending',
});
expect(queue.enqueue).toHaveBeenCalledWith('process_outbox_event', { eventId: 'event-1' });
model.create.mockResolvedValue({ id: 'event-2' });
await service.enqueueFollowRequestApprovedNotification('actor', 'recipient', 'request');
expect(model.create).toHaveBeenCalledWith({
eventType: 'follow_request_approved_notification',
payload: { actorId: 'actor', recipientId: 'recipient', requestId: 'request' },
status: 'pending',
});
});
it('processes follow request approval notifications once', async () => {
const event = {
id: 'e-approved',
eventType: 'follow_request_approved_notification',
status: 'pending',
attempts: 0,
payload: { actorId: 'owner', recipientId: 'requester', requestId: 'request' },
save: jest.fn(),
};
model.findById.mockReturnValue({ exec: jest.fn().mockResolvedValue(event) });
await service.processEvent(event.id);
expect(notifications.createFollowRequestApprovedNotification).toHaveBeenCalledWith({
actorId: 'owner',
recipientId: 'requester',
requestId: 'request',
});
expect(event.status).toBe('processed');
});
it('ignores missing and already processed events', async () => {
model.findById.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null) });
await service.processEvent('missing');
model.findById.mockReturnValueOnce({ exec: jest.fn().mockResolvedValue({ status: 'processed' }) });
await service.processEvent('done');
expect(notifications.createFollowNotification).not.toHaveBeenCalled();
});
it('processes follow notifications and records completion atomically', async () => {
const event = {
id: 'e1', eventType: 'follow_notification', status: 'pending', attempts: 0,
payload: { actorId: 'a', recipientId: 'r', referenceId: 'post' }, save: jest.fn(),
};
model.findById.mockReturnValue({ exec: jest.fn().mockResolvedValue(event) });
await service.processEvent('e1');
expect(notifications.createFollowNotification).toHaveBeenCalledWith('a', 'r', 'post');
expect(event).toEqual(expect.objectContaining({ status: 'processed', attempts: 1, lastError: '', processedAt: expect.any(Date) }));
expect(event.save).toHaveBeenCalled();
});
it('marks failed events, logs non-Error failures, and still saves attempts', async () => {
const event = {
id: 'e2', eventType: 'follow_notification', status: 'pending', attempts: 2,
payload: {}, save: jest.fn(),
};
model.findById.mockReturnValue({ exec: jest.fn().mockResolvedValue(event) });
(notifications.createFollowNotification as jest.Mock).mockRejectedValue('network');
await service.processEvent('e2');
expect(event).toEqual(expect.objectContaining({ status: 'failed', lastError: 'unknown outbox error', attempts: 3 }));
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ eventId: 'e2' }), 'OutboxService');
expect(event.save).toHaveBeenCalled();
});
});