From cdc198cec4147bc8bb8030ac7b99fa601a7b12bc Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 31 Jan 2025 16:02:31 -0500 Subject: [PATCH 01/12] feat: updateFileMetadata use case --- .../domain/dtos/UpdateFileMetadataDTO.ts | 7 +++ .../domain/repositories/IFilesRepository.ts | 5 ++ .../domain/useCases/UpdateFileMetadata.ts | 25 ++++++++++ src/files/index.ts | 6 ++- .../infra/repositories/FilesRepository.ts | 20 ++++++++ .../users/DeleteCurrentApiToken.test.ts | 1 - .../integration/files/FilesRepository.test.ts | 44 ++++++++++++++++++ test/testHelpers/files/filesHelper.ts | 8 ++++ test/unit/files/UpdateFileMetadata.test.ts | 46 +++++++++++++++++++ 9 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/files/domain/dtos/UpdateFileMetadataDTO.ts create mode 100644 src/files/domain/useCases/UpdateFileMetadata.ts create mode 100644 test/unit/files/UpdateFileMetadata.test.ts diff --git a/src/files/domain/dtos/UpdateFileMetadataDTO.ts b/src/files/domain/dtos/UpdateFileMetadataDTO.ts new file mode 100644 index 00000000..f26798f4 --- /dev/null +++ b/src/files/domain/dtos/UpdateFileMetadataDTO.ts @@ -0,0 +1,7 @@ +export interface UpdateFileMetadataDTO { + description?: string + prevFreeform?: string + categories?: string[] + dataFileTags?: string[] + restrict?: boolean +} diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index 775cf18a..ea7f3a46 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -8,6 +8,7 @@ import { FileModel } from '../models/FileModel' import { Dataset } from '../../../datasets' import { FileUploadDestination } from '../models/FileUploadDestination' import { UploadedFileDTO } from '../dtos/UploadedFileDTO' +import { UpdateFileMetadataDTO } from '../dtos/UpdateFileMetadataDTO' export interface IFilesRepository { getDatasetFiles( @@ -63,4 +64,8 @@ export interface IFilesRepository { deleteFile(fileId: number | string): Promise restrictFile(fileId: number | string, restrict: boolean): Promise + updateFileMetadata( + fileId: number | string, + updateFileMetadataDTO: UpdateFileMetadataDTO + ): Promise } diff --git a/src/files/domain/useCases/UpdateFileMetadata.ts b/src/files/domain/useCases/UpdateFileMetadata.ts new file mode 100644 index 00000000..1e7eb2e5 --- /dev/null +++ b/src/files/domain/useCases/UpdateFileMetadata.ts @@ -0,0 +1,25 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IFilesRepository } from '../repositories/IFilesRepository' +import { UpdateFileMetadataDTO } from '../dtos/UpdateFileMetadataDTO' + +export class UpdateFileMetadata implements UseCase { + private filesRepository: IFilesRepository + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository + } + + /** + * Updates the metadata for a particular File. + * + * @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). + * @param {UpdateFileMetadataDTO} [updateFileMetadataDTO] - The DTO containing the metadata updates. + * @returns {Promise} + */ + async execute( + fileId: number | string, + updateFileMetadataDTO: UpdateFileMetadataDTO + ): Promise { + await this.filesRepository.updateFileMetadata(fileId, updateFileMetadataDTO) + } +} diff --git a/src/files/index.ts b/src/files/index.ts index 3809b4cf..6fde2f2a 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -13,6 +13,7 @@ import { DirectUploadClient } from './infra/clients/DirectUploadClient' import { AddUploadedFilesToDataset } from './domain/useCases/AddUploadedFilesToDataset' import { DeleteFile } from './domain/useCases/DeleteFile' import { RestrictFile } from './domain/useCases/RestrictFile' +import { UpdateFileMetadata } from './domain/useCases/UpdateFileMetadata' const filesRepository = new FilesRepository() const directUploadClient = new DirectUploadClient(filesRepository) @@ -30,6 +31,7 @@ const uploadFile = new UploadFile(directUploadClient) const addUploadedFilesToDataset = new AddUploadedFilesToDataset(filesRepository) const deleteFile = new DeleteFile(filesRepository) const restrictFile = new RestrictFile(filesRepository) +const updateFileMetadata = new UpdateFileMetadata(filesRepository) export { getDatasetFiles, @@ -44,7 +46,8 @@ export { uploadFile, addUploadedFilesToDataset, deleteFile, - restrictFile + restrictFile, + updateFileMetadata } export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel' @@ -74,3 +77,4 @@ export { FileDownloadSizeMode } from './domain/models/FileDownloadSizeMode' export { FilesSubset } from './domain/models/FilesSubset' export { FilePreview, FilePreviewChecksum } from './domain/models/FilePreview' export { UploadedFileDTO } from './domain/dtos/UploadedFileDTO' +export { UpdateFileMetadataDTO } from './domain/dtos/UpdateFileMetadataDTO' diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 76e4605b..c45c2880 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -18,6 +18,7 @@ import { Dataset } from '../../../datasets' import { FileUploadDestination } from '../../domain/models/FileUploadDestination' import { transformUploadDestinationsResponseToUploadDestination } from './transformers/fileUploadDestinationsTransformers' import { UploadedFileDTO } from '../../domain/dtos/UploadedFileDTO' +import { UpdateFileMetadataDTO } from '../../domain/dtos/UpdateFileMetadataDTO' import { ApiConstants } from '../../../core/infra/repositories/ApiConstants' export interface GetFilesQueryParams { @@ -309,4 +310,23 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { throw error }) } + + public async updateFileMetadata( + fileId: string | number, + updateFileMetadata: UpdateFileMetadataDTO + ): Promise { + const formData = new FormData() + formData.append('jsonData', JSON.stringify(updateFileMetadata)) + + return this.doPost( + this.buildApiEndpoint(this.filesResourceName, `${fileId}/metadata`), + formData, + {}, + ApiConstants.CONTENT_TYPE_MULTIPART_FORM_DATA + ) + .then(() => undefined) + .catch((error) => { + throw error + }) + } } diff --git a/test/functional/users/DeleteCurrentApiToken.test.ts b/test/functional/users/DeleteCurrentApiToken.test.ts index 2cc0cc39..2f69ba91 100644 --- a/test/functional/users/DeleteCurrentApiToken.test.ts +++ b/test/functional/users/DeleteCurrentApiToken.test.ts @@ -24,7 +24,6 @@ describe('execute', () => { const testApiToken = await createApiTokenViaApi('deleteCurrentApiTokenFTUser') ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, testApiToken) await deleteCurrentApiToken.execute() - // Since the token has been deleted, the next call using it should return a WriteError await expect(deleteCurrentApiToken.execute()).rejects.toBeInstanceOf(WriteError) }) }) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 12ee137d..33dc0138 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -41,6 +41,7 @@ import { deleteCollectionViaApi, setStorageDriverViaApi } from '../../testHelpers/collections/collectionHelper' +import { getFileMetadata } from '../../testHelpers/files/filesHelper' describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository() @@ -854,4 +855,47 @@ describe('FilesRepository', () => { await expect(setFileToRestricted(nonExistentFiledId)).rejects.toThrow(expectedError) }) }) + + describe('updateFileMetadata', () => { + test('should update file metadata when file exists', async () => { + const getDatasetFiles = await sut.getDatasetFiles( + testDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + const fileid = getDatasetFiles.files[0].id + console.log('updateFileMetadata fileid', fileid) + const testFileMetadata = { + description: 'My description bbb.', + categories: ['Data'], + restrict: false + } + + const actual = await sut.updateFileMetadata(fileid, testFileMetadata) + const getFileMetadataResult = await getFileMetadata(fileid).catch(() => { + throw new Error(`Error while getting file metadata ${fileid}`) + }) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + expect(actual).toBeUndefined() + expect(getFileMetadataResult.data.description).toBe(testFileMetadata.description) + expect(getFileMetadataResult.data.categories).toEqual(testFileMetadata.categories) + expect(getFileMetadataResult.data.restricted).toBe(testFileMetadata.restrict) + }) + + test('should return error when file does not exist', async () => { + const nonExistentFiledId = 4000 + const testFileMetadata = { + description: 'My description bbb.', + categories: ['Data'], + restrict: false + } + const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) + + await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( + errorExpected + ) + }) + }) }) diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index 666551bf..48074653 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -227,3 +227,11 @@ export const updateFileTabularTags = async ( } ) } + +export const getFileMetadata = async (fileId: number): Promise => { + return await axios.get(`${TestConstants.TEST_API_URL}/files/${fileId}/metadata`, { + headers: { + 'X-Dataverse-Key': process.env.TEST_API_KEY + } + }) +} diff --git a/test/unit/files/UpdateFileMetadata.test.ts b/test/unit/files/UpdateFileMetadata.test.ts new file mode 100644 index 00000000..41255e48 --- /dev/null +++ b/test/unit/files/UpdateFileMetadata.test.ts @@ -0,0 +1,46 @@ +import { UpdateFileMetadata } from '../../../src/files/domain/useCases/UpdateFileMetadata' +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository' +import { WriteError } from '../../../src/core/domain/repositories/WriteError' +import { createFileMetadataWithCategories } from '../../testHelpers/files/filesHelper' + +describe('UpdateFileMetadata', () => { + const testFileMetadata = createFileMetadataWithCategories() + test('should updated file metadata with correct parameters and id', async () => { + const filesRepositoryStub: IFilesRepository = {} as IFilesRepository + filesRepositoryStub.updateFileMetadata = jest.fn().mockResolvedValue(testFileMetadata) + + const sut = new UpdateFileMetadata(filesRepositoryStub) + + await sut.execute(1, testFileMetadata) + + expect(filesRepositoryStub.updateFileMetadata).toHaveBeenCalledWith(1, testFileMetadata) + expect(filesRepositoryStub.updateFileMetadata).toHaveBeenCalledTimes(1) + }) + + test('should return the updated file metadata with correct parameters and persisten Id', async () => { + const filesRepositoryStub: IFilesRepository = { + updateFileMetadata: jest.fn().mockResolvedValue(testFileMetadata) + } as unknown as IFilesRepository + + const sut = new UpdateFileMetadata(filesRepositoryStub) + + await sut.execute('doi:10.5072/FK2/HC6KTB', testFileMetadata) + + expect(filesRepositoryStub.updateFileMetadata).toHaveBeenCalledWith( + 'doi:10.5072/FK2/HC6KTB', + testFileMetadata + ) + expect(filesRepositoryStub.updateFileMetadata).toHaveBeenCalledTimes(1) + }) + + test('should throw an error if the repository throws an error', async () => { + const filesRepositoryStub: IFilesRepository = { + updateFileMetadata: jest.fn().mockRejectedValue(new WriteError()) + } as unknown as IFilesRepository + + const sut = new UpdateFileMetadata(filesRepositoryStub) + + await expect(sut.execute(1, testFileMetadata)).rejects.toThrow(WriteError) + expect(filesRepositoryStub.updateFileMetadata).toHaveBeenCalledWith(1, testFileMetadata) + }) +}) From 6064d60dccdebabb6fe538ba0325d409327025ba Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 31 Jan 2025 16:43:54 -0500 Subject: [PATCH 02/12] feat: fix testcase, and add updateFileMetadata to usecases.md --- docs/useCases.md | 35 ++++++++++++++++++- .../integration/files/FilesRepository.test.ts | 18 +--------- test/testHelpers/files/filesHelper.ts | 8 +++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index 6a5be49d..fdd797b9 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1237,6 +1237,39 @@ The following error might arise from the `AddUploadedFileToDataset` use case: - AddUploadedFileToDatasetError: This error indicates that there was an error while adding the uploaded file to the dataset. +#### Updating File Metadata Use Cases + +This use case is designed to update or edit metadata information for a specific file by a numeric identifier or persistemt identifier. + +##### Update File Metadata + +###### Example call: + +````typescript +import { updateFileMetadata } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const fileId: number | string = 123 +const updateFileMetadataDTO = { + description: 'My description bbb.', + categories: ['Data'], + restrict: false +} + +await updateFileMetadata.execute(fileId, updateFileMetadataDTO).then((fileId) => { + console.log(`File updated successfully with file ID: ${fileId}`) +}) + +_See [use case](../src/files/domain/useCases/UpdateFileMetadata.ts) implementation_. + +This use case supports the following optional parameters for updateFileMetadataDTO: + +- **description**: (string) +- **prevFreeform**: (string) +- **categories**: (string[]) +- **dataFileTags**: (string[]) +- **restrict**: (boolean) #### Delete a File Deletes a File. @@ -1253,7 +1286,7 @@ const fileId = 12345 deleteFile.execute(fileId) /* ... */ -``` +```` _See [use case](../src/files/domain/useCases/DeleteFile.ts) implementation_. diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 33dc0138..1b332deb 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -41,7 +41,6 @@ import { deleteCollectionViaApi, setStorageDriverViaApi } from '../../testHelpers/collections/collectionHelper' -import { getFileMetadata } from '../../testHelpers/files/filesHelper' describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository() @@ -858,30 +857,15 @@ describe('FilesRepository', () => { describe('updateFileMetadata', () => { test('should update file metadata when file exists', async () => { - const getDatasetFiles = await sut.getDatasetFiles( - testDatasetIds.numericId, - latestDatasetVersionId, - false, - FileOrderCriteria.NAME_AZ - ) - const fileid = getDatasetFiles.files[0].id - console.log('updateFileMetadata fileid', fileid) const testFileMetadata = { description: 'My description bbb.', categories: ['Data'], restrict: false } - const actual = await sut.updateFileMetadata(fileid, testFileMetadata) - const getFileMetadataResult = await getFileMetadata(fileid).catch(() => { - throw new Error(`Error while getting file metadata ${fileid}`) - }) + const actual = await sut.updateFileMetadata(testFileId, testFileMetadata) - await new Promise((resolve) => setTimeout(resolve, 1000)) expect(actual).toBeUndefined() - expect(getFileMetadataResult.data.description).toBe(testFileMetadata.description) - expect(getFileMetadataResult.data.categories).toEqual(testFileMetadata.categories) - expect(getFileMetadataResult.data.restricted).toBe(testFileMetadata.restrict) }) test('should return error when file does not exist', async () => { diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index 48074653..bae836de 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -235,3 +235,11 @@ export const getFileMetadata = async (fileId: number): Promise => } }) } + +export const createFileMetadataWithCategories = (): FileMetadata => { + return { + categories: ['category1', 'category2'], + description: 'description', + directoryLabel: 'directoryLabel' + } +} From 78d2719ca22c514a68fb453ce80f650c799ff7e7 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 31 Jan 2025 17:12:59 -0500 Subject: [PATCH 03/12] fix: testcase --- .../integration/files/FilesRepository.test.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 1b332deb..fc44c613 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -856,30 +856,40 @@ describe('FilesRepository', () => { }) describe('updateFileMetadata', () => { - test('should update file metadata when file exists', async () => { + test('should return error when file does not exist', async () => { + const nonExistentFiledId = 4000 const testFileMetadata = { description: 'My description bbb.', categories: ['Data'], restrict: false } + const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) - const actual = await sut.updateFileMetadata(testFileId, testFileMetadata) - - expect(actual).toBeUndefined() + await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( + errorExpected + ) }) - test('should return error when file does not exist', async () => { - const nonExistentFiledId = 4000 + test('should update file metadata when file exists', async () => { + const getDatasetFilesResponse = await sut.getDatasetFiles( + testDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + + console.log('fileInfo', getDatasetFilesResponse) + + const fileId = getDatasetFilesResponse.files[0].id const testFileMetadata = { description: 'My description bbb.', categories: ['Data'], restrict: false } - const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) - await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( - errorExpected - ) + const actual = await sut.updateFileMetadata(fileId, testFileMetadata) + + expect(actual).toBeUndefined() }) }) }) From 32b47de56f37b7eba8201ef3e4840e1931865881 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 31 Jan 2025 17:29:50 -0500 Subject: [PATCH 04/12] fix: change the order of a testcase --- .../integration/files/FilesRepository.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index fc44c613..af0861d1 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -576,6 +576,42 @@ describe('FilesRepository', () => { }) }) + describe('updateFileMetadata', () => { + test('should return error when file does not exist', async () => { + const nonExistentFiledId = 4000 + const testFileMetadata = { + description: 'My description bbb.', + categories: ['Data'], + restrict: false + } + const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) + + await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( + errorExpected + ) + }) + + test('should update file metadata when file exists', async () => { + const getDatasetFilesResponse = await sut.getDatasetFiles( + testDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + + const fileId = getDatasetFilesResponse.files[0].id + const testFileMetadata = { + description: 'My description bbb.', + categories: ['Data'], + restrict: false + } + + const actual = await sut.updateFileMetadata(fileId, testFileMetadata) + + expect(actual).toBeUndefined() + }) + }) + describe('getFileUploadDestination', () => { const testCollectionAlias = 'getFileUploadDestinationsTestCollection' let testDataset2Ids: CreatedDatasetIdentifiers From 6440fbd9f3f58fd623d5f6971d9ea2615cf7b38b Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Sat, 1 Feb 2025 00:00:08 -0500 Subject: [PATCH 05/12] feat: update testcase --- .../integration/files/FilesRepository.test.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index af0861d1..48908e33 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -577,6 +577,25 @@ describe('FilesRepository', () => { }) describe('updateFileMetadata', () => { + const testCollectionAlias = 'updateFileMetadataTestCollection' + let testDataset2Ids: CreatedDatasetIdentifiers + + beforeAll(async () => { + await createCollectionViaApi(testCollectionAlias) + await setStorageDriverViaApi(testCollectionAlias, 'LocalStack') + testDataset2Ids = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testCollectionAlias + ) + await createSinglepartFileBlob() + await createMultipartFileBlob() + }) + + afterAll(async () => { + await deleteUnpublishedDatasetViaApi(testDataset2Ids.numericId) + await deleteCollectionViaApi(testCollectionAlias) + }) + test('should return error when file does not exist', async () => { const nonExistentFiledId = 4000 const testFileMetadata = { @@ -593,7 +612,7 @@ describe('FilesRepository', () => { test('should update file metadata when file exists', async () => { const getDatasetFilesResponse = await sut.getDatasetFiles( - testDatasetIds.numericId, + testDataset2Ids.numericId, latestDatasetVersionId, false, FileOrderCriteria.NAME_AZ From 2353a8f6da5f87a569e89a7d3d854b2a6ee9e01d Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Sat, 1 Feb 2025 00:12:54 -0500 Subject: [PATCH 06/12] feat: update order of testcase --- .../integration/files/FilesRepository.test.ts | 90 ++++++++----------- 1 file changed, 35 insertions(+), 55 deletions(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 48908e33..8f3d86bf 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -534,6 +534,41 @@ describe('FilesRepository', () => { }) }) + describe('updateFileMetadata', () => { + test('should return error when file does not exist', async () => { + const testFileMetadata = { + description: 'My description bbb.', + categories: ['Data'], + restrict: false + } + const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) + + await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( + errorExpected + ) + }) + + test('should update file metadata when file exists', async () => { + const getDatasetFilesResponse = await sut.getDatasetFiles( + testDatasetIds.numericId, + latestDatasetVersionId, + false, + FileOrderCriteria.NAME_AZ + ) + + const fileId = getDatasetFilesResponse.files[0].id + const testFileMetadata = { + description: 'My description bbb.', + categories: ['Data'], + restrict: false + } + + const actual = await sut.updateFileMetadata(fileId, testFileMetadata) + + expect(actual).toBeUndefined() + }) + }) + describe('getFileCitation', () => { test('should return citation when file exists', async () => { const actualFileCitation = await sut.getFileCitation( @@ -576,61 +611,6 @@ describe('FilesRepository', () => { }) }) - describe('updateFileMetadata', () => { - const testCollectionAlias = 'updateFileMetadataTestCollection' - let testDataset2Ids: CreatedDatasetIdentifiers - - beforeAll(async () => { - await createCollectionViaApi(testCollectionAlias) - await setStorageDriverViaApi(testCollectionAlias, 'LocalStack') - testDataset2Ids = await createDataset.execute( - TestConstants.TEST_NEW_DATASET_DTO, - testCollectionAlias - ) - await createSinglepartFileBlob() - await createMultipartFileBlob() - }) - - afterAll(async () => { - await deleteUnpublishedDatasetViaApi(testDataset2Ids.numericId) - await deleteCollectionViaApi(testCollectionAlias) - }) - - test('should return error when file does not exist', async () => { - const nonExistentFiledId = 4000 - const testFileMetadata = { - description: 'My description bbb.', - categories: ['Data'], - restrict: false - } - const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) - - await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( - errorExpected - ) - }) - - test('should update file metadata when file exists', async () => { - const getDatasetFilesResponse = await sut.getDatasetFiles( - testDataset2Ids.numericId, - latestDatasetVersionId, - false, - FileOrderCriteria.NAME_AZ - ) - - const fileId = getDatasetFilesResponse.files[0].id - const testFileMetadata = { - description: 'My description bbb.', - categories: ['Data'], - restrict: false - } - - const actual = await sut.updateFileMetadata(fileId, testFileMetadata) - - expect(actual).toBeUndefined() - }) - }) - describe('getFileUploadDestination', () => { const testCollectionAlias = 'getFileUploadDestinationsTestCollection' let testDataset2Ids: CreatedDatasetIdentifiers From 661d81dadf59ff8d9862a242ce0ae22d82cbade2 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 24 Feb 2025 10:39:15 -0500 Subject: [PATCH 07/12] feat: update file update metadata test --- .../integration/files/FilesRepository.test.ts | 110 ++++++------------ 1 file changed, 37 insertions(+), 73 deletions(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 8f3d86bf..262e1d9f 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -534,41 +534,6 @@ describe('FilesRepository', () => { }) }) - describe('updateFileMetadata', () => { - test('should return error when file does not exist', async () => { - const testFileMetadata = { - description: 'My description bbb.', - categories: ['Data'], - restrict: false - } - const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) - - await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( - errorExpected - ) - }) - - test('should update file metadata when file exists', async () => { - const getDatasetFilesResponse = await sut.getDatasetFiles( - testDatasetIds.numericId, - latestDatasetVersionId, - false, - FileOrderCriteria.NAME_AZ - ) - - const fileId = getDatasetFilesResponse.files[0].id - const testFileMetadata = { - description: 'My description bbb.', - categories: ['Data'], - restrict: false - } - - const actual = await sut.updateFileMetadata(fileId, testFileMetadata) - - expect(actual).toBeUndefined() - }) - }) - describe('getFileCitation', () => { test('should return citation when file exists', async () => { const actualFileCitation = await sut.getFileCitation( @@ -682,6 +647,43 @@ describe('FilesRepository', () => { }) }) + describe('updateFileMetadata', () => { + test('should update file metadata when file exists', async () => { + const testFileMetadata = { + description: 'My description test.', + categories: ['Data'], + restrict: false + } + + const actual = await sut.updateFileMetadata(testFileId, testFileMetadata) + + expect(actual).toBeUndefined() + + const fileInfo: FileModel = (await sut.getFile( + testFileId, + DatasetNotNumberedVersion.LATEST, + false + )) as FileModel + + expect(fileInfo.description).toBe(testFileMetadata.description) + expect(fileInfo.categories).toEqual(testFileMetadata.categories) + expect(fileInfo.restricted).toBe(testFileMetadata.restrict) + }) + + test('should return error when file does not exist', async () => { + const testFileMetadata = { + description: 'My description test.', + categories: ['Data'], + restrict: false + } + const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) + + await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( + errorExpected + ) + }) + }) + describe('deleteFile', () => { let deleFileTestDatasetIds: CreatedDatasetIdentifiers const testTextFile1Name = 'test-file-1.txt' @@ -889,42 +891,4 @@ describe('FilesRepository', () => { await expect(setFileToRestricted(nonExistentFiledId)).rejects.toThrow(expectedError) }) }) - - describe('updateFileMetadata', () => { - test('should return error when file does not exist', async () => { - const nonExistentFiledId = 4000 - const testFileMetadata = { - description: 'My description bbb.', - categories: ['Data'], - restrict: false - } - const errorExpected = new WriteError(`[400] Error attempting get the requested data file.`) - - await expect(sut.updateFileMetadata(nonExistentFiledId, testFileMetadata)).rejects.toThrow( - errorExpected - ) - }) - - test('should update file metadata when file exists', async () => { - const getDatasetFilesResponse = await sut.getDatasetFiles( - testDatasetIds.numericId, - latestDatasetVersionId, - false, - FileOrderCriteria.NAME_AZ - ) - - console.log('fileInfo', getDatasetFilesResponse) - - const fileId = getDatasetFilesResponse.files[0].id - const testFileMetadata = { - description: 'My description bbb.', - categories: ['Data'], - restrict: false - } - - const actual = await sut.updateFileMetadata(fileId, testFileMetadata) - - expect(actual).toBeUndefined() - }) - }) }) From af00b1a7bf214c79ec1029e3cbbfd9ff683a3378 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 24 Feb 2025 13:04:10 -0500 Subject: [PATCH 08/12] feat: update functional test --- docs/useCases.md | 6 +- .../files/UpdateFileMetadata.test.ts | 104 ++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 test/functional/files/UpdateFileMetadata.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index fdd797b9..bf5c3129 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1245,7 +1245,7 @@ This use case is designed to update or edit metadata information for a specific ###### Example call: -````typescript +```typescript import { updateFileMetadata } from '@iqss/dataverse-client-javascript' /* ... */ @@ -1260,6 +1260,7 @@ const updateFileMetadataDTO = { await updateFileMetadata.execute(fileId, updateFileMetadataDTO).then((fileId) => { console.log(`File updated successfully with file ID: ${fileId}`) }) +``` _See [use case](../src/files/domain/useCases/UpdateFileMetadata.ts) implementation_. @@ -1270,6 +1271,7 @@ This use case supports the following optional parameters for updateFileMetadataD - **categories**: (string[]) - **dataFileTags**: (string[]) - **restrict**: (boolean) + #### Delete a File Deletes a File. @@ -1286,7 +1288,7 @@ const fileId = 12345 deleteFile.execute(fileId) /* ... */ -```` +``` _See [use case](../src/files/domain/useCases/DeleteFile.ts) implementation_. diff --git a/test/functional/files/UpdateFileMetadata.test.ts b/test/functional/files/UpdateFileMetadata.test.ts new file mode 100644 index 00000000..7944b79e --- /dev/null +++ b/test/functional/files/UpdateFileMetadata.test.ts @@ -0,0 +1,104 @@ +import { + ApiConfig, + createDataset, + CreatedDatasetIdentifiers, + WriteError, + updateFileMetadata, + getFile, + DatasetNotNumberedVersion, + getDatasetFiles +} from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { + createCollectionViaApi, + deleteCollectionViaApi +} from '../../testHelpers/collections/collectionHelper' +import { deleteUnpublishedDatasetViaApi } from '../../testHelpers/datasets/datasetHelper' +import { uploadFileViaApi } from '../../testHelpers/files/filesHelper' +import { TestConstants } from '../../testHelpers/TestConstants' +import { UpdateFileMetadataDTO } from '../../../src/files/domain/dtos/UpdateFileMetadataDTO' +import { FileModel } from '../../../src/files/domain/models/FileModel' + +describe('execute', () => { + const testCollectionAlias = 'updateFileMetadatFunctionalTest' + let testDatasetIds: CreatedDatasetIdentifiers + const testTextFile1Name = 'test-file-1.txt' + const metadataUpdate: UpdateFileMetadataDTO = { + description: 'This is a test file', + categories: ['file'], + restrict: true + } + + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + await createCollectionViaApi(testCollectionAlias) + + try { + testDatasetIds = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + testCollectionAlias + ) + } catch (error) { + throw new Error('Tests beforeAll(): Error while creating test dataset') + } + + await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name).catch(() => { + throw new Error(`Tests beforeAll(): Error while uploading file ${testTextFile1Name}`) + }) + }) + + afterAll(async () => { + try { + await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId) + } catch (error) { + throw new Error('Tests afterAll(): Error while deleting test dataset') + } + + try { + await deleteCollectionViaApi(testCollectionAlias) + } catch (error) { + throw new Error('Tests afterAll(): Error while deleting test collection') + } + }) + + test('should successfully update metadata of a file', async () => { + const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId) + const fileId = datasetFiles.files[0].id + + try { + await updateFileMetadata.execute(fileId, metadataUpdate) + } catch (error) { + throw new Error('File metadata should be updated') + } finally { + const fileInfo: FileModel = (await getFile.execute( + fileId, + DatasetNotNumberedVersion.LATEST + )) as FileModel + + expect(fileInfo.description).toEqual(metadataUpdate.description) + expect(fileInfo.categories).toEqual(metadataUpdate.categories) + expect(fileInfo.restricted).toEqual(metadataUpdate.restrict) + } + }) + + test('should throw an error when the file id does not exist', async () => { + let writeError: WriteError | undefined = undefined + const nonExistentFileId = 5 + + try { + await updateFileMetadata.execute(nonExistentFileId, metadataUpdate) + throw new Error('Use case should throw an error') + } catch (error) { + writeError = error as WriteError + } finally { + expect(writeError).toBeInstanceOf(WriteError) + expect(writeError?.message).toEqual( + `There was an error when writing the resource. Reason was: [400] Error attempting get the requested data file.` + ) + } + }) +}) From b8a8478aa3590cb71d95243d39938ed8fe52b93d Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 27 Feb 2025 16:32:52 -0500 Subject: [PATCH 09/12] fix: update documentation --- docs/useCases.md | 14 +++----------- src/files/domain/useCases/UpdateFileMetadata.ts | 1 + 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index 1b37c1cb..9690f493 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1257,11 +1257,9 @@ The following error might arise from the `AddUploadedFileToDataset` use case: - AddUploadedFileToDatasetError: This error indicates that there was an error while adding the uploaded file to the dataset. -#### Updating File Metadata Use Cases +#### Update File Metadata -This use case is designed to update or edit metadata information for a specific file by a numeric identifier or persistemt identifier. - -##### Update File Metadata +Updates Metadata of a File. ###### Example call: @@ -1284,13 +1282,7 @@ await updateFileMetadata.execute(fileId, updateFileMetadataDTO).then((fileId) => _See [use case](../src/files/domain/useCases/UpdateFileMetadata.ts) implementation_. -This use case supports the following optional parameters for updateFileMetadataDTO: - -- **description**: (string) -- **prevFreeform**: (string) -- **categories**: (string[]) -- **dataFileTags**: (string[]) -- **restrict**: (boolean) +The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers. #### Delete a File diff --git a/src/files/domain/useCases/UpdateFileMetadata.ts b/src/files/domain/useCases/UpdateFileMetadata.ts index 1e7eb2e5..e0196793 100644 --- a/src/files/domain/useCases/UpdateFileMetadata.ts +++ b/src/files/domain/useCases/UpdateFileMetadata.ts @@ -11,6 +11,7 @@ export class UpdateFileMetadata implements UseCase { /** * Updates the metadata for a particular File. + * More detailed information about the file restriction behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata * * @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). * @param {UpdateFileMetadataDTO} [updateFileMetadataDTO] - The DTO containing the metadata updates. From ca9f86c51277b46b369c1def7ee778d0b8ff0c3e Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Thu, 27 Feb 2025 16:35:00 -0500 Subject: [PATCH 10/12] chore: a typo fix --- src/files/domain/useCases/UpdateFileMetadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files/domain/useCases/UpdateFileMetadata.ts b/src/files/domain/useCases/UpdateFileMetadata.ts index e0196793..f06c96c3 100644 --- a/src/files/domain/useCases/UpdateFileMetadata.ts +++ b/src/files/domain/useCases/UpdateFileMetadata.ts @@ -11,7 +11,7 @@ export class UpdateFileMetadata implements UseCase { /** * Updates the metadata for a particular File. - * More detailed information about the file restriction behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata + * More detailed information about updating a file's metadata behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#updating-file-metadata * * @param {number | string} [fileId] - The file identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). * @param {UpdateFileMetadataDTO} [updateFileMetadataDTO] - The DTO containing the metadata updates. From 2d18f217a6279aa241c2ee5e0895c3d1b72b55cb Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 28 Feb 2025 09:02:21 -0500 Subject: [PATCH 11/12] update solr --- test/environment/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/environment/.env b/test/environment/.env index 80e9a14e..0c691d9b 100644 --- a/test/environment/.env +++ b/test/environment/.env @@ -1,6 +1,6 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse -SOLR_VERSION=9.3.0 +SOLR_VERSION=9.8.0 DATAVERSE_IMAGE_REGISTRY=docker.io DATAVERSE_IMAGE_TAG=unstable DATAVERSE_BOOTSTRAP_TIMEOUT=5m From 1922064bb6970a5ac3a41fc62c541f78215b845b Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 5 Mar 2025 12:09:21 -0500 Subject: [PATCH 12/12] CHORE: fix lint --- test/testHelpers/files/filesHelper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index f94669ba..9de72830 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -251,6 +251,7 @@ export const createFileMetadataWithCategories = (): FileMetadata => { description: 'description', directoryLabel: 'directoryLabel' } +} export const calculateBlobChecksum = (blob: Buffer, checksumAlgorithm: string): string => { const hash = crypto.createHash(checksumAlgorithm)