From 0305bee59ef77deba86e88f73f3b208f910371b0 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 9 May 2025 12:32:53 -0400 Subject: [PATCH 1/6] feat: update filehasbeendeleted --- docs/useCases.md | 24 ++++++ .../domain/repositories/IFilesRepository.ts | 2 + .../domain/useCases/FileHasBeenDeleted.ts | 16 ++++ src/files/index.ts | 5 +- .../infra/repositories/FilesRepository.ts | 8 ++ .../integration/files/FilesRepository.test.ts | 77 +++++++++++++++++++ test/unit/files/FileHasBeenDeleted.test.ts | 33 ++++++++ 7 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/files/domain/useCases/FileHasBeenDeleted.ts create mode 100644 test/unit/files/FileHasBeenDeleted.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 6a221419..bd889e5b 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1571,6 +1571,30 @@ If restrict is false then enableAccessRequest and termsOfAccess are ignored If restrict is true and enableAccessRequest is false then termsOfAccess is required. The enableAccessRequest and termsOfAccess are applied to the Draft version of the Dataset and affect all of the restricted files in said Draft version. +#### File Has Been Deleted + +Check if the file has been deleted, return a boolean. + +##### Example call: + +```typescript +import { fileHasBeenDeleted } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const fileId = 12345 + +await fileHasBeenDeleted.execute(fileId).then((deleted: boolean) => { + /* ... */ +}) + +/* ... */ +``` + +_See [use case](../src/files/domain/useCases/FileHasBeenDeleted.ts) implementation_. + +The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers. + ## Metadata Blocks ### Metadata Blocks read use cases diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index fa473d71..89bfd222 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -84,4 +84,6 @@ export interface IFilesRepository { categories: string[], replace?: boolean ): Promise + + fileHasBeenDeleted(fileId: number | string): Promise } diff --git a/src/files/domain/useCases/FileHasBeenDeleted.ts b/src/files/domain/useCases/FileHasBeenDeleted.ts new file mode 100644 index 00000000..953acaa6 --- /dev/null +++ b/src/files/domain/useCases/FileHasBeenDeleted.ts @@ -0,0 +1,16 @@ +import { IFilesRepository } from '../repositories/IFilesRepository' +import { UseCase } from '../../../core/domain/useCases/UseCase' + +export class FileHasBeenDeleted implements UseCase { + constructor(private readonly filesRepository: IFilesRepository) {} + + /** + * Returns a boolean, indicating whether the file has been deleted or not. + * + * @param {number | string} [fileId] - The File identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). + * @returns {Promise} - A boolean indicating whether the file has been deleted or not. + */ + async execute(fileId: number | string): Promise { + return await this.filesRepository.fileHasBeenDeleted(fileId) + } +} diff --git a/src/files/index.ts b/src/files/index.ts index aac93272..aa787638 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -17,6 +17,7 @@ import { RestrictFile } from './domain/useCases/RestrictFile' import { UpdateFileMetadata } from './domain/useCases/UpdateFileMetadata' import { UpdateFileTabularTags } from './domain/useCases/UpdateFileTabularTags' import { UpdateFileCategories } from './domain/useCases/UpdateFileCategories' +import { FileHasBeenDeleted } from './domain/useCases/FileHasBeenDeleted' const filesRepository = new FilesRepository() const directUploadClient = new DirectUploadClient(filesRepository) @@ -38,6 +39,7 @@ const restrictFile = new RestrictFile(filesRepository) const updateFileMetadata = new UpdateFileMetadata(filesRepository) const updateFileTabularTags = new UpdateFileTabularTags(filesRepository) const updateFileCategories = new UpdateFileCategories(filesRepository) +const fileHasBeenDeleted = new FileHasBeenDeleted(filesRepository) export { getDatasetFiles, @@ -56,7 +58,8 @@ export { updateFileMetadata, updateFileTabularTags, updateFileCategories, - replaceFile + replaceFile, + fileHasBeenDeleted } export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel' diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 41c49b38..86063cc9 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -415,4 +415,12 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { throw error }) } + + public async fileHasBeenDeleted(fileId: number | string): Promise { + return this.doGet(this.buildApiEndpoint(this.filesResourceName, 'hasBeenDeleted', fileId), true) + .then((response) => response.data.data) + .catch((error) => { + throw error + }) + } } diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 2b0b2655..f1efd3d4 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -840,6 +840,83 @@ describe('FilesRepository', () => { }) }) + describe('fileHasBeenDeleted', () => { + let deleFileTestDatasetIds: CreatedDatasetIdentifiers + const testTextFile1Name = 'test-file-1.txt' + let fileId: number + + beforeAll(async () => { + try { + deleFileTestDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + } catch (error) { + throw new Error('Tests beforeEach(): Error while creating test dataset') + } + await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { + throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`) + }) + + const datasetFiles = await sut.getDatasetFiles( + deleFileTestDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + + fileId = datasetFiles.files[0].id + }) + + afterAll(async () => { + await deletePublishedDatasetViaApi(deleFileTestDatasetIds.persistentId) + }) + + test('should return False if a file has not been deleted', async () => { + const hasBeenDeleted = await sut.fileHasBeenDeleted(fileId) + expect(hasBeenDeleted).toBe(false) + }) + + test('should return error if the dataset is unpublished and the file has been deleted', async () => { + await sut.deleteFile(fileId) + + const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + + await expect(sut.fileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + }) + + test('should return True when the dataset is published and the file has not been deleted', async () => { + await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { + throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`) + }) + + await publishDatasetViaApi(deleFileTestDatasetIds.numericId).catch(() => { + throw new Error('Error while publishing test Dataset') + }) + await waitForNoLocks(deleFileTestDatasetIds.numericId, 10) + + const datasetFiles = await sut.getDatasetFiles( + deleFileTestDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + + fileId = datasetFiles.files[0].id + + const hasBeenDeleted = await sut.fileHasBeenDeleted(fileId) + expect(hasBeenDeleted).toBe(false) + + await sut.deleteFile(fileId) + + const actual = await sut.fileHasBeenDeleted(fileId) + expect(actual).toBe(true) + }) + + test('should return error when file does not exist', async () => { + const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + + await expect(sut.fileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + }) + }) + describe('restrictFile', () => { let restrictFileDatasetIds: CreatedDatasetIdentifiers const testTextFile1Name = 'test-file-1.txt' diff --git a/test/unit/files/FileHasBeenDeleted.test.ts b/test/unit/files/FileHasBeenDeleted.test.ts new file mode 100644 index 00000000..822d6c7c --- /dev/null +++ b/test/unit/files/FileHasBeenDeleted.test.ts @@ -0,0 +1,33 @@ +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository' +import { FileHasBeenDeleted } from '../../../src/files/domain/useCases/FileHasBeenDeleted' +import { ReadError } from '../../../src' + +describe('execute', () => { + test('should return true when file has been deleted', async () => { + const filesRepositoryStub: IFilesRepository = {} as IFilesRepository + filesRepositoryStub.fileHasBeenDeleted = jest.fn().mockResolvedValue(true) + const sut = new FileHasBeenDeleted(filesRepositoryStub) + + const result = await sut.execute(1) + + expect(result).toBe(true) + }) + + test('should return false when file has not been deleted', async () => { + const filesRepositoryStub: IFilesRepository = {} as IFilesRepository + filesRepositoryStub.fileHasBeenDeleted = jest.fn().mockResolvedValue(false) + const sut = new FileHasBeenDeleted(filesRepositoryStub) + + const result = await sut.execute(1) + + expect(result).toBe(false) + }) + + test('should return error result on repository error', async () => { + const filesRepositoryStub: IFilesRepository = {} as IFilesRepository + filesRepositoryStub.fileHasBeenDeleted = jest.fn().mockRejectedValue(new ReadError()) + const sut = new FileHasBeenDeleted(filesRepositoryStub) + + await expect(sut.execute(1)).rejects.toThrow(ReadError) + }) +}) From e74537bd4069b5a8a2009c57d768b5b155ca5ff9 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 9 May 2025 13:09:30 -0400 Subject: [PATCH 2/6] chore: correct some words --- docs/useCases.md | 2 +- test/integration/files/FilesRepository.test.ts | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index bd889e5b..5441ecfe 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1584,7 +1584,7 @@ import { fileHasBeenDeleted } from '@iqss/dataverse-client-javascript' const fileId = 12345 -await fileHasBeenDeleted.execute(fileId).then((deleted: boolean) => { +await fileHasBeenDeleted.execute(fileId).then((hasBeenDeleted: boolean) => { /* ... */ }) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index f1efd3d4..27705fb1 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -849,10 +849,11 @@ describe('FilesRepository', () => { try { deleFileTestDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) } catch (error) { - throw new Error('Tests beforeEach(): Error while creating test dataset') + throw new Error('Tests beforeAll(): Error while creating test dataset') } + await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { - throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`) + throw new Error(`Tests beforeAll(): Error while uploading file ${testTextFile1Name}`) }) const datasetFiles = await sut.getDatasetFiles( @@ -861,7 +862,6 @@ describe('FilesRepository', () => { false, FileOrderCriteria.NAME_AZ ) - fileId = datasetFiles.files[0].id }) @@ -878,13 +878,12 @@ describe('FilesRepository', () => { await sut.deleteFile(fileId) const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) - await expect(sut.fileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) }) test('should return True when the dataset is published and the file has not been deleted', async () => { await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { - throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`) + throw new Error(`Error while uploading file ${testTextFile1Name}`) }) await publishDatasetViaApi(deleFileTestDatasetIds.numericId).catch(() => { @@ -898,16 +897,15 @@ describe('FilesRepository', () => { false, FileOrderCriteria.NAME_AZ ) - fileId = datasetFiles.files[0].id - const hasBeenDeleted = await sut.fileHasBeenDeleted(fileId) - expect(hasBeenDeleted).toBe(false) + const notDeleted = await sut.fileHasBeenDeleted(fileId) + expect(notDeleted).toBe(false) await sut.deleteFile(fileId) - const actual = await sut.fileHasBeenDeleted(fileId) - expect(actual).toBe(true) + const hasBeenDeleted = await sut.fileHasBeenDeleted(fileId) + expect(hasBeenDeleted).toBe(true) }) test('should return error when file does not exist', async () => { From 6fe22d3fa88a7ab70a0914ddec681e91c7d3d008 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 13 May 2025 10:53:00 -0400 Subject: [PATCH 3/6] add: a test case for replacing file & fix name convention --- docs/useCases.md | 6 +- .../domain/repositories/IFilesRepository.ts | 2 +- ...eenDeleted.ts => GetFileHasBeenDeleted.ts} | 4 +- src/files/index.ts | 6 +- .../infra/repositories/FilesRepository.ts | 2 +- .../integration/files/FilesRepository.test.ts | 90 +++++++++++++++++-- ....test.ts => GetFileHasBeenDeleted.test.ts} | 14 +-- 7 files changed, 99 insertions(+), 25 deletions(-) rename src/files/domain/useCases/{FileHasBeenDeleted.ts => GetFileHasBeenDeleted.ts} (82%) rename test/unit/files/{FileHasBeenDeleted.test.ts => GetFileHasBeenDeleted.test.ts} (60%) diff --git a/docs/useCases.md b/docs/useCases.md index 5441ecfe..9c2069ef 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1578,20 +1578,20 @@ Check if the file has been deleted, return a boolean. ##### Example call: ```typescript -import { fileHasBeenDeleted } from '@iqss/dataverse-client-javascript' +import { getFileHasBeenDeleted } from '@iqss/dataverse-client-javascript' /* ... */ const fileId = 12345 -await fileHasBeenDeleted.execute(fileId).then((hasBeenDeleted: boolean) => { +await getFileHasBeenDeleted.execute(fileId).then((hasBeenDeleted: boolean) => { /* ... */ }) /* ... */ ``` -_See [use case](../src/files/domain/useCases/FileHasBeenDeleted.ts) implementation_. +_See [use case](../src/files/domain/useCases/GetFileHasBeenDeleted.ts) implementation_. The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers. diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index 89bfd222..fc132f1b 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -85,5 +85,5 @@ export interface IFilesRepository { replace?: boolean ): Promise - fileHasBeenDeleted(fileId: number | string): Promise + getFileHasBeenDeleted(fileId: number | string): Promise } diff --git a/src/files/domain/useCases/FileHasBeenDeleted.ts b/src/files/domain/useCases/GetFileHasBeenDeleted.ts similarity index 82% rename from src/files/domain/useCases/FileHasBeenDeleted.ts rename to src/files/domain/useCases/GetFileHasBeenDeleted.ts index 953acaa6..e00c9eef 100644 --- a/src/files/domain/useCases/FileHasBeenDeleted.ts +++ b/src/files/domain/useCases/GetFileHasBeenDeleted.ts @@ -1,7 +1,7 @@ import { IFilesRepository } from '../repositories/IFilesRepository' import { UseCase } from '../../../core/domain/useCases/UseCase' -export class FileHasBeenDeleted implements UseCase { +export class GetFileHasBeenDeleted implements UseCase { constructor(private readonly filesRepository: IFilesRepository) {} /** @@ -11,6 +11,6 @@ export class FileHasBeenDeleted implements UseCase { * @returns {Promise} - A boolean indicating whether the file has been deleted or not. */ async execute(fileId: number | string): Promise { - return await this.filesRepository.fileHasBeenDeleted(fileId) + return await this.filesRepository.getFileHasBeenDeleted(fileId) } } diff --git a/src/files/index.ts b/src/files/index.ts index aa787638..4ab13af2 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -17,7 +17,7 @@ import { RestrictFile } from './domain/useCases/RestrictFile' import { UpdateFileMetadata } from './domain/useCases/UpdateFileMetadata' import { UpdateFileTabularTags } from './domain/useCases/UpdateFileTabularTags' import { UpdateFileCategories } from './domain/useCases/UpdateFileCategories' -import { FileHasBeenDeleted } from './domain/useCases/FileHasBeenDeleted' +import { GetFileHasBeenDeleted } from './domain/useCases/GetFileHasBeenDeleted' const filesRepository = new FilesRepository() const directUploadClient = new DirectUploadClient(filesRepository) @@ -39,7 +39,7 @@ const restrictFile = new RestrictFile(filesRepository) const updateFileMetadata = new UpdateFileMetadata(filesRepository) const updateFileTabularTags = new UpdateFileTabularTags(filesRepository) const updateFileCategories = new UpdateFileCategories(filesRepository) -const fileHasBeenDeleted = new FileHasBeenDeleted(filesRepository) +const getFileHasBeenDeleted = new GetFileHasBeenDeleted(filesRepository) export { getDatasetFiles, @@ -59,7 +59,7 @@ export { updateFileTabularTags, updateFileCategories, replaceFile, - fileHasBeenDeleted + getFileHasBeenDeleted } export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel' diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 86063cc9..c2fd88a1 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -416,7 +416,7 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { }) } - public async fileHasBeenDeleted(fileId: number | string): Promise { + public async getFileHasBeenDeleted(fileId: number | string): Promise { return this.doGet(this.buildApiEndpoint(this.filesResourceName, 'hasBeenDeleted', fileId), true) .then((response) => response.data.data) .catch((error) => { diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 27705fb1..7de191b5 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -1,3 +1,4 @@ +import * as crypto from 'crypto' import { FilesRepository } from '../../../src/files/infra/repositories/FilesRepository' import { ApiConfig, @@ -43,6 +44,7 @@ import { } from '../../testHelpers/collections/collectionHelper' import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO' import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository' +import { DirectUploadClient } from '../../../src/files/infra/clients/DirectUploadClient' describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository() @@ -840,9 +842,10 @@ describe('FilesRepository', () => { }) }) - describe('fileHasBeenDeleted', () => { + describe('getFileHasBeenDeleted', () => { let deleFileTestDatasetIds: CreatedDatasetIdentifiers const testTextFile1Name = 'test-file-1.txt' + const testTextFile2Name = 'test-file-2.txt' let fileId: number beforeAll(async () => { @@ -870,7 +873,7 @@ describe('FilesRepository', () => { }) test('should return False if a file has not been deleted', async () => { - const hasBeenDeleted = await sut.fileHasBeenDeleted(fileId) + const hasBeenDeleted = await sut.getFileHasBeenDeleted(fileId) expect(hasBeenDeleted).toBe(false) }) @@ -878,7 +881,7 @@ describe('FilesRepository', () => { await sut.deleteFile(fileId) const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) - await expect(sut.fileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + await expect(sut.getFileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) }) test('should return True when the dataset is published and the file has not been deleted', async () => { @@ -899,22 +902,93 @@ describe('FilesRepository', () => { ) fileId = datasetFiles.files[0].id - const notDeleted = await sut.fileHasBeenDeleted(fileId) + const notDeleted = await sut.getFileHasBeenDeleted(fileId) expect(notDeleted).toBe(false) await sut.deleteFile(fileId) - const hasBeenDeleted = await sut.fileHasBeenDeleted(fileId) + const hasBeenDeleted = await sut.getFileHasBeenDeleted(fileId) expect(hasBeenDeleted).toBe(true) }) - test('should return error when file does not exist', async () => { - const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + test('should return True when file has been replaced', async () => { + const directUploadSut: DirectUploadClient = new DirectUploadClient(sut) + + // Upload original file + await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { + throw new Error(`Error while uploading file ${testTextFile1Name}`) + }) + + const datasetFiles = await sut.getDatasetFiles( + deleFileTestDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + const originalFileId = datasetFiles.files[0].id + + const createTestFileUploadDestination = async (file: File, testDatasetId: number) => { + const filesRepository = new FilesRepository() + const destination = await filesRepository.getFileUploadDestination(testDatasetId, file) + destination.urls.forEach((destinationUrl, index) => { + destination.urls[index] = destinationUrl.replace('localstack', 'localhost') + }) + return destination + } + + // Create a new file and replace the original file + const newFileBlob = await createSinglepartFileBlob(testTextFile2Name, 2000) + const newDestination = await createTestFileUploadDestination( + newFileBlob, + deleFileTestDatasetIds.numericId + ) + + const progressMock = jest.fn() + const abortController = new AbortController() + + const newStorageId = await directUploadSut.uploadFile( + deleFileTestDatasetIds.numericId, + newFileBlob, + progressMock, + abortController, + newDestination + ) - await expect(sut.fileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + const fileArrayBuffer = await newFileBlob.arrayBuffer() + const fileBuffer = Buffer.from(fileArrayBuffer) + + const calculateBlobChecksum = (blob: Buffer): string => { + const hash = crypto.createHash('md5') + hash.update(blob) + return hash.digest('hex') + } + + const newUploadedFileDTO = { + fileName: newFileBlob.name, + storageId: newStorageId, + checksumType: 'md5', + checksumValue: calculateBlobChecksum(fileBuffer), + mimeType: newFileBlob.type + } + + //Publish the dataset and check if the original file has been deleted + await publishDatasetViaApi(deleFileTestDatasetIds.numericId) + + await waitForNoLocks(deleFileTestDatasetIds.numericId, 10) + + await sut.replaceFile(originalFileId, newUploadedFileDTO) + + const isDeleted = await sut.getFileHasBeenDeleted(originalFileId) + expect(isDeleted).toBe(true) }) }) + test('should return error when file does not exist', async () => { + const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + + await expect(sut.getFileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + }) + describe('restrictFile', () => { let restrictFileDatasetIds: CreatedDatasetIdentifiers const testTextFile1Name = 'test-file-1.txt' diff --git a/test/unit/files/FileHasBeenDeleted.test.ts b/test/unit/files/GetFileHasBeenDeleted.test.ts similarity index 60% rename from test/unit/files/FileHasBeenDeleted.test.ts rename to test/unit/files/GetFileHasBeenDeleted.test.ts index 822d6c7c..394e4cb7 100644 --- a/test/unit/files/FileHasBeenDeleted.test.ts +++ b/test/unit/files/GetFileHasBeenDeleted.test.ts @@ -1,12 +1,12 @@ import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository' -import { FileHasBeenDeleted } from '../../../src/files/domain/useCases/FileHasBeenDeleted' +import { GetFileHasBeenDeleted } from '../../../src/files/domain/useCases/GetFileHasBeenDeleted' import { ReadError } from '../../../src' describe('execute', () => { test('should return true when file has been deleted', async () => { const filesRepositoryStub: IFilesRepository = {} as IFilesRepository - filesRepositoryStub.fileHasBeenDeleted = jest.fn().mockResolvedValue(true) - const sut = new FileHasBeenDeleted(filesRepositoryStub) + filesRepositoryStub.getFileHasBeenDeleted = jest.fn().mockResolvedValue(true) + const sut = new GetFileHasBeenDeleted(filesRepositoryStub) const result = await sut.execute(1) @@ -15,8 +15,8 @@ describe('execute', () => { test('should return false when file has not been deleted', async () => { const filesRepositoryStub: IFilesRepository = {} as IFilesRepository - filesRepositoryStub.fileHasBeenDeleted = jest.fn().mockResolvedValue(false) - const sut = new FileHasBeenDeleted(filesRepositoryStub) + filesRepositoryStub.getFileHasBeenDeleted = jest.fn().mockResolvedValue(false) + const sut = new GetFileHasBeenDeleted(filesRepositoryStub) const result = await sut.execute(1) @@ -25,8 +25,8 @@ describe('execute', () => { test('should return error result on repository error', async () => { const filesRepositoryStub: IFilesRepository = {} as IFilesRepository - filesRepositoryStub.fileHasBeenDeleted = jest.fn().mockRejectedValue(new ReadError()) - const sut = new FileHasBeenDeleted(filesRepositoryStub) + filesRepositoryStub.getFileHasBeenDeleted = jest.fn().mockRejectedValue(new ReadError()) + const sut = new GetFileHasBeenDeleted(filesRepositoryStub) await expect(sut.execute(1)).rejects.toThrow(ReadError) }) From 7eefc1f54aed66f7c531e1656eea741f08f9c692 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 13 May 2025 11:25:18 -0400 Subject: [PATCH 4/6] fix: naming in a test --- test/integration/files/FilesRepository.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 7de191b5..aff10183 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -884,7 +884,7 @@ describe('FilesRepository', () => { await expect(sut.getFileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) }) - test('should return True when the dataset is published and the file has not been deleted', async () => { + test('should return correctly when the file has or has not been deleted, in a published dataset', async () => { await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { throw new Error(`Error while uploading file ${testTextFile1Name}`) }) @@ -902,13 +902,13 @@ describe('FilesRepository', () => { ) fileId = datasetFiles.files[0].id - const notDeleted = await sut.getFileHasBeenDeleted(fileId) - expect(notDeleted).toBe(false) + const fileHasNotBeenDeleted = await sut.getFileHasBeenDeleted(fileId) + expect(fileHasNotBeenDeleted).toBe(false) await sut.deleteFile(fileId) - const hasBeenDeleted = await sut.getFileHasBeenDeleted(fileId) - expect(hasBeenDeleted).toBe(true) + const fileHasBeenDeleted = await sut.getFileHasBeenDeleted(fileId) + expect(fileHasBeenDeleted).toBe(true) }) test('should return True when file has been replaced', async () => { @@ -928,8 +928,7 @@ describe('FilesRepository', () => { const originalFileId = datasetFiles.files[0].id const createTestFileUploadDestination = async (file: File, testDatasetId: number) => { - const filesRepository = new FilesRepository() - const destination = await filesRepository.getFileUploadDestination(testDatasetId, file) + const destination = await sut.getFileUploadDestination(testDatasetId, file) destination.urls.forEach((destinationUrl, index) => { destination.urls[index] = destinationUrl.replace('localstack', 'localhost') }) From 8846d27924307b24eb907697d5a21c122a2c37fe Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 13 May 2025 12:54:40 -0400 Subject: [PATCH 5/6] fix: the replace test --- .../integration/files/FilesRepository.test.ts | 90 ++++++++++++------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index aff10183..6f37458b 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -847,10 +847,19 @@ describe('FilesRepository', () => { const testTextFile1Name = 'test-file-1.txt' const testTextFile2Name = 'test-file-2.txt' let fileId: number + const testCollectionAlias = 'getFileHasBeenDeletedTestCollection' + let singlepartFile: File beforeAll(async () => { try { - deleFileTestDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + await createCollectionViaApi(testCollectionAlias) + await setStorageDriverViaApi(testCollectionAlias, 'LocalStack') + deleFileTestDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testCollectionAlias + ) + + singlepartFile = await createSinglepartFileBlob() } catch (error) { throw new Error('Tests beforeAll(): Error while creating test dataset') } @@ -909,23 +918,13 @@ describe('FilesRepository', () => { const fileHasBeenDeleted = await sut.getFileHasBeenDeleted(fileId) expect(fileHasBeenDeleted).toBe(true) + deletePublishedDatasetViaApi(deleFileTestDatasetIds.persistentId) }) test('should return True when file has been replaced', async () => { const directUploadSut: DirectUploadClient = new DirectUploadClient(sut) - - // Upload original file - await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { - throw new Error(`Error while uploading file ${testTextFile1Name}`) - }) - - const datasetFiles = await sut.getDatasetFiles( - deleFileTestDatasetIds.numericId, - latestDatasetVersionId, - false, - FileOrderCriteria.NAME_AZ - ) - const originalFileId = datasetFiles.files[0].id + const progressMock = jest.fn() + const abortController = new AbortController() const createTestFileUploadDestination = async (file: File, testDatasetId: number) => { const destination = await sut.getFileUploadDestination(testDatasetId, file) @@ -935,6 +934,46 @@ describe('FilesRepository', () => { return destination } + const calculateBlobChecksum = (blob: Buffer): string => { + const hash = crypto.createHash('md5') + hash.update(blob) + return hash.digest('hex') + } + + const fileArrayBuffer = await singlepartFile.arrayBuffer() + const fileBuffer = Buffer.from(fileArrayBuffer) + + const destination = await createTestFileUploadDestination( + singlepartFile, + deleFileTestDatasetIds.numericId + ) + + const actualStorageId = await directUploadSut.uploadFile( + deleFileTestDatasetIds.numericId, + singlepartFile, + progressMock, + abortController, + destination + ) + + const uploadedFileDTO = { + fileName: singlepartFile.name, + storageId: actualStorageId, + checksumType: 'md5', + checksumValue: calculateBlobChecksum(fileBuffer), + mimeType: singlepartFile.type + } + // Upload original file + await sut.addUploadedFilesToDataset(deleFileTestDatasetIds.numericId, [uploadedFileDTO]) + + const originalDatasetFiles = await sut.getDatasetFiles( + deleFileTestDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + true, + FileOrderCriteria.NAME_AZ + ) + const originalFileId = originalDatasetFiles.files[0].id + // Create a new file and replace the original file const newFileBlob = await createSinglepartFileBlob(testTextFile2Name, 2000) const newDestination = await createTestFileUploadDestination( @@ -942,9 +981,6 @@ describe('FilesRepository', () => { deleFileTestDatasetIds.numericId ) - const progressMock = jest.fn() - const abortController = new AbortController() - const newStorageId = await directUploadSut.uploadFile( deleFileTestDatasetIds.numericId, newFileBlob, @@ -953,20 +989,14 @@ describe('FilesRepository', () => { newDestination ) - const fileArrayBuffer = await newFileBlob.arrayBuffer() - const fileBuffer = Buffer.from(fileArrayBuffer) - - const calculateBlobChecksum = (blob: Buffer): string => { - const hash = crypto.createHash('md5') - hash.update(blob) - return hash.digest('hex') - } + const fileArrayBuffer2 = await newFileBlob.arrayBuffer() + const fileBuffer2 = Buffer.from(fileArrayBuffer2) const newUploadedFileDTO = { fileName: newFileBlob.name, storageId: newStorageId, checksumType: 'md5', - checksumValue: calculateBlobChecksum(fileBuffer), + checksumValue: calculateBlobChecksum(fileBuffer2), mimeType: newFileBlob.type } @@ -980,12 +1010,12 @@ describe('FilesRepository', () => { const isDeleted = await sut.getFileHasBeenDeleted(originalFileId) expect(isDeleted).toBe(true) }) - }) - test('should return error when file does not exist', async () => { - const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + test('should return error when file does not exist', async () => { + const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) - await expect(sut.getFileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + await expect(sut.getFileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + }) }) describe('restrictFile', () => { From fbd9e611480b2cdb0923159a8d7631ce292076b8 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 14 May 2025 13:20:52 -0400 Subject: [PATCH 6/6] fix: naming and refactor the replace test --- docs/useCases.md | 9 +- .../domain/repositories/IFilesRepository.ts | 2 +- ...FileHasBeenDeleted.ts => IsFileDeleted.ts} | 4 +- src/files/index.ts | 6 +- .../infra/repositories/FilesRepository.ts | 2 +- .../integration/files/FilesRepository.test.ts | 256 +++++++++--------- ...nDeleted.test.ts => IsFileDeleted.test.ts} | 14 +- 7 files changed, 141 insertions(+), 152 deletions(-) rename src/files/domain/useCases/{GetFileHasBeenDeleted.ts => IsFileDeleted.ts} (82%) rename test/unit/files/{GetFileHasBeenDeleted.test.ts => IsFileDeleted.test.ts} (60%) diff --git a/docs/useCases.md b/docs/useCases.md index 9c2069ef..e615a266 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -52,6 +52,7 @@ The different use cases currently available in the package are classified below, - [Get the size of Downloading all the files of a Dataset Version](#get-the-size-of-downloading-all-the-files-of-a-dataset-version) - [Get User Permissions on a File](#get-user-permissions-on-a-file) - [List Files in a Dataset](#list-files-in-a-dataset) + - [Is File Deleted](#is-file-deleted) - [Files write use cases](#files-write-use-cases) - [File Uploading Use Cases](#file-uploading-use-cases) - [Delete a File](#delete-a-file) @@ -1571,27 +1572,27 @@ If restrict is false then enableAccessRequest and termsOfAccess are ignored If restrict is true and enableAccessRequest is false then termsOfAccess is required. The enableAccessRequest and termsOfAccess are applied to the Draft version of the Dataset and affect all of the restricted files in said Draft version. -#### File Has Been Deleted +#### Is File Deleted Check if the file has been deleted, return a boolean. ##### Example call: ```typescript -import { getFileHasBeenDeleted } from '@iqss/dataverse-client-javascript' +import { isFileDeleted } from '@iqss/dataverse-client-javascript' /* ... */ const fileId = 12345 -await getFileHasBeenDeleted.execute(fileId).then((hasBeenDeleted: boolean) => { +await isFileDeleted.execute(fileId).then((isDeleted: boolean) => { /* ... */ }) /* ... */ ``` -_See [use case](../src/files/domain/useCases/GetFileHasBeenDeleted.ts) implementation_. +_See [use case](../src/files/domain/useCases/isFileDeleted.ts) implementation_. The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers. diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index fc132f1b..83596db3 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -85,5 +85,5 @@ export interface IFilesRepository { replace?: boolean ): Promise - getFileHasBeenDeleted(fileId: number | string): Promise + isFileDeleted(fileId: number | string): Promise } diff --git a/src/files/domain/useCases/GetFileHasBeenDeleted.ts b/src/files/domain/useCases/IsFileDeleted.ts similarity index 82% rename from src/files/domain/useCases/GetFileHasBeenDeleted.ts rename to src/files/domain/useCases/IsFileDeleted.ts index e00c9eef..58bbc45f 100644 --- a/src/files/domain/useCases/GetFileHasBeenDeleted.ts +++ b/src/files/domain/useCases/IsFileDeleted.ts @@ -1,7 +1,7 @@ import { IFilesRepository } from '../repositories/IFilesRepository' import { UseCase } from '../../../core/domain/useCases/UseCase' -export class GetFileHasBeenDeleted implements UseCase { +export class IsFileDeleted implements UseCase { constructor(private readonly filesRepository: IFilesRepository) {} /** @@ -11,6 +11,6 @@ export class GetFileHasBeenDeleted implements UseCase { * @returns {Promise} - A boolean indicating whether the file has been deleted or not. */ async execute(fileId: number | string): Promise { - return await this.filesRepository.getFileHasBeenDeleted(fileId) + return await this.filesRepository.isFileDeleted(fileId) } } diff --git a/src/files/index.ts b/src/files/index.ts index 4ab13af2..47b86cd6 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -17,7 +17,7 @@ import { RestrictFile } from './domain/useCases/RestrictFile' import { UpdateFileMetadata } from './domain/useCases/UpdateFileMetadata' import { UpdateFileTabularTags } from './domain/useCases/UpdateFileTabularTags' import { UpdateFileCategories } from './domain/useCases/UpdateFileCategories' -import { GetFileHasBeenDeleted } from './domain/useCases/GetFileHasBeenDeleted' +import { IsFileDeleted } from './domain/useCases/IsFileDeleted' const filesRepository = new FilesRepository() const directUploadClient = new DirectUploadClient(filesRepository) @@ -39,7 +39,7 @@ const restrictFile = new RestrictFile(filesRepository) const updateFileMetadata = new UpdateFileMetadata(filesRepository) const updateFileTabularTags = new UpdateFileTabularTags(filesRepository) const updateFileCategories = new UpdateFileCategories(filesRepository) -const getFileHasBeenDeleted = new GetFileHasBeenDeleted(filesRepository) +const isFileDeleted = new IsFileDeleted(filesRepository) export { getDatasetFiles, @@ -59,7 +59,7 @@ export { updateFileTabularTags, updateFileCategories, replaceFile, - getFileHasBeenDeleted + isFileDeleted } export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel' diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index c2fd88a1..2fcf308e 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -416,7 +416,7 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { }) } - public async getFileHasBeenDeleted(fileId: number | string): Promise { + public async isFileDeleted(fileId: number | string): Promise { return this.doGet(this.buildApiEndpoint(this.filesResourceName, 'hasBeenDeleted', fileId), true) .then((response) => response.data.data) .catch((error) => { diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 6f37458b..85187168 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -40,6 +40,7 @@ import { import { createCollectionViaApi, deleteCollectionViaApi, + publishCollectionViaApi, setStorageDriverViaApi } from '../../testHelpers/collections/collectionHelper' import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO' @@ -842,66 +843,38 @@ describe('FilesRepository', () => { }) }) - describe('getFileHasBeenDeleted', () => { - let deleFileTestDatasetIds: CreatedDatasetIdentifiers + describe('isFileDeleted', () => { const testTextFile1Name = 'test-file-1.txt' const testTextFile2Name = 'test-file-2.txt' + const testCollectionAlias = 'isFileDeletedTestCollection' + + let deleFileTestDatasetIds: CreatedDatasetIdentifiers let fileId: number - const testCollectionAlias = 'getFileHasBeenDeletedTestCollection' let singlepartFile: File - beforeAll(async () => { - try { - await createCollectionViaApi(testCollectionAlias) - await setStorageDriverViaApi(testCollectionAlias, 'LocalStack') - deleFileTestDatasetIds = await createDataset.execute( - TestConstants.TEST_NEW_DATASET_DTO, - testCollectionAlias - ) + const createTestFileUploadDestination = async (file: File, datasetId: number) => { + const destination = await sut.getFileUploadDestination(datasetId, file) + destination.urls = destination.urls.map((url) => url.replace('localstack', 'localhost')) + return destination + } - singlepartFile = await createSinglepartFileBlob() - } catch (error) { - throw new Error('Tests beforeAll(): Error while creating test dataset') - } + const calculateBlobChecksum = (blob: Buffer): string => { + return crypto.createHash('md5').update(blob).digest('hex') + } - await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { - throw new Error(`Tests beforeAll(): Error while uploading file ${testTextFile1Name}`) - }) + beforeAll(async () => { + await createCollectionViaApi(testCollectionAlias) + await setStorageDriverViaApi(testCollectionAlias, 'LocalStack') + await publishCollectionViaApi(testCollectionAlias) - const datasetFiles = await sut.getDatasetFiles( - deleFileTestDatasetIds.numericId, - latestDatasetVersionId, - false, - FileOrderCriteria.NAME_AZ + deleFileTestDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testCollectionAlias ) - fileId = datasetFiles.files[0].id - }) - - afterAll(async () => { - await deletePublishedDatasetViaApi(deleFileTestDatasetIds.persistentId) - }) - - test('should return False if a file has not been deleted', async () => { - const hasBeenDeleted = await sut.getFileHasBeenDeleted(fileId) - expect(hasBeenDeleted).toBe(false) - }) - - test('should return error if the dataset is unpublished and the file has been deleted', async () => { - await sut.deleteFile(fileId) - - const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) - await expect(sut.getFileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) - }) - test('should return correctly when the file has or has not been deleted, in a published dataset', async () => { - await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name).catch(() => { - throw new Error(`Error while uploading file ${testTextFile1Name}`) - }) + singlepartFile = await createSinglepartFileBlob() - await publishDatasetViaApi(deleFileTestDatasetIds.numericId).catch(() => { - throw new Error('Error while publishing test Dataset') - }) - await waitForNoLocks(deleFileTestDatasetIds.numericId, 10) + await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name) const datasetFiles = await sut.getDatasetFiles( deleFileTestDatasetIds.numericId, @@ -910,111 +883,126 @@ describe('FilesRepository', () => { FileOrderCriteria.NAME_AZ ) fileId = datasetFiles.files[0].id + }) - const fileHasNotBeenDeleted = await sut.getFileHasBeenDeleted(fileId) - expect(fileHasNotBeenDeleted).toBe(false) - - await sut.deleteFile(fileId) + describe('Basic deletion scenarios', () => { + test('should return False if a file has not been deleted', async () => { + const hasBeenDeleted = await sut.isFileDeleted(fileId) + expect(hasBeenDeleted).toBe(false) + }) - const fileHasBeenDeleted = await sut.getFileHasBeenDeleted(fileId) - expect(fileHasBeenDeleted).toBe(true) - deletePublishedDatasetViaApi(deleFileTestDatasetIds.persistentId) - }) + test('should return error if the dataset is unpublished and the file has been deleted', async () => { + await sut.deleteFile(fileId) - test('should return True when file has been replaced', async () => { - const directUploadSut: DirectUploadClient = new DirectUploadClient(sut) - const progressMock = jest.fn() - const abortController = new AbortController() + const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + await expect(sut.isFileDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + }) - const createTestFileUploadDestination = async (file: File, testDatasetId: number) => { - const destination = await sut.getFileUploadDestination(testDatasetId, file) - destination.urls.forEach((destinationUrl, index) => { - destination.urls[index] = destinationUrl.replace('localstack', 'localhost') - }) - return destination - } + test('should return correctly when the file has or has not been deleted, in a published dataset', async () => { + await uploadFileViaApi(deleFileTestDatasetIds.numericId, testTextFile1Name) + await publishDatasetViaApi(deleFileTestDatasetIds.numericId) + await waitForNoLocks(deleFileTestDatasetIds.numericId, 10) - const calculateBlobChecksum = (blob: Buffer): string => { - const hash = crypto.createHash('md5') - hash.update(blob) - return hash.digest('hex') - } + const datasetFiles = await sut.getDatasetFiles( + deleFileTestDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + fileId = datasetFiles.files[0].id - const fileArrayBuffer = await singlepartFile.arrayBuffer() - const fileBuffer = Buffer.from(fileArrayBuffer) + const fileHasNotBeenDeleted = await sut.isFileDeleted(fileId) + expect(fileHasNotBeenDeleted).toBe(false) - const destination = await createTestFileUploadDestination( - singlepartFile, - deleFileTestDatasetIds.numericId - ) + await sut.deleteFile(fileId) - const actualStorageId = await directUploadSut.uploadFile( - deleFileTestDatasetIds.numericId, - singlepartFile, - progressMock, - abortController, - destination - ) + const fileHasBeenDeleted = await sut.isFileDeleted(fileId) + expect(fileHasBeenDeleted).toBe(true) + }) - const uploadedFileDTO = { - fileName: singlepartFile.name, - storageId: actualStorageId, - checksumType: 'md5', - checksumValue: calculateBlobChecksum(fileBuffer), - mimeType: singlepartFile.type - } - // Upload original file - await sut.addUploadedFilesToDataset(deleFileTestDatasetIds.numericId, [uploadedFileDTO]) + test('should return error when file does not exist', async () => { + const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + await expect(sut.isFileDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + }) + }) - const originalDatasetFiles = await sut.getDatasetFiles( - deleFileTestDatasetIds.numericId, - DatasetNotNumberedVersion.LATEST, - true, - FileOrderCriteria.NAME_AZ - ) - const originalFileId = originalDatasetFiles.files[0].id + describe('File replacement scenario', () => { + test('should return True when file has been replaced', async () => { + const directUploadSut = new DirectUploadClient(sut) + const progressMock = jest.fn() + const abortController = new AbortController() + + // Upload original file + const originalBuffer = Buffer.from(await singlepartFile.arrayBuffer()) + const originalDestination = await createTestFileUploadDestination( + singlepartFile, + deleFileTestDatasetIds.numericId + ) - // Create a new file and replace the original file - const newFileBlob = await createSinglepartFileBlob(testTextFile2Name, 2000) - const newDestination = await createTestFileUploadDestination( - newFileBlob, - deleFileTestDatasetIds.numericId - ) + const originalStorageId = await directUploadSut.uploadFile( + deleFileTestDatasetIds.numericId, + singlepartFile, + progressMock, + abortController, + originalDestination + ) - const newStorageId = await directUploadSut.uploadFile( - deleFileTestDatasetIds.numericId, - newFileBlob, - progressMock, - abortController, - newDestination - ) + const originalUploadedFileDTO = { + fileName: singlepartFile.name, + storageId: originalStorageId, + checksumType: 'md5', + checksumValue: calculateBlobChecksum(originalBuffer), + mimeType: singlepartFile.type + } - const fileArrayBuffer2 = await newFileBlob.arrayBuffer() - const fileBuffer2 = Buffer.from(fileArrayBuffer2) + await sut.addUploadedFilesToDataset(deleFileTestDatasetIds.numericId, [ + originalUploadedFileDTO + ]) - const newUploadedFileDTO = { - fileName: newFileBlob.name, - storageId: newStorageId, - checksumType: 'md5', - checksumValue: calculateBlobChecksum(fileBuffer2), - mimeType: newFileBlob.type - } + const originalFileId = ( + await sut.getDatasetFiles( + deleFileTestDatasetIds.numericId, + DatasetNotNumberedVersion.LATEST, + true, + FileOrderCriteria.NAME_AZ + ) + ).files[0].id + + // Create and upload replacement file + const newFileBlob = await createSinglepartFileBlob(testTextFile2Name, 2000) + const newBuffer = Buffer.from(await newFileBlob.arrayBuffer()) + const newDestination = await createTestFileUploadDestination( + newFileBlob, + deleFileTestDatasetIds.numericId + ) - //Publish the dataset and check if the original file has been deleted - await publishDatasetViaApi(deleFileTestDatasetIds.numericId) + const newStorageId = await directUploadSut.uploadFile( + deleFileTestDatasetIds.numericId, + newFileBlob, + progressMock, + abortController, + newDestination + ) - await waitForNoLocks(deleFileTestDatasetIds.numericId, 10) + const newUploadedFileDTO = { + fileName: newFileBlob.name, + storageId: newStorageId, + checksumType: 'md5', + checksumValue: calculateBlobChecksum(newBuffer), + mimeType: newFileBlob.type + } - await sut.replaceFile(originalFileId, newUploadedFileDTO) + await publishDatasetViaApi(deleFileTestDatasetIds.numericId) + await waitForNoLocks(deleFileTestDatasetIds.numericId, 10) - const isDeleted = await sut.getFileHasBeenDeleted(originalFileId) - expect(isDeleted).toBe(true) - }) + await sut.replaceFile(originalFileId, newUploadedFileDTO) - test('should return error when file does not exist', async () => { - const expectedError = new ReadError(`[404] File with ID ${nonExistentFiledId} not found.`) + const isDeleted = await sut.isFileDeleted(originalFileId) + expect(isDeleted).toBe(true) - await expect(sut.getFileHasBeenDeleted(nonExistentFiledId)).rejects.toThrow(expectedError) + await deletePublishedDatasetViaApi(deleFileTestDatasetIds.persistentId) + await deleteCollectionViaApi(testCollectionAlias) + }) }) }) diff --git a/test/unit/files/GetFileHasBeenDeleted.test.ts b/test/unit/files/IsFileDeleted.test.ts similarity index 60% rename from test/unit/files/GetFileHasBeenDeleted.test.ts rename to test/unit/files/IsFileDeleted.test.ts index 394e4cb7..d15ad51a 100644 --- a/test/unit/files/GetFileHasBeenDeleted.test.ts +++ b/test/unit/files/IsFileDeleted.test.ts @@ -1,12 +1,12 @@ import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository' -import { GetFileHasBeenDeleted } from '../../../src/files/domain/useCases/GetFileHasBeenDeleted' +import { IsFileDeleted } from '../../../src/files/domain/useCases/IsFileDeleted' import { ReadError } from '../../../src' describe('execute', () => { test('should return true when file has been deleted', async () => { const filesRepositoryStub: IFilesRepository = {} as IFilesRepository - filesRepositoryStub.getFileHasBeenDeleted = jest.fn().mockResolvedValue(true) - const sut = new GetFileHasBeenDeleted(filesRepositoryStub) + filesRepositoryStub.isFileDeleted = jest.fn().mockResolvedValue(true) + const sut = new IsFileDeleted(filesRepositoryStub) const result = await sut.execute(1) @@ -15,8 +15,8 @@ describe('execute', () => { test('should return false when file has not been deleted', async () => { const filesRepositoryStub: IFilesRepository = {} as IFilesRepository - filesRepositoryStub.getFileHasBeenDeleted = jest.fn().mockResolvedValue(false) - const sut = new GetFileHasBeenDeleted(filesRepositoryStub) + filesRepositoryStub.isFileDeleted = jest.fn().mockResolvedValue(false) + const sut = new IsFileDeleted(filesRepositoryStub) const result = await sut.execute(1) @@ -25,8 +25,8 @@ describe('execute', () => { test('should return error result on repository error', async () => { const filesRepositoryStub: IFilesRepository = {} as IFilesRepository - filesRepositoryStub.getFileHasBeenDeleted = jest.fn().mockRejectedValue(new ReadError()) - const sut = new GetFileHasBeenDeleted(filesRepositoryStub) + filesRepositoryStub.isFileDeleted = jest.fn().mockRejectedValue(new ReadError()) + const sut = new IsFileDeleted(filesRepositoryStub) await expect(sut.execute(1)).rejects.toThrow(ReadError) })