diff --git a/src/origdatablocks/origdatablocks.controller.spec.ts b/src/origdatablocks/origdatablocks.controller.spec.ts index 96004b284..7a86a117e 100644 --- a/src/origdatablocks/origdatablocks.controller.spec.ts +++ b/src/origdatablocks/origdatablocks.controller.spec.ts @@ -10,15 +10,19 @@ import { Request } from "express"; class OrigDatablocksServiceMock { findOne = jest.fn(); findByIdAndUpdate = jest.fn(); + aggregateSizeAndFileCount = jest.fn(); } -class DatasetsServiceMock {} +class DatasetsServiceMock { + findByIdAndUpdate = jest.fn(); +} class CaslAbilityFactoryMock {} describe("OrigDatablocksController", () => { let controller: OrigDatablocksController; let origDatablocksService: OrigDatablocksServiceMock; + let datasetsService: DatasetsServiceMock; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -34,10 +38,10 @@ describe("OrigDatablocksController", () => { controller = module.get(OrigDatablocksController); origDatablocksService = module.get( OrigDatablocksService, - ); - - // Mock internal methods - controller["updateDatasetSizeAndFiles"] = jest.fn(); + ) as unknown as OrigDatablocksServiceMock; + datasetsService = module.get( + DatasetsService, + ) as unknown as DatasetsServiceMock; }); it("should be defined", () => { @@ -55,6 +59,11 @@ describe("OrigDatablocksController", () => { datasetId: "ds1", }; + beforeEach(() => { + // Isolate the update handler from the side-effect helper + controller["updateDatasetSizeAndFiles"] = jest.fn(); + }); + it("should throw NotFoundException if datablock not found before update", async () => { origDatablocksService.findOne.mockResolvedValue(null); @@ -160,4 +169,40 @@ describe("OrigDatablocksController", () => { }); }); }); + + describe("updateDatasetSizeAndFiles", () => { + it("should use aggregateSizeAndFileCount and update the dataset", async () => { + origDatablocksService.aggregateSizeAndFileCount.mockResolvedValue({ + size: 2000, + numberOfFiles: 5, + }); + + await controller["updateDatasetSizeAndFiles"]("testPid"); + + expect( + origDatablocksService.aggregateSizeAndFileCount, + ).toHaveBeenCalledWith("testPid"); + expect(datasetsService.findByIdAndUpdate).toHaveBeenCalledWith( + "testPid", + { + size: 2000, + numberOfFiles: 5, + }, + ); + }); + + it("should propagate zero totals when no origdatablocks exist", async () => { + origDatablocksService.aggregateSizeAndFileCount.mockResolvedValue({ + size: 0, + numberOfFiles: 0, + }); + + await controller["updateDatasetSizeAndFiles"]("emptyPid"); + + expect(datasetsService.findByIdAndUpdate).toHaveBeenCalledWith( + "emptyPid", + { size: 0, numberOfFiles: 0 }, + ); + }); + }); }); diff --git a/src/origdatablocks/origdatablocks.controller.ts b/src/origdatablocks/origdatablocks.controller.ts index 33d99e5dd..7bfefce7d 100644 --- a/src/origdatablocks/origdatablocks.controller.ts +++ b/src/origdatablocks/origdatablocks.controller.ts @@ -40,7 +40,6 @@ import { IOrigDatablockFields } from "./interfaces/origdatablocks.interface"; import { plainToInstance } from "class-transformer"; import { validate, ValidationError } from "class-validator"; import { DatasetsService } from "src/datasets/datasets.service"; -import { PartialUpdateDatasetDto } from "src/datasets/dto/update-dataset.dto"; import { filterDescription, filterExample, parseDate } from "src/common/utils"; import { JWTUser } from "src/auth/interfaces/jwt-user.interface"; import { DatasetClass } from "src/datasets/schemas/dataset.schema"; @@ -213,22 +212,10 @@ export class OrigDatablocksController { } async updateDatasetSizeAndFiles(pid: string) { - // updates datasets size - const parsedFilters: IFilters = - { where: { datasetId: pid } }; - const datasetOrigdatablocks = - await this.origDatablocksService.findAll(parsedFilters); - - const updateDatasetDto: PartialUpdateDatasetDto = { - size: datasetOrigdatablocks - .map((od) => od.size) - .reduce((ps, a) => ps + a, 0), - numberOfFiles: datasetOrigdatablocks - .map((od) => od.dataFileList.length) - .reduce((ps, a) => ps + a, 0), - }; + const { size, numberOfFiles } = + await this.origDatablocksService.aggregateSizeAndFileCount(pid); - await this.datasetsService.findByIdAndUpdate(pid, updateDatasetDto); + await this.datasetsService.findByIdAndUpdate(pid, { size, numberOfFiles }); } @UseGuards(PoliciesGuard) diff --git a/src/origdatablocks/origdatablocks.service.spec.ts b/src/origdatablocks/origdatablocks.service.spec.ts index 7d5ca7999..5aff25fef 100644 --- a/src/origdatablocks/origdatablocks.service.spec.ts +++ b/src/origdatablocks/origdatablocks.service.spec.ts @@ -47,6 +47,7 @@ describe("OrigdatablocksService", () => { find: jest.fn(), create: jest.fn(), exec: jest.fn(), + aggregate: jest.fn(), }, }, ], @@ -61,4 +62,44 @@ describe("OrigdatablocksService", () => { it("should be defined", () => { expect(service).toBeDefined(); }); + + describe("aggregateSizeAndFileCount", () => { + it("should return summed size and file count from origdatablocks", async () => { + (model.aggregate as jest.Mock).mockReturnValue({ + exec: jest + .fn() + .mockResolvedValue([{ _id: null, size: 5000, numberOfFiles: 3 }]), + }); + + const result = await service.aggregateSizeAndFileCount("testPid"); + + expect(result).toEqual({ size: 5000, numberOfFiles: 3 }); + }); + + it("should return zeros when no origdatablocks exist for the dataset", async () => { + (model.aggregate as jest.Mock).mockReturnValue({ + exec: jest.fn().mockResolvedValue([]), + }); + + const result = await service.aggregateSizeAndFileCount("emptyPid"); + + expect(result).toEqual({ size: 0, numberOfFiles: 0 }); + }); + + it("should match on the given datasetId", async () => { + (model.aggregate as jest.Mock).mockReturnValue({ + exec: jest + .fn() + .mockResolvedValue([{ _id: null, size: 0, numberOfFiles: 0 }]), + }); + + await service.aggregateSizeAndFileCount("ds123"); + + expect(model.aggregate).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ $match: { datasetId: "ds123" } }), + ]), + ); + }); + }); }); diff --git a/src/origdatablocks/origdatablocks.service.ts b/src/origdatablocks/origdatablocks.service.ts index 9d0e793f4..93fa40012 100644 --- a/src/origdatablocks/origdatablocks.service.ts +++ b/src/origdatablocks/origdatablocks.service.ts @@ -382,4 +382,26 @@ export class OrigDatablocksService { .exec(); return { count }; } + + async aggregateSizeAndFileCount( + datasetId: string, + ): Promise<{ size: number; numberOfFiles: number }> { + const [result] = await this.origDatablockModel + .aggregate<{ size: number; numberOfFiles: number }>([ + { $match: { datasetId } }, + { + $group: { + _id: null, + size: { $sum: "$size" }, + numberOfFiles: { + $sum: { $size: { $ifNull: ["$dataFileList", []] } }, + }, + }, + }, + ]) + .exec(); + return result + ? { size: result.size, numberOfFiles: result.numberOfFiles } + : { size: 0, numberOfFiles: 0 }; + } }