diff --git a/docs/useCases.md b/docs/useCases.md index ff537daa..94b36731 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -860,7 +860,7 @@ _See [use case](../src/datasets/domain/useCases/GetDatasetAvailableDatasetType.t #### Create a Dataset -Creates a new Dataset in a collection, given a [DatasetDTO](../src/datasets/domain/dtos/DatasetDTO.ts) object and an optional collection identifier, which defaults to `:root`. +Creates a new Dataset in a collection, given a [DatasetDTO](../src/datasets/domain/dtos/DatasetDTO.ts) object, an optional collection identifier, which defaults to `:root`, and an optional dataset type. This use case validates the submitted fields of each metadata block and can return errors of type [ResourceValidationError](../src/core/domain/useCases/validators/errors/ResourceValidationError.ts), which include sufficient information to determine which field value is invalid and why. @@ -915,7 +915,7 @@ createDataset.execute(datasetDTO).then((newDatasetIds: CreatedDatasetIdentifiers _See [use case](../src/datasets/domain/useCases/CreateDataset.ts) implementation_. -The above example creates the new dataset in the root collection since no collection identifier is specified. If you want to create the dataset in a different collection, you must add the collection identifier as a second parameter in the use case call. +The above example creates the new dataset in the root collection since no collection identifier is specified. If you want to create the dataset in a different collection, you must add the collection identifier as a second parameter in the use case call. If you want the dataset type to be anything other than dataset, first [check available dataset types](#get-dataset-available-dataset-types) and then add the name of the dataset type as the third parameter. The use case returns a [CreatedDatasetIdentifiers](../src/datasets/domain/models/CreatedDatasetIdentifiers.ts) object, which includes the persistent and numeric identifiers of the created dataset. diff --git a/src/datasets/domain/models/Dataset.ts b/src/datasets/domain/models/Dataset.ts index caecd3ae..e858de9e 100644 --- a/src/datasets/domain/models/Dataset.ts +++ b/src/datasets/domain/models/Dataset.ts @@ -14,6 +14,7 @@ export interface Dataset { citationDate?: string metadataBlocks: DatasetMetadataBlocks isPartOf: DvObjectOwnerNode + datasetType?: string } export interface DatasetVersionInfo { diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 3fe1c7ab..e78816c4 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -46,7 +46,8 @@ export interface IDatasetsRepository { createDataset( newDataset: DatasetDTO, datasetMetadataBlocks: MetadataBlock[], - collectionId: string + collectionId: string, + datasetType?: string ): Promise publishDataset(datasetId: number | string, versionUpdateType: VersionUpdateType): Promise updateDataset( diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts index 65bffae4..090c0721 100644 --- a/src/datasets/domain/useCases/CreateDataset.ts +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -20,6 +20,7 @@ export class CreateDataset extends DatasetWriteUseCase} * @throws {ResourceValidationError} - If there are validation errors related to the provided information. * @throws {ReadError} - If there are errors while reading data. @@ -27,10 +28,16 @@ export class CreateDataset extends DatasetWriteUseCase { const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset) this.getNewDatasetValidator().validate(newDataset, metadataBlocks) - return this.getDatasetsRepository().createDataset(newDataset, metadataBlocks, collectionId) + return this.getDatasetsRepository().createDataset( + newDataset, + metadataBlocks, + collectionId, + datasetType + ) } } diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 99b380df..1545a43d 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -208,11 +208,16 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi public async createDataset( newDataset: DatasetDTO, datasetMetadataBlocks: MetadataBlock[], - collectionId: string + collectionId: string, + datasetType?: string ): Promise { return this.doPost( `/dataverses/${collectionId}/datasets`, - transformDatasetModelToNewDatasetRequestPayload(newDataset, datasetMetadataBlocks) + transformDatasetModelToNewDatasetRequestPayload( + newDataset, + datasetMetadataBlocks, + datasetType + ) ) .then((response) => { const responseData = response.data.data diff --git a/src/datasets/infra/repositories/transformers/DatasetPayload.ts b/src/datasets/infra/repositories/transformers/DatasetPayload.ts index bf6a70fb..b0535677 100644 --- a/src/datasets/infra/repositories/transformers/DatasetPayload.ts +++ b/src/datasets/infra/repositories/transformers/DatasetPayload.ts @@ -36,6 +36,7 @@ export interface DatasetPayload { files: FilePayload[] isPartOf: OwnerNodePayload deaccessionNote?: string + datasetType?: string } export interface DatasetLicensePayload { diff --git a/src/datasets/infra/repositories/transformers/datasetTransformers.ts b/src/datasets/infra/repositories/transformers/datasetTransformers.ts index 7d771fe5..bbb4c9fc 100644 --- a/src/datasets/infra/repositories/transformers/datasetTransformers.ts +++ b/src/datasets/infra/repositories/transformers/datasetTransformers.ts @@ -31,6 +31,7 @@ import { MetadataBlock, MetadataFieldInfo } from '../../../../metadataBlocks' const turndownService = new TurndownService() export interface NewDatasetRequestPayload { + datasetType?: string datasetVersion: { license?: DatasetLicense metadataBlocks: Record @@ -96,9 +97,11 @@ export const transformDatasetModelToUpdateDatasetRequestPayload = ( export const transformDatasetModelToNewDatasetRequestPayload = ( dataset: DatasetDTO, - metadataBlocks: MetadataBlock[] + metadataBlocks: MetadataBlock[], + datasetType?: string ): NewDatasetRequestPayload => { return { + datasetType, datasetVersion: { ...(dataset.license && { license: dataset.license }), metadataBlocks: transformMetadataBlockModelsToRequestPayload( @@ -293,6 +296,9 @@ export const transformVersionPayloadToDataset = ( if ('citationDate' in versionPayload) { datasetModel.citationDate = versionPayload.citationDate } + if ('datasetType' in versionPayload) { + datasetModel.datasetType = versionPayload.datasetType + } return datasetModel } diff --git a/test/functional/datasets/CreateDataset.test.ts b/test/functional/datasets/CreateDataset.test.ts index f90eff00..1394a3a4 100644 --- a/test/functional/datasets/CreateDataset.test.ts +++ b/test/functional/datasets/CreateDataset.test.ts @@ -1,5 +1,5 @@ import { createDataset, DatasetDTO } from '../../../src/datasets' -import { ApiConfig } from '../../../src' +import { ApiConfig, WriteError } from '../../../src' import { TestConstants } from '../../testHelpers/TestConstants' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' import { FieldValidationError } from '../../../src/datasets/domain/useCases/validators/errors/FieldValidationError' @@ -61,6 +61,58 @@ describe('execute', () => { } }) + test('should successfully create a new dataset when a valid dataset type is sent', async () => { + const testNewDataset = { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'Dataset created using the createDataset use case', + author: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org' + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataversedemo.org' + } + ], + datasetContact: [ + { + datasetContactEmail: 'finch@mailinator.com', + datasetContactName: 'Finch, Fiona' + } + ], + dsDescription: [ + { + dsDescriptionValue: 'This is the description of the dataset.' + } + ], + subject: ['Medicine, Health and Life Sciences'] + } + } + ] + } + expect.assertions(3) + + try { + const defaultDatasetType = 'dataset' + const createdDatasetIdentifiers = await createDataset.execute( + testNewDataset, + ':root', + defaultDatasetType + ) + + expect(createdDatasetIdentifiers).not.toBeNull() + expect(createdDatasetIdentifiers.numericId).not.toBeNull() + expect(createdDatasetIdentifiers.persistentId).not.toBeNull() + await deleteUnpublishedDatasetViaApi(createdDatasetIdentifiers.numericId) + } catch (error) { + throw new Error('Dataset should be created') + } + }) + test('should throw an error when a first level required field is missing', async () => { const testNewDataset = { metadataBlockValues: [ @@ -213,4 +265,52 @@ describe('execute', () => { ) } }) + + test('should throw an error when an invalid dataset type is sent', async () => { + const testNewDataset = { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'Dataset created using the createDataset use case', + author: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org' + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataversedemo.org' + } + ], + datasetContact: [ + { + datasetContactEmail: 'finch@mailinator.com', + datasetContactName: 'Finch, Fiona' + } + ], + dsDescription: [ + { + dsDescriptionValue: 'This is the description of the dataset.' + } + ], + subject: ['Medicine, Health and Life Sciences'] + } + } + ] + } + expect.assertions(1) + let writeError: WriteError | undefined = undefined + try { + const invalidDatasetType = 'doesNotExist' + await createDataset.execute(testNewDataset, ':root', invalidDatasetType) + throw new Error('Use case should throw an error') + } catch (error) { + writeError = error as WriteError + } finally { + expect(writeError?.message).toEqual( + 'There was an error when writing the resource. Reason was: [400] Error parsing Json: Invalid dataset type: doesNotExist' + ) + } + }) }) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 5374b5f8..af669e7c 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -110,6 +110,7 @@ describe('DatasetsRepository', () => { const filesRepositorySut = new FilesRepository() const directUploadSut: DirectUploadClient = new DirectUploadClient(filesRepositorySut) + const defaultDatasetType = 'dataset' beforeAll(async () => { ApiConfig.init( @@ -827,6 +828,64 @@ describe('DatasetsRepository', () => { expect(actualCreatedDataset.metadataBlocks[0].fields.subject).toContain( 'Medicine, Health and Life Sciences' ) + // even though we didn't provide a dataset type, it should be created with the default one + expect(actualCreatedDataset.datasetType).toBe(defaultDatasetType) + }) + }) + + describe('createDatasetWithDatasetType', () => { + test('should create a dataset with the provided dataset type', async () => { + const testNewDataset = { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'Dataset created using the createDataset use case', + author: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org' + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataversedemo.org' + } + ], + datasetContact: [ + { + datasetContactEmail: 'finch@mailinator.com', + datasetContactName: 'Finch, Fiona' + } + ], + dsDescription: [ + { + dsDescriptionValue: 'This is the description of the dataset.' + } + ], + subject: ['Medicine, Health and Life Sciences'] + } + } + ] + } + + const metadataBlocksRepository = new MetadataBlocksRepository() + const citationMetadataBlock = await metadataBlocksRepository.getMetadataBlockByName( + 'citation' + ) + const createdDataset = await sut.createDataset( + testNewDataset, + [citationMetadataBlock], + ROOT_COLLECTION_ALIAS, + defaultDatasetType + ) + const actualCreatedDataset = await sut.getDataset( + createdDataset.numericId, + DatasetNotNumberedVersion.LATEST, + false, + false + ) + + expect(actualCreatedDataset.datasetType).toBe(defaultDatasetType) }) }) diff --git a/test/testHelpers/datasets/datasetHelper.ts b/test/testHelpers/datasets/datasetHelper.ts index 65575bdc..4cb1ee79 100644 --- a/test/testHelpers/datasets/datasetHelper.ts +++ b/test/testHelpers/datasets/datasetHelper.ts @@ -694,9 +694,11 @@ export const createDatasetMetadataBlockModel = (): MetadataBlock => { } export const createNewDatasetRequestPayload = ( - license?: DatasetLicense + license?: DatasetLicense, + datasetType?: string ): NewDatasetRequestPayload => { return { + datasetType, datasetVersion: { ...(license && { license }), metadataBlocks: { diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index 92c73d78..edc69abb 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -51,7 +51,49 @@ describe('execute', () => { expect(datasetsRepositoryStub.createDataset).toHaveBeenCalledWith( testDataset, testMetadataBlocks, - ROOT_COLLECTION_ID + ROOT_COLLECTION_ID, + undefined + ) + }) + + test('should return a dataset type', async () => { + const testCreatedDatasetIdentifiers: CreatedDatasetIdentifiers = { + persistentId: 'test', + numericId: 1 + } + + const datasetsRepositoryStub = {} + datasetsRepositoryStub.createDataset = jest + .fn() + .mockResolvedValue(testCreatedDatasetIdentifiers) + + const datasetValidatorStub = {} + datasetValidatorStub.validate = jest.fn().mockResolvedValue(undefined) + + const metadataBlocksRepositoryStub = {} + metadataBlocksRepositoryStub.getMetadataBlockByName = jest + .fn() + .mockResolvedValue(testMetadataBlocks[0]) + + const sut = new CreateDataset( + datasetsRepositoryStub, + metadataBlocksRepositoryStub, + datasetValidatorStub + ) + + const actual = await sut.execute(testDataset, ROOT_COLLECTION_ID, 'software') + + expect(actual).toEqual(testCreatedDatasetIdentifiers) + + expect(metadataBlocksRepositoryStub.getMetadataBlockByName).toHaveBeenCalledWith( + testMetadataBlocks[0].name + ) + expect(datasetValidatorStub.validate).toHaveBeenCalledWith(testDataset, testMetadataBlocks) + expect(datasetsRepositoryStub.createDataset).toHaveBeenCalledWith( + testDataset, + testMetadataBlocks, + ROOT_COLLECTION_ID, + 'software' ) }) @@ -111,7 +153,8 @@ describe('execute', () => { expect(datasetsRepositoryStub.createDataset).toHaveBeenCalledWith( testDataset, testMetadataBlocks, - ROOT_COLLECTION_ID + ROOT_COLLECTION_ID, + undefined ) }) diff --git a/test/unit/datasets/datasetTransformers.test.ts b/test/unit/datasets/datasetTransformers.test.ts index e659f533..7e4185c7 100644 --- a/test/unit/datasets/datasetTransformers.test.ts +++ b/test/unit/datasets/datasetTransformers.test.ts @@ -33,4 +33,28 @@ describe('transformNewDatasetModelToRequestPayload', () => { expect(actual).toEqual(expectedNewDatasetRequestPayload) }) + + it('should correctly transform a new dataset model to a new dataset request payload when it contains a license and a datasetType', () => { + const testDataset = createDatasetDTO( + undefined, + undefined, + undefined, + undefined, + undefined, + createDatasetLicenseModel() + ) + const testMetadataBlocks = [createDatasetMetadataBlockModel()] + const datasetType = 'software' + const expectedNewDatasetRequestPayload = createNewDatasetRequestPayload( + createDatasetLicenseModel(), + datasetType + ) + const actual = transformDatasetModelToNewDatasetRequestPayload( + testDataset, + testMetadataBlocks, + datasetType + ) + + expect(actual).toEqual(expectedNewDatasetRequestPayload) + }) })