fix: empty and restore over 1,000 items (#12751)

This commit is contained in:
Jason Rasmussen
2024-09-18 09:57:52 -04:00
committed by GitHub
parent 4f25cec6df
commit 6740c67ed8
39 changed files with 540 additions and 145 deletions

View File

@@ -1,22 +1,24 @@
import { BadRequestException } from '@nestjs/common';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ITrashRepository } from 'src/interfaces/trash.interface';
import { TrashService } from 'src/services/trash.service';
import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub';
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newTrashRepositoryMock } from 'test/repositories/trash.repository.mock';
import { Mocked } from 'vitest';
describe(TrashService.name, () => {
let sut: TrashService;
let accessMock: IAccessRepositoryMock;
let assetMock: Mocked<IAssetRepository>;
let jobMock: Mocked<IJobRepository>;
let eventMock: Mocked<IEventRepository>;
let jobMock: Mocked<IJobRepository>;
let trashMock: Mocked<ITrashRepository>;
let loggerMock: Mocked<ILoggerRepository>;
it('should work', () => {
expect(sut).toBeDefined();
@@ -24,11 +26,12 @@ describe(TrashService.name, () => {
beforeEach(() => {
accessMock = newAccessRepositoryMock();
assetMock = newAssetRepositoryMock();
eventMock = newEventRepositoryMock();
jobMock = newJobRepositoryMock();
trashMock = newTrashRepositoryMock();
loggerMock = newLoggerRepositoryMock();
sut = new TrashService(accessMock, assetMock, jobMock, eventMock);
sut = new TrashService(accessMock, eventMock, jobMock, trashMock, loggerMock);
});
describe('restoreAssets', () => {
@@ -40,44 +43,70 @@ describe(TrashService.name, () => {
).rejects.toBeInstanceOf(BadRequestException);
});
it('should handle an empty list', async () => {
await expect(sut.restoreAssets(authStub.user1, { ids: [] })).resolves.toEqual({ count: 0 });
expect(accessMock.asset.checkOwnerAccess).not.toHaveBeenCalled();
});
it('should restore a batch of assets', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2']));
await sut.restoreAssets(authStub.user1, { ids: ['asset1', 'asset2'] });
expect(assetMock.restoreAll).toHaveBeenCalledWith(['asset1', 'asset2']);
expect(trashMock.restoreAll).toHaveBeenCalledWith(['asset1', 'asset2']);
expect(jobMock.queue.mock.calls).toEqual([]);
});
});
describe('restore', () => {
it('should handle an empty trash', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [], hasNextPage: false });
await expect(sut.restore(authStub.user1)).resolves.toBeUndefined();
expect(assetMock.restoreAll).not.toHaveBeenCalled();
expect(eventMock.clientSend).not.toHaveBeenCalled();
trashMock.getDeletedIds.mockResolvedValue({ items: [], hasNextPage: false });
trashMock.restore.mockResolvedValue(0);
await expect(sut.restore(authStub.user1)).resolves.toEqual({ count: 0 });
expect(trashMock.restore).toHaveBeenCalledWith('user-id');
});
it('should restore and notify', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [assetStub.image], hasNextPage: false });
await expect(sut.restore(authStub.user1)).resolves.toBeUndefined();
expect(assetMock.restoreAll).toHaveBeenCalledWith([assetStub.image.id]);
expect(eventMock.emit).toHaveBeenCalledWith('assets.restore', { assetIds: ['asset-id'], userId: 'user-id' });
it('should restore', async () => {
trashMock.getDeletedIds.mockResolvedValue({ items: ['asset-id'], hasNextPage: false });
trashMock.restore.mockResolvedValue(1);
await expect(sut.restore(authStub.user1)).resolves.toEqual({ count: 1 });
expect(trashMock.restore).toHaveBeenCalledWith('user-id');
});
});
describe('empty', () => {
it('should handle an empty trash', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [], hasNextPage: false });
await expect(sut.empty(authStub.user1)).resolves.toBeUndefined();
expect(jobMock.queueAll).toHaveBeenCalledWith([]);
trashMock.getDeletedIds.mockResolvedValue({ items: [], hasNextPage: false });
trashMock.empty.mockResolvedValue(0);
await expect(sut.empty(authStub.user1)).resolves.toEqual({ count: 0 });
expect(jobMock.queue).not.toHaveBeenCalled();
});
it('should empty the trash', async () => {
assetMock.getByUserId.mockResolvedValue({ items: [assetStub.image], hasNextPage: false });
await expect(sut.empty(authStub.user1)).resolves.toBeUndefined();
trashMock.getDeletedIds.mockResolvedValue({ items: ['asset-id'], hasNextPage: false });
trashMock.empty.mockResolvedValue(1);
await expect(sut.empty(authStub.user1)).resolves.toEqual({ count: 1 });
expect(trashMock.empty).toHaveBeenCalledWith('user-id');
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_TRASH_EMPTY, data: {} });
});
});
describe('onAssetsDelete', () => {
it('should queue the empty trash job', async () => {
await expect(sut.onAssetsDelete()).resolves.toBeUndefined();
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.QUEUE_TRASH_EMPTY, data: {} });
});
});
describe('handleQueueEmptyTrash', () => {
it('should queue asset delete jobs', async () => {
trashMock.getDeletedIds.mockResolvedValue({ items: ['asset-1'], hasNextPage: false });
await expect(sut.handleQueueEmptyTrash()).resolves.toEqual(JobStatus.SUCCESS);
expect(jobMock.queueAll).toHaveBeenCalledWith([
{ name: JobName.ASSET_DELETION, data: { id: assetStub.image.id, deleteOnDisk: true } },
{
name: JobName.ASSET_DELETION,
data: { id: 'asset-1', deleteOnDisk: true },
},
]);
});
});