Files
back_end_oudelaa/src/infrastructure/maintenance/counter-reconciliation.service.spec.ts
T

81 lines
3.7 KiB
TypeScript

import { Types } from 'mongoose';
import { AppQueueService } from '../queue/app-queue.service';
import { CounterReconciliationService } from './counter-reconciliation.service';
import { MaintenanceController } from './maintenance.controller';
describe('CounterReconciliationService', () => {
const postOne = new Types.ObjectId();
const postTwo = new Types.ObjectId();
let rows: Record<string, Array<{ _id: Types.ObjectId; count: number }>>;
let postsBulkWrite: jest.Mock;
let collection: jest.Mock;
const queue = { registerProcessor: jest.fn(), enqueue: jest.fn() } as unknown as AppQueueService;
beforeEach(() => {
jest.clearAllMocks();
rows = {
likes: [{ _id: postOne, count: 3 }],
comments: [{ _id: postOne, count: 2 }],
saves: [{ _id: postTwo, count: 5 }],
postshares: [{ _id: postOne, count: 1 }],
};
postsBulkWrite = jest.fn();
collection = jest.fn((name: string) =>
name === 'posts'
? {
find: jest.fn(() => ({ toArray: jest.fn().mockResolvedValue([{ _id: postOne }, { _id: postTwo }]) })),
bulkWrite: postsBulkWrite,
}
: { aggregate: jest.fn(() => ({ toArray: jest.fn().mockResolvedValue(rows[name]) })) },
);
});
it('registers and enqueues reconciliation jobs', async () => {
const service = new CounterReconciliationService({ collection } as never, queue);
service.onModuleInit();
expect(queue.registerProcessor).toHaveBeenCalledWith(
'maintenance.reconcile-post-counters',
expect.any(Function),
);
const callback = (queue.registerProcessor as jest.Mock).mock.calls[0][1] as () => Promise<void>;
const reconcile = jest.spyOn(service, 'reconcile').mockResolvedValue({ dryRun: false, scannedPosts: 0, updates: 0 });
await callback();
expect(reconcile).toHaveBeenCalledWith(false);
await expect(service.enqueue()).resolves.toEqual({ queued: true });
expect(queue.enqueue).toHaveBeenCalledWith('maintenance.reconcile-post-counters', {});
});
it('computes every counter without writes during a dry run', async () => {
const service = new CounterReconciliationService({ collection } as never, queue);
await expect(service.reconcile()).resolves.toEqual({ dryRun: true, scannedPosts: 2, updates: 2 });
expect(postsBulkWrite).not.toHaveBeenCalled();
const likesAggregate = collection.mock.results.find((result) => result.value.aggregate)?.value.aggregate;
expect(likesAggregate).toHaveBeenCalledWith([
{ $match: { targetType: 'post' } },
{ $group: { _id: '$targetId', count: { $sum: 1 } } },
{ $match: { _id: { $type: 'objectId' } } },
]);
});
it('writes reconciled values including zeros', async () => {
const service = new CounterReconciliationService({ collection } as never, queue);
await service.reconcile(false);
expect(postsBulkWrite).toHaveBeenCalledWith(
[
expect.objectContaining({ updateOne: expect.objectContaining({ update: { $set: { likesCount: 3, commentsCount: 2, savesCount: 0, shareCount: 1 } } }) }),
expect.objectContaining({ updateOne: expect.objectContaining({ update: { $set: { likesCount: 0, commentsCount: 0, savesCount: 5, shareCount: 0 } } }) }),
],
{ ordered: false },
);
});
it('controller executes only on an explicit true flag', async () => {
const counters = { enqueue: jest.fn().mockResolvedValue({ queued: true }), reconcile: jest.fn().mockResolvedValue({ dryRun: true }) };
const controller = new MaintenanceController(counters as never);
await controller.reconcile('true');
await controller.reconcile('TRUE');
expect(counters.enqueue).toHaveBeenCalledTimes(1);
expect(counters.reconcile).toHaveBeenCalledWith(true);
});
});