From fcd6e7344a72d8363997f1ea441fcaefea1f0901 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 10 Jan 2024 13:30:31 +0000 Subject: [PATCH 01/37] Stash: CreateDataset use case WIP. Pending validation and data access logic --- .../validators/NewResourceValidator.ts | 5 ++ .../validators/errors/EmptyFieldError.ts | 7 ++ .../validators/errors/FieldValidationError.ts | 11 +++ .../errors/ResourceValidationError.ts | 5 ++ src/datasets/domain/models/NewDataset.ts | 9 +++ .../repositories/IDatasetsRepository.ts | 2 + src/datasets/domain/useCases/CreateDataset.ts | 20 +++++ .../validators/NewDatasetValidator.ts | 27 +++++++ .../infra/repositories/DatasetsRepository.ts | 5 ++ .../domain/models/MetadataBlock.ts | 3 + .../transformers/metadataBlockTransformers.ts | 3 + test/testHelpers/datasets/newDatasetHelper.ts | 29 +++++++ .../metadataBlocks/metadataBlockHelper.ts | 16 ++++ test/unit/datasets/CreateDataset.test.ts | 76 +++++++++++++++++++ 14 files changed, 218 insertions(+) create mode 100644 src/core/domain/useCases/validators/NewResourceValidator.ts create mode 100644 src/core/domain/useCases/validators/errors/EmptyFieldError.ts create mode 100644 src/core/domain/useCases/validators/errors/FieldValidationError.ts create mode 100644 src/core/domain/useCases/validators/errors/ResourceValidationError.ts create mode 100644 src/datasets/domain/models/NewDataset.ts create mode 100644 src/datasets/domain/useCases/CreateDataset.ts create mode 100644 src/datasets/domain/useCases/validators/NewDatasetValidator.ts create mode 100644 test/testHelpers/datasets/newDatasetHelper.ts create mode 100644 test/unit/datasets/CreateDataset.test.ts diff --git a/src/core/domain/useCases/validators/NewResourceValidator.ts b/src/core/domain/useCases/validators/NewResourceValidator.ts new file mode 100644 index 00000000..a816bd07 --- /dev/null +++ b/src/core/domain/useCases/validators/NewResourceValidator.ts @@ -0,0 +1,5 @@ +import { ResourceValidationError } from './errors/ResourceValidationError'; + +export interface NewResourceValidator { + validate(resource: T): Promise; +} diff --git a/src/core/domain/useCases/validators/errors/EmptyFieldError.ts b/src/core/domain/useCases/validators/errors/EmptyFieldError.ts new file mode 100644 index 00000000..c098508c --- /dev/null +++ b/src/core/domain/useCases/validators/errors/EmptyFieldError.ts @@ -0,0 +1,7 @@ +import { FieldValidationError } from './FieldValidationError'; + +export class EmptyFieldError extends FieldValidationError { + constructor(field: string) { + super(field, 'The field should not be empty.'); + } +} diff --git a/src/core/domain/useCases/validators/errors/FieldValidationError.ts b/src/core/domain/useCases/validators/errors/FieldValidationError.ts new file mode 100644 index 00000000..4c58e0bf --- /dev/null +++ b/src/core/domain/useCases/validators/errors/FieldValidationError.ts @@ -0,0 +1,11 @@ +import { ResourceValidationError } from './ResourceValidationError'; + +export class FieldValidationError extends ResourceValidationError { + constructor(field: string, reason?: string) { + let message = `There was an error when validating the field ${field}.`; + if (reason) { + message += ` Reason was: ${reason}`; + } + super(message); + } +} diff --git a/src/core/domain/useCases/validators/errors/ResourceValidationError.ts b/src/core/domain/useCases/validators/errors/ResourceValidationError.ts new file mode 100644 index 00000000..4d4f7cbb --- /dev/null +++ b/src/core/domain/useCases/validators/errors/ResourceValidationError.ts @@ -0,0 +1,5 @@ +export class ResourceValidationError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/src/datasets/domain/models/NewDataset.ts b/src/datasets/domain/models/NewDataset.ts new file mode 100644 index 00000000..5321ae32 --- /dev/null +++ b/src/datasets/domain/models/NewDataset.ts @@ -0,0 +1,9 @@ +import { Author, DatasetContact, DatasetDescription } from './Dataset'; + +export interface NewDataset { + title: string; + authors: Author[]; + contacts: DatasetContact[]; + descriptions: DatasetDescription[]; + subjects: string[]; +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index bc16c3f7..698fc261 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -2,6 +2,7 @@ import { Dataset } from '../models/Dataset'; import { DatasetUserPermissions } from '../models/DatasetUserPermissions'; import { DatasetLock } from '../models/DatasetLock'; import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'; +import { NewDataset } from '../models/NewDataset'; export interface IDatasetsRepository { getDatasetSummaryFieldNames(): Promise; @@ -12,4 +13,5 @@ export interface IDatasetsRepository { getDatasetUserPermissions(datasetId: number | string): Promise; getDatasetLocks(datasetId: number | string): Promise; getAllDatasetPreviews(limit?: number, offset?: number): Promise; + createDataset(newDataset: NewDataset): Promise; } diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts new file mode 100644 index 00000000..77b188af --- /dev/null +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -0,0 +1,20 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IDatasetsRepository } from '../repositories/IDatasetsRepository'; +import { NewDataset } from '../models/NewDataset'; +import { NewResourceValidator } from '../../../core/domain/useCases/validators/NewResourceValidator'; + +export class CreateDataset implements UseCase { + private datasetsRepository: IDatasetsRepository; + private newDatasetValidator: NewResourceValidator; + + constructor(datasetsRepository: IDatasetsRepository, newDatasetValidator: NewResourceValidator) { + this.datasetsRepository = datasetsRepository; + this.newDatasetValidator = newDatasetValidator; + } + + async execute(newDataset: NewDataset): Promise { + return await this.newDatasetValidator.validate(newDataset).then(async () => { + return await this.datasetsRepository.createDataset(newDataset); + }); + } +} diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts new file mode 100644 index 00000000..73678457 --- /dev/null +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -0,0 +1,27 @@ +import { NewDataset } from '../../models/NewDataset'; +import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; +import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; +import { MetadataBlock } from '../../../../metadataBlocks'; +import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; + +export class NewDatasetValidator implements NewResourceValidator { + private metadataBlockRepository: IMetadataBlocksRepository; + + constructor(metadataBlockRepository: IMetadataBlocksRepository) { + this.metadataBlockRepository = metadataBlockRepository; + } + + async validate(resource: NewDataset): Promise { + console.log(resource); + return await this.metadataBlockRepository + .getMetadataBlockByName('citation') + .then((citationMetadataBlock: MetadataBlock) => { + console.log(citationMetadataBlock); + // TODO apply validation based on citation metadata block info + // missing field -> throw + }) + .catch((error) => { + throw new ResourceValidationError(error); + }); + } +} diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 541f1698..aae73f21 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -8,6 +8,7 @@ import { DatasetLock } from '../../domain/models/DatasetLock'; import { transformDatasetLocksResponseToDatasetLocks } from './transformers/datasetLocksTransformers'; import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transformers/datasetPreviewsTransformers'; import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'; +import { NewDataset } from '../../domain/models/NewDataset'; export interface GetAllDatasetPreviewsQueryParams { per_page?: number; @@ -106,4 +107,8 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error; }); } + + public async createDataset(newDataset: NewDataset): Promise { + console.log(newDataset); + } } diff --git a/src/metadataBlocks/domain/models/MetadataBlock.ts b/src/metadataBlocks/domain/models/MetadataBlock.ts index b95bf799..dc60b5b1 100644 --- a/src/metadataBlocks/domain/models/MetadataBlock.ts +++ b/src/metadataBlocks/domain/models/MetadataBlock.ts @@ -14,6 +14,9 @@ export interface MetadataFieldInfo { description: string; multiple: boolean; isControlledVocabulary: boolean; + controlledVocabularyValues?: string[]; displayFormat: string; childMetadataFields?: Record; + isRequired: boolean; + displayOrder: number; } diff --git a/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts b/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts index 9ed9c1ba..35685156 100644 --- a/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts +++ b/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts @@ -31,6 +31,9 @@ const transformPayloadMetadataFieldInfo = ( multiple: metadataFieldInfoPayload.multiple, isControlledVocabulary: metadataFieldInfoPayload.isControlledVocabulary, displayFormat: metadataFieldInfoPayload.displayFormat, + // TODO + isRequired: true, + displayOrder: 0, }; if (!isChild && metadataFieldInfoPayload.hasOwnProperty('childFields')) { const childMetadataFieldsPayload = metadataFieldInfoPayload.childFields; diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts new file mode 100644 index 00000000..53f64ba3 --- /dev/null +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -0,0 +1,29 @@ +import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; + +export const createNewDatasetModel = (): NewDataset => { + return { + title: 'test', + authors: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + ], + subjects: ['Subject1', 'Subject2'], + contacts: [ + { + datasetContactName: 'Admin, Dataverse', + datasetContactEmail: 'someemail@test.com', + }, + ], + descriptions: [ + { + dsDescriptionValue: 'test', + }, + ], + }; +}; diff --git a/test/testHelpers/metadataBlocks/metadataBlockHelper.ts b/test/testHelpers/metadataBlocks/metadataBlockHelper.ts index 54b12cfe..4ff63661 100644 --- a/test/testHelpers/metadataBlocks/metadataBlockHelper.ts +++ b/test/testHelpers/metadataBlocks/metadataBlockHelper.ts @@ -16,6 +16,8 @@ export const createMetadataBlockModel = (): MetadataBlock => { multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', + isRequired: true, + displayOrder: 0, }, testField2: { name: 'testName2', @@ -27,6 +29,8 @@ export const createMetadataBlockModel = (): MetadataBlock => { multiple: true, isControlledVocabulary: false, displayFormat: '', + isRequired: true, + displayOrder: 0, childMetadataFields: { testField3: { name: 'testName3', @@ -38,6 +42,8 @@ export const createMetadataBlockModel = (): MetadataBlock => { multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', + isRequired: true, + displayOrder: 0, }, testField4: { name: 'testName4', @@ -49,6 +55,8 @@ export const createMetadataBlockModel = (): MetadataBlock => { multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', + isRequired: true, + displayOrder: 0, }, }, }, @@ -72,6 +80,8 @@ export const createMetadataBlockPayload = (): any => { multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', + isRequired: true, + displayOrder: 0, }, testField2: { name: 'testName2', @@ -83,6 +93,8 @@ export const createMetadataBlockPayload = (): any => { multiple: true, isControlledVocabulary: false, displayFormat: '', + isRequired: true, + displayOrder: 0, childFields: { testField3: { name: 'testName3', @@ -94,6 +106,8 @@ export const createMetadataBlockPayload = (): any => { multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', + isRequired: true, + displayOrder: 0, }, testField4: { name: 'testName4', @@ -105,6 +119,8 @@ export const createMetadataBlockPayload = (): any => { multiple: false, isControlledVocabulary: false, displayFormat: '#VALUE', + isRequired: true, + displayOrder: 0, }, }, }, diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts new file mode 100644 index 00000000..f0b7ec2a --- /dev/null +++ b/test/unit/datasets/CreateDataset.test.ts @@ -0,0 +1,76 @@ +import { CreateDataset } from '../../../src/datasets/domain/useCases/CreateDataset'; +import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { NewResourceValidator } from '../../../src/core/domain/useCases/validators/NewResourceValidator'; +import { createNewDatasetModel } from '../../testHelpers/datasets/newDatasetHelper'; +import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; +import { ResourceValidationError } from '../../../src/core/domain/useCases/validators/errors/ResourceValidationError'; +import { WriteError } from '../../../src'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + const testDataset = createNewDatasetModel(); + + afterEach(() => { + sandbox.restore(); + }); + + test('should call repository when validation is successful', async () => { + const datasetsRepositoryStub = {}; + const createDatasetStub = sandbox.stub(); + datasetsRepositoryStub.createDataset = createDatasetStub; + + const newDatasetValidatorMock = >{}; + const validateMock = sandbox.stub().resolves(); + newDatasetValidatorMock.validate = validateMock; + + const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorMock); + + await sut.execute(testDataset); + + assert.calledWithExactly(validateMock, testDataset); + assert.calledWithExactly(createDatasetStub, testDataset); + + assert.callOrder(validateMock, createDatasetStub); + }); + + test('should throw ResourceValidationError and not call repository when validation is unsuccessful', async () => { + const datasetsRepositoryStub = {}; + const createDatasetStub = sandbox.stub(); + datasetsRepositoryStub.createDataset = createDatasetStub; + + const newDatasetValidatorMock = >{}; + const testValidationError = new ResourceValidationError('Test error'); + const validateMock = sandbox.stub().throwsException(testValidationError); + newDatasetValidatorMock.validate = validateMock; + + const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorMock); + let actualError: ResourceValidationError = undefined; + await sut.execute(testDataset).catch((e) => (actualError = e)); + assert.match(actualError, testValidationError); + + assert.calledWithExactly(validateMock, testDataset); + assert.notCalled(createDatasetStub); + }); + + test('should throw WriteError when validation is successful and repository raises an error', async () => { + const datasetsRepositoryMock = {}; + const testWriteError = new WriteError('Test error'); + const createDatasetMock = sandbox.stub().throwsException(testWriteError); + datasetsRepositoryMock.createDataset = createDatasetMock; + + const newDatasetValidatorMock = >{}; + const validateMock = sandbox.stub().resolves(); + newDatasetValidatorMock.validate = validateMock; + + const sut = new CreateDataset(datasetsRepositoryMock, newDatasetValidatorMock); + let actualError: ResourceValidationError = undefined; + await sut.execute(testDataset).catch((e) => (actualError = e)); + assert.match(actualError, testWriteError); + + assert.calledWithExactly(validateMock, testDataset); + assert.calledWithExactly(createDatasetMock, testDataset); + + assert.callOrder(validateMock, createDatasetMock); + }); +}); From 3a24289cbec76992d60068652b351f9a4bf28a4f Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 10 Jan 2024 14:03:55 +0000 Subject: [PATCH 02/37] Changed: NewDataset model properties --- src/datasets/domain/models/NewDataset.ts | 17 +++++---- test/testHelpers/datasets/newDatasetHelper.ts | 35 ++++++++----------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/datasets/domain/models/NewDataset.ts b/src/datasets/domain/models/NewDataset.ts index 5321ae32..36b35c19 100644 --- a/src/datasets/domain/models/NewDataset.ts +++ b/src/datasets/domain/models/NewDataset.ts @@ -1,9 +1,14 @@ -import { Author, DatasetContact, DatasetDescription } from './Dataset'; +import { DatasetMetadataSubField } from './Dataset'; export interface NewDataset { - title: string; - authors: Author[]; - contacts: DatasetContact[]; - descriptions: DatasetDescription[]; - subjects: string[]; + metadataBlockValues: NewDatasetMetadataBlockValues[]; } + +export interface NewDatasetMetadataBlockValues { + name: string; + fields: NewDatasetMetadataFields; +} + +export type NewDatasetMetadataFields = Record; + +export type NewDatasetMetadataFieldValue = string | string[] | DatasetMetadataSubField | DatasetMetadataSubField[]; diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 53f64ba3..9811951b 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -2,27 +2,22 @@ import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; export const createNewDatasetModel = (): NewDataset => { return { - title: 'test', - authors: [ + metadataBlockValues: [ { - authorName: 'Admin, Dataverse', - authorAffiliation: 'Dataverse.org', - }, - { - authorName: 'Owner, Dataverse', - authorAffiliation: 'Dataverse.org', - }, - ], - subjects: ['Subject1', 'Subject2'], - contacts: [ - { - datasetContactName: 'Admin, Dataverse', - datasetContactEmail: 'someemail@test.com', - }, - ], - descriptions: [ - { - dsDescriptionValue: 'test', + name: 'citation', + fields: { + title: 'test dataset', + author: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + ], + }, }, ], }; From 644f228b788cd7334c611533b88b08035a64f205 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Jan 2024 09:32:30 +0000 Subject: [PATCH 03/37] Stash: NewDatasetValidator logic WIP --- .../validators/errors/EmptyFieldError.ts | 4 +- .../validators/errors/FieldValidationError.ts | 28 +++++++- src/datasets/domain/models/NewDataset.ts | 10 ++- .../validators/NewDatasetValidator.ts | 38 +++++++---- test/integration/environment/.env | 4 +- test/testHelpers/datasets/newDatasetHelper.ts | 65 +++++++++++++++++++ test/unit/datasets/CreateDataset.test.ts | 38 +++++------ .../unit/datasets/NewDatasetValidator.test.ts | 24 +++++++ 8 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 test/unit/datasets/NewDatasetValidator.test.ts diff --git a/src/core/domain/useCases/validators/errors/EmptyFieldError.ts b/src/core/domain/useCases/validators/errors/EmptyFieldError.ts index c098508c..52c3ae80 100644 --- a/src/core/domain/useCases/validators/errors/EmptyFieldError.ts +++ b/src/core/domain/useCases/validators/errors/EmptyFieldError.ts @@ -1,7 +1,7 @@ import { FieldValidationError } from './FieldValidationError'; export class EmptyFieldError extends FieldValidationError { - constructor(field: string) { - super(field, 'The field should not be empty.'); + constructor(metadataFieldName: string, citationBlockName: string, parentMetadataFieldName?: string) { + super(metadataFieldName, citationBlockName, parentMetadataFieldName, 'The field should not be empty.'); } } diff --git a/src/core/domain/useCases/validators/errors/FieldValidationError.ts b/src/core/domain/useCases/validators/errors/FieldValidationError.ts index 4c58e0bf..093b28a7 100644 --- a/src/core/domain/useCases/validators/errors/FieldValidationError.ts +++ b/src/core/domain/useCases/validators/errors/FieldValidationError.ts @@ -1,11 +1,33 @@ import { ResourceValidationError } from './ResourceValidationError'; export class FieldValidationError extends ResourceValidationError { - constructor(field: string, reason?: string) { - let message = `There was an error when validating the field ${field}.`; + private citationBlockName: string; + private metadataFieldName: string; + private parentMetadataFieldName?: string; + + constructor(metadataFieldName: string, citationBlockName: string, parentMetadataFieldName?: string, reason?: string) { + let message = `There was an error when validating the field ${metadataFieldName} from metadata block ${citationBlockName}`; + if (metadataFieldName) { + message += ` with parent field ${parentMetadataFieldName}`; + } if (reason) { - message += ` Reason was: ${reason}`; + message += `. Reason was: ${reason}`; } super(message); + this.citationBlockName = citationBlockName; + this.metadataFieldName = metadataFieldName; + this.parentMetadataFieldName = parentMetadataFieldName; + } + + getCitationBlockName(): string { + return this.citationBlockName; + } + + getMetadataFieldName(): string { + return this.metadataFieldName; + } + + getParentMetadataFieldName(): string | undefined { + return this.parentMetadataFieldName; } } diff --git a/src/datasets/domain/models/NewDataset.ts b/src/datasets/domain/models/NewDataset.ts index 36b35c19..d7e587ea 100644 --- a/src/datasets/domain/models/NewDataset.ts +++ b/src/datasets/domain/models/NewDataset.ts @@ -1,5 +1,3 @@ -import { DatasetMetadataSubField } from './Dataset'; - export interface NewDataset { metadataBlockValues: NewDatasetMetadataBlockValues[]; } @@ -11,4 +9,10 @@ export interface NewDatasetMetadataBlockValues { export type NewDatasetMetadataFields = Record; -export type NewDatasetMetadataFieldValue = string | string[] | DatasetMetadataSubField | DatasetMetadataSubField[]; +export type NewDatasetMetadataFieldValue = + | string + | string[] + | NewDatasetMetadataSubFieldValue + | NewDatasetMetadataSubFieldValue[]; + +export type NewDatasetMetadataSubFieldValue = Record; diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 73678457..0be7381d 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -1,8 +1,10 @@ -import { NewDataset } from '../../models/NewDataset'; +import { NewDataset, NewDatasetMetadataFieldValue } from '../../models/NewDataset'; import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; -import { MetadataBlock } from '../../../../metadataBlocks'; +import { MetadataBlock, MetadataFieldInfo } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; +import { WriteError } from '../../../../core'; +import { EmptyFieldError } from '../../../../core/domain/useCases/validators/errors/EmptyFieldError'; export class NewDatasetValidator implements NewResourceValidator { private metadataBlockRepository: IMetadataBlocksRepository; @@ -12,16 +14,26 @@ export class NewDatasetValidator implements NewResourceValidator { } async validate(resource: NewDataset): Promise { - console.log(resource); - return await this.metadataBlockRepository - .getMetadataBlockByName('citation') - .then((citationMetadataBlock: MetadataBlock) => { - console.log(citationMetadataBlock); - // TODO apply validation based on citation metadata block info - // missing field -> throw - }) - .catch((error) => { - throw new ResourceValidationError(error); - }); + for (const metadataBlockValues of resource.metadataBlockValues) { + const newDatasetMetadataBlockName = metadataBlockValues.name; + await this.metadataBlockRepository + .getMetadataBlockByName(newDatasetMetadataBlockName) + .then((metadataBlock: MetadataBlock) => { + Object.keys(metadataBlock.metadataFields).map((metadataFieldKey) => { + const metadataFieldInfo: MetadataFieldInfo = metadataBlock.metadataFields[metadataFieldKey]; + const newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue = + metadataBlockValues.fields[metadataFieldKey]; + if (metadataFieldInfo.isRequired && newDatasetMetadataFieldValue == undefined) { + throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName); + } + if (metadataFieldInfo.childMetadataFields != undefined) { + // TODO: child fields validation + } + }); + }) + .catch((error: WriteError) => { + throw new ResourceValidationError(error.message); + }); + } } } diff --git a/test/integration/environment/.env b/test/integration/environment/.env index 80e9a14e..2141e353 100644 --- a/test/integration/environment/.env +++ b/test/integration/environment/.env @@ -1,6 +1,6 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse SOLR_VERSION=9.3.0 -DATAVERSE_IMAGE_REGISTRY=docker.io -DATAVERSE_IMAGE_TAG=unstable +DATAVERSE_IMAGE_REGISTRY=ghcr.io +DATAVERSE_IMAGE_TAG=10216-metadatablocks-payload-extension DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 9811951b..92b7c9c9 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -1,4 +1,5 @@ import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; +import { MetadataBlock } from '../../../src'; export const createNewDatasetModel = (): NewDataset => { return { @@ -22,3 +23,67 @@ export const createNewDatasetModel = (): NewDataset => { ], }; }; + +export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { + return { + id: 1, + name: 'citation', + displayName: 'Citation Metadata', + metadataFields: { + title: { + name: 'title', + displayName: 'title', + title: 'title', + type: 'DatasetField', + watermark: 'watermark', + description: 'description', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: true, + displayOrder: 0, + }, + author: { + name: 'author', + displayName: 'author', + title: 'author', + type: 'NONE', + watermark: 'watermark', + description: 'description', + multiple: true, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: true, + displayOrder: 1, + childMetadataFields: { + authorName: { + name: 'authorName', + displayName: 'author name', + title: 'author name', + type: 'TEXT', + watermark: 'watermark', + description: 'description', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: true, + displayOrder: 2, + }, + authorAffiliation: { + name: 'authorAffiliation', + displayName: 'author affiliation', + title: 'author affiliation', + type: 'TEXT', + watermark: 'watermark', + description: 'descriprion', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: false, + displayOrder: 3, + }, + }, + }, + }, + }; +}; diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index f0b7ec2a..78897bd6 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -20,18 +20,18 @@ describe('execute', () => { const createDatasetStub = sandbox.stub(); datasetsRepositoryStub.createDataset = createDatasetStub; - const newDatasetValidatorMock = >{}; - const validateMock = sandbox.stub().resolves(); - newDatasetValidatorMock.validate = validateMock; + const newDatasetValidatorStub = >{}; + const validateStub = sandbox.stub().resolves(); + newDatasetValidatorStub.validate = validateStub; - const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorMock); + const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorStub); await sut.execute(testDataset); - assert.calledWithExactly(validateMock, testDataset); + assert.calledWithExactly(validateStub, testDataset); assert.calledWithExactly(createDatasetStub, testDataset); - assert.callOrder(validateMock, createDatasetStub); + assert.callOrder(validateStub, createDatasetStub); }); test('should throw ResourceValidationError and not call repository when validation is unsuccessful', async () => { @@ -39,38 +39,38 @@ describe('execute', () => { const createDatasetStub = sandbox.stub(); datasetsRepositoryStub.createDataset = createDatasetStub; - const newDatasetValidatorMock = >{}; + const newDatasetValidatorStub = >{}; const testValidationError = new ResourceValidationError('Test error'); - const validateMock = sandbox.stub().throwsException(testValidationError); - newDatasetValidatorMock.validate = validateMock; + const validateStub = sandbox.stub().throwsException(testValidationError); + newDatasetValidatorStub.validate = validateStub; - const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorMock); + const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorStub); let actualError: ResourceValidationError = undefined; await sut.execute(testDataset).catch((e) => (actualError = e)); assert.match(actualError, testValidationError); - assert.calledWithExactly(validateMock, testDataset); + assert.calledWithExactly(validateStub, testDataset); assert.notCalled(createDatasetStub); }); test('should throw WriteError when validation is successful and repository raises an error', async () => { - const datasetsRepositoryMock = {}; + const datasetsRepositoryStub = {}; const testWriteError = new WriteError('Test error'); - const createDatasetMock = sandbox.stub().throwsException(testWriteError); - datasetsRepositoryMock.createDataset = createDatasetMock; + const createDatasetStub = sandbox.stub().throwsException(testWriteError); + datasetsRepositoryStub.createDataset = createDatasetStub; - const newDatasetValidatorMock = >{}; + const newDatasetValidatorStub = >{}; const validateMock = sandbox.stub().resolves(); - newDatasetValidatorMock.validate = validateMock; + newDatasetValidatorStub.validate = validateMock; - const sut = new CreateDataset(datasetsRepositoryMock, newDatasetValidatorMock); + const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorStub); let actualError: ResourceValidationError = undefined; await sut.execute(testDataset).catch((e) => (actualError = e)); assert.match(actualError, testWriteError); assert.calledWithExactly(validateMock, testDataset); - assert.calledWithExactly(createDatasetMock, testDataset); + assert.calledWithExactly(createDatasetStub, testDataset); - assert.callOrder(validateMock, createDatasetMock); + assert.callOrder(validateMock, createDatasetStub); }); }); diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts new file mode 100644 index 00000000..0f3331c5 --- /dev/null +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -0,0 +1,24 @@ +import { NewDatasetValidator } from '../../../src/datasets/domain/useCases/validators/NewDatasetValidator'; +import { createSandbox, SinonSandbox } from 'sinon'; +import { createNewDatasetModel, createNewDatasetMetadataBlockModel } from '../../testHelpers/datasets/newDatasetHelper'; +import { fail } from 'assert'; +import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + test('should not raise exception when new dataset is valid', async () => { + const testNewDataset = createNewDatasetModel(); + const testMetadataBlock = createNewDatasetMetadataBlockModel(); + const metadataBlocksRepositoryStub = {}; + const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + const sut = new NewDatasetValidator(metadataBlocksRepositoryStub); + + await sut.validate(testNewDataset).catch((e) => fail(e)); + }); +}); From 2c356338f94810ca3b420610b12c92f5c51e4b9b Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Jan 2024 12:31:05 +0000 Subject: [PATCH 04/37] Added: test cases for NewDatasetValidator empty field error handling --- .../validators/errors/FieldValidationError.ts | 20 +++-------- .../validators/NewDatasetValidator.ts | 35 ++++++++---------- test/testHelpers/datasets/newDatasetHelper.ts | 13 +++++++ .../unit/datasets/NewDatasetValidator.test.ts | 36 +++++++++++++++++-- 4 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/core/domain/useCases/validators/errors/FieldValidationError.ts b/src/core/domain/useCases/validators/errors/FieldValidationError.ts index 093b28a7..ff1c830b 100644 --- a/src/core/domain/useCases/validators/errors/FieldValidationError.ts +++ b/src/core/domain/useCases/validators/errors/FieldValidationError.ts @@ -1,13 +1,13 @@ import { ResourceValidationError } from './ResourceValidationError'; export class FieldValidationError extends ResourceValidationError { - private citationBlockName: string; - private metadataFieldName: string; - private parentMetadataFieldName?: string; + citationBlockName: string; + metadataFieldName: string; + parentMetadataFieldName?: string; constructor(metadataFieldName: string, citationBlockName: string, parentMetadataFieldName?: string, reason?: string) { let message = `There was an error when validating the field ${metadataFieldName} from metadata block ${citationBlockName}`; - if (metadataFieldName) { + if (parentMetadataFieldName) { message += ` with parent field ${parentMetadataFieldName}`; } if (reason) { @@ -18,16 +18,4 @@ export class FieldValidationError extends ResourceValidationError { this.metadataFieldName = metadataFieldName; this.parentMetadataFieldName = parentMetadataFieldName; } - - getCitationBlockName(): string { - return this.citationBlockName; - } - - getMetadataFieldName(): string { - return this.metadataFieldName; - } - - getParentMetadataFieldName(): string | undefined { - return this.parentMetadataFieldName; - } } diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 0be7381d..9c5fd433 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -1,9 +1,8 @@ import { NewDataset, NewDatasetMetadataFieldValue } from '../../models/NewDataset'; import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; -import { MetadataBlock, MetadataFieldInfo } from '../../../../metadataBlocks'; +import { MetadataFieldInfo } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; -import { WriteError } from '../../../../core'; import { EmptyFieldError } from '../../../../core/domain/useCases/validators/errors/EmptyFieldError'; export class NewDatasetValidator implements NewResourceValidator { @@ -16,24 +15,20 @@ export class NewDatasetValidator implements NewResourceValidator { async validate(resource: NewDataset): Promise { for (const metadataBlockValues of resource.metadataBlockValues) { const newDatasetMetadataBlockName = metadataBlockValues.name; - await this.metadataBlockRepository - .getMetadataBlockByName(newDatasetMetadataBlockName) - .then((metadataBlock: MetadataBlock) => { - Object.keys(metadataBlock.metadataFields).map((metadataFieldKey) => { - const metadataFieldInfo: MetadataFieldInfo = metadataBlock.metadataFields[metadataFieldKey]; - const newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue = - metadataBlockValues.fields[metadataFieldKey]; - if (metadataFieldInfo.isRequired && newDatasetMetadataFieldValue == undefined) { - throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName); - } - if (metadataFieldInfo.childMetadataFields != undefined) { - // TODO: child fields validation - } - }); - }) - .catch((error: WriteError) => { - throw new ResourceValidationError(error.message); - }); + + const metadataBlock = await this.metadataBlockRepository.getMetadataBlockByName(newDatasetMetadataBlockName); + for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { + const metadataFieldInfo: MetadataFieldInfo = metadataBlock.metadataFields[metadataFieldKey]; + const newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue = metadataBlockValues.fields[metadataFieldKey]; + + if (metadataFieldInfo.isRequired && newDatasetMetadataFieldValue == undefined) { + throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName); + } + + if (metadataFieldInfo.childMetadataFields != undefined) { + // TODO: child fields validation + } + } } } } diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 92b7c9c9..2cf5a77f 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -24,6 +24,19 @@ export const createNewDatasetModel = (): NewDataset => { }; }; +export const createNewDatasetModelWithoutRequiredField = (): NewDataset => { + return { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'test dataset', + }, + }, + ], + }; +}; + export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { return { id: 1, diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index 0f3331c5..d029a50c 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -1,8 +1,13 @@ import { NewDatasetValidator } from '../../../src/datasets/domain/useCases/validators/NewDatasetValidator'; -import { createSandbox, SinonSandbox } from 'sinon'; -import { createNewDatasetModel, createNewDatasetMetadataBlockModel } from '../../testHelpers/datasets/newDatasetHelper'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { + createNewDatasetModel, + createNewDatasetMetadataBlockModel, + createNewDatasetModelWithoutRequiredField, +} from '../../testHelpers/datasets/newDatasetHelper'; import { fail } from 'assert'; import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; +import { EmptyFieldError } from '../../../src/core/domain/useCases/validators/errors/EmptyFieldError'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); @@ -11,7 +16,7 @@ describe('execute', () => { sandbox.restore(); }); - test('should not raise exception when new dataset is valid', async () => { + test('should not raise validation error when new dataset is valid', async () => { const testNewDataset = createNewDatasetModel(); const testMetadataBlock = createNewDatasetMetadataBlockModel(); const metadataBlocksRepositoryStub = {}; @@ -21,4 +26,29 @@ describe('execute', () => { await sut.validate(testNewDataset).catch((e) => fail(e)); }); + + test('should raise empty field error when a first level field is missing', async () => { + const testNewDataset = createNewDatasetModelWithoutRequiredField(); + const testMetadataBlock = createNewDatasetMetadataBlockModel(); + const metadataBlocksRepositoryStub = {}; + const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + const sut = new NewDatasetValidator(metadataBlocksRepositoryStub); + + await sut + .validate(testNewDataset) + .then(() => { + fail('Validation should fail'); + }) + .catch((error) => { + const emptyFieldError = error as EmptyFieldError; + assert.match(emptyFieldError.citationBlockName, 'citation'); + assert.match(emptyFieldError.metadataFieldName, 'author'); + assert.match(emptyFieldError.parentMetadataFieldName, undefined); + assert.match( + emptyFieldError.message, + 'There was an error when validating the field author from metadata block citation. Reason was: The field should not be empty.', + ); + }); + }); }); From 1b4b29a9160f6b8f396576ecea0bfb9b99f14fcb Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Jan 2024 14:23:29 +0000 Subject: [PATCH 05/37] Stash: multiple field value validation WIP --- .../validators/NewDatasetValidator.ts | 69 +++++++++++++++++++ test/testHelpers/datasets/newDatasetHelper.ts | 49 +++++++++---- .../unit/datasets/NewDatasetValidator.test.ts | 33 ++++++++- 3 files changed, 135 insertions(+), 16 deletions(-) diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 9c5fd433..7ed745a2 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -4,6 +4,7 @@ import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/rep import { MetadataFieldInfo } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; import { EmptyFieldError } from '../../../../core/domain/useCases/validators/errors/EmptyFieldError'; +import { FieldValidationError } from '../../../../core/domain/useCases/validators/errors/FieldValidationError'; export class NewDatasetValidator implements NewResourceValidator { private metadataBlockRepository: IMetadataBlocksRepository; @@ -25,10 +26,78 @@ export class NewDatasetValidator implements NewResourceValidator { throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName); } + this.validateMetadataFieldValueType( + metadataFieldInfo, + metadataFieldKey, + newDatasetMetadataFieldValue, + newDatasetMetadataBlockName, + ); + if (metadataFieldInfo.childMetadataFields != undefined) { // TODO: child fields validation } } } } + + validateMetadataFieldValueType( + metadataFieldInfo: MetadataFieldInfo, + metadataFieldKey: string, + newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, + newDatasetMetadataBlockName: string, + ): void { + if (metadataFieldInfo.multiple) { + if (!Array.isArray(newDatasetMetadataFieldValue)) { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + undefined, + 'Expecting an array of values.', + ); + } + if (this.isValidArrayType(newDatasetMetadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + undefined, + 'Expecting an array of sub fields, not strings.', + ); + } else if (this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + undefined, + 'Expecting an array of strings, not sub fields.', + ); + } else if ( + !this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && + !this.isValidArrayType(newDatasetMetadataFieldValue, 'string') + ) { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + undefined, + 'The provided array of values is not valid.', + ); + } + } + } + + private isValidArrayType( + newDatasetMetadataFieldValue: Array, + expectedType: 'string' | 'object', + ): boolean { + return newDatasetMetadataFieldValue.every( + (item: string | NewDatasetMetadataFieldValue) => typeof item === expectedType, + ); + } + + private createValidationError( + metadataFieldKey: string, + newDatasetMetadataBlockName: string, + parentMetadataFieldName: string | undefined, + reason: string, + ): FieldValidationError { + return new FieldValidationError(metadataFieldKey, newDatasetMetadataBlockName, parentMetadataFieldName, reason); + } } diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 2cf5a77f..9d99eb3b 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -1,36 +1,59 @@ -import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; +import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; import { MetadataBlock } from '../../../src'; -export const createNewDatasetModel = (): NewDataset => { +export const createNewDatasetModel = (authorFieldValue?: NewDatasetMetadataFieldValue | string): NewDataset => { + const validAuthorFieldValue = [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + ]; + return { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'test dataset', + author: authorFieldValue !== undefined ? authorFieldValue : validAuthorFieldValue, + }, + }, + ], + }; +}; + +export const createNewDatasetModelWithoutFirstLevelRequiredField = (): NewDataset => { return { metadataBlockValues: [ { name: 'citation', fields: { title: 'test dataset', - author: [ - { - authorName: 'Admin, Dataverse', - authorAffiliation: 'Dataverse.org', - }, - { - authorName: 'Owner, Dataverse', - authorAffiliation: 'Dataverse.org', - }, - ], }, }, ], }; }; -export const createNewDatasetModelWithoutRequiredField = (): NewDataset => { +export const createNewDatasetModelWithoutSecondLevelRequiredField = (): NewDataset => { return { metadataBlockValues: [ { name: 'citation', fields: { title: 'test dataset', + author: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + { + authorAffiliation: 'Dataverse.org', + }, + ], }, }, ], diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index d029a50c..d0bba684 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -3,11 +3,12 @@ import { assert, createSandbox, SinonSandbox } from 'sinon'; import { createNewDatasetModel, createNewDatasetMetadataBlockModel, - createNewDatasetModelWithoutRequiredField, + createNewDatasetModelWithoutFirstLevelRequiredField, } from '../../testHelpers/datasets/newDatasetHelper'; import { fail } from 'assert'; import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { EmptyFieldError } from '../../../src/core/domain/useCases/validators/errors/EmptyFieldError'; +import { FieldValidationError } from '../../../src/core/domain/useCases/validators/errors/FieldValidationError'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); @@ -27,8 +28,8 @@ describe('execute', () => { await sut.validate(testNewDataset).catch((e) => fail(e)); }); - test('should raise empty field error when a first level field is missing', async () => { - const testNewDataset = createNewDatasetModelWithoutRequiredField(); + test('should raise an empty field error when a first level field is missing', async () => { + const testNewDataset = createNewDatasetModelWithoutFirstLevelRequiredField(); const testMetadataBlock = createNewDatasetMetadataBlockModel(); const metadataBlocksRepositoryStub = {}; const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); @@ -51,4 +52,30 @@ describe('execute', () => { ); }); }); + + test('should raise an error when the provided field value for a multiple field is a string', async () => { + const invalidAuthorFieldValue = 'invalidValue'; + const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); + const testMetadataBlock = createNewDatasetMetadataBlockModel(); + const metadataBlocksRepositoryStub = {}; + const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + const sut = new NewDatasetValidator(metadataBlocksRepositoryStub); + + await sut + .validate(testNewDataset) + .then(() => { + fail('Validation should fail'); + }) + .catch((error) => { + const emptyFieldError = error as FieldValidationError; + assert.match(emptyFieldError.citationBlockName, 'citation'); + assert.match(emptyFieldError.metadataFieldName, 'author'); + assert.match(emptyFieldError.parentMetadataFieldName, undefined); + assert.match( + emptyFieldError.message, + 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of values.', + ); + }); + }); }); From 32ce475b80e4927cff34aa9f82bfdcad3a6d8f31 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 12 Jan 2024 14:30:04 +0000 Subject: [PATCH 06/37] Refactor: NewDatasetValidator unit test --- .../unit/datasets/NewDatasetValidator.test.ts | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index d0bba684..7816e5be 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -17,24 +17,24 @@ describe('execute', () => { sandbox.restore(); }); - test('should not raise validation error when new dataset is valid', async () => { - const testNewDataset = createNewDatasetModel(); + function setupMetadataBlocksRepositoryStub(): IMetadataBlocksRepository { const testMetadataBlock = createNewDatasetMetadataBlockModel(); const metadataBlocksRepositoryStub = {}; const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; - const sut = new NewDatasetValidator(metadataBlocksRepositoryStub); + return metadataBlocksRepositoryStub; + } + + test('should not raise validation error when new dataset is valid', async () => { + const testNewDataset = createNewDatasetModel(); + const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); await sut.validate(testNewDataset).catch((e) => fail(e)); }); test('should raise an empty field error when a first level field is missing', async () => { const testNewDataset = createNewDatasetModelWithoutFirstLevelRequiredField(); - const testMetadataBlock = createNewDatasetMetadataBlockModel(); - const metadataBlocksRepositoryStub = {}; - const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); - metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; - const sut = new NewDatasetValidator(metadataBlocksRepositoryStub); + const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); await sut .validate(testNewDataset) @@ -56,11 +56,7 @@ describe('execute', () => { test('should raise an error when the provided field value for a multiple field is a string', async () => { const invalidAuthorFieldValue = 'invalidValue'; const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); - const testMetadataBlock = createNewDatasetMetadataBlockModel(); - const metadataBlocksRepositoryStub = {}; - const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); - metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; - const sut = new NewDatasetValidator(metadataBlocksRepositoryStub); + const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); await sut .validate(testNewDataset) @@ -68,12 +64,12 @@ describe('execute', () => { fail('Validation should fail'); }) .catch((error) => { - const emptyFieldError = error as FieldValidationError; - assert.match(emptyFieldError.citationBlockName, 'citation'); - assert.match(emptyFieldError.metadataFieldName, 'author'); - assert.match(emptyFieldError.parentMetadataFieldName, undefined); + const fieldValidationError = error as FieldValidationError; + assert.match(fieldValidationError.citationBlockName, 'citation'); + assert.match(fieldValidationError.metadataFieldName, 'author'); + assert.match(fieldValidationError.parentMetadataFieldName, undefined); assert.match( - emptyFieldError.message, + fieldValidationError.message, 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of values.', ); }); From 330689675ae0f6fc940a313972c604c06fd2bb78 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 15 Jan 2024 11:01:25 +0000 Subject: [PATCH 07/37] Added: test case to NewDatasetValidator and refactor --- .../unit/datasets/NewDatasetValidator.test.ts | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index 7816e5be..ed402972 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -9,6 +9,7 @@ import { fail } from 'assert'; import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { EmptyFieldError } from '../../../src/core/domain/useCases/validators/errors/EmptyFieldError'; import { FieldValidationError } from '../../../src/core/domain/useCases/validators/errors/FieldValidationError'; +import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); @@ -25,6 +26,25 @@ describe('execute', () => { return metadataBlocksRepositoryStub; } + async function runValidateExpectingFieldValidationError( + newDataset: NewDataset, + expectedErrorMessage: string, + ): Promise { + const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); + await sut + .validate(newDataset) + .then(() => { + fail('Validation should fail'); + }) + .catch((error) => { + const fieldValidationError = error as T; + assert.match(fieldValidationError.citationBlockName, 'citation'); + assert.match(fieldValidationError.metadataFieldName, 'author'); + assert.match(fieldValidationError.parentMetadataFieldName, undefined); + assert.match(fieldValidationError.message, expectedErrorMessage); + }); + } + test('should not raise validation error when new dataset is valid', async () => { const testNewDataset = createNewDatasetModel(); const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); @@ -33,45 +53,27 @@ describe('execute', () => { }); test('should raise an empty field error when a first level field is missing', async () => { - const testNewDataset = createNewDatasetModelWithoutFirstLevelRequiredField(); - const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); - - await sut - .validate(testNewDataset) - .then(() => { - fail('Validation should fail'); - }) - .catch((error) => { - const emptyFieldError = error as EmptyFieldError; - assert.match(emptyFieldError.citationBlockName, 'citation'); - assert.match(emptyFieldError.metadataFieldName, 'author'); - assert.match(emptyFieldError.parentMetadataFieldName, undefined); - assert.match( - emptyFieldError.message, - 'There was an error when validating the field author from metadata block citation. Reason was: The field should not be empty.', - ); - }); + await runValidateExpectingFieldValidationError( + createNewDatasetModelWithoutFirstLevelRequiredField(), + 'There was an error when validating the field author from metadata block citation. Reason was: The field should not be empty.', + ); }); test('should raise an error when the provided field value for a multiple field is a string', async () => { const invalidAuthorFieldValue = 'invalidValue'; const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); - const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of values.', + ); + }); - await sut - .validate(testNewDataset) - .then(() => { - fail('Validation should fail'); - }) - .catch((error) => { - const fieldValidationError = error as FieldValidationError; - assert.match(fieldValidationError.citationBlockName, 'citation'); - assert.match(fieldValidationError.metadataFieldName, 'author'); - assert.match(fieldValidationError.parentMetadataFieldName, undefined); - assert.match( - fieldValidationError.message, - 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of values.', - ); - }); + test('should raise an error when the provided field value is an array of strings and the field expects an array of objects', async () => { + const invalidAuthorFieldValue = ['invalidValue1', 'invalidValue2']; + const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of sub fields, not strings', + ); }); }); From fa3a6c3918bf61d22cf8b801bb63fc6425b48e74 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 15 Jan 2024 11:16:28 +0000 Subject: [PATCH 08/37] Added: NewDatasetValidator test case for field type error --- test/testHelpers/datasets/newDatasetHelper.ts | 20 ++++++++++++++- .../unit/datasets/NewDatasetValidator.test.ts | 25 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 9d99eb3b..0cf77d79 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -1,7 +1,10 @@ import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; import { MetadataBlock } from '../../../src'; -export const createNewDatasetModel = (authorFieldValue?: NewDatasetMetadataFieldValue | string): NewDataset => { +export const createNewDatasetModel = ( + authorFieldValue?: NewDatasetMetadataFieldValue | string, + alternativeTitleValue?: NewDatasetMetadataFieldValue | string, +): NewDataset => { const validAuthorFieldValue = [ { authorName: 'Admin, Dataverse', @@ -12,6 +15,7 @@ export const createNewDatasetModel = (authorFieldValue?: NewDatasetMetadataField authorAffiliation: 'Dataverse.org', }, ]; + const validAlternativeTitleValue = ['alternative1', 'alternative2']; return { metadataBlockValues: [ { @@ -19,6 +23,7 @@ export const createNewDatasetModel = (authorFieldValue?: NewDatasetMetadataField fields: { title: 'test dataset', author: authorFieldValue !== undefined ? authorFieldValue : validAuthorFieldValue, + alternativeTitle: alternativeTitleValue !== undefined ? alternativeTitleValue : validAlternativeTitleValue, }, }, ], @@ -120,6 +125,19 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { }, }, }, + alternativeTitle: { + name: 'alternativeTitle', + displayName: 'Alternative Title', + title: 'Alternative Title', + type: 'TEXT', + watermark: '', + description: 'Either 1) a title commonly used to refer to the Dataset or 2) an abbreviation of the main title', + multiple: true, + isControlledVocabulary: false, + displayFormat: '', + isRequired: true, + displayOrder: 4, + }, }, }; }; diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index ed402972..a67b3065 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -28,6 +28,7 @@ describe('execute', () => { async function runValidateExpectingFieldValidationError( newDataset: NewDataset, + expectedMetadataFieldName: string, expectedErrorMessage: string, ): Promise { const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); @@ -39,7 +40,7 @@ describe('execute', () => { .catch((error) => { const fieldValidationError = error as T; assert.match(fieldValidationError.citationBlockName, 'citation'); - assert.match(fieldValidationError.metadataFieldName, 'author'); + assert.match(fieldValidationError.metadataFieldName, expectedMetadataFieldName); assert.match(fieldValidationError.parentMetadataFieldName, undefined); assert.match(fieldValidationError.message, expectedErrorMessage); }); @@ -55,6 +56,7 @@ describe('execute', () => { test('should raise an empty field error when a first level field is missing', async () => { await runValidateExpectingFieldValidationError( createNewDatasetModelWithoutFirstLevelRequiredField(), + 'author', 'There was an error when validating the field author from metadata block citation. Reason was: The field should not be empty.', ); }); @@ -64,6 +66,7 @@ describe('execute', () => { const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); await runValidateExpectingFieldValidationError( testNewDataset, + 'author', 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of values.', ); }); @@ -73,7 +76,27 @@ describe('execute', () => { const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); await runValidateExpectingFieldValidationError( testNewDataset, + 'author', 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of sub fields, not strings', ); }); + + test('should raise an error when the provided field value is an array of objects and the field expects an array of strings', async () => { + const invalidAlternativeTitleFieldValue = [ + { + invalidSubfield1: 'invalid value 1', + invalidSubfield2: 'invalid value 2', + }, + { + invalidSubfield1: 'invalid value 1', + invalidSubfield2: 'invalid value 2', + }, + ]; + const testNewDataset = createNewDatasetModel(undefined, invalidAlternativeTitleFieldValue); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'alternativeTitle', + 'There was an error when validating the field alternativeTitle from metadata block citation. Reason was: Expecting an array of strings, not sub fields', + ); + }); }); From 77651142a851108c74008acfa029c85fb6069f1c Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 15 Jan 2024 12:41:00 +0000 Subject: [PATCH 09/37] Added: not multiple field validation logic to NewDatasetValidator --- .../validators/NewDatasetValidator.ts | 36 ++++++++++++++++--- test/testHelpers/datasets/newDatasetHelper.ts | 4 ++- .../unit/datasets/NewDatasetValidator.test.ts | 29 +++++++++++++-- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 7ed745a2..b98c89de 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -22,10 +22,6 @@ export class NewDatasetValidator implements NewResourceValidator { const metadataFieldInfo: MetadataFieldInfo = metadataBlock.metadataFields[metadataFieldKey]; const newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue = metadataBlockValues.fields[metadataFieldKey]; - if (metadataFieldInfo.isRequired && newDatasetMetadataFieldValue == undefined) { - throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName); - } - this.validateMetadataFieldValueType( metadataFieldInfo, metadataFieldKey, @@ -35,17 +31,30 @@ export class NewDatasetValidator implements NewResourceValidator { if (metadataFieldInfo.childMetadataFields != undefined) { // TODO: child fields validation + /* for (const childMetadataFieldKey of Object.keys(metadataFieldInfo.childMetadataFields)) { + const childMetadataFieldInfo: MetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; + const newDatasetChildMetadataFieldValue: NewDatasetMetadataSubFieldValue | NewDatasetMetadataSubFieldValue[] = newDatasetMetadataFieldValue[childMetadataFieldKey] as NewDatasetMetadataSubFieldValue | string + this.validateMetadataFieldValueType( + childMetadataFieldInfo, + childMetadataFieldKey, + newDatasetMetadataFieldValue, + newDatasetMetadataBlockName, + ); + } */ } } } } - validateMetadataFieldValueType( + private validateMetadataFieldValueType( metadataFieldInfo: MetadataFieldInfo, metadataFieldKey: string, newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, newDatasetMetadataBlockName: string, ): void { + if (metadataFieldInfo.isRequired && newDatasetMetadataFieldValue == undefined) { + throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName); + } if (metadataFieldInfo.multiple) { if (!Array.isArray(newDatasetMetadataFieldValue)) { throw this.createValidationError( @@ -80,6 +89,23 @@ export class NewDatasetValidator implements NewResourceValidator { 'The provided array of values is not valid.', ); } + } else { + if (Array.isArray(newDatasetMetadataFieldValue)) { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + undefined, + 'Expecting a single field, not an array.', + ); + } + if (typeof newDatasetMetadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + undefined, + 'Expecting a string, not sub fields.', + ); + } } } diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 0cf77d79..fc6298b5 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -2,9 +2,11 @@ import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/ import { MetadataBlock } from '../../../src'; export const createNewDatasetModel = ( + titleFieldValue?: NewDatasetMetadataFieldValue | string, authorFieldValue?: NewDatasetMetadataFieldValue | string, alternativeTitleValue?: NewDatasetMetadataFieldValue | string, ): NewDataset => { + const validTitle = 'test dataset'; const validAuthorFieldValue = [ { authorName: 'Admin, Dataverse', @@ -21,7 +23,7 @@ export const createNewDatasetModel = ( { name: 'citation', fields: { - title: 'test dataset', + title: titleFieldValue !== undefined ? titleFieldValue : validTitle, author: authorFieldValue !== undefined ? authorFieldValue : validAuthorFieldValue, alternativeTitle: alternativeTitleValue !== undefined ? alternativeTitleValue : validAlternativeTitleValue, }, diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index a67b3065..b0c69bba 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -61,9 +61,32 @@ describe('execute', () => { ); }); + test('should raise an error when the provided field value for an unique field is an array', async () => { + const invalidTitleFieldValue = ['title1', 'title2']; + const testNewDataset = createNewDatasetModel(invalidTitleFieldValue, undefined, undefined); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'title', + 'There was an error when validating the field title from metadata block citation. Reason was: Expecting a single field, not an array.', + ); + }); + + test('should raise an error when the provided field value is an object and the field expects a string', async () => { + const invalidTitleFieldValue = { + invalidSubfield1: 'invalid value 1', + invalidSubfield2: 'invalid value 2', + }; + const testNewDataset = createNewDatasetModel(invalidTitleFieldValue, undefined, undefined); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'title', + 'There was an error when validating the field title from metadata block citation. Reason was: Expecting a string, not sub fields.', + ); + }); + test('should raise an error when the provided field value for a multiple field is a string', async () => { const invalidAuthorFieldValue = 'invalidValue'; - const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); + const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'author', @@ -73,7 +96,7 @@ describe('execute', () => { test('should raise an error when the provided field value is an array of strings and the field expects an array of objects', async () => { const invalidAuthorFieldValue = ['invalidValue1', 'invalidValue2']; - const testNewDataset = createNewDatasetModel(invalidAuthorFieldValue); + const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'author', @@ -92,7 +115,7 @@ describe('execute', () => { invalidSubfield2: 'invalid value 2', }, ]; - const testNewDataset = createNewDatasetModel(undefined, invalidAlternativeTitleFieldValue); + const testNewDataset = createNewDatasetModel(undefined, undefined, invalidAlternativeTitleFieldValue); await runValidateExpectingFieldValidationError( testNewDataset, 'alternativeTitle', From 3deaac7aa107a926da8d3df1613735212966e425 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 08:48:32 +0000 Subject: [PATCH 10/37] Added: child field validation logic and refactor to NewDatasetValidator --- src/datasets/domain/models/NewDataset.ts | 6 +- .../validators/NewDatasetValidator.ts | 206 ++++++++++++------ .../unit/datasets/NewDatasetValidator.test.ts | 40 +++- 3 files changed, 176 insertions(+), 76 deletions(-) diff --git a/src/datasets/domain/models/NewDataset.ts b/src/datasets/domain/models/NewDataset.ts index d7e587ea..eaa0bd73 100644 --- a/src/datasets/domain/models/NewDataset.ts +++ b/src/datasets/domain/models/NewDataset.ts @@ -12,7 +12,7 @@ export type NewDatasetMetadataFields = Record; +export type NewDatasetMetadataChildFieldValue = Record; diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index b98c89de..91ba42c4 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -1,4 +1,4 @@ -import { NewDataset, NewDatasetMetadataFieldValue } from '../../models/NewDataset'; +import { NewDataset, NewDatasetMetadataFieldValue, NewDatasetMetadataChildFieldValue } from '../../models/NewDataset'; import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { MetadataFieldInfo } from '../../../../metadataBlocks'; @@ -22,7 +22,7 @@ export class NewDatasetValidator implements NewResourceValidator { const metadataFieldInfo: MetadataFieldInfo = metadataBlock.metadataFields[metadataFieldKey]; const newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue = metadataBlockValues.fields[metadataFieldKey]; - this.validateMetadataFieldValueType( + this.validateMetadataFieldValue( metadataFieldInfo, metadataFieldKey, newDatasetMetadataFieldValue, @@ -30,82 +30,162 @@ export class NewDatasetValidator implements NewResourceValidator { ); if (metadataFieldInfo.childMetadataFields != undefined) { - // TODO: child fields validation - /* for (const childMetadataFieldKey of Object.keys(metadataFieldInfo.childMetadataFields)) { - const childMetadataFieldInfo: MetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; - const newDatasetChildMetadataFieldValue: NewDatasetMetadataSubFieldValue | NewDatasetMetadataSubFieldValue[] = newDatasetMetadataFieldValue[childMetadataFieldKey] as NewDatasetMetadataSubFieldValue | string - this.validateMetadataFieldValueType( - childMetadataFieldInfo, - childMetadataFieldKey, - newDatasetMetadataFieldValue, + const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); + if (metadataFieldInfo.multiple) { + const newDatasetMetadataFieldChildFieldValues = + newDatasetMetadataFieldValue as NewDatasetMetadataChildFieldValue[]; + for (const metadataChildFieldValue of newDatasetMetadataFieldChildFieldValues) { + this.validateChildMetadataFieldValues( + childMetadataFieldKeys, + metadataFieldInfo, + metadataChildFieldValue, + newDatasetMetadataBlockName, + metadataFieldKey, + ); + } + } else { + const metadataChildFieldValue = newDatasetMetadataFieldValue as NewDatasetMetadataChildFieldValue; + this.validateChildMetadataFieldValues( + childMetadataFieldKeys, + metadataFieldInfo, + metadataChildFieldValue, newDatasetMetadataBlockName, + metadataFieldKey, ); - } */ + } } } } } - private validateMetadataFieldValueType( + private validateChildMetadataFieldValues( + childMetadataFieldKeys: string[], + metadataFieldInfo: MetadataFieldInfo, + metadataChildFieldValue: Record, + newDatasetMetadataBlockName: string, + metadataParentFieldKey: string, + ) { + for (const childMetadataFieldKey of childMetadataFieldKeys) { + const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; + this.validateMetadataFieldValue( + childMetadataFieldInfo, + childMetadataFieldKey, + metadataChildFieldValue[childMetadataFieldKey], + newDatasetMetadataBlockName, + metadataParentFieldKey, + ); + } + } + + private validateMetadataFieldValue( metadataFieldInfo: MetadataFieldInfo, metadataFieldKey: string, newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, newDatasetMetadataBlockName: string, + metadataParentFieldKey?: string, ): void { - if (metadataFieldInfo.isRequired && newDatasetMetadataFieldValue == undefined) { - throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName); + if ( + newDatasetMetadataFieldValue == undefined || + newDatasetMetadataFieldValue == null || + (typeof newDatasetMetadataFieldValue == 'string' && newDatasetMetadataFieldValue.trim() === '') + ) { + if (metadataFieldInfo.isRequired) { + throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName, metadataParentFieldKey); + } else { + return; + } } if (metadataFieldInfo.multiple) { - if (!Array.isArray(newDatasetMetadataFieldValue)) { - throw this.createValidationError( - metadataFieldKey, - newDatasetMetadataBlockName, - undefined, - 'Expecting an array of values.', - ); - } - if (this.isValidArrayType(newDatasetMetadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { - throw this.createValidationError( - metadataFieldKey, - newDatasetMetadataBlockName, - undefined, - 'Expecting an array of sub fields, not strings.', - ); - } else if (this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { - throw this.createValidationError( - metadataFieldKey, - newDatasetMetadataBlockName, - undefined, - 'Expecting an array of strings, not sub fields.', - ); - } else if ( - !this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && - !this.isValidArrayType(newDatasetMetadataFieldValue, 'string') - ) { - throw this.createValidationError( - metadataFieldKey, - newDatasetMetadataBlockName, - undefined, - 'The provided array of values is not valid.', - ); - } + this.validateMultipleMetadataField( + newDatasetMetadataFieldValue, + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + metadataFieldInfo, + ); } else { - if (Array.isArray(newDatasetMetadataFieldValue)) { - throw this.createValidationError( - metadataFieldKey, - newDatasetMetadataBlockName, - undefined, - 'Expecting a single field, not an array.', - ); - } - if (typeof newDatasetMetadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { - throw this.createValidationError( - metadataFieldKey, - newDatasetMetadataBlockName, - undefined, - 'Expecting a string, not sub fields.', - ); - } + this.validateSingleMetadataField( + newDatasetMetadataFieldValue, + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + metadataFieldInfo, + ); + } + } + + private validateMultipleMetadataField( + newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, + metadataFieldKey: string, + newDatasetMetadataBlockName: string, + metadataParentFieldKey: string, + metadataFieldInfo: MetadataFieldInfo, + ) { + if (!Array.isArray(newDatasetMetadataFieldValue)) { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + 'Expecting an array of values.', + ); + } + if (this.isValidArrayType(newDatasetMetadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + 'Expecting an array of child fields, not strings.', + ); + } else if (this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + 'Expecting an array of strings, not child fields.', + ); + } else if ( + !this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && + !this.isValidArrayType(newDatasetMetadataFieldValue, 'string') + ) { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + 'The provided array of values is not valid.', + ); + } + } + + private validateSingleMetadataField( + newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, + metadataFieldKey: string, + newDatasetMetadataBlockName: string, + metadataParentFieldKey: string, + metadataFieldInfo: MetadataFieldInfo, + ) { + if (Array.isArray(newDatasetMetadataFieldValue)) { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + 'Expecting a single field, not an array.', + ); + } + if (typeof newDatasetMetadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + 'Expecting a string, not child fields.', + ); + } + if (typeof newDatasetMetadataFieldValue === 'string' && metadataFieldInfo.type === 'NONE') { + throw this.createValidationError( + metadataFieldKey, + newDatasetMetadataBlockName, + metadataParentFieldKey, + 'Expecting child fields, not a string.', + ); } } diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index b0c69bba..a0c48cd4 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -30,6 +30,7 @@ describe('execute', () => { newDataset: NewDataset, expectedMetadataFieldName: string, expectedErrorMessage: string, + expectedParentMetadataFieldName?: string, ): Promise { const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); await sut @@ -41,7 +42,7 @@ describe('execute', () => { const fieldValidationError = error as T; assert.match(fieldValidationError.citationBlockName, 'citation'); assert.match(fieldValidationError.metadataFieldName, expectedMetadataFieldName); - assert.match(fieldValidationError.parentMetadataFieldName, undefined); + assert.match(fieldValidationError.parentMetadataFieldName, expectedParentMetadataFieldName); assert.match(fieldValidationError.message, expectedErrorMessage); }); } @@ -73,14 +74,14 @@ describe('execute', () => { test('should raise an error when the provided field value is an object and the field expects a string', async () => { const invalidTitleFieldValue = { - invalidSubfield1: 'invalid value 1', - invalidSubfield2: 'invalid value 2', + invalidChildField1: 'invalid value 1', + invalidChildField2: 'invalid value 2', }; const testNewDataset = createNewDatasetModel(invalidTitleFieldValue, undefined, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'title', - 'There was an error when validating the field title from metadata block citation. Reason was: Expecting a string, not sub fields.', + 'There was an error when validating the field title from metadata block citation. Reason was: Expecting a string, not child fields.', ); }); @@ -100,26 +101,45 @@ describe('execute', () => { await runValidateExpectingFieldValidationError( testNewDataset, 'author', - 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of sub fields, not strings', + 'There was an error when validating the field author from metadata block citation. Reason was: Expecting an array of child fields, not strings', ); }); test('should raise an error when the provided field value is an array of objects and the field expects an array of strings', async () => { const invalidAlternativeTitleFieldValue = [ { - invalidSubfield1: 'invalid value 1', - invalidSubfield2: 'invalid value 2', + invalidChildField1: 'invalid value 1', + invalidChildField2: 'invalid value 2', }, { - invalidSubfield1: 'invalid value 1', - invalidSubfield2: 'invalid value 2', + invalidChildField1: 'invalid value 1', + invalidChildField2: 'invalid value 2', }, ]; const testNewDataset = createNewDatasetModel(undefined, undefined, invalidAlternativeTitleFieldValue); await runValidateExpectingFieldValidationError( testNewDataset, 'alternativeTitle', - 'There was an error when validating the field alternativeTitle from metadata block citation. Reason was: Expecting an array of strings, not sub fields', + 'There was an error when validating the field alternativeTitle from metadata block citation. Reason was: Expecting an array of strings, not child fields', + ); + }); + + test('should raise an empty field error when a child field is missing', async () => { + const invalidAuthorFieldValue = [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + { + authorAffiliation: 'Dataverse.org', + }, + ]; + const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'authorName', + 'There was an error when validating the field authorName from metadata block citation with parent field author. Reason was: The field should not be empty.', + 'author', ); }); }); From 822f384a610806655128a967acb9aaf50c022ce5 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 12:53:56 +0000 Subject: [PATCH 11/37] Added: empty required array field error handling and refactor to NewDatasetValidator --- .../errors/ControlledVocabularyFieldError.ts | 18 ++ .../validators/errors/EmptyFieldError.ts | 15 +- .../validators/errors/FieldValidationError.ts | 13 +- .../validators/NewDatasetValidator.ts | 254 +++++++++++------- test/testHelpers/datasets/newDatasetHelper.ts | 6 +- .../unit/datasets/NewDatasetValidator.test.ts | 19 +- 6 files changed, 218 insertions(+), 107 deletions(-) create mode 100644 src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts diff --git a/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts b/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts new file mode 100644 index 00000000..77c0c33c --- /dev/null +++ b/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts @@ -0,0 +1,18 @@ +import { FieldValidationError } from './FieldValidationError'; + +export class ControlledVocabularyFieldError extends FieldValidationError { + constructor( + metadataFieldName: string, + citationBlockName: string, + parentMetadataFieldName?: string, + fieldPosition?: number, + ) { + super( + metadataFieldName, + citationBlockName, + parentMetadataFieldName, + fieldPosition, + 'The field does have a valid controlled vocabulary value.', + ); + } +} diff --git a/src/core/domain/useCases/validators/errors/EmptyFieldError.ts b/src/core/domain/useCases/validators/errors/EmptyFieldError.ts index 52c3ae80..e1ca1d7a 100644 --- a/src/core/domain/useCases/validators/errors/EmptyFieldError.ts +++ b/src/core/domain/useCases/validators/errors/EmptyFieldError.ts @@ -1,7 +1,18 @@ import { FieldValidationError } from './FieldValidationError'; export class EmptyFieldError extends FieldValidationError { - constructor(metadataFieldName: string, citationBlockName: string, parentMetadataFieldName?: string) { - super(metadataFieldName, citationBlockName, parentMetadataFieldName, 'The field should not be empty.'); + constructor( + metadataFieldName: string, + citationBlockName: string, + parentMetadataFieldName?: string, + fieldPosition?: number, + ) { + super( + metadataFieldName, + citationBlockName, + parentMetadataFieldName, + fieldPosition, + 'The field should not be empty.', + ); } } diff --git a/src/core/domain/useCases/validators/errors/FieldValidationError.ts b/src/core/domain/useCases/validators/errors/FieldValidationError.ts index ff1c830b..6efa5e6e 100644 --- a/src/core/domain/useCases/validators/errors/FieldValidationError.ts +++ b/src/core/domain/useCases/validators/errors/FieldValidationError.ts @@ -4,12 +4,22 @@ export class FieldValidationError extends ResourceValidationError { citationBlockName: string; metadataFieldName: string; parentMetadataFieldName?: string; + fieldPosition?: number; - constructor(metadataFieldName: string, citationBlockName: string, parentMetadataFieldName?: string, reason?: string) { + constructor( + metadataFieldName: string, + citationBlockName: string, + parentMetadataFieldName?: string, + fieldPosition?: number, + reason?: string, + ) { let message = `There was an error when validating the field ${metadataFieldName} from metadata block ${citationBlockName}`; if (parentMetadataFieldName) { message += ` with parent field ${parentMetadataFieldName}`; } + if (fieldPosition) { + message += ` in position ${fieldPosition}`; + } if (reason) { message += `. Reason was: ${reason}`; } @@ -17,5 +27,6 @@ export class FieldValidationError extends ResourceValidationError { this.citationBlockName = citationBlockName; this.metadataFieldName = metadataFieldName; this.parentMetadataFieldName = parentMetadataFieldName; + this.fieldPosition = fieldPosition; } } diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 91ba42c4..96d3a9bc 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -5,6 +5,7 @@ import { MetadataFieldInfo } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; import { EmptyFieldError } from '../../../../core/domain/useCases/validators/errors/EmptyFieldError'; import { FieldValidationError } from '../../../../core/domain/useCases/validators/errors/FieldValidationError'; +import { ControlledVocabularyFieldError } from '../../../../core/domain/useCases/validators/errors/ControlledVocabularyFieldError'; export class NewDatasetValidator implements NewResourceValidator { private metadataBlockRepository: IMetadataBlocksRepository; @@ -15,195 +16,252 @@ export class NewDatasetValidator implements NewResourceValidator { async validate(resource: NewDataset): Promise { for (const metadataBlockValues of resource.metadataBlockValues) { - const newDatasetMetadataBlockName = metadataBlockValues.name; + const metadataBlockName = metadataBlockValues.name; - const metadataBlock = await this.metadataBlockRepository.getMetadataBlockByName(newDatasetMetadataBlockName); + const metadataBlock = await this.metadataBlockRepository.getMetadataBlockByName(metadataBlockName); for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { - const metadataFieldInfo: MetadataFieldInfo = metadataBlock.metadataFields[metadataFieldKey]; - const newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue = metadataBlockValues.fields[metadataFieldKey]; - - this.validateMetadataFieldValue( - metadataFieldInfo, + this.validateMetadataField( + metadataBlock.metadataFields[metadataFieldKey], metadataFieldKey, - newDatasetMetadataFieldValue, - newDatasetMetadataBlockName, + metadataBlockValues.fields[metadataFieldKey], + metadataBlockName, ); - - if (metadataFieldInfo.childMetadataFields != undefined) { - const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); - if (metadataFieldInfo.multiple) { - const newDatasetMetadataFieldChildFieldValues = - newDatasetMetadataFieldValue as NewDatasetMetadataChildFieldValue[]; - for (const metadataChildFieldValue of newDatasetMetadataFieldChildFieldValues) { - this.validateChildMetadataFieldValues( - childMetadataFieldKeys, - metadataFieldInfo, - metadataChildFieldValue, - newDatasetMetadataBlockName, - metadataFieldKey, - ); - } - } else { - const metadataChildFieldValue = newDatasetMetadataFieldValue as NewDatasetMetadataChildFieldValue; - this.validateChildMetadataFieldValues( - childMetadataFieldKeys, - metadataFieldInfo, - metadataChildFieldValue, - newDatasetMetadataBlockName, - metadataFieldKey, - ); - } - } } } } - private validateChildMetadataFieldValues( - childMetadataFieldKeys: string[], - metadataFieldInfo: MetadataFieldInfo, - metadataChildFieldValue: Record, - newDatasetMetadataBlockName: string, - metadataParentFieldKey: string, - ) { - for (const childMetadataFieldKey of childMetadataFieldKeys) { - const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; - this.validateMetadataFieldValue( - childMetadataFieldInfo, - childMetadataFieldKey, - metadataChildFieldValue[childMetadataFieldKey], - newDatasetMetadataBlockName, - metadataParentFieldKey, - ); - } - } - - private validateMetadataFieldValue( + private validateMetadataField( metadataFieldInfo: MetadataFieldInfo, metadataFieldKey: string, - newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, - newDatasetMetadataBlockName: string, + metadataFieldValue: NewDatasetMetadataFieldValue, + metadataBlockName: string, metadataParentFieldKey?: string, + metadataFieldPosition?: number, ): void { if ( - newDatasetMetadataFieldValue == undefined || - newDatasetMetadataFieldValue == null || - (typeof newDatasetMetadataFieldValue == 'string' && newDatasetMetadataFieldValue.trim() === '') + metadataFieldValue == undefined || + metadataFieldValue == null || + (typeof metadataFieldValue == 'string' && metadataFieldValue.trim() === '') || + (Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0) ) { if (metadataFieldInfo.isRequired) { - throw new EmptyFieldError(metadataFieldKey, newDatasetMetadataBlockName, metadataParentFieldKey); + throw new EmptyFieldError(metadataFieldKey, metadataBlockName, metadataParentFieldKey, metadataFieldPosition); } else { return; } } if (metadataFieldInfo.multiple) { this.validateMultipleMetadataField( - newDatasetMetadataFieldValue, + metadataFieldValue, metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, metadataFieldInfo, + metadataFieldPosition, ); } else { this.validateSingleMetadataField( - newDatasetMetadataFieldValue, + metadataFieldValue, metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, metadataFieldInfo, + metadataFieldPosition, ); } } private validateMultipleMetadataField( - newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, + metadataFieldValue: NewDatasetMetadataFieldValue, metadataFieldKey: string, - newDatasetMetadataBlockName: string, + metadataBlockName: string, metadataParentFieldKey: string, metadataFieldInfo: MetadataFieldInfo, + metadataFieldPosition: number, ) { - if (!Array.isArray(newDatasetMetadataFieldValue)) { - throw this.createValidationError( + if (!Array.isArray(metadataFieldValue)) { + throw this.createGeneralValidationError( metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, + metadataFieldPosition, 'Expecting an array of values.', ); } - if (this.isValidArrayType(newDatasetMetadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { - throw this.createValidationError( + if (this.isValidArrayType(metadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { + throw this.createGeneralValidationError( metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, + metadataFieldPosition, 'Expecting an array of child fields, not strings.', ); - } else if (this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { - throw this.createValidationError( + } else if (this.isValidArrayType(metadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { + throw this.createGeneralValidationError( metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, + metadataFieldPosition, 'Expecting an array of strings, not child fields.', ); } else if ( - !this.isValidArrayType(newDatasetMetadataFieldValue, 'object') && - !this.isValidArrayType(newDatasetMetadataFieldValue, 'string') + !this.isValidArrayType(metadataFieldValue, 'object') && + !this.isValidArrayType(metadataFieldValue, 'string') ) { - throw this.createValidationError( + throw this.createGeneralValidationError( metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, + metadataFieldPosition, 'The provided array of values is not valid.', ); } + + const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValue[]; + fieldValues.forEach((value, metadataFieldPosition) => { + this.validateFieldValue( + metadataFieldInfo, + value, + metadataBlockName, + metadataFieldKey, + metadataParentFieldKey, + metadataFieldPosition, + ); + }); } private validateSingleMetadataField( - newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, + metadataFieldValue: NewDatasetMetadataFieldValue, metadataFieldKey: string, - newDatasetMetadataBlockName: string, + metadataBlockName: string, metadataParentFieldKey: string, metadataFieldInfo: MetadataFieldInfo, + metadataFieldPosition: number, ) { - if (Array.isArray(newDatasetMetadataFieldValue)) { - throw this.createValidationError( + if (Array.isArray(metadataFieldValue)) { + throw this.createGeneralValidationError( metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, + metadataFieldPosition, 'Expecting a single field, not an array.', ); } - if (typeof newDatasetMetadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { - throw this.createValidationError( + if (typeof metadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { + throw this.createGeneralValidationError( metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, + metadataFieldPosition, 'Expecting a string, not child fields.', ); } - if (typeof newDatasetMetadataFieldValue === 'string' && metadataFieldInfo.type === 'NONE') { - throw this.createValidationError( + if (typeof metadataFieldValue === 'string' && metadataFieldInfo.type === 'NONE') { + throw this.createGeneralValidationError( metadataFieldKey, - newDatasetMetadataBlockName, + metadataBlockName, metadataParentFieldKey, + metadataFieldPosition, 'Expecting child fields, not a string.', ); } + this.validateFieldValue( + metadataFieldInfo, + metadataFieldValue, + metadataBlockName, + metadataFieldKey, + metadataParentFieldKey, + metadataFieldPosition, + ); + } + + private validateFieldValue( + metadataFieldInfo: MetadataFieldInfo, + value: NewDatasetMetadataFieldValue, + metadataBlockName: string, + metadataFieldKey: string, + metadataParentFieldKey: string, + metadataFieldPosition: number, + ) { + if (metadataFieldInfo.isControlledVocabulary) { + this.validateControlledVocabularyFieldValue( + metadataFieldInfo, + value as string, + metadataBlockName, + metadataFieldKey, + metadataParentFieldKey, + metadataFieldPosition, + ); + } else if (metadataFieldInfo.childMetadataFields != undefined) { + this.validateChildMetadataFieldValues( + metadataFieldInfo, + value as NewDatasetMetadataChildFieldValue, + metadataBlockName, + metadataFieldKey, + metadataFieldPosition, + ); + } + } + + private validateControlledVocabularyFieldValue( + metadataFieldInfo: MetadataFieldInfo, + controledVocabularyValue: string, + metadataBlockName: string, + metadataFieldKey: string, + metadataParentFieldKey?: string, + metadataFieldPosition?: number, + ) { + if (!metadataFieldInfo.controlledVocabularyValues.includes(controledVocabularyValue)) { + throw new ControlledVocabularyFieldError( + metadataFieldKey, + metadataBlockName, + metadataParentFieldKey, + metadataFieldPosition, + ); + } + } + + private validateChildMetadataFieldValues( + metadataFieldInfo: MetadataFieldInfo, + metadataChildFieldValue: NewDatasetMetadataChildFieldValue, + metadataBlockName: string, + metadataParentFieldKey: string, + metadataFieldPosition?: number, + ) { + const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); + for (const childMetadataFieldKey of childMetadataFieldKeys) { + const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; + this.validateMetadataField( + childMetadataFieldInfo, + childMetadataFieldKey, + metadataChildFieldValue[childMetadataFieldKey], + metadataBlockName, + metadataParentFieldKey, + metadataFieldPosition, + ); + } } private isValidArrayType( - newDatasetMetadataFieldValue: Array, + metadataFieldValue: Array, expectedType: 'string' | 'object', ): boolean { - return newDatasetMetadataFieldValue.every( - (item: string | NewDatasetMetadataFieldValue) => typeof item === expectedType, - ); + return metadataFieldValue.every((item: string | NewDatasetMetadataFieldValue) => typeof item === expectedType); } - private createValidationError( + private createGeneralValidationError( metadataFieldKey: string, - newDatasetMetadataBlockName: string, + metadataBlockName: string, parentMetadataFieldName: string | undefined, + metadataFieldPosition: number | undefined, reason: string, ): FieldValidationError { - return new FieldValidationError(metadataFieldKey, newDatasetMetadataBlockName, parentMetadataFieldName, reason); + return new FieldValidationError( + metadataFieldKey, + metadataBlockName, + parentMetadataFieldName, + metadataFieldPosition, + reason, + ); } } diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index fc6298b5..2d91d344 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -2,9 +2,9 @@ import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/ import { MetadataBlock } from '../../../src'; export const createNewDatasetModel = ( - titleFieldValue?: NewDatasetMetadataFieldValue | string, - authorFieldValue?: NewDatasetMetadataFieldValue | string, - alternativeTitleValue?: NewDatasetMetadataFieldValue | string, + titleFieldValue?: NewDatasetMetadataFieldValue, + authorFieldValue?: NewDatasetMetadataFieldValue, + alternativeTitleValue?: NewDatasetMetadataFieldValue, ): NewDataset => { const validTitle = 'test dataset'; const validAuthorFieldValue = [ diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index a0c48cd4..c3efb7b1 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -9,7 +9,7 @@ import { fail } from 'assert'; import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { EmptyFieldError } from '../../../src/core/domain/useCases/validators/errors/EmptyFieldError'; import { FieldValidationError } from '../../../src/core/domain/useCases/validators/errors/FieldValidationError'; -import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; +import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); @@ -31,6 +31,7 @@ describe('execute', () => { expectedMetadataFieldName: string, expectedErrorMessage: string, expectedParentMetadataFieldName?: string, + expectedPosition?: number, ): Promise { const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); await sut @@ -43,6 +44,7 @@ describe('execute', () => { assert.match(fieldValidationError.citationBlockName, 'citation'); assert.match(fieldValidationError.metadataFieldName, expectedMetadataFieldName); assert.match(fieldValidationError.parentMetadataFieldName, expectedParentMetadataFieldName); + assert.match(fieldValidationError.fieldPosition, expectedPosition); assert.match(fieldValidationError.message, expectedErrorMessage); }); } @@ -54,7 +56,7 @@ describe('execute', () => { await sut.validate(testNewDataset).catch((e) => fail(e)); }); - test('should raise an empty field error when a first level field is missing', async () => { + test('should raise an empty field error when a first level required string field is missing', async () => { await runValidateExpectingFieldValidationError( createNewDatasetModelWithoutFirstLevelRequiredField(), 'author', @@ -62,6 +64,16 @@ describe('execute', () => { ); }); + test('should raise an empty field error when a first level required array field is empty', async () => { + const invalidAuthorFieldValue : NewDatasetMetadataFieldValue = []; + const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'author', + 'There was an error when validating the field author from metadata block citation. Reason was: The field should not be empty.', + ); + }); + test('should raise an error when the provided field value for an unique field is an array', async () => { const invalidTitleFieldValue = ['title1', 'title2']; const testNewDataset = createNewDatasetModel(invalidTitleFieldValue, undefined, undefined); @@ -138,8 +150,9 @@ describe('execute', () => { await runValidateExpectingFieldValidationError( testNewDataset, 'authorName', - 'There was an error when validating the field authorName from metadata block citation with parent field author. Reason was: The field should not be empty.', + 'There was an error when validating the field authorName from metadata block citation with parent field author in position 1. Reason was: The field should not be empty.', 'author', + 1, ); }); }); From 19c6400169aa5e765eea78e998da74e057c8ad38 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 12:55:53 +0000 Subject: [PATCH 12/37] Refactor: validation methods extracted in NewDatasetValidator --- .../useCases/validators/NewDatasetValidator.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 96d3a9bc..390c7c17 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -41,8 +41,8 @@ export class NewDatasetValidator implements NewResourceValidator { if ( metadataFieldValue == undefined || metadataFieldValue == null || - (typeof metadataFieldValue == 'string' && metadataFieldValue.trim() === '') || - (Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0) + this.isEmptyString(metadataFieldValue) || + this.isEmptyArray(metadataFieldValue) ) { if (metadataFieldInfo.isRequired) { throw new EmptyFieldError(metadataFieldKey, metadataBlockName, metadataParentFieldKey, metadataFieldPosition); @@ -242,6 +242,14 @@ export class NewDatasetValidator implements NewResourceValidator { } } + private isEmptyString(metadataFieldValue: NewDatasetMetadataFieldValue): boolean { + return typeof metadataFieldValue == 'string' && metadataFieldValue.trim() === ''; + } + + private isEmptyArray(metadataFieldValue: NewDatasetMetadataFieldValue): boolean { + return Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0; + } + private isValidArrayType( metadataFieldValue: Array, expectedType: 'string' | 'object', From 6d5028d974586b9979e6aae8936ac5404140156c Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 12:57:58 +0000 Subject: [PATCH 13/37] Refactor: validateMetadataBlock extracted in NewDatasetValidator --- .../validators/NewDatasetValidator.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 390c7c17..684e5f23 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -1,4 +1,9 @@ -import { NewDataset, NewDatasetMetadataFieldValue, NewDatasetMetadataChildFieldValue } from '../../models/NewDataset'; +import { + NewDataset, + NewDatasetMetadataFieldValue, + NewDatasetMetadataChildFieldValue, + NewDatasetMetadataBlockValues, +} from '../../models/NewDataset'; import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { MetadataFieldInfo } from '../../../../metadataBlocks'; @@ -16,17 +21,20 @@ export class NewDatasetValidator implements NewResourceValidator { async validate(resource: NewDataset): Promise { for (const metadataBlockValues of resource.metadataBlockValues) { - const metadataBlockName = metadataBlockValues.name; + await this.validateMetadataBlock(metadataBlockValues); + } + } - const metadataBlock = await this.metadataBlockRepository.getMetadataBlockByName(metadataBlockName); - for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { - this.validateMetadataField( - metadataBlock.metadataFields[metadataFieldKey], - metadataFieldKey, - metadataBlockValues.fields[metadataFieldKey], - metadataBlockName, - ); - } + private async validateMetadataBlock(metadataBlockValues: NewDatasetMetadataBlockValues) { + const metadataBlockName = metadataBlockValues.name; + const metadataBlock = await this.metadataBlockRepository.getMetadataBlockByName(metadataBlockName); + for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { + this.validateMetadataField( + metadataBlock.metadataFields[metadataFieldKey], + metadataFieldKey, + metadataBlockValues.fields[metadataFieldKey], + metadataBlockName, + ); } } From 705cd2c64f1d511e525d52bc4334b7542d235055 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 13:17:24 +0000 Subject: [PATCH 14/37] Added: date format validation to NewDatasetValidator and associated unit test --- .../validators/errors/DateFormatFieldError.ts | 18 ++++++++++ .../validators/NewDatasetValidator.ts | 33 ++++++++++++++++++- test/testHelpers/datasets/newDatasetHelper.ts | 30 +++++++++++++---- .../unit/datasets/NewDatasetValidator.test.ts | 32 +++++++++++++++--- 4 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 src/core/domain/useCases/validators/errors/DateFormatFieldError.ts diff --git a/src/core/domain/useCases/validators/errors/DateFormatFieldError.ts b/src/core/domain/useCases/validators/errors/DateFormatFieldError.ts new file mode 100644 index 00000000..a6b36fa5 --- /dev/null +++ b/src/core/domain/useCases/validators/errors/DateFormatFieldError.ts @@ -0,0 +1,18 @@ +import { FieldValidationError } from './FieldValidationError'; + +export class DateFormatFieldError extends FieldValidationError { + constructor( + metadataFieldName: string, + citationBlockName: string, + parentMetadataFieldName?: string, + fieldPosition?: number, + ) { + super( + metadataFieldName, + citationBlockName, + parentMetadataFieldName, + fieldPosition, + 'The field requires a valid date format (YYYY-MM-DD).', + ); + } +} diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 684e5f23..502d3e4e 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -11,6 +11,7 @@ import { ResourceValidationError } from '../../../../core/domain/useCases/valida import { EmptyFieldError } from '../../../../core/domain/useCases/validators/errors/EmptyFieldError'; import { FieldValidationError } from '../../../../core/domain/useCases/validators/errors/FieldValidationError'; import { ControlledVocabularyFieldError } from '../../../../core/domain/useCases/validators/errors/ControlledVocabularyFieldError'; +import { DateFormatFieldError } from '../../../../core/domain/useCases/validators/errors/DateFormatFieldError'; export class NewDatasetValidator implements NewResourceValidator { private metadataBlockRepository: IMetadataBlocksRepository; @@ -200,7 +201,19 @@ export class NewDatasetValidator implements NewResourceValidator { metadataParentFieldKey, metadataFieldPosition, ); - } else if (metadataFieldInfo.childMetadataFields != undefined) { + } + + if (metadataFieldInfo.type == 'DATE') { + this.validateDateFieldValue( + value as string, + metadataBlockName, + metadataFieldKey, + metadataParentFieldKey, + metadataFieldPosition, + ); + } + + if (metadataFieldInfo.childMetadataFields != undefined) { this.validateChildMetadataFieldValues( metadataFieldInfo, value as NewDatasetMetadataChildFieldValue, @@ -229,6 +242,24 @@ export class NewDatasetValidator implements NewResourceValidator { } } + private validateDateFieldValue( + dateFieldValue: string, + metadataBlockName: string, + metadataFieldKey: string, + metadataParentFieldKey?: string, + metadataFieldPosition?: number, + ) { + const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateFormatRegex.test(dateFieldValue)) { + throw new DateFormatFieldError( + metadataFieldKey, + metadataBlockName, + metadataParentFieldKey, + metadataFieldPosition, + ); + } + } + private validateChildMetadataFieldValues( metadataFieldInfo: MetadataFieldInfo, metadataChildFieldValue: NewDatasetMetadataChildFieldValue, diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 2d91d344..19445326 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -4,7 +4,8 @@ import { MetadataBlock } from '../../../src'; export const createNewDatasetModel = ( titleFieldValue?: NewDatasetMetadataFieldValue, authorFieldValue?: NewDatasetMetadataFieldValue, - alternativeTitleValue?: NewDatasetMetadataFieldValue, + alternativeRequiredTitleValue?: NewDatasetMetadataFieldValue, + timePeriodCoveredStartValue?: NewDatasetMetadataFieldValue, ): NewDataset => { const validTitle = 'test dataset'; const validAuthorFieldValue = [ @@ -17,7 +18,7 @@ export const createNewDatasetModel = ( authorAffiliation: 'Dataverse.org', }, ]; - const validAlternativeTitleValue = ['alternative1', 'alternative2']; + const validAlternativeRequiredTitleValue = ['alternative1', 'alternative2']; return { metadataBlockValues: [ { @@ -25,7 +26,11 @@ export const createNewDatasetModel = ( fields: { title: titleFieldValue !== undefined ? titleFieldValue : validTitle, author: authorFieldValue !== undefined ? authorFieldValue : validAuthorFieldValue, - alternativeTitle: alternativeTitleValue !== undefined ? alternativeTitleValue : validAlternativeTitleValue, + alternativeRequiredTitle: + alternativeRequiredTitleValue !== undefined + ? alternativeRequiredTitleValue + : validAlternativeRequiredTitleValue, + ...(timePeriodCoveredStartValue && { timePeriodCoveredStart: timePeriodCoveredStartValue }), }, }, ], @@ -127,9 +132,9 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { }, }, }, - alternativeTitle: { - name: 'alternativeTitle', - displayName: 'Alternative Title', + alternativeRequiredTitle: { + name: 'alternativeRequiredTitle', + displayName: 'Alternative Required Title', title: 'Alternative Title', type: 'TEXT', watermark: '', @@ -140,6 +145,19 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { isRequired: true, displayOrder: 4, }, + timePeriodCoveredStart: { + name: 'timePeriodCoveredStart', + displayName: 'Time Period Start Date', + title: 'Start Date', + type: 'DATE', + watermark: 'YYYY-MM-DD', + description: 'The start date of the time period that the data refer to', + multiple: false, + isControlledVocabulary: false, + displayFormat: '#NAME: #VALUE ', + isRequired: false, + displayOrder: 4, + }, }, }; }; diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index c3efb7b1..07dad8d2 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -65,7 +65,7 @@ describe('execute', () => { }); test('should raise an empty field error when a first level required array field is empty', async () => { - const invalidAuthorFieldValue : NewDatasetMetadataFieldValue = []; + const invalidAuthorFieldValue: NewDatasetMetadataFieldValue = []; const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); await runValidateExpectingFieldValidationError( testNewDataset, @@ -131,12 +131,12 @@ describe('execute', () => { const testNewDataset = createNewDatasetModel(undefined, undefined, invalidAlternativeTitleFieldValue); await runValidateExpectingFieldValidationError( testNewDataset, - 'alternativeTitle', - 'There was an error when validating the field alternativeTitle from metadata block citation. Reason was: Expecting an array of strings, not child fields', + 'alternativeRequiredTitle', + 'There was an error when validating the field alternativeRequiredTitle from metadata block citation. Reason was: Expecting an array of strings, not child fields', ); }); - test('should raise an empty field error when a child field is missing', async () => { + test('should raise an empty field error when a required child field is missing', async () => { const invalidAuthorFieldValue = [ { authorName: 'Admin, Dataverse', @@ -155,4 +155,28 @@ describe('execute', () => { 1, ); }); + + test('should not raise an empty field error when a not required child field is missing', async () => { + const authorFieldValue = [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + { + authorName: 'John, Doe', + }, + ]; + const testNewDataset = createNewDatasetModel(undefined, authorFieldValue, undefined); + const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); + await sut.validate(testNewDataset).catch((e) => fail(e)); + }); + + test('should raise a date format validation error when a date field has an invalid format', async () => { + const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, '1-1-2020'); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'timePeriodCoveredStart', + 'There was an error when validating the field timePeriodCoveredStart from metadata block citation. Reason was: The field requires a valid date format (YYYY-MM-DD).', + ); + }); }); From 335fcecbed42a7532091bc559c7913b685c216d0 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 13:35:40 +0000 Subject: [PATCH 15/37] Added: test cases for controlled vocabulary and date fields --- .../errors/ControlledVocabularyFieldError.ts | 2 +- test/testHelpers/datasets/newDatasetHelper.ts | 72 ++++++++++++++++++- .../unit/datasets/NewDatasetValidator.test.ts | 25 ++++++- 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts b/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts index 77c0c33c..b628f53f 100644 --- a/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts +++ b/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts @@ -12,7 +12,7 @@ export class ControlledVocabularyFieldError extends FieldValidationError { citationBlockName, parentMetadataFieldName, fieldPosition, - 'The field does have a valid controlled vocabulary value.', + 'The field does not have a valid controlled vocabulary value.', ); } } diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 19445326..f24fab2d 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -6,6 +6,7 @@ export const createNewDatasetModel = ( authorFieldValue?: NewDatasetMetadataFieldValue, alternativeRequiredTitleValue?: NewDatasetMetadataFieldValue, timePeriodCoveredStartValue?: NewDatasetMetadataFieldValue, + contributorTypeValue?: NewDatasetMetadataFieldValue, ): NewDataset => { const validTitle = 'test dataset'; const validAuthorFieldValue = [ @@ -31,6 +32,14 @@ export const createNewDatasetModel = ( ? alternativeRequiredTitleValue : validAlternativeRequiredTitleValue, ...(timePeriodCoveredStartValue && { timePeriodCoveredStart: timePeriodCoveredStartValue }), + ...(contributorTypeValue && { + contributor: [ + { + contributorName: 'Admin, Dataverse', + contributorType: contributorTypeValue as string, + }, + ], + }), }, }, ], @@ -156,7 +165,68 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { isControlledVocabulary: false, displayFormat: '#NAME: #VALUE ', isRequired: false, - displayOrder: 4, + displayOrder: 5, + }, + contributor: { + name: 'contributor', + displayName: 'Contributor', + title: 'Contributor', + type: 'NONE', + watermark: '', + description: + 'The entity, such as a person or organization, responsible for collecting, managing, or otherwise contributing to the development of the Dataset', + multiple: true, + isControlledVocabulary: false, + displayFormat: ':', + isRequired: false, + displayOrder: 6, + childMetadataFields: { + contributorType: { + name: 'contributorType', + displayName: 'Contributor Type', + title: 'Type', + type: 'TEXT', + watermark: '', + description: 'Indicates the type of contribution made to the dataset', + multiple: false, + isControlledVocabulary: true, + displayFormat: '#VALUE ', + isRequired: false, + displayOrder: 7, + controlledVocabularyValues: [ + 'Data Collector', + 'Data Curator', + 'Data Manager', + 'Editor', + 'Funder', + 'Hosting Institution', + 'Project Leader', + 'Project Manager', + 'Project Member', + 'Related Person', + 'Researcher', + 'Research Group', + 'Rights Holder', + 'Sponsor', + 'Supervisor', + 'Work Package Leader', + 'Other', + ], + }, + contributorName: { + name: 'contributorName', + displayName: 'Contributor Name', + title: 'Name', + type: 'TEXT', + watermark: '1) FamilyName, GivenName or 2) Organization', + description: "The name of the contributor, e.g. the person's name or the name of an organization", + multiple: false, + isControlledVocabulary: false, + displayFormat: '#VALUE', + isRequired: true, + displayOrder: 8, + }, + }, }, }, }; diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index 07dad8d2..cdd14ce9 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -49,7 +49,7 @@ describe('execute', () => { }); } - test('should not raise validation error when new dataset is valid', async () => { + test('should not raise a validation error when a new dataset with only the required fields is valid', async () => { const testNewDataset = createNewDatasetModel(); const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); @@ -179,4 +179,27 @@ describe('execute', () => { 'There was an error when validating the field timePeriodCoveredStart from metadata block citation. Reason was: The field requires a valid date format (YYYY-MM-DD).', ); }); + + test('should not raise a date format validation error when a date field has a valid format', async () => { + const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, '2020-01-01'); + const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); + await sut.validate(testNewDataset).catch((e) => fail(e)); + }); + + test('should raise a controlled vocabulary error when a controlled vocabulary field has an invalid format', async () => { + const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, undefined, 'Wrong Value'); + await runValidateExpectingFieldValidationError( + testNewDataset, + 'contributorType', + 'There was an error when validating the field contributorType from metadata block citation with parent field contributor. Reason was: The field does not have a valid controlled vocabulary value.', + 'contributor', + 0, + ); + }); + + test('should not raise a controlled vocabulary error when the value for a controlled vocabulary field is correct', async () => { + const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, undefined, 'Project Member'); + const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); + await sut.validate(testNewDataset).catch((e) => fail(e)); + }); }); From b577bc829370c31848ff4c1f33c9f2a289f05fbd Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 13:50:21 +0000 Subject: [PATCH 16/37] Refactor: dataset validator resource validation errors localtion --- .../domain/useCases/validators/NewDatasetValidator.ts | 8 ++++---- .../validators/errors/ControlledVocabularyFieldError.ts | 0 .../useCases/validators/errors/DateFormatFieldError.ts | 0 .../domain/useCases/validators/errors/EmptyFieldError.ts | 0 .../useCases/validators/errors/FieldValidationError.ts | 2 +- test/unit/datasets/NewDatasetValidator.test.ts | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) rename src/{core => datasets}/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts (100%) rename src/{core => datasets}/domain/useCases/validators/errors/DateFormatFieldError.ts (100%) rename src/{core => datasets}/domain/useCases/validators/errors/EmptyFieldError.ts (100%) rename src/{core => datasets}/domain/useCases/validators/errors/FieldValidationError.ts (88%) diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 502d3e4e..61b1dfbc 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -8,10 +8,10 @@ import { NewResourceValidator } from '../../../../core/domain/useCases/validator import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { MetadataFieldInfo } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; -import { EmptyFieldError } from '../../../../core/domain/useCases/validators/errors/EmptyFieldError'; -import { FieldValidationError } from '../../../../core/domain/useCases/validators/errors/FieldValidationError'; -import { ControlledVocabularyFieldError } from '../../../../core/domain/useCases/validators/errors/ControlledVocabularyFieldError'; -import { DateFormatFieldError } from '../../../../core/domain/useCases/validators/errors/DateFormatFieldError'; +import { EmptyFieldError } from './errors/EmptyFieldError'; +import { FieldValidationError } from './errors/FieldValidationError'; +import { ControlledVocabularyFieldError } from './errors/ControlledVocabularyFieldError'; +import { DateFormatFieldError } from './errors/DateFormatFieldError'; export class NewDatasetValidator implements NewResourceValidator { private metadataBlockRepository: IMetadataBlocksRepository; diff --git a/src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts b/src/datasets/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts similarity index 100% rename from src/core/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts rename to src/datasets/domain/useCases/validators/errors/ControlledVocabularyFieldError.ts diff --git a/src/core/domain/useCases/validators/errors/DateFormatFieldError.ts b/src/datasets/domain/useCases/validators/errors/DateFormatFieldError.ts similarity index 100% rename from src/core/domain/useCases/validators/errors/DateFormatFieldError.ts rename to src/datasets/domain/useCases/validators/errors/DateFormatFieldError.ts diff --git a/src/core/domain/useCases/validators/errors/EmptyFieldError.ts b/src/datasets/domain/useCases/validators/errors/EmptyFieldError.ts similarity index 100% rename from src/core/domain/useCases/validators/errors/EmptyFieldError.ts rename to src/datasets/domain/useCases/validators/errors/EmptyFieldError.ts diff --git a/src/core/domain/useCases/validators/errors/FieldValidationError.ts b/src/datasets/domain/useCases/validators/errors/FieldValidationError.ts similarity index 88% rename from src/core/domain/useCases/validators/errors/FieldValidationError.ts rename to src/datasets/domain/useCases/validators/errors/FieldValidationError.ts index 6efa5e6e..2c3c6a2e 100644 --- a/src/core/domain/useCases/validators/errors/FieldValidationError.ts +++ b/src/datasets/domain/useCases/validators/errors/FieldValidationError.ts @@ -1,4 +1,4 @@ -import { ResourceValidationError } from './ResourceValidationError'; +import { ResourceValidationError } from '../../../../../core/domain/useCases/validators/errors/ResourceValidationError'; export class FieldValidationError extends ResourceValidationError { citationBlockName: string; diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index cdd14ce9..ba5713e9 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -7,8 +7,8 @@ import { } from '../../testHelpers/datasets/newDatasetHelper'; import { fail } from 'assert'; import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; -import { EmptyFieldError } from '../../../src/core/domain/useCases/validators/errors/EmptyFieldError'; -import { FieldValidationError } from '../../../src/core/domain/useCases/validators/errors/FieldValidationError'; +import { EmptyFieldError } from '../../../src/datasets/domain/useCases/validators/errors/EmptyFieldError'; +import { FieldValidationError } from '../../../src/datasets/domain/useCases/validators/errors/FieldValidationError'; import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; describe('execute', () => { From 9fda8b5167712f902e81363b050e64ecf2aaee71 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 16 Jan 2024 13:52:32 +0000 Subject: [PATCH 17/37] Added: createDataset use case export --- src/datasets/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 07a1574a..64fd5b63 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -7,6 +7,9 @@ import { GetPrivateUrlDatasetCitation } from './domain/useCases/GetPrivateUrlDat import { GetDatasetUserPermissions } from './domain/useCases/GetDatasetUserPermissions'; import { GetDatasetLocks } from './domain/useCases/GetDatasetLocks'; import { GetAllDatasetPreviews } from './domain/useCases/GetAllDatasetPreviews'; +import { NewDatasetValidator } from './domain/useCases/validators/NewDatasetValidator'; +import { MetadataBlocksRepository } from '../metadataBlocks/infra/repositories/MetadataBlocksRepository'; +import { CreateDataset } from './domain/useCases/CreateDataset'; const datasetsRepository = new DatasetsRepository(); @@ -19,6 +22,9 @@ const getDatasetUserPermissions = new GetDatasetUserPermissions(datasetsReposito const getDatasetLocks = new GetDatasetLocks(datasetsRepository); const getAllDatasetPreviews = new GetAllDatasetPreviews(datasetsRepository); +const newDatasetValidator = new NewDatasetValidator(new MetadataBlocksRepository()); +const createDataset = new CreateDataset(datasetsRepository, newDatasetValidator); + export { getDatasetSummaryFieldNames, getDataset, @@ -28,6 +34,7 @@ export { getDatasetUserPermissions, getDatasetLocks, getAllDatasetPreviews, + createDataset, }; export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion'; export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions'; From b36c23c7c1118e408d1d12a7aa4d8bacc53f43e6 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 17 Jan 2024 10:50:56 +0000 Subject: [PATCH 18/37] Refactor: using NewDatasetMetadataFieldAndValueInfo to group multiple params in NewDatasetValidator --- .../validators/NewDatasetValidator.ts | 256 ++++++------------ 1 file changed, 86 insertions(+), 170 deletions(-) diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 61b1dfbc..88660e95 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -13,6 +13,15 @@ import { FieldValidationError } from './errors/FieldValidationError'; import { ControlledVocabularyFieldError } from './errors/ControlledVocabularyFieldError'; import { DateFormatFieldError } from './errors/DateFormatFieldError'; +export interface NewDatasetMetadataFieldAndValueInfo { + metadataFieldInfo: MetadataFieldInfo; + metadataFieldKey: string; + metadataFieldValue: NewDatasetMetadataFieldValue; + metadataBlockName: string; + metadataParentFieldKey?: string; + metadataFieldPosition?: number; +} + export class NewDatasetValidator implements NewResourceValidator { private metadataBlockRepository: IMetadataBlocksRepository; @@ -30,23 +39,18 @@ export class NewDatasetValidator implements NewResourceValidator { const metadataBlockName = metadataBlockValues.name; const metadataBlock = await this.metadataBlockRepository.getMetadataBlockByName(metadataBlockName); for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { - this.validateMetadataField( - metadataBlock.metadataFields[metadataFieldKey], - metadataFieldKey, - metadataBlockValues.fields[metadataFieldKey], - metadataBlockName, - ); + this.validateMetadataField({ + metadataFieldInfo: metadataBlock.metadataFields[metadataFieldKey], + metadataFieldKey: metadataFieldKey, + metadataFieldValue: metadataBlockValues.fields[metadataFieldKey], + metadataBlockName: metadataBlockName, + }); } } - private validateMetadataField( - metadataFieldInfo: MetadataFieldInfo, - metadataFieldKey: string, - metadataFieldValue: NewDatasetMetadataFieldValue, - metadataBlockName: string, - metadataParentFieldKey?: string, - metadataFieldPosition?: number, - ): void { + private validateMetadataField(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { + const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; if ( metadataFieldValue == undefined || metadataFieldValue == null || @@ -54,63 +58,37 @@ export class NewDatasetValidator implements NewResourceValidator { this.isEmptyArray(metadataFieldValue) ) { if (metadataFieldInfo.isRequired) { - throw new EmptyFieldError(metadataFieldKey, metadataBlockName, metadataParentFieldKey, metadataFieldPosition); + throw new EmptyFieldError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + ); } else { return; } } if (metadataFieldInfo.multiple) { - this.validateMultipleMetadataField( - metadataFieldValue, - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldInfo, - metadataFieldPosition, - ); + this.validateMultipleMetadataField(newDatasetMetadataFieldAndValueInfo); } else { - this.validateSingleMetadataField( - metadataFieldValue, - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldInfo, - metadataFieldPosition, - ); + this.validateSingleMetadataField(newDatasetMetadataFieldAndValueInfo); } } - private validateMultipleMetadataField( - metadataFieldValue: NewDatasetMetadataFieldValue, - metadataFieldKey: string, - metadataBlockName: string, - metadataParentFieldKey: string, - metadataFieldInfo: MetadataFieldInfo, - metadataFieldPosition: number, - ) { + private validateMultipleMetadataField(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; if (!Array.isArray(metadataFieldValue)) { - throw this.createGeneralValidationError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, - 'Expecting an array of values.', - ); + throw this.createGeneralValidationError(newDatasetMetadataFieldAndValueInfo, 'Expecting an array of values.'); } if (this.isValidArrayType(metadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { throw this.createGeneralValidationError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo, 'Expecting an array of child fields, not strings.', ); } else if (this.isValidArrayType(metadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { throw this.createGeneralValidationError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo, 'Expecting an array of strings, not child fields.', ); } else if ( @@ -118,166 +96,107 @@ export class NewDatasetValidator implements NewResourceValidator { !this.isValidArrayType(metadataFieldValue, 'string') ) { throw this.createGeneralValidationError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo, 'The provided array of values is not valid.', ); } const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValue[]; fieldValues.forEach((value, metadataFieldPosition) => { - this.validateFieldValue( - metadataFieldInfo, - value, - metadataBlockName, - metadataFieldKey, - metadataParentFieldKey, - metadataFieldPosition, - ); + this.validateFieldValue({ + metadataFieldInfo: metadataFieldInfo, + metadataFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldValue: value, + metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, + metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldPosition: metadataFieldPosition, + }); }); } - private validateSingleMetadataField( - metadataFieldValue: NewDatasetMetadataFieldValue, - metadataFieldKey: string, - metadataBlockName: string, - metadataParentFieldKey: string, - metadataFieldInfo: MetadataFieldInfo, - metadataFieldPosition: number, - ) { + private validateSingleMetadataField(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; if (Array.isArray(metadataFieldValue)) { throw this.createGeneralValidationError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo, 'Expecting a single field, not an array.', ); } if (typeof metadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { throw this.createGeneralValidationError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo, 'Expecting a string, not child fields.', ); } if (typeof metadataFieldValue === 'string' && metadataFieldInfo.type === 'NONE') { throw this.createGeneralValidationError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo, 'Expecting child fields, not a string.', ); } - this.validateFieldValue( - metadataFieldInfo, - metadataFieldValue, - metadataBlockName, - metadataFieldKey, - metadataParentFieldKey, - metadataFieldPosition, - ); + this.validateFieldValue(newDatasetMetadataFieldAndValueInfo); } - private validateFieldValue( - metadataFieldInfo: MetadataFieldInfo, - value: NewDatasetMetadataFieldValue, - metadataBlockName: string, - metadataFieldKey: string, - metadataParentFieldKey: string, - metadataFieldPosition: number, - ) { + private validateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; if (metadataFieldInfo.isControlledVocabulary) { - this.validateControlledVocabularyFieldValue( - metadataFieldInfo, - value as string, - metadataBlockName, - metadataFieldKey, - metadataParentFieldKey, - metadataFieldPosition, - ); + this.validateControlledVocabularyFieldValue(newDatasetMetadataFieldAndValueInfo); } if (metadataFieldInfo.type == 'DATE') { - this.validateDateFieldValue( - value as string, - metadataBlockName, - metadataFieldKey, - metadataParentFieldKey, - metadataFieldPosition, - ); + this.validateDateFieldValue(newDatasetMetadataFieldAndValueInfo); } if (metadataFieldInfo.childMetadataFields != undefined) { - this.validateChildMetadataFieldValues( - metadataFieldInfo, - value as NewDatasetMetadataChildFieldValue, - metadataBlockName, - metadataFieldKey, - metadataFieldPosition, - ); + this.validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo); } } private validateControlledVocabularyFieldValue( - metadataFieldInfo: MetadataFieldInfo, - controledVocabularyValue: string, - metadataBlockName: string, - metadataFieldKey: string, - metadataParentFieldKey?: string, - metadataFieldPosition?: number, + newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, ) { - if (!metadataFieldInfo.controlledVocabularyValues.includes(controledVocabularyValue)) { + if ( + !newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.controlledVocabularyValues.includes( + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string, + ) + ) { throw new ControlledVocabularyFieldError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, ); } } - private validateDateFieldValue( - dateFieldValue: string, - metadataBlockName: string, - metadataFieldKey: string, - metadataParentFieldKey?: string, - metadataFieldPosition?: number, - ) { + private validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; - if (!dateFormatRegex.test(dateFieldValue)) { + if (!dateFormatRegex.test(newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string)) { throw new DateFormatFieldError( - metadataFieldKey, - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, ); } } - private validateChildMetadataFieldValues( - metadataFieldInfo: MetadataFieldInfo, - metadataChildFieldValue: NewDatasetMetadataChildFieldValue, - metadataBlockName: string, - metadataParentFieldKey: string, - metadataFieldPosition?: number, - ) { + private validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); for (const childMetadataFieldKey of childMetadataFieldKeys) { const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; - this.validateMetadataField( - childMetadataFieldInfo, - childMetadataFieldKey, - metadataChildFieldValue[childMetadataFieldKey], - metadataBlockName, - metadataParentFieldKey, - metadataFieldPosition, - ); + this.validateMetadataField({ + metadataFieldInfo: childMetadataFieldInfo, + metadataFieldKey: childMetadataFieldKey, + metadataFieldValue: ( + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValue + )[childMetadataFieldKey], + metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, + metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + }); } } @@ -297,17 +216,14 @@ export class NewDatasetValidator implements NewResourceValidator { } private createGeneralValidationError( - metadataFieldKey: string, - metadataBlockName: string, - parentMetadataFieldName: string | undefined, - metadataFieldPosition: number | undefined, + newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, reason: string, ): FieldValidationError { return new FieldValidationError( - metadataFieldKey, - metadataBlockName, - parentMetadataFieldName, - metadataFieldPosition, + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, reason, ); } From c9bdf84ba1afda2d09a9cf106757e18d65b3d3a6 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 17 Jan 2024 15:18:33 +0000 Subject: [PATCH 19/37] Added: typeClass property to MetadataFieldInfo --- src/metadataBlocks/domain/models/MetadataBlock.ts | 1 + .../transformers/metadataBlockTransformers.ts | 6 +++--- test/testHelpers/datasets/newDatasetHelper.ts | 9 +++++++++ test/testHelpers/metadataBlocks/metadataBlockHelper.ts | 8 ++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/metadataBlocks/domain/models/MetadataBlock.ts b/src/metadataBlocks/domain/models/MetadataBlock.ts index dc60b5b1..834d7908 100644 --- a/src/metadataBlocks/domain/models/MetadataBlock.ts +++ b/src/metadataBlocks/domain/models/MetadataBlock.ts @@ -10,6 +10,7 @@ export interface MetadataFieldInfo { displayName: string; title: string; type: string; + typeClass: string; watermark: string; description: string; multiple: boolean; diff --git a/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts b/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts index 35685156..5f29b60c 100644 --- a/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts +++ b/src/metadataBlocks/infra/repositories/transformers/metadataBlockTransformers.ts @@ -31,9 +31,9 @@ const transformPayloadMetadataFieldInfo = ( multiple: metadataFieldInfoPayload.multiple, isControlledVocabulary: metadataFieldInfoPayload.isControlledVocabulary, displayFormat: metadataFieldInfoPayload.displayFormat, - // TODO - isRequired: true, - displayOrder: 0, + isRequired: metadataFieldInfoPayload.isRequired, + displayOrder: metadataFieldInfoPayload.displayOrder, + typeClass: metadataFieldInfoPayload.typeClass, }; if (!isChild && metadataFieldInfoPayload.hasOwnProperty('childFields')) { const childMetadataFieldsPayload = metadataFieldInfoPayload.childFields; diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index f24fab2d..642e4752 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -99,6 +99,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: true, displayOrder: 0, + typeClass: 'primitive', }, author: { name: 'author', @@ -112,6 +113,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: true, displayOrder: 1, + typeClass: 'compound', childMetadataFields: { authorName: { name: 'authorName', @@ -125,6 +127,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: true, displayOrder: 2, + typeClass: 'primitive', }, authorAffiliation: { name: 'authorAffiliation', @@ -138,6 +141,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: false, displayOrder: 3, + typeClass: 'primitive', }, }, }, @@ -153,6 +157,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: '', isRequired: true, displayOrder: 4, + typeClass: 'primitive', }, timePeriodCoveredStart: { name: 'timePeriodCoveredStart', @@ -166,6 +171,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: '#NAME: #VALUE ', isRequired: false, displayOrder: 5, + typeClass: 'primitive', }, contributor: { name: 'contributor', @@ -180,6 +186,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: ':', isRequired: false, displayOrder: 6, + typeClass: 'compound', childMetadataFields: { contributorType: { name: 'contributorType', @@ -212,6 +219,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { 'Work Package Leader', 'Other', ], + typeClass: 'controlledVocabulary', }, contributorName: { name: 'contributorName', @@ -225,6 +233,7 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: true, displayOrder: 8, + typeClass: 'primitive', }, }, }, diff --git a/test/testHelpers/metadataBlocks/metadataBlockHelper.ts b/test/testHelpers/metadataBlocks/metadataBlockHelper.ts index 4ff63661..28b013be 100644 --- a/test/testHelpers/metadataBlocks/metadataBlockHelper.ts +++ b/test/testHelpers/metadataBlocks/metadataBlockHelper.ts @@ -18,6 +18,7 @@ export const createMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: true, displayOrder: 0, + typeClass: 'primitive', }, testField2: { name: 'testName2', @@ -31,6 +32,7 @@ export const createMetadataBlockModel = (): MetadataBlock => { displayFormat: '', isRequired: true, displayOrder: 0, + typeClass: 'compound', childMetadataFields: { testField3: { name: 'testName3', @@ -44,6 +46,7 @@ export const createMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: true, displayOrder: 0, + typeClass: 'primitive', }, testField4: { name: 'testName4', @@ -57,6 +60,7 @@ export const createMetadataBlockModel = (): MetadataBlock => { displayFormat: '#VALUE', isRequired: true, displayOrder: 0, + typeClass: 'primitive', }, }, }, @@ -82,6 +86,7 @@ export const createMetadataBlockPayload = (): any => { displayFormat: '#VALUE', isRequired: true, displayOrder: 0, + typeClass: 'primitive', }, testField2: { name: 'testName2', @@ -95,6 +100,7 @@ export const createMetadataBlockPayload = (): any => { displayFormat: '', isRequired: true, displayOrder: 0, + typeClass: 'compound', childFields: { testField3: { name: 'testName3', @@ -108,6 +114,7 @@ export const createMetadataBlockPayload = (): any => { displayFormat: '#VALUE', isRequired: true, displayOrder: 0, + typeClass: 'primitive', }, testField4: { name: 'testName4', @@ -121,6 +128,7 @@ export const createMetadataBlockPayload = (): any => { displayFormat: '#VALUE', isRequired: true, displayOrder: 0, + typeClass: 'primitive', }, }, }, From 76775e464be22308f25768c1e3a091c45ef0c485 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 18 Jan 2024 13:24:54 +0000 Subject: [PATCH 20/37] Added: newDatasetTransformers WIP and calculating metadatablocks before calling validation and repository method --- .../validators/NewResourceValidator.ts | 5 +- src/datasets/domain/models/NewDataset.ts | 3 + .../repositories/IDatasetsRepository.ts | 3 +- src/datasets/domain/useCases/CreateDataset.ts | 31 +++- .../validators/NewDatasetValidator.ts | 24 ++-- src/datasets/index.ts | 4 +- .../infra/repositories/DatasetsRepository.ts | 5 +- .../transformers/newDatasetTransformers.ts | 135 ++++++++++++++++++ test/unit/datasets/CreateDataset.test.ts | 34 +++-- .../unit/datasets/NewDatasetValidator.test.ts | 30 ++-- 10 files changed, 216 insertions(+), 58 deletions(-) create mode 100644 src/datasets/infra/repositories/transformers/newDatasetTransformers.ts diff --git a/src/core/domain/useCases/validators/NewResourceValidator.ts b/src/core/domain/useCases/validators/NewResourceValidator.ts index a816bd07..2abb70ae 100644 --- a/src/core/domain/useCases/validators/NewResourceValidator.ts +++ b/src/core/domain/useCases/validators/NewResourceValidator.ts @@ -1,5 +1,6 @@ import { ResourceValidationError } from './errors/ResourceValidationError'; -export interface NewResourceValidator { - validate(resource: T): Promise; +export interface NewResourceValidator { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + validate(...args: any[]): Promise; } diff --git a/src/datasets/domain/models/NewDataset.ts b/src/datasets/domain/models/NewDataset.ts index eaa0bd73..2795c192 100644 --- a/src/datasets/domain/models/NewDataset.ts +++ b/src/datasets/domain/models/NewDataset.ts @@ -1,4 +1,7 @@ +import { DatasetLicense } from './Dataset'; + export interface NewDataset { + license?: DatasetLicense; metadataBlockValues: NewDatasetMetadataBlockValues[]; } diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 698fc261..06ff92a9 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -3,6 +3,7 @@ import { DatasetUserPermissions } from '../models/DatasetUserPermissions'; import { DatasetLock } from '../models/DatasetLock'; import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'; import { NewDataset } from '../models/NewDataset'; +import { MetadataBlock } from '../../../metadataBlocks'; export interface IDatasetsRepository { getDatasetSummaryFieldNames(): Promise; @@ -13,5 +14,5 @@ export interface IDatasetsRepository { getDatasetUserPermissions(datasetId: number | string): Promise; getDatasetLocks(datasetId: number | string): Promise; getAllDatasetPreviews(limit?: number, offset?: number): Promise; - createDataset(newDataset: NewDataset): Promise; + createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[]): Promise; } diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts index 77b188af..1d5905d4 100644 --- a/src/datasets/domain/useCases/CreateDataset.ts +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -1,20 +1,41 @@ import { UseCase } from '../../../core/domain/useCases/UseCase'; import { IDatasetsRepository } from '../repositories/IDatasetsRepository'; -import { NewDataset } from '../models/NewDataset'; +import { NewDataset, NewDatasetMetadataBlockValues } from '../models/NewDataset'; import { NewResourceValidator } from '../../../core/domain/useCases/validators/NewResourceValidator'; +import { IMetadataBlocksRepository } from '../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; +import { MetadataBlock } from '../../../metadataBlocks'; export class CreateDataset implements UseCase { private datasetsRepository: IDatasetsRepository; - private newDatasetValidator: NewResourceValidator; + private metadataBlocksRepository: IMetadataBlocksRepository; + private newDatasetValidator: NewResourceValidator; - constructor(datasetsRepository: IDatasetsRepository, newDatasetValidator: NewResourceValidator) { + constructor( + datasetsRepository: IDatasetsRepository, + metadataBlocksRepository: IMetadataBlocksRepository, + newDatasetValidator: NewResourceValidator, + ) { this.datasetsRepository = datasetsRepository; + this.metadataBlocksRepository = metadataBlocksRepository; this.newDatasetValidator = newDatasetValidator; } async execute(newDataset: NewDataset): Promise { - return await this.newDatasetValidator.validate(newDataset).then(async () => { - return await this.datasetsRepository.createDataset(newDataset); + const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset); + return await this.newDatasetValidator.validate(newDataset, metadataBlocks).then(async () => { + return await this.datasetsRepository.createDataset(newDataset, metadataBlocks); }); } + + async getNewDatasetMetadataBlocks(newDataset: NewDataset): Promise { + let metadataBlocks: MetadataBlock[] = []; + for (const metadataBlockValue in newDataset.metadataBlockValues) { + metadataBlocks.push( + await this.metadataBlocksRepository.getMetadataBlockByName( + (metadataBlockValue as unknown as NewDatasetMetadataBlockValues).name, + ), + ); + } + return metadataBlocks; + } } diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 88660e95..712fb1f2 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -5,8 +5,7 @@ import { NewDatasetMetadataBlockValues, } from '../../models/NewDataset'; import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; -import { IMetadataBlocksRepository } from '../../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; -import { MetadataFieldInfo } from '../../../../metadataBlocks'; +import { MetadataFieldInfo, MetadataBlock } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; import { EmptyFieldError } from './errors/EmptyFieldError'; import { FieldValidationError } from './errors/FieldValidationError'; @@ -22,22 +21,21 @@ export interface NewDatasetMetadataFieldAndValueInfo { metadataFieldPosition?: number; } -export class NewDatasetValidator implements NewResourceValidator { - private metadataBlockRepository: IMetadataBlocksRepository; - - constructor(metadataBlockRepository: IMetadataBlocksRepository) { - this.metadataBlockRepository = metadataBlockRepository; - } - - async validate(resource: NewDataset): Promise { +export class NewDatasetValidator implements NewResourceValidator { + async validate(resource: NewDataset, metadataBlocks: MetadataBlock[]): Promise { for (const metadataBlockValues of resource.metadataBlockValues) { - await this.validateMetadataBlock(metadataBlockValues); + await this.validateMetadataBlock(metadataBlockValues, metadataBlocks); } } - private async validateMetadataBlock(metadataBlockValues: NewDatasetMetadataBlockValues) { + private async validateMetadataBlock( + metadataBlockValues: NewDatasetMetadataBlockValues, + metadataBlocks: MetadataBlock[], + ) { const metadataBlockName = metadataBlockValues.name; - const metadataBlock = await this.metadataBlockRepository.getMetadataBlockByName(metadataBlockName); + const metadataBlock: MetadataBlock = metadataBlocks.find( + (metadataBlock) => metadataBlock.name === metadataBlockName, + ); for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { this.validateMetadataField({ metadataFieldInfo: metadataBlock.metadataFields[metadataFieldKey], diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 64fd5b63..4e67636a 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -21,9 +21,7 @@ const getPrivateUrlDatasetCitation = new GetPrivateUrlDatasetCitation(datasetsRe const getDatasetUserPermissions = new GetDatasetUserPermissions(datasetsRepository); const getDatasetLocks = new GetDatasetLocks(datasetsRepository); const getAllDatasetPreviews = new GetAllDatasetPreviews(datasetsRepository); - -const newDatasetValidator = new NewDatasetValidator(new MetadataBlocksRepository()); -const createDataset = new CreateDataset(datasetsRepository, newDatasetValidator); +const createDataset = new CreateDataset(datasetsRepository, new MetadataBlocksRepository(), new NewDatasetValidator()); export { getDatasetSummaryFieldNames, diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index aae73f21..537c1df3 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -9,6 +9,7 @@ import { transformDatasetLocksResponseToDatasetLocks } from './transformers/data import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transformers/datasetPreviewsTransformers'; import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'; import { NewDataset } from '../../domain/models/NewDataset'; +import { MetadataBlock } from '../../../metadataBlocks'; export interface GetAllDatasetPreviewsQueryParams { per_page?: number; @@ -108,7 +109,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }); } - public async createDataset(newDataset: NewDataset): Promise { - console.log(newDataset); + public async createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[]): Promise { + console.log(newDataset + ' ' + datasetMetadataBlocks.length); } } diff --git a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts new file mode 100644 index 00000000..8713ddc0 --- /dev/null +++ b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts @@ -0,0 +1,135 @@ +import { + NewDataset, + NewDatasetMetadataBlockValues, + NewDatasetMetadataFields, + NewDatasetMetadataFieldValue, + NewDatasetMetadataChildFieldValue, +} from '../../../domain/models/NewDataset'; +import { DatasetLicense } from '../../../domain/models/Dataset'; +import { MetadataBlock, MetadataFieldInfo } from '../../../../metadataBlocks'; + +export interface NewDatasetRequestPayload { + license?: DatasetLicense; + metadataBlocks: Record; +} + +export interface MetadataBlockRequestPayload { + fields: MetadataFieldRequestPayload[]; + displayName: string; +} + +export interface MetadataFieldRequestPayload { + value: MetadataFieldValueRequestPayload; + typeClass: string; + multiple: boolean; + typeName: string; +} + +export type MetadataFieldValueRequestPayload = + | string + | string[] + | Record + | Record[]; + +export const transformNewDatasetModelToRequestPayload = ( + newDataset: NewDataset, + metadataBlocks: MetadataBlock[], +): NewDatasetRequestPayload => { + return { + license: newDataset.license, + metadataBlocks: transformMetadataBlockModelsToRequestPayload(newDataset.metadataBlockValues, metadataBlocks), + }; +}; + +export const transformMetadataBlockModelsToRequestPayload = ( + metadataBlockValuesModels: NewDatasetMetadataBlockValues[], + metadataBlocks: MetadataBlock[], +): Record => { + let metadataBlocksRequestPayload: Record = {}; + for (const item in metadataBlockValuesModels) { + const metadataBlockValuesModel: NewDatasetMetadataBlockValues = item as unknown as NewDatasetMetadataBlockValues; + const metadataBlock: MetadataBlock = metadataBlocks.find( + (metadataBlock) => metadataBlock.name === (item as unknown as NewDatasetMetadataBlockValues).name, + ); + metadataBlocksRequestPayload[metadataBlockValuesModel.name] = { + displayName: metadataBlock.displayName, + fields: transformMetadataFieldModelsToRequestPayload( + metadataBlockValuesModel.fields, + metadataBlock.metadataFields, + ), + }; + } + return metadataBlocksRequestPayload; +}; + +export const transformMetadataFieldModelsToRequestPayload = ( + metadataFieldsModel: NewDatasetMetadataFields, + metadataFields: Record, +): MetadataFieldRequestPayload[] => { + let metadataFieldsRequestPayload: MetadataFieldRequestPayload[] = []; + for (const metadataFieldKey of Object.keys(metadataFieldsModel)) { + const metadataFieldValue: NewDatasetMetadataFieldValue = metadataFieldsModel[metadataFieldKey]; + metadataFieldsRequestPayload.push( + transformMetadataFieldValueToRequestPayload( + metadataFieldValue, + metadataFieldKey, + metadataFields[metadataFieldKey], + ), + ); + } + return metadataFieldsRequestPayload; +}; + +export const transformMetadataFieldValueToRequestPayload = ( + metadataFieldValue: NewDatasetMetadataFieldValue, + metadataFieldKey: string, + metadataFieldInfo: MetadataFieldInfo, +): MetadataFieldRequestPayload => { + let value: MetadataFieldValueRequestPayload; + if (Array.isArray(metadataFieldValue)) { + if (metadataFieldValue.every((item: unknown) => typeof item === 'string')) { + value = metadataFieldValue as string[]; + } else { + let value: Record[] = []; + for (const item in metadataFieldValue as NewDatasetMetadataChildFieldValue[]) { + value.push( + transformMetadataChildFieldValueToRequestPayload( + item as unknown as NewDatasetMetadataChildFieldValue, + metadataFieldInfo, + ), + ); + } + } + } else if (typeof metadataFieldValue == 'string') { + value = metadataFieldValue; + } else { + value = transformMetadataChildFieldValueToRequestPayload( + metadataFieldValue as unknown as NewDatasetMetadataChildFieldValue, + metadataFieldInfo, + ); + } + return { + value: value, + typeClass: metadataFieldInfo.typeClass, + multiple: metadataFieldInfo.multiple, + typeName: metadataFieldKey, + }; +}; + +export const transformMetadataChildFieldValueToRequestPayload = ( + metadataFieldValue: NewDatasetMetadataChildFieldValue, + metadataFieldInfo: MetadataFieldInfo, +): Record => { + let metadataChildFieldRequestPayload: Record = {}; + for (const metadataChildFieldKey of Object.keys(metadataFieldValue)) { + const childMetadataFieldInfo: MetadataFieldInfo = metadataFieldInfo.childMetadataFields[metadataChildFieldKey]; + const value: string = metadataFieldValue[metadataChildFieldKey] as unknown as string; + metadataChildFieldRequestPayload[metadataChildFieldKey] = { + value: value, + typeClass: childMetadataFieldInfo.typeClass, + multiple: childMetadataFieldInfo.multiple, + typeName: metadataChildFieldKey, + }; + } + return metadataChildFieldRequestPayload; +}; diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index 78897bd6..260e68f1 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -2,34 +2,42 @@ import { CreateDataset } from '../../../src/datasets/domain/useCases/CreateDatas import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'; import { assert, createSandbox, SinonSandbox } from 'sinon'; import { NewResourceValidator } from '../../../src/core/domain/useCases/validators/NewResourceValidator'; -import { createNewDatasetModel } from '../../testHelpers/datasets/newDatasetHelper'; -import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; +import { createNewDatasetModel, createNewDatasetMetadataBlockModel } from '../../testHelpers/datasets/newDatasetHelper'; import { ResourceValidationError } from '../../../src/core/domain/useCases/validators/errors/ResourceValidationError'; import { WriteError } from '../../../src'; +import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); const testDataset = createNewDatasetModel(); + const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; afterEach(() => { sandbox.restore(); }); + function setupMetadataBlocksRepositoryStub(): IMetadataBlocksRepository { + const metadataBlocksRepositoryStub = {}; + const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlocks[0]); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + return metadataBlocksRepositoryStub; + } + test('should call repository when validation is successful', async () => { const datasetsRepositoryStub = {}; const createDatasetStub = sandbox.stub(); datasetsRepositoryStub.createDataset = createDatasetStub; - const newDatasetValidatorStub = >{}; + const newDatasetValidatorStub = {}; const validateStub = sandbox.stub().resolves(); newDatasetValidatorStub.validate = validateStub; - const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorStub); + const sut = new CreateDataset(datasetsRepositoryStub, setupMetadataBlocksRepositoryStub(), newDatasetValidatorStub); await sut.execute(testDataset); - assert.calledWithExactly(validateStub, testDataset); - assert.calledWithExactly(createDatasetStub, testDataset); + assert.calledWithExactly(validateStub, testDataset, testMetadataBlocks); + assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks); assert.callOrder(validateStub, createDatasetStub); }); @@ -39,17 +47,17 @@ describe('execute', () => { const createDatasetStub = sandbox.stub(); datasetsRepositoryStub.createDataset = createDatasetStub; - const newDatasetValidatorStub = >{}; + const newDatasetValidatorStub = {}; const testValidationError = new ResourceValidationError('Test error'); const validateStub = sandbox.stub().throwsException(testValidationError); newDatasetValidatorStub.validate = validateStub; - const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorStub); + const sut = new CreateDataset(datasetsRepositoryStub, setupMetadataBlocksRepositoryStub(), newDatasetValidatorStub); let actualError: ResourceValidationError = undefined; await sut.execute(testDataset).catch((e) => (actualError = e)); assert.match(actualError, testValidationError); - assert.calledWithExactly(validateStub, testDataset); + assert.calledWithExactly(validateStub, testDataset, testMetadataBlocks); assert.notCalled(createDatasetStub); }); @@ -59,17 +67,17 @@ describe('execute', () => { const createDatasetStub = sandbox.stub().throwsException(testWriteError); datasetsRepositoryStub.createDataset = createDatasetStub; - const newDatasetValidatorStub = >{}; + const newDatasetValidatorStub = {}; const validateMock = sandbox.stub().resolves(); newDatasetValidatorStub.validate = validateMock; - const sut = new CreateDataset(datasetsRepositoryStub, newDatasetValidatorStub); + const sut = new CreateDataset(datasetsRepositoryStub, setupMetadataBlocksRepositoryStub(), newDatasetValidatorStub); let actualError: ResourceValidationError = undefined; await sut.execute(testDataset).catch((e) => (actualError = e)); assert.match(actualError, testWriteError); - assert.calledWithExactly(validateMock, testDataset); - assert.calledWithExactly(createDatasetStub, testDataset); + assert.calledWithExactly(validateMock, testDataset, testMetadataBlocks); + assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks); assert.callOrder(validateMock, createDatasetStub); }); diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index ba5713e9..daf77e27 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -6,26 +6,18 @@ import { createNewDatasetModelWithoutFirstLevelRequiredField, } from '../../testHelpers/datasets/newDatasetHelper'; import { fail } from 'assert'; -import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { EmptyFieldError } from '../../../src/datasets/domain/useCases/validators/errors/EmptyFieldError'; import { FieldValidationError } from '../../../src/datasets/domain/useCases/validators/errors/FieldValidationError'; import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); + const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; afterEach(() => { sandbox.restore(); }); - function setupMetadataBlocksRepositoryStub(): IMetadataBlocksRepository { - const testMetadataBlock = createNewDatasetMetadataBlockModel(); - const metadataBlocksRepositoryStub = {}; - const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlock); - metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; - return metadataBlocksRepositoryStub; - } - async function runValidateExpectingFieldValidationError( newDataset: NewDataset, expectedMetadataFieldName: string, @@ -33,9 +25,9 @@ describe('execute', () => { expectedParentMetadataFieldName?: string, expectedPosition?: number, ): Promise { - const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); + const sut = new NewDatasetValidator(); await sut - .validate(newDataset) + .validate(newDataset, testMetadataBlocks) .then(() => { fail('Validation should fail'); }) @@ -51,9 +43,9 @@ describe('execute', () => { test('should not raise a validation error when a new dataset with only the required fields is valid', async () => { const testNewDataset = createNewDatasetModel(); - const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); + const sut = new NewDatasetValidator(); - await sut.validate(testNewDataset).catch((e) => fail(e)); + await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); test('should raise an empty field error when a first level required string field is missing', async () => { @@ -167,8 +159,8 @@ describe('execute', () => { }, ]; const testNewDataset = createNewDatasetModel(undefined, authorFieldValue, undefined); - const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); - await sut.validate(testNewDataset).catch((e) => fail(e)); + const sut = new NewDatasetValidator(); + await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); test('should raise a date format validation error when a date field has an invalid format', async () => { @@ -182,8 +174,8 @@ describe('execute', () => { test('should not raise a date format validation error when a date field has a valid format', async () => { const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, '2020-01-01'); - const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); - await sut.validate(testNewDataset).catch((e) => fail(e)); + const sut = new NewDatasetValidator(); + await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); test('should raise a controlled vocabulary error when a controlled vocabulary field has an invalid format', async () => { @@ -199,7 +191,7 @@ describe('execute', () => { test('should not raise a controlled vocabulary error when the value for a controlled vocabulary field is correct', async () => { const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, undefined, 'Project Member'); - const sut = new NewDatasetValidator(setupMetadataBlocksRepositoryStub()); - await sut.validate(testNewDataset).catch((e) => fail(e)); + const sut = new NewDatasetValidator(); + await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); }); From 1a1d08a9929126f28f1002dc6e070be08714e031 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 18 Jan 2024 13:29:21 +0000 Subject: [PATCH 21/37] Fixed: test name --- test/unit/datasets/NewDatasetValidator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index daf77e27..c083ba06 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -10,7 +10,7 @@ import { EmptyFieldError } from '../../../src/datasets/domain/useCases/validator import { FieldValidationError } from '../../../src/datasets/domain/useCases/validators/errors/FieldValidationError'; import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; -describe('execute', () => { +describe('validate', () => { const sandbox: SinonSandbox = createSandbox(); const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; From 46222d920359c270747c86256a82cf3c06fe03b8 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 18 Jan 2024 13:55:56 +0000 Subject: [PATCH 22/37] Fixed: newDatasetTransformers --- .../transformers/newDatasetTransformers.ts | 22 ++++++++----------- .../datasets/newDatasetTransformers.test.ts | 19 ++++++++++++++++ 2 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 test/unit/datasets/newDatasetTransformers.test.ts diff --git a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts index 8713ddc0..5da33c3a 100644 --- a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts +++ b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts @@ -36,7 +36,7 @@ export const transformNewDatasetModelToRequestPayload = ( metadataBlocks: MetadataBlock[], ): NewDatasetRequestPayload => { return { - license: newDataset.license, + ...(newDataset.license && { license: newDataset.license }), metadataBlocks: transformMetadataBlockModelsToRequestPayload(newDataset.metadataBlockValues, metadataBlocks), }; }; @@ -46,10 +46,9 @@ export const transformMetadataBlockModelsToRequestPayload = ( metadataBlocks: MetadataBlock[], ): Record => { let metadataBlocksRequestPayload: Record = {}; - for (const item in metadataBlockValuesModels) { - const metadataBlockValuesModel: NewDatasetMetadataBlockValues = item as unknown as NewDatasetMetadataBlockValues; + metadataBlockValuesModels.forEach(function (metadataBlockValuesModel: NewDatasetMetadataBlockValues) { const metadataBlock: MetadataBlock = metadataBlocks.find( - (metadataBlock) => metadataBlock.name === (item as unknown as NewDatasetMetadataBlockValues).name, + (metadataBlock) => metadataBlock.name == metadataBlockValuesModel.name, ); metadataBlocksRequestPayload[metadataBlockValuesModel.name] = { displayName: metadataBlock.displayName, @@ -58,7 +57,7 @@ export const transformMetadataBlockModelsToRequestPayload = ( metadataBlock.metadataFields, ), }; - } + }); return metadataBlocksRequestPayload; }; @@ -87,26 +86,23 @@ export const transformMetadataFieldValueToRequestPayload = ( ): MetadataFieldRequestPayload => { let value: MetadataFieldValueRequestPayload; if (Array.isArray(metadataFieldValue)) { - if (metadataFieldValue.every((item: unknown) => typeof item === 'string')) { + if (typeof metadataFieldValue[0] == 'string') { value = metadataFieldValue as string[]; } else { let value: Record[] = []; - for (const item in metadataFieldValue as NewDatasetMetadataChildFieldValue[]) { + metadataFieldValue.forEach(function (metadataFieldValue: NewDatasetMetadataFieldValue) { value.push( transformMetadataChildFieldValueToRequestPayload( - item as unknown as NewDatasetMetadataChildFieldValue, + metadataFieldValue as NewDatasetMetadataChildFieldValue, metadataFieldInfo, ), ); - } + }); } } else if (typeof metadataFieldValue == 'string') { value = metadataFieldValue; } else { - value = transformMetadataChildFieldValueToRequestPayload( - metadataFieldValue as unknown as NewDatasetMetadataChildFieldValue, - metadataFieldInfo, - ); + value = transformMetadataChildFieldValueToRequestPayload(metadataFieldValue, metadataFieldInfo); } return { value: value, diff --git a/test/unit/datasets/newDatasetTransformers.test.ts b/test/unit/datasets/newDatasetTransformers.test.ts new file mode 100644 index 00000000..ed3dfdc5 --- /dev/null +++ b/test/unit/datasets/newDatasetTransformers.test.ts @@ -0,0 +1,19 @@ +import { createSandbox, SinonSandbox } from 'sinon'; +import { createNewDatasetMetadataBlockModel, createNewDatasetModel } from '../../testHelpers/datasets/newDatasetHelper'; +import { transformNewDatasetModelToRequestPayload } from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; + +describe('transformNewDatasetModelToRequestPayload', () => { + const sandbox: SinonSandbox = createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + test('should not raise a validation error when a new dataset with only the required fields is valid', async () => { + const testNewDataset = createNewDatasetModel(); + const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; + + const actual = transformNewDatasetModelToRequestPayload(testNewDataset, testMetadataBlocks); + console.log(actual); + }); +}); From 529686415a13e1a42cd152c39c1f0b3eac6956a3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 18 Jan 2024 15:23:47 +0000 Subject: [PATCH 23/37] Stash: newDatasetTransformers fixes and tests WIP --- .../transformers/newDatasetTransformers.ts | 14 ++-- test/testHelpers/datasets/newDatasetHelper.ts | 8 ++ .../datasets/newDatasetTransformers.test.ts | 74 ++++++++++++++++++- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts index 5da33c3a..6c8d2021 100644 --- a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts +++ b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts @@ -9,8 +9,10 @@ import { DatasetLicense } from '../../../domain/models/Dataset'; import { MetadataBlock, MetadataFieldInfo } from '../../../../metadataBlocks'; export interface NewDatasetRequestPayload { - license?: DatasetLicense; - metadataBlocks: Record; + datasetVersion: { + license?: DatasetLicense; + metadataBlocks: Record; + }; } export interface MetadataBlockRequestPayload { @@ -36,8 +38,10 @@ export const transformNewDatasetModelToRequestPayload = ( metadataBlocks: MetadataBlock[], ): NewDatasetRequestPayload => { return { - ...(newDataset.license && { license: newDataset.license }), - metadataBlocks: transformMetadataBlockModelsToRequestPayload(newDataset.metadataBlockValues, metadataBlocks), + datasetVersion: { + ...(newDataset.license && { license: newDataset.license }), + metadataBlocks: transformMetadataBlockModelsToRequestPayload(newDataset.metadataBlockValues, metadataBlocks), + }, }; }; @@ -51,11 +55,11 @@ export const transformMetadataBlockModelsToRequestPayload = ( (metadataBlock) => metadataBlock.name == metadataBlockValuesModel.name, ); metadataBlocksRequestPayload[metadataBlockValuesModel.name] = { - displayName: metadataBlock.displayName, fields: transformMetadataFieldModelsToRequestPayload( metadataBlockValuesModel.fields, metadataBlock.metadataFields, ), + displayName: metadataBlock.displayName, }; }); return metadataBlocksRequestPayload; diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 642e4752..b2a10b78 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -81,6 +81,14 @@ export const createNewDatasetModelWithoutSecondLevelRequiredField = (): NewDatas }; }; +/** + * + * This method creates a simplified and altered version of the Citation Metadata Block, only for testing purposes. + * For this reason some of the metadata fields do not correspond to the real ones. + * + * @returns {MetadataBlock} A MetadataBlock testing instance. + * + **/ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { return { id: 1, diff --git a/test/unit/datasets/newDatasetTransformers.test.ts b/test/unit/datasets/newDatasetTransformers.test.ts index ed3dfdc5..01e0050c 100644 --- a/test/unit/datasets/newDatasetTransformers.test.ts +++ b/test/unit/datasets/newDatasetTransformers.test.ts @@ -1,6 +1,9 @@ -import { createSandbox, SinonSandbox } from 'sinon'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; import { createNewDatasetMetadataBlockModel, createNewDatasetModel } from '../../testHelpers/datasets/newDatasetHelper'; -import { transformNewDatasetModelToRequestPayload } from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; +import { + transformNewDatasetModelToRequestPayload, + NewDatasetRequestPayload, +} from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; describe('transformNewDatasetModelToRequestPayload', () => { const sandbox: SinonSandbox = createSandbox(); @@ -9,11 +12,74 @@ describe('transformNewDatasetModelToRequestPayload', () => { sandbox.restore(); }); - test('should not raise a validation error when a new dataset with only the required fields is valid', async () => { + test('happy path WIP', async () => { const testNewDataset = createNewDatasetModel(); const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; + const expected: NewDatasetRequestPayload = { + datasetVersion: { + metadataBlocks: { + citation: { + fields: [ + { + value: 'test dataset', + typeClass: 'primitive', + multiple: false, + typeName: 'title', + }, + { + value: [ + { + authorName: { + value: 'Admin, Dataverse', + typeClass: 'primitive', + multiple: false, + typeName: 'authorName', + }, + authorAffiliation: { + value: 'Dataverse.org', + typeClass: 'primitive', + multiple: false, + typeName: 'authorAffiliation', + }, + }, + { + authorName: { + value: 'Owner, Dataverse', + typeClass: 'primitive', + multiple: false, + typeName: 'authorName', + }, + authorAffiliation: { + value: 'Dataverse.org', + typeClass: 'primitive', + multiple: false, + typeName: 'authorAffiliation', + }, + }, + ], + typeClass: 'compound', + multiple: true, + typeName: 'author', + }, + { + value: ['alternative1', 'alternative2'], + typeClass: 'primitive', + multiple: true, + typeName: 'alternativeRequiredTitle', + }, + ], + displayName: 'Citation Metadata', + }, + }, + }, + }; + const actual = transformNewDatasetModelToRequestPayload(testNewDataset, testMetadataBlocks); - console.log(actual); + //assert.match(actual, expected); + assert.match( + actual.datasetVersion.metadataBlocks.citation.fields[1].value, + expected.datasetVersion.metadataBlocks.citation.fields[1].value, + ); }); }); From 5ad4e25ec22895b287a88f895ef72520fc541827 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 19 Jan 2024 10:25:51 +0000 Subject: [PATCH 24/37] Added: newDatasetTransformers tweaks and fixes and passing unit test --- .../transformers/newDatasetTransformers.ts | 90 ++++++++++--------- test/testHelpers/datasets/newDatasetHelper.ts | 62 +++++++++++++ .../datasets/newDatasetTransformers.test.ts | 86 ++---------------- 3 files changed, 118 insertions(+), 120 deletions(-) diff --git a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts index 6c8d2021..b6c8fbab 100644 --- a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts +++ b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts @@ -46,17 +46,17 @@ export const transformNewDatasetModelToRequestPayload = ( }; export const transformMetadataBlockModelsToRequestPayload = ( - metadataBlockValuesModels: NewDatasetMetadataBlockValues[], + newDatasetMetadataBlocksValues: NewDatasetMetadataBlockValues[], metadataBlocks: MetadataBlock[], ): Record => { let metadataBlocksRequestPayload: Record = {}; - metadataBlockValuesModels.forEach(function (metadataBlockValuesModel: NewDatasetMetadataBlockValues) { + newDatasetMetadataBlocksValues.forEach(function (newDatasetMetadataBlockValues: NewDatasetMetadataBlockValues) { const metadataBlock: MetadataBlock = metadataBlocks.find( - (metadataBlock) => metadataBlock.name == metadataBlockValuesModel.name, + (metadataBlock) => metadataBlock.name == newDatasetMetadataBlockValues.name, ); - metadataBlocksRequestPayload[metadataBlockValuesModel.name] = { + metadataBlocksRequestPayload[newDatasetMetadataBlockValues.name] = { fields: transformMetadataFieldModelsToRequestPayload( - metadataBlockValuesModel.fields, + newDatasetMetadataBlockValues.fields, metadataBlock.metadataFields, ), displayName: metadataBlock.displayName, @@ -66,64 +66,67 @@ export const transformMetadataBlockModelsToRequestPayload = ( }; export const transformMetadataFieldModelsToRequestPayload = ( - metadataFieldsModel: NewDatasetMetadataFields, - metadataFields: Record, + newDatasetMetadataFields: NewDatasetMetadataFields, + metadataBlockFields: Record, ): MetadataFieldRequestPayload[] => { let metadataFieldsRequestPayload: MetadataFieldRequestPayload[] = []; - for (const metadataFieldKey of Object.keys(metadataFieldsModel)) { - const metadataFieldValue: NewDatasetMetadataFieldValue = metadataFieldsModel[metadataFieldKey]; - metadataFieldsRequestPayload.push( - transformMetadataFieldValueToRequestPayload( - metadataFieldValue, - metadataFieldKey, - metadataFields[metadataFieldKey], + for (const metadataFieldKey of Object.keys(newDatasetMetadataFields)) { + const newDatasetMetadataChildFieldValue: NewDatasetMetadataFieldValue = newDatasetMetadataFields[metadataFieldKey]; + metadataFieldsRequestPayload.push({ + value: transformMetadataFieldValueToRequestPayload( + newDatasetMetadataChildFieldValue, + metadataBlockFields[metadataFieldKey], ), - ); + typeClass: metadataBlockFields[metadataFieldKey].typeClass, + multiple: metadataBlockFields[metadataFieldKey].multiple, + typeName: metadataFieldKey, + }); } return metadataFieldsRequestPayload; }; export const transformMetadataFieldValueToRequestPayload = ( - metadataFieldValue: NewDatasetMetadataFieldValue, - metadataFieldKey: string, - metadataFieldInfo: MetadataFieldInfo, -): MetadataFieldRequestPayload => { + newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, + metadataBlockFieldInfo: MetadataFieldInfo, +): MetadataFieldValueRequestPayload => { let value: MetadataFieldValueRequestPayload; - if (Array.isArray(metadataFieldValue)) { - if (typeof metadataFieldValue[0] == 'string') { - value = metadataFieldValue as string[]; + if (metadataBlockFieldInfo.multiple) { + const newDatasetMetadataChildFieldValues = newDatasetMetadataFieldValue as + | string[] + | NewDatasetMetadataChildFieldValue[]; + if (typeof newDatasetMetadataChildFieldValues[0] == 'string') { + value = newDatasetMetadataFieldValue as string[]; } else { - let value: Record[] = []; - metadataFieldValue.forEach(function (metadataFieldValue: NewDatasetMetadataFieldValue) { - value.push( - transformMetadataChildFieldValueToRequestPayload( - metadataFieldValue as NewDatasetMetadataChildFieldValue, - metadataFieldInfo, - ), + value = []; + (newDatasetMetadataChildFieldValues as NewDatasetMetadataChildFieldValue[]).forEach(function ( + childMetadataFieldValue: NewDatasetMetadataChildFieldValue, + ) { + (value as Record[]).push( + transformMetadataChildFieldValueToRequestPayload(childMetadataFieldValue, metadataBlockFieldInfo), ); }); } - } else if (typeof metadataFieldValue == 'string') { - value = metadataFieldValue; } else { - value = transformMetadataChildFieldValueToRequestPayload(metadataFieldValue, metadataFieldInfo); + if (typeof newDatasetMetadataFieldValue == 'string') { + value = newDatasetMetadataFieldValue; + } else { + value = transformMetadataChildFieldValueToRequestPayload( + newDatasetMetadataFieldValue as NewDatasetMetadataChildFieldValue, + metadataBlockFieldInfo, + ); + } } - return { - value: value, - typeClass: metadataFieldInfo.typeClass, - multiple: metadataFieldInfo.multiple, - typeName: metadataFieldKey, - }; + return value; }; export const transformMetadataChildFieldValueToRequestPayload = ( - metadataFieldValue: NewDatasetMetadataChildFieldValue, - metadataFieldInfo: MetadataFieldInfo, + newDatasetMetadataChildFieldValue: NewDatasetMetadataChildFieldValue, + metadataBlockFieldInfo: MetadataFieldInfo, ): Record => { let metadataChildFieldRequestPayload: Record = {}; - for (const metadataChildFieldKey of Object.keys(metadataFieldValue)) { - const childMetadataFieldInfo: MetadataFieldInfo = metadataFieldInfo.childMetadataFields[metadataChildFieldKey]; - const value: string = metadataFieldValue[metadataChildFieldKey] as unknown as string; + for (const metadataChildFieldKey of Object.keys(newDatasetMetadataChildFieldValue)) { + const childMetadataFieldInfo: MetadataFieldInfo = metadataBlockFieldInfo.childMetadataFields[metadataChildFieldKey]; + const value: string = newDatasetMetadataChildFieldValue[metadataChildFieldKey] as unknown as string; metadataChildFieldRequestPayload[metadataChildFieldKey] = { value: value, typeClass: childMetadataFieldInfo.typeClass, @@ -131,5 +134,6 @@ export const transformMetadataChildFieldValueToRequestPayload = ( typeName: metadataChildFieldKey, }; } + return metadataChildFieldRequestPayload; }; diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index b2a10b78..64f5e589 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -1,5 +1,6 @@ import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; import { MetadataBlock } from '../../../src'; +import { NewDatasetRequestPayload } from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; export const createNewDatasetModel = ( titleFieldValue?: NewDatasetMetadataFieldValue, @@ -248,3 +249,64 @@ export const createNewDatasetMetadataBlockModel = (): MetadataBlock => { }, }; }; + +export const createNewDatasetRequestPayload = (): NewDatasetRequestPayload => { + return { + datasetVersion: { + metadataBlocks: { + citation: { + fields: [ + { + value: 'test dataset', + typeClass: 'primitive', + multiple: false, + typeName: 'title', + }, + { + value: [ + { + authorName: { + value: 'Admin, Dataverse', + typeClass: 'primitive', + multiple: false, + typeName: 'authorName', + }, + authorAffiliation: { + value: 'Dataverse.org', + typeClass: 'primitive', + multiple: false, + typeName: 'authorAffiliation', + }, + }, + { + authorName: { + value: 'Owner, Dataverse', + typeClass: 'primitive', + multiple: false, + typeName: 'authorName', + }, + authorAffiliation: { + value: 'Dataverse.org', + typeClass: 'primitive', + multiple: false, + typeName: 'authorAffiliation', + }, + }, + ], + typeClass: 'compound', + multiple: true, + typeName: 'author', + }, + { + value: ['alternative1', 'alternative2'], + typeClass: 'primitive', + multiple: true, + typeName: 'alternativeRequiredTitle', + }, + ], + displayName: 'Citation Metadata', + }, + }, + }, + }; +}; diff --git a/test/unit/datasets/newDatasetTransformers.test.ts b/test/unit/datasets/newDatasetTransformers.test.ts index 01e0050c..6804b736 100644 --- a/test/unit/datasets/newDatasetTransformers.test.ts +++ b/test/unit/datasets/newDatasetTransformers.test.ts @@ -1,85 +1,17 @@ -import { assert, createSandbox, SinonSandbox } from 'sinon'; -import { createNewDatasetMetadataBlockModel, createNewDatasetModel } from '../../testHelpers/datasets/newDatasetHelper'; +import { assert } from 'sinon'; import { - transformNewDatasetModelToRequestPayload, - NewDatasetRequestPayload, -} from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; + createNewDatasetMetadataBlockModel, + createNewDatasetModel, + createNewDatasetRequestPayload, +} from '../../testHelpers/datasets/newDatasetHelper'; +import { transformNewDatasetModelToRequestPayload } from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; describe('transformNewDatasetModelToRequestPayload', () => { - const sandbox: SinonSandbox = createSandbox(); - - afterEach(() => { - sandbox.restore(); - }); - - test('happy path WIP', async () => { + test('should correctly transform a new dataset model to a new dataset request payload', async () => { const testNewDataset = createNewDatasetModel(); const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; - - const expected: NewDatasetRequestPayload = { - datasetVersion: { - metadataBlocks: { - citation: { - fields: [ - { - value: 'test dataset', - typeClass: 'primitive', - multiple: false, - typeName: 'title', - }, - { - value: [ - { - authorName: { - value: 'Admin, Dataverse', - typeClass: 'primitive', - multiple: false, - typeName: 'authorName', - }, - authorAffiliation: { - value: 'Dataverse.org', - typeClass: 'primitive', - multiple: false, - typeName: 'authorAffiliation', - }, - }, - { - authorName: { - value: 'Owner, Dataverse', - typeClass: 'primitive', - multiple: false, - typeName: 'authorName', - }, - authorAffiliation: { - value: 'Dataverse.org', - typeClass: 'primitive', - multiple: false, - typeName: 'authorAffiliation', - }, - }, - ], - typeClass: 'compound', - multiple: true, - typeName: 'author', - }, - { - value: ['alternative1', 'alternative2'], - typeClass: 'primitive', - multiple: true, - typeName: 'alternativeRequiredTitle', - }, - ], - displayName: 'Citation Metadata', - }, - }, - }, - }; - + const expectedNewDatasetRequestPayload = createNewDatasetRequestPayload(); const actual = transformNewDatasetModelToRequestPayload(testNewDataset, testMetadataBlocks); - //assert.match(actual, expected); - assert.match( - actual.datasetVersion.metadataBlocks.citation.fields[1].value, - expected.datasetVersion.metadataBlocks.citation.fields[1].value, - ); + assert.match(actual, expectedNewDatasetRequestPayload); }); }); From 8f09830f6a06f1e315de98114dc0c8bef779969f Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 19 Jan 2024 11:33:04 +0000 Subject: [PATCH 25/37] Stash: createDataset repository logic WIP --- .../repositories/IDatasetsRepository.ts | 2 +- src/datasets/domain/useCases/CreateDataset.ts | 4 +- .../infra/repositories/DatasetsRepository.ts | 17 +++++++- test/unit/datasets/CreateDataset.test.ts | 4 +- test/unit/datasets/DatasetsRepository.test.ts | 40 +++++++++++++++++++ 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 06ff92a9..2540cc9e 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -14,5 +14,5 @@ export interface IDatasetsRepository { getDatasetUserPermissions(datasetId: number | string): Promise; getDatasetLocks(datasetId: number | string): Promise; getAllDatasetPreviews(limit?: number, offset?: number): Promise; - createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[]): Promise; + createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[], collectionId: string): Promise; } diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts index 1d5905d4..fd51289f 100644 --- a/src/datasets/domain/useCases/CreateDataset.ts +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -20,10 +20,10 @@ export class CreateDataset implements UseCase { this.newDatasetValidator = newDatasetValidator; } - async execute(newDataset: NewDataset): Promise { + async execute(newDataset: NewDataset, collectionId: string = 'root'): Promise { const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset); return await this.newDatasetValidator.validate(newDataset, metadataBlocks).then(async () => { - return await this.datasetsRepository.createDataset(newDataset, metadataBlocks); + return await this.datasetsRepository.createDataset(newDataset, metadataBlocks, collectionId); }); } diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 537c1df3..dfb099d2 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -10,6 +10,7 @@ import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transf import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'; import { NewDataset } from '../../domain/models/NewDataset'; import { MetadataBlock } from '../../../metadataBlocks'; +import { transformNewDatasetModelToRequestPayload } from './transformers/newDatasetTransformers'; export interface GetAllDatasetPreviewsQueryParams { per_page?: number; @@ -18,6 +19,7 @@ export interface GetAllDatasetPreviewsQueryParams { export class DatasetsRepository extends ApiRepository implements IDatasetsRepository { private readonly datasetsResourceName: string = 'datasets'; + private readonly dataversesResourceName: string = 'dataverses'; public async getDatasetSummaryFieldNames(): Promise { return this.doGet(this.buildApiEndpoint(this.datasetsResourceName, 'summaryFieldNames')) @@ -109,7 +111,18 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }); } - public async createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[]): Promise { - console.log(newDataset + ' ' + datasetMetadataBlocks.length); + public async createDataset( + newDataset: NewDataset, + datasetMetadataBlocks: MetadataBlock[], + collectionId: string, + ): Promise { + return this.doPost( + this.buildApiEndpoint(this.dataversesResourceName, `datasets`, collectionId), + transformNewDatasetModelToRequestPayload(newDataset, datasetMetadataBlocks), + ) + .then(() => {}) + .catch((error) => { + throw error; + }); } } diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index 260e68f1..6b626a78 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -37,7 +37,7 @@ describe('execute', () => { await sut.execute(testDataset); assert.calledWithExactly(validateStub, testDataset, testMetadataBlocks); - assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks); + assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks, 'root') assert.callOrder(validateStub, createDatasetStub); }); @@ -77,7 +77,7 @@ describe('execute', () => { assert.match(actualError, testWriteError); assert.calledWithExactly(validateMock, testDataset, testMetadataBlocks); - assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks); + assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks, 'root'); assert.callOrder(validateMock, createDatasetStub); }); diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 261df777..9179a3f2 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -17,6 +17,11 @@ import { createDatasetPreviewModel, createDatasetPreviewPayload, } from '../../testHelpers/datasets/datasetPreviewHelper'; +import { + createNewDatasetModel, + createNewDatasetMetadataBlockModel, + createNewDatasetRequestPayload, +} from '../../testHelpers/datasets/newDatasetHelper'; describe('DatasetsRepository', () => { const sandbox: SinonSandbox = createSandbox(); @@ -600,4 +605,39 @@ describe('DatasetsRepository', () => { expect(error).to.be.instanceOf(Error); }); }); + + describe('createDataset', () => { + const testNewDataset = createNewDatasetModel(); + const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; + const testCollectionName = 'test'; + const expectedNewDatasetRequestPayloadJson = JSON.stringify(createNewDatasetRequestPayload()); + + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionName}/datasets`; + + test('should call the API with a correct request payload', async () => { + const axiosPostMock = sandbox.stub(axios, 'post'); + + // API Key auth + await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); + + assert.calledWithExactly( + axiosPostMock, + expectedApiEndpoint, + expectedNewDatasetRequestPayloadJson, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); + + assert.calledWithExactly( + axiosPostMock, + expectedApiEndpoint, + expectedNewDatasetRequestPayloadJson, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + }); + }); }); From e4670cf31a411c724c6a4b775ae583222ae92650 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 19 Jan 2024 11:40:39 +0000 Subject: [PATCH 26/37] Added: completed createDataset repository logic with passing tests --- .../infra/repositories/DatasetsRepository.ts | 3 +-- test/unit/datasets/DatasetsRepository.test.ts | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index dfb099d2..f33b381e 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -19,7 +19,6 @@ export interface GetAllDatasetPreviewsQueryParams { export class DatasetsRepository extends ApiRepository implements IDatasetsRepository { private readonly datasetsResourceName: string = 'datasets'; - private readonly dataversesResourceName: string = 'dataverses'; public async getDatasetSummaryFieldNames(): Promise { return this.doGet(this.buildApiEndpoint(this.datasetsResourceName, 'summaryFieldNames')) @@ -117,7 +116,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi collectionId: string, ): Promise { return this.doPost( - this.buildApiEndpoint(this.dataversesResourceName, `datasets`, collectionId), + `/dataverses/${collectionId}/datasets`, transformNewDatasetModelToRequestPayload(newDataset, datasetMetadataBlocks), ) .then(() => {}) diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 9179a3f2..d581d38e 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -22,6 +22,7 @@ import { createNewDatasetMetadataBlockModel, createNewDatasetRequestPayload, } from '../../testHelpers/datasets/newDatasetHelper'; +import { WriteError } from '../../../src'; describe('DatasetsRepository', () => { const sandbox: SinonSandbox = createSandbox(); @@ -615,13 +616,13 @@ describe('DatasetsRepository', () => { const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionName}/datasets`; test('should call the API with a correct request payload', async () => { - const axiosPostMock = sandbox.stub(axios, 'post'); + const axiosPostStub = sandbox.stub(axios, 'post').resolves(); // API Key auth await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); assert.calledWithExactly( - axiosPostMock, + axiosPostStub, expectedApiEndpoint, expectedNewDatasetRequestPayloadJson, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, @@ -633,11 +634,26 @@ describe('DatasetsRepository', () => { await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); assert.calledWithExactly( - axiosPostMock, + axiosPostStub, expectedApiEndpoint, expectedNewDatasetRequestPayloadJson, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, ); }); + + test('should return error result on error response', async () => { + const axiosPostStub = sandbox.stub(axios, 'post').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: WriteError = undefined; + await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosPostStub, + expectedApiEndpoint, + expectedNewDatasetRequestPayloadJson, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); }); }); From 0a4464e52a96735fe31f3693f585bba7c91da13d Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 19 Jan 2024 12:10:04 +0000 Subject: [PATCH 27/37] Stash: createDataset IT WIP (failing) --- .../datasets/DatasetsRepository.test.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index a3064195..a9c7eaa4 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -12,6 +12,8 @@ import { DatasetNotNumberedVersion, DatasetLockType, DatasetPreviewSubset } from import { fail } from 'assert'; import { ApiConfig } from '../../../src'; import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'; +import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; +import { MetadataBlocksRepository } from '../../../src/metadataBlocks/infra/repositories/MetadataBlocksRepository'; describe('DatasetsRepository', () => { const sut: DatasetsRepository = new DatasetsRepository(); @@ -243,4 +245,54 @@ describe('DatasetsRepository', () => { expect(typeof actualDatasetCitation).toBe('string'); }); }); + + describe('createDataset', () => { + const testNewDatasetTitle = 'Dataset created using the createDataset use case'; + const testNewDataset: NewDataset = { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: testNewDatasetTitle, + author: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataverse.org', + }, + ], + datasetContact: [ + { + datasetContactEmail: 'finch@mailinator.com', + datasetContactName: 'Finch, Fiona', + }, + ], + dsDescription: [ + { + dsDescriptionValue: 'This is the description of the dataset.', + }, + ], + subject: ['Medicine, Health and Life Sciences'], + }, + }, + ], + }; + + test('should create a dataset', async () => { + const metadataBlocksRepository = new MetadataBlocksRepository(); + const citationMetadataBlock = await metadataBlocksRepository.getMetadataBlockByName('citation'); + + await sut.createDataset(testNewDataset, [citationMetadataBlock], 'root').catch(() => { + assert.fail('Error while creating the Dataset'); + }); + + await new Promise((resolve) => setTimeout(resolve, 2000)); + + const actualCreatedDataset = await sut.getDataset(4, latestVersionId, false); + expect(actualCreatedDataset.metadataBlocks[0].fields.title).toBe(testNewDatasetTitle); + }); + }); }); From 97c47fdbad01efb1a0aa83aa0334de3acc55c6f3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 19 Jan 2024 16:40:49 +0000 Subject: [PATCH 28/37] Changed: returning ids when the dataset is created --- .../models/CreatedDatasetIdentifiers.ts | 4 + .../repositories/IDatasetsRepository.ts | 2 +- src/datasets/domain/useCases/CreateDataset.ts | 5 +- .../infra/repositories/DatasetsRepository.ts | 11 +- .../datasets/DatasetsRepository.test.ts | 106 +++++++++++------- test/integration/environment/setup.js | 5 +- test/unit/datasets/CreateDataset.test.ts | 16 ++- test/unit/datasets/DatasetsRepository.test.ts | 25 ++++- 8 files changed, 118 insertions(+), 56 deletions(-) create mode 100644 src/datasets/domain/models/CreatedDatasetIdentifiers.ts diff --git a/src/datasets/domain/models/CreatedDatasetIdentifiers.ts b/src/datasets/domain/models/CreatedDatasetIdentifiers.ts new file mode 100644 index 00000000..c4dc825a --- /dev/null +++ b/src/datasets/domain/models/CreatedDatasetIdentifiers.ts @@ -0,0 +1,4 @@ +export interface CreatedDatasetIdentifiers { + persistentId: string; + numericId: number; +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 2540cc9e..929e8f54 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -14,5 +14,5 @@ export interface IDatasetsRepository { getDatasetUserPermissions(datasetId: number | string): Promise; getDatasetLocks(datasetId: number | string): Promise; getAllDatasetPreviews(limit?: number, offset?: number): Promise; - createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[], collectionId: string): Promise; + createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[], collectionId: string): Promise; } diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts index fd51289f..7840446c 100644 --- a/src/datasets/domain/useCases/CreateDataset.ts +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -4,8 +4,9 @@ import { NewDataset, NewDatasetMetadataBlockValues } from '../models/NewDataset' import { NewResourceValidator } from '../../../core/domain/useCases/validators/NewResourceValidator'; import { IMetadataBlocksRepository } from '../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { MetadataBlock } from '../../../metadataBlocks'; +import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers'; -export class CreateDataset implements UseCase { +export class CreateDataset implements UseCase { private datasetsRepository: IDatasetsRepository; private metadataBlocksRepository: IMetadataBlocksRepository; private newDatasetValidator: NewResourceValidator; @@ -20,7 +21,7 @@ export class CreateDataset implements UseCase { this.newDatasetValidator = newDatasetValidator; } - async execute(newDataset: NewDataset, collectionId: string = 'root'): Promise { + async execute(newDataset: NewDataset, collectionId: string = 'root'): Promise { const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset); return await this.newDatasetValidator.validate(newDataset, metadataBlocks).then(async () => { return await this.datasetsRepository.createDataset(newDataset, metadataBlocks, collectionId); diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index f33b381e..ba0dfd75 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -11,6 +11,7 @@ import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'; import { NewDataset } from '../../domain/models/NewDataset'; import { MetadataBlock } from '../../../metadataBlocks'; import { transformNewDatasetModelToRequestPayload } from './transformers/newDatasetTransformers'; +import { CreatedDatasetIdentifiers } from '../../domain/models/CreatedDatasetIdentifiers'; export interface GetAllDatasetPreviewsQueryParams { per_page?: number; @@ -114,12 +115,18 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[], collectionId: string, - ): Promise { + ): Promise { return this.doPost( `/dataverses/${collectionId}/datasets`, transformNewDatasetModelToRequestPayload(newDataset, datasetMetadataBlocks), ) - .then(() => {}) + .then((response) => { + const responseData = response.data.data; + return { + persistentId: responseData.persistentId, + numericId: responseData.id, + }; + }) .catch((error) => { throw error; }); diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index a9c7eaa4..d6003261 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -14,6 +14,7 @@ import { ApiConfig } from '../../../src'; import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'; import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; import { MetadataBlocksRepository } from '../../../src/metadataBlocks/infra/repositories/MetadataBlocksRepository'; +import { Author, DatasetContact, DatasetDescription } from '../../../src/datasets/domain/models/Dataset'; describe('DatasetsRepository', () => { const sut: DatasetsRepository = new DatasetsRepository(); @@ -247,52 +248,75 @@ describe('DatasetsRepository', () => { }); describe('createDataset', () => { - const testNewDatasetTitle = 'Dataset created using the createDataset use case'; - const testNewDataset: NewDataset = { - metadataBlockValues: [ - { - name: 'citation', - fields: { - title: testNewDatasetTitle, - author: [ - { - authorName: 'Admin, Dataverse', - authorAffiliation: 'Dataverse.org', - }, - { - authorName: 'Owner, Dataverse', - authorAffiliation: 'Dataverse.org', - }, - ], - datasetContact: [ - { - datasetContactEmail: 'finch@mailinator.com', - datasetContactName: 'Finch, Fiona', - }, - ], - dsDescription: [ - { - dsDescriptionValue: 'This is the description of the dataset.', - }, - ], - subject: ['Medicine, Health and Life Sciences'], + test('should create a dataset with the provided dataset citation fields', async () => { + const testTitle = 'Dataset created using the createDataset use case'; + const testAuthorName1 = 'Admin, Dataverse'; + const testAuthorName2 = 'Owner, Dataverse'; + const testAuthorAffiliation1 = 'Dataverse.org'; + const testAuthorAffiliation2 = 'Dataversedemo.org'; + const testContactEmail = 'finch@mailinator.com'; + const testContactName = 'Finch, Fiona'; + const testDescription = 'This is the description of the dataset.'; + const testSubject = ['Medicine, Health and Life Sciences']; + + const testNewDataset: NewDataset = { + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: testTitle, + author: [ + { + authorName: testAuthorName1, + authorAffiliation: testAuthorAffiliation1, + }, + { + authorName: testAuthorName2, + authorAffiliation: testAuthorAffiliation2, + }, + ], + datasetContact: [ + { + datasetContactEmail: testContactEmail, + datasetContactName: testContactName, + }, + ], + dsDescription: [ + { + dsDescriptionValue: testDescription, + }, + ], + subject: testSubject, + }, }, - }, - ], - }; + ], + }; - test('should create a dataset', async () => { const metadataBlocksRepository = new MetadataBlocksRepository(); const citationMetadataBlock = await metadataBlocksRepository.getMetadataBlockByName('citation'); + const createdDataset = await sut.createDataset(testNewDataset, [citationMetadataBlock], 'root'); + const actualCreatedDataset = await sut.getDataset(createdDataset.numericId, latestVersionId, false); - await sut.createDataset(testNewDataset, [citationMetadataBlock], 'root').catch(() => { - assert.fail('Error while creating the Dataset'); - }); - - await new Promise((resolve) => setTimeout(resolve, 2000)); - - const actualCreatedDataset = await sut.getDataset(4, latestVersionId, false); - expect(actualCreatedDataset.metadataBlocks[0].fields.title).toBe(testNewDatasetTitle); + expect(actualCreatedDataset.metadataBlocks[0].fields.title).toBe(testTitle); + expect((actualCreatedDataset.metadataBlocks[0].fields.author[0] as Author).authorName).toBe(testAuthorName1); + expect((actualCreatedDataset.metadataBlocks[0].fields.author[0] as Author).authorAffiliation).toBe( + testAuthorAffiliation1, + ); + expect((actualCreatedDataset.metadataBlocks[0].fields.author[1] as Author).authorName).toBe(testAuthorName2); + expect((actualCreatedDataset.metadataBlocks[0].fields.author[1] as Author).authorAffiliation).toBe( + testAuthorAffiliation2, + ); + expect( + (actualCreatedDataset.metadataBlocks[0].fields.datasetContact[0] as DatasetContact).datasetContactEmail, + ).toBe(testContactEmail); + expect( + (actualCreatedDataset.metadataBlocks[0].fields.datasetContact[0] as DatasetContact).datasetContactName, + ).toBe(testContactName); + expect( + (actualCreatedDataset.metadataBlocks[0].fields.dsDescription[0] as DatasetDescription).dsDescriptionValue, + ).toBe(testDescription); + expect(actualCreatedDataset.metadataBlocks[0].fields.subject[0]).toBe(testSubject[0]); + expect(actualCreatedDataset.metadataBlocks[0].fields.subject[1]).toBe(testSubject[1]); }); }); }); diff --git a/test/integration/environment/setup.js b/test/integration/environment/setup.js index 57cf5c06..c618ce63 100644 --- a/test/integration/environment/setup.js +++ b/test/integration/environment/setup.js @@ -51,12 +51,11 @@ async function setupTestFixtures() { console.log('Creating test datasets...'); await createDatasetViaApi(datasetJson1) .then() - .catch((error) => { + .catch(() => { console.error('Tests setup: Error while creating test Dataset 1'); }); await createDatasetViaApi(datasetJson2) - .then() - .catch((error) => { + .catch(() => { console.error('Tests setup: Error while creating test Dataset 2'); }); console.log('Test datasets created'); diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index 6b626a78..88977a77 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -1,4 +1,5 @@ import { CreateDataset } from '../../../src/datasets/domain/useCases/CreateDataset'; +import { CreatedDatasetIdentifiers } from '../../../src/datasets/domain/models/CreatedDatasetIdentifiers'; import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'; import { assert, createSandbox, SinonSandbox } from 'sinon'; import { NewResourceValidator } from '../../../src/core/domain/useCases/validators/NewResourceValidator'; @@ -23,9 +24,14 @@ describe('execute', () => { return metadataBlocksRepositoryStub; } - test('should call repository when validation is successful', async () => { + test('should return new dataset identifiers when validation is successful and repository call is successful', async () => { + const testCreatedDatasetIdentifiers: CreatedDatasetIdentifiers = { + persistentId: 'test', + numericId: 1, + }; + const datasetsRepositoryStub = {}; - const createDatasetStub = sandbox.stub(); + const createDatasetStub = sandbox.stub().returns(testCreatedDatasetIdentifiers); datasetsRepositoryStub.createDataset = createDatasetStub; const newDatasetValidatorStub = {}; @@ -34,10 +40,12 @@ describe('execute', () => { const sut = new CreateDataset(datasetsRepositoryStub, setupMetadataBlocksRepositoryStub(), newDatasetValidatorStub); - await sut.execute(testDataset); + const actual = await sut.execute(testDataset); + + assert.match(actual, testCreatedDatasetIdentifiers); assert.calledWithExactly(validateStub, testDataset, testMetadataBlocks); - assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks, 'root') + assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks, 'root'); assert.callOrder(validateStub, createDatasetStub); }); diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index d581d38e..eba9f682 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -613,13 +613,28 @@ describe('DatasetsRepository', () => { const testCollectionName = 'test'; const expectedNewDatasetRequestPayloadJson = JSON.stringify(createNewDatasetRequestPayload()); + const testCreatedDatasetIdentifiers = { + persistentId: 'test', + numericId: 1, + }; + + const testCreateDatasetResponse = { + data: { + status: 'OK', + data: { + id: testCreatedDatasetIdentifiers.numericId, + persistentId: testCreatedDatasetIdentifiers.persistentId, + }, + }, + }; + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionName}/datasets`; test('should call the API with a correct request payload', async () => { - const axiosPostStub = sandbox.stub(axios, 'post').resolves(); + const axiosPostStub = sandbox.stub(axios, 'post').resolves(testCreateDatasetResponse); // API Key auth - await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); + let actual = await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); assert.calledWithExactly( axiosPostStub, @@ -628,10 +643,12 @@ describe('DatasetsRepository', () => { TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, ); + assert.match(actual, testCreatedDatasetIdentifiers); + // Session cookie auth ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); + actual = await sut.createDataset(testNewDataset, testMetadataBlocks, testCollectionName); assert.calledWithExactly( axiosPostStub, @@ -639,6 +656,8 @@ describe('DatasetsRepository', () => { expectedNewDatasetRequestPayloadJson, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, ); + + assert.match(actual, testCreatedDatasetIdentifiers); }); test('should return error result on error response', async () => { From d9aa27a6f6f9d585e1c0f0dce9786f6a5b7c1a16 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 19 Jan 2024 16:44:27 +0000 Subject: [PATCH 29/37] Added: missing models exported in index.ts --- src/datasets/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 4e67636a..f611e4a8 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -50,3 +50,11 @@ export { } from './domain/models/Dataset'; export { DatasetPreview } from './domain/models/DatasetPreview'; export { DatasetPreviewSubset } from './domain/models/DatasetPreviewSubset'; +export { + NewDataset, + NewDatasetMetadataBlockValues, + NewDatasetMetadataFields, + NewDatasetMetadataFieldValue, + NewDatasetMetadataChildFieldValue, +} from './domain/models/NewDataset'; +export { CreatedDatasetIdentifiers } from './domain/models/CreatedDatasetIdentifiers'; From db836d8bd6714634c4976eb6dca6dc432ed188bc Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 19 Jan 2024 16:45:37 +0000 Subject: [PATCH 30/37] Fixed: missing import in IDatasetsRepository --- src/datasets/domain/repositories/IDatasetsRepository.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 929e8f54..84ca932f 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -4,6 +4,7 @@ import { DatasetLock } from '../models/DatasetLock'; import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'; import { NewDataset } from '../models/NewDataset'; import { MetadataBlock } from '../../../metadataBlocks'; +import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers'; export interface IDatasetsRepository { getDatasetSummaryFieldNames(): Promise; From b86911f0a523c805489cee5742cc3f8f6a3380e2 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 22 Jan 2024 12:33:37 +0000 Subject: [PATCH 31/37] Refactor: DTO convention for new dataset creation --- src/datasets/domain/dtos/NewDatasetDTO.ts | 21 ++++++++++ src/datasets/domain/models/NewDataset.ts | 21 ---------- .../repositories/IDatasetsRepository.ts | 4 +- src/datasets/domain/useCases/CreateDataset.ts | 8 ++-- .../validators/NewDatasetValidator.ts | 30 +++++++-------- src/datasets/index.ts | 12 +++--- .../infra/repositories/DatasetsRepository.ts | 4 +- .../transformers/newDatasetTransformers.ts | 34 ++++++++--------- .../datasets/DatasetsRepository.test.ts | 4 +- test/testHelpers/datasets/newDatasetHelper.ts | 20 +++++----- test/unit/datasets/CreateDataset.test.ts | 4 +- test/unit/datasets/DatasetsRepository.test.ts | 4 +- .../unit/datasets/NewDatasetValidator.test.ts | 38 +++++++++---------- .../datasets/newDatasetTransformers.test.ts | 4 +- 14 files changed, 104 insertions(+), 104 deletions(-) create mode 100644 src/datasets/domain/dtos/NewDatasetDTO.ts delete mode 100644 src/datasets/domain/models/NewDataset.ts diff --git a/src/datasets/domain/dtos/NewDatasetDTO.ts b/src/datasets/domain/dtos/NewDatasetDTO.ts new file mode 100644 index 00000000..5d6cdaff --- /dev/null +++ b/src/datasets/domain/dtos/NewDatasetDTO.ts @@ -0,0 +1,21 @@ +import { DatasetLicense } from '../models/Dataset'; + +export interface NewDatasetDTO { + license?: DatasetLicense; + metadataBlockValues: NewDatasetMetadataBlockValuesDTO[]; +} + +export interface NewDatasetMetadataBlockValuesDTO { + name: string; + fields: NewDatasetMetadataFieldsDTO; +} + +export type NewDatasetMetadataFieldsDTO = Record; + +export type NewDatasetMetadataFieldValueDTO = + | string + | string[] + | NewDatasetMetadataChildFieldValueDTO + | NewDatasetMetadataChildFieldValueDTO[]; + +export type NewDatasetMetadataChildFieldValueDTO = Record; diff --git a/src/datasets/domain/models/NewDataset.ts b/src/datasets/domain/models/NewDataset.ts deleted file mode 100644 index 2795c192..00000000 --- a/src/datasets/domain/models/NewDataset.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DatasetLicense } from './Dataset'; - -export interface NewDataset { - license?: DatasetLicense; - metadataBlockValues: NewDatasetMetadataBlockValues[]; -} - -export interface NewDatasetMetadataBlockValues { - name: string; - fields: NewDatasetMetadataFields; -} - -export type NewDatasetMetadataFields = Record; - -export type NewDatasetMetadataFieldValue = - | string - | string[] - | NewDatasetMetadataChildFieldValue - | NewDatasetMetadataChildFieldValue[]; - -export type NewDatasetMetadataChildFieldValue = Record; diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 84ca932f..41c7ca78 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -2,7 +2,7 @@ import { Dataset } from '../models/Dataset'; import { DatasetUserPermissions } from '../models/DatasetUserPermissions'; import { DatasetLock } from '../models/DatasetLock'; import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset'; -import { NewDataset } from '../models/NewDataset'; +import { NewDatasetDTO } from '../dtos/NewDatasetDTO'; import { MetadataBlock } from '../../../metadataBlocks'; import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers'; @@ -15,5 +15,5 @@ export interface IDatasetsRepository { getDatasetUserPermissions(datasetId: number | string): Promise; getDatasetLocks(datasetId: number | string): Promise; getAllDatasetPreviews(limit?: number, offset?: number): Promise; - createDataset(newDataset: NewDataset, datasetMetadataBlocks: MetadataBlock[], collectionId: string): Promise; + createDataset(newDataset: NewDatasetDTO, datasetMetadataBlocks: MetadataBlock[], collectionId: string): Promise; } diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts index 7840446c..da73e517 100644 --- a/src/datasets/domain/useCases/CreateDataset.ts +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -1,6 +1,6 @@ import { UseCase } from '../../../core/domain/useCases/UseCase'; import { IDatasetsRepository } from '../repositories/IDatasetsRepository'; -import { NewDataset, NewDatasetMetadataBlockValues } from '../models/NewDataset'; +import { NewDatasetDTO, NewDatasetMetadataBlockValuesDTO } from '../dtos/NewDatasetDTO'; import { NewResourceValidator } from '../../../core/domain/useCases/validators/NewResourceValidator'; import { IMetadataBlocksRepository } from '../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository'; import { MetadataBlock } from '../../../metadataBlocks'; @@ -21,19 +21,19 @@ export class CreateDataset implements UseCase { this.newDatasetValidator = newDatasetValidator; } - async execute(newDataset: NewDataset, collectionId: string = 'root'): Promise { + async execute(newDataset: NewDatasetDTO, collectionId: string = 'root'): Promise { const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset); return await this.newDatasetValidator.validate(newDataset, metadataBlocks).then(async () => { return await this.datasetsRepository.createDataset(newDataset, metadataBlocks, collectionId); }); } - async getNewDatasetMetadataBlocks(newDataset: NewDataset): Promise { + async getNewDatasetMetadataBlocks(newDataset: NewDatasetDTO): Promise { let metadataBlocks: MetadataBlock[] = []; for (const metadataBlockValue in newDataset.metadataBlockValues) { metadataBlocks.push( await this.metadataBlocksRepository.getMetadataBlockByName( - (metadataBlockValue as unknown as NewDatasetMetadataBlockValues).name, + (metadataBlockValue as unknown as NewDatasetMetadataBlockValuesDTO).name, ), ); } diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 712fb1f2..d5f63692 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -1,9 +1,9 @@ import { - NewDataset, - NewDatasetMetadataFieldValue, - NewDatasetMetadataChildFieldValue, - NewDatasetMetadataBlockValues, -} from '../../models/NewDataset'; + NewDatasetDTO, + NewDatasetMetadataFieldValueDTO, + NewDatasetMetadataChildFieldValueDTO, + NewDatasetMetadataBlockValuesDTO, +} from '../../dtos/NewDatasetDTO'; import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; import { MetadataFieldInfo, MetadataBlock } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; @@ -15,21 +15,21 @@ import { DateFormatFieldError } from './errors/DateFormatFieldError'; export interface NewDatasetMetadataFieldAndValueInfo { metadataFieldInfo: MetadataFieldInfo; metadataFieldKey: string; - metadataFieldValue: NewDatasetMetadataFieldValue; + metadataFieldValue: NewDatasetMetadataFieldValueDTO; metadataBlockName: string; metadataParentFieldKey?: string; metadataFieldPosition?: number; } export class NewDatasetValidator implements NewResourceValidator { - async validate(resource: NewDataset, metadataBlocks: MetadataBlock[]): Promise { + async validate(resource: NewDatasetDTO, metadataBlocks: MetadataBlock[]): Promise { for (const metadataBlockValues of resource.metadataBlockValues) { await this.validateMetadataBlock(metadataBlockValues, metadataBlocks); } } private async validateMetadataBlock( - metadataBlockValues: NewDatasetMetadataBlockValues, + metadataBlockValues: NewDatasetMetadataBlockValuesDTO, metadataBlocks: MetadataBlock[], ) { const metadataBlockName = metadataBlockValues.name; @@ -99,7 +99,7 @@ export class NewDatasetValidator implements NewResourceValidator { ); } - const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValue[]; + const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValueDTO[]; fieldValues.forEach((value, metadataFieldPosition) => { this.validateFieldValue({ metadataFieldInfo: metadataFieldInfo, @@ -189,7 +189,7 @@ export class NewDatasetValidator implements NewResourceValidator { metadataFieldInfo: childMetadataFieldInfo, metadataFieldKey: childMetadataFieldKey, metadataFieldValue: ( - newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValue + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO )[childMetadataFieldKey], metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, @@ -198,19 +198,19 @@ export class NewDatasetValidator implements NewResourceValidator { } } - private isEmptyString(metadataFieldValue: NewDatasetMetadataFieldValue): boolean { + private isEmptyString(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean { return typeof metadataFieldValue == 'string' && metadataFieldValue.trim() === ''; } - private isEmptyArray(metadataFieldValue: NewDatasetMetadataFieldValue): boolean { - return Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0; + private isEmptyArray(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean { + return Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0; } private isValidArrayType( - metadataFieldValue: Array, + metadataFieldValue: Array, expectedType: 'string' | 'object', ): boolean { - return metadataFieldValue.every((item: string | NewDatasetMetadataFieldValue) => typeof item === expectedType); + return metadataFieldValue.every((item: string | NewDatasetMetadataFieldValueDTO) => typeof item === expectedType); } private createGeneralValidationError( diff --git a/src/datasets/index.ts b/src/datasets/index.ts index f611e4a8..5028cf1f 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -51,10 +51,10 @@ export { export { DatasetPreview } from './domain/models/DatasetPreview'; export { DatasetPreviewSubset } from './domain/models/DatasetPreviewSubset'; export { - NewDataset, - NewDatasetMetadataBlockValues, - NewDatasetMetadataFields, - NewDatasetMetadataFieldValue, - NewDatasetMetadataChildFieldValue, -} from './domain/models/NewDataset'; + NewDatasetDTO as NewDataset, + NewDatasetMetadataBlockValuesDTO as NewDatasetMetadataBlockValues, + NewDatasetMetadataFieldsDTO as NewDatasetMetadataFields, + NewDatasetMetadataFieldValueDTO as NewDatasetMetadataFieldValue, + NewDatasetMetadataChildFieldValueDTO as NewDatasetMetadataChildFieldValue, +} from './domain/dtos/NewDatasetDTO'; export { CreatedDatasetIdentifiers } from './domain/models/CreatedDatasetIdentifiers'; diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index ba0dfd75..3323c429 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -8,7 +8,7 @@ import { DatasetLock } from '../../domain/models/DatasetLock'; import { transformDatasetLocksResponseToDatasetLocks } from './transformers/datasetLocksTransformers'; import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transformers/datasetPreviewsTransformers'; import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset'; -import { NewDataset } from '../../domain/models/NewDataset'; +import { NewDatasetDTO } from '../../domain/dtos/NewDatasetDTO'; import { MetadataBlock } from '../../../metadataBlocks'; import { transformNewDatasetModelToRequestPayload } from './transformers/newDatasetTransformers'; import { CreatedDatasetIdentifiers } from '../../domain/models/CreatedDatasetIdentifiers'; @@ -112,7 +112,7 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi } public async createDataset( - newDataset: NewDataset, + newDataset: NewDatasetDTO, datasetMetadataBlocks: MetadataBlock[], collectionId: string, ): Promise { diff --git a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts index b6c8fbab..b4ff1f37 100644 --- a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts +++ b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts @@ -1,10 +1,10 @@ import { - NewDataset, - NewDatasetMetadataBlockValues, - NewDatasetMetadataFields, - NewDatasetMetadataFieldValue, - NewDatasetMetadataChildFieldValue, -} from '../../../domain/models/NewDataset'; + NewDatasetDTO, + NewDatasetMetadataBlockValuesDTO, + NewDatasetMetadataFieldsDTO, + NewDatasetMetadataFieldValueDTO, + NewDatasetMetadataChildFieldValueDTO, +} from '../../../domain/dtos/NewDatasetDTO'; import { DatasetLicense } from '../../../domain/models/Dataset'; import { MetadataBlock, MetadataFieldInfo } from '../../../../metadataBlocks'; @@ -34,7 +34,7 @@ export type MetadataFieldValueRequestPayload = | Record[]; export const transformNewDatasetModelToRequestPayload = ( - newDataset: NewDataset, + newDataset: NewDatasetDTO, metadataBlocks: MetadataBlock[], ): NewDatasetRequestPayload => { return { @@ -46,11 +46,11 @@ export const transformNewDatasetModelToRequestPayload = ( }; export const transformMetadataBlockModelsToRequestPayload = ( - newDatasetMetadataBlocksValues: NewDatasetMetadataBlockValues[], + newDatasetMetadataBlocksValues: NewDatasetMetadataBlockValuesDTO[], metadataBlocks: MetadataBlock[], ): Record => { let metadataBlocksRequestPayload: Record = {}; - newDatasetMetadataBlocksValues.forEach(function (newDatasetMetadataBlockValues: NewDatasetMetadataBlockValues) { + newDatasetMetadataBlocksValues.forEach(function (newDatasetMetadataBlockValues: NewDatasetMetadataBlockValuesDTO) { const metadataBlock: MetadataBlock = metadataBlocks.find( (metadataBlock) => metadataBlock.name == newDatasetMetadataBlockValues.name, ); @@ -66,12 +66,12 @@ export const transformMetadataBlockModelsToRequestPayload = ( }; export const transformMetadataFieldModelsToRequestPayload = ( - newDatasetMetadataFields: NewDatasetMetadataFields, + newDatasetMetadataFields: NewDatasetMetadataFieldsDTO, metadataBlockFields: Record, ): MetadataFieldRequestPayload[] => { let metadataFieldsRequestPayload: MetadataFieldRequestPayload[] = []; for (const metadataFieldKey of Object.keys(newDatasetMetadataFields)) { - const newDatasetMetadataChildFieldValue: NewDatasetMetadataFieldValue = newDatasetMetadataFields[metadataFieldKey]; + const newDatasetMetadataChildFieldValue: NewDatasetMetadataFieldValueDTO = newDatasetMetadataFields[metadataFieldKey]; metadataFieldsRequestPayload.push({ value: transformMetadataFieldValueToRequestPayload( newDatasetMetadataChildFieldValue, @@ -86,20 +86,20 @@ export const transformMetadataFieldModelsToRequestPayload = ( }; export const transformMetadataFieldValueToRequestPayload = ( - newDatasetMetadataFieldValue: NewDatasetMetadataFieldValue, + newDatasetMetadataFieldValue: NewDatasetMetadataFieldValueDTO, metadataBlockFieldInfo: MetadataFieldInfo, ): MetadataFieldValueRequestPayload => { let value: MetadataFieldValueRequestPayload; if (metadataBlockFieldInfo.multiple) { const newDatasetMetadataChildFieldValues = newDatasetMetadataFieldValue as | string[] - | NewDatasetMetadataChildFieldValue[]; + | NewDatasetMetadataChildFieldValueDTO[]; if (typeof newDatasetMetadataChildFieldValues[0] == 'string') { value = newDatasetMetadataFieldValue as string[]; } else { value = []; - (newDatasetMetadataChildFieldValues as NewDatasetMetadataChildFieldValue[]).forEach(function ( - childMetadataFieldValue: NewDatasetMetadataChildFieldValue, + (newDatasetMetadataChildFieldValues as NewDatasetMetadataChildFieldValueDTO[]).forEach(function ( + childMetadataFieldValue: NewDatasetMetadataChildFieldValueDTO, ) { (value as Record[]).push( transformMetadataChildFieldValueToRequestPayload(childMetadataFieldValue, metadataBlockFieldInfo), @@ -111,7 +111,7 @@ export const transformMetadataFieldValueToRequestPayload = ( value = newDatasetMetadataFieldValue; } else { value = transformMetadataChildFieldValueToRequestPayload( - newDatasetMetadataFieldValue as NewDatasetMetadataChildFieldValue, + newDatasetMetadataFieldValue as NewDatasetMetadataChildFieldValueDTO, metadataBlockFieldInfo, ); } @@ -120,7 +120,7 @@ export const transformMetadataFieldValueToRequestPayload = ( }; export const transformMetadataChildFieldValueToRequestPayload = ( - newDatasetMetadataChildFieldValue: NewDatasetMetadataChildFieldValue, + newDatasetMetadataChildFieldValue: NewDatasetMetadataChildFieldValueDTO, metadataBlockFieldInfo: MetadataFieldInfo, ): Record => { let metadataChildFieldRequestPayload: Record = {}; diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index d6003261..d1c1d602 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -12,7 +12,7 @@ import { DatasetNotNumberedVersion, DatasetLockType, DatasetPreviewSubset } from import { fail } from 'assert'; import { ApiConfig } from '../../../src'; import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'; -import { NewDataset } from '../../../src/datasets/domain/models/NewDataset'; +import { NewDatasetDTO } from '../../../src/datasets/domain/dtos/NewDatasetDTO'; import { MetadataBlocksRepository } from '../../../src/metadataBlocks/infra/repositories/MetadataBlocksRepository'; import { Author, DatasetContact, DatasetDescription } from '../../../src/datasets/domain/models/Dataset'; @@ -259,7 +259,7 @@ describe('DatasetsRepository', () => { const testDescription = 'This is the description of the dataset.'; const testSubject = ['Medicine, Health and Life Sciences']; - const testNewDataset: NewDataset = { + const testNewDataset: NewDatasetDTO = { metadataBlockValues: [ { name: 'citation', diff --git a/test/testHelpers/datasets/newDatasetHelper.ts b/test/testHelpers/datasets/newDatasetHelper.ts index 64f5e589..4ff08439 100644 --- a/test/testHelpers/datasets/newDatasetHelper.ts +++ b/test/testHelpers/datasets/newDatasetHelper.ts @@ -1,14 +1,14 @@ -import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; +import { NewDatasetDTO, NewDatasetMetadataFieldValueDTO } from '../../../src/datasets/domain/dtos/NewDatasetDTO'; import { MetadataBlock } from '../../../src'; import { NewDatasetRequestPayload } from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; -export const createNewDatasetModel = ( - titleFieldValue?: NewDatasetMetadataFieldValue, - authorFieldValue?: NewDatasetMetadataFieldValue, - alternativeRequiredTitleValue?: NewDatasetMetadataFieldValue, - timePeriodCoveredStartValue?: NewDatasetMetadataFieldValue, - contributorTypeValue?: NewDatasetMetadataFieldValue, -): NewDataset => { +export const createNewDatasetDTO = ( + titleFieldValue?: NewDatasetMetadataFieldValueDTO, + authorFieldValue?: NewDatasetMetadataFieldValueDTO, + alternativeRequiredTitleValue?: NewDatasetMetadataFieldValueDTO, + timePeriodCoveredStartValue?: NewDatasetMetadataFieldValueDTO, + contributorTypeValue?: NewDatasetMetadataFieldValueDTO, +): NewDatasetDTO => { const validTitle = 'test dataset'; const validAuthorFieldValue = [ { @@ -47,7 +47,7 @@ export const createNewDatasetModel = ( }; }; -export const createNewDatasetModelWithoutFirstLevelRequiredField = (): NewDataset => { +export const createNewDatasetDTOWithoutFirstLevelRequiredField = (): NewDatasetDTO => { return { metadataBlockValues: [ { @@ -60,7 +60,7 @@ export const createNewDatasetModelWithoutFirstLevelRequiredField = (): NewDatase }; }; -export const createNewDatasetModelWithoutSecondLevelRequiredField = (): NewDataset => { +export const createNewDatasetDTOWithoutSecondLevelRequiredField = (): NewDatasetDTO => { return { metadataBlockValues: [ { diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index 88977a77..6cfd4544 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -3,14 +3,14 @@ import { CreatedDatasetIdentifiers } from '../../../src/datasets/domain/models/C import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'; import { assert, createSandbox, SinonSandbox } from 'sinon'; import { NewResourceValidator } from '../../../src/core/domain/useCases/validators/NewResourceValidator'; -import { createNewDatasetModel, createNewDatasetMetadataBlockModel } from '../../testHelpers/datasets/newDatasetHelper'; +import { createNewDatasetDTO, createNewDatasetMetadataBlockModel } from '../../testHelpers/datasets/newDatasetHelper'; import { ResourceValidationError } from '../../../src/core/domain/useCases/validators/errors/ResourceValidationError'; import { WriteError } from '../../../src'; import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); - const testDataset = createNewDatasetModel(); + const testDataset = createNewDatasetDTO(); const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; afterEach(() => { diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index eba9f682..c33bbeb4 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -18,7 +18,7 @@ import { createDatasetPreviewPayload, } from '../../testHelpers/datasets/datasetPreviewHelper'; import { - createNewDatasetModel, + createNewDatasetDTO, createNewDatasetMetadataBlockModel, createNewDatasetRequestPayload, } from '../../testHelpers/datasets/newDatasetHelper'; @@ -608,7 +608,7 @@ describe('DatasetsRepository', () => { }); describe('createDataset', () => { - const testNewDataset = createNewDatasetModel(); + const testNewDataset = createNewDatasetDTO(); const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; const testCollectionName = 'test'; const expectedNewDatasetRequestPayloadJson = JSON.stringify(createNewDatasetRequestPayload()); diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index c083ba06..82ca5274 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -1,14 +1,14 @@ import { NewDatasetValidator } from '../../../src/datasets/domain/useCases/validators/NewDatasetValidator'; import { assert, createSandbox, SinonSandbox } from 'sinon'; import { - createNewDatasetModel, + createNewDatasetDTO, createNewDatasetMetadataBlockModel, - createNewDatasetModelWithoutFirstLevelRequiredField, + createNewDatasetDTOWithoutFirstLevelRequiredField, } from '../../testHelpers/datasets/newDatasetHelper'; import { fail } from 'assert'; import { EmptyFieldError } from '../../../src/datasets/domain/useCases/validators/errors/EmptyFieldError'; import { FieldValidationError } from '../../../src/datasets/domain/useCases/validators/errors/FieldValidationError'; -import { NewDataset, NewDatasetMetadataFieldValue } from '../../../src/datasets/domain/models/NewDataset'; +import { NewDatasetDTO, NewDatasetMetadataFieldValueDTO } from '../../../src/datasets/domain/dtos/NewDatasetDTO'; describe('validate', () => { const sandbox: SinonSandbox = createSandbox(); @@ -19,7 +19,7 @@ describe('validate', () => { }); async function runValidateExpectingFieldValidationError( - newDataset: NewDataset, + newDataset: NewDatasetDTO, expectedMetadataFieldName: string, expectedErrorMessage: string, expectedParentMetadataFieldName?: string, @@ -42,7 +42,7 @@ describe('validate', () => { } test('should not raise a validation error when a new dataset with only the required fields is valid', async () => { - const testNewDataset = createNewDatasetModel(); + const testNewDataset = createNewDatasetDTO(); const sut = new NewDatasetValidator(); await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); @@ -50,15 +50,15 @@ describe('validate', () => { test('should raise an empty field error when a first level required string field is missing', async () => { await runValidateExpectingFieldValidationError( - createNewDatasetModelWithoutFirstLevelRequiredField(), + createNewDatasetDTOWithoutFirstLevelRequiredField(), 'author', 'There was an error when validating the field author from metadata block citation. Reason was: The field should not be empty.', ); }); test('should raise an empty field error when a first level required array field is empty', async () => { - const invalidAuthorFieldValue: NewDatasetMetadataFieldValue = []; - const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); + const invalidAuthorFieldValue: NewDatasetMetadataFieldValueDTO = []; + const testNewDataset = createNewDatasetDTO(undefined, invalidAuthorFieldValue, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'author', @@ -68,7 +68,7 @@ describe('validate', () => { test('should raise an error when the provided field value for an unique field is an array', async () => { const invalidTitleFieldValue = ['title1', 'title2']; - const testNewDataset = createNewDatasetModel(invalidTitleFieldValue, undefined, undefined); + const testNewDataset = createNewDatasetDTO(invalidTitleFieldValue, undefined, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'title', @@ -81,7 +81,7 @@ describe('validate', () => { invalidChildField1: 'invalid value 1', invalidChildField2: 'invalid value 2', }; - const testNewDataset = createNewDatasetModel(invalidTitleFieldValue, undefined, undefined); + const testNewDataset = createNewDatasetDTO(invalidTitleFieldValue, undefined, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'title', @@ -91,7 +91,7 @@ describe('validate', () => { test('should raise an error when the provided field value for a multiple field is a string', async () => { const invalidAuthorFieldValue = 'invalidValue'; - const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); + const testNewDataset = createNewDatasetDTO(undefined, invalidAuthorFieldValue, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'author', @@ -101,7 +101,7 @@ describe('validate', () => { test('should raise an error when the provided field value is an array of strings and the field expects an array of objects', async () => { const invalidAuthorFieldValue = ['invalidValue1', 'invalidValue2']; - const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); + const testNewDataset = createNewDatasetDTO(undefined, invalidAuthorFieldValue, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'author', @@ -120,7 +120,7 @@ describe('validate', () => { invalidChildField2: 'invalid value 2', }, ]; - const testNewDataset = createNewDatasetModel(undefined, undefined, invalidAlternativeTitleFieldValue); + const testNewDataset = createNewDatasetDTO(undefined, undefined, invalidAlternativeTitleFieldValue); await runValidateExpectingFieldValidationError( testNewDataset, 'alternativeRequiredTitle', @@ -138,7 +138,7 @@ describe('validate', () => { authorAffiliation: 'Dataverse.org', }, ]; - const testNewDataset = createNewDatasetModel(undefined, invalidAuthorFieldValue, undefined); + const testNewDataset = createNewDatasetDTO(undefined, invalidAuthorFieldValue, undefined); await runValidateExpectingFieldValidationError( testNewDataset, 'authorName', @@ -158,13 +158,13 @@ describe('validate', () => { authorName: 'John, Doe', }, ]; - const testNewDataset = createNewDatasetModel(undefined, authorFieldValue, undefined); + const testNewDataset = createNewDatasetDTO(undefined, authorFieldValue, undefined); const sut = new NewDatasetValidator(); await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); test('should raise a date format validation error when a date field has an invalid format', async () => { - const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, '1-1-2020'); + const testNewDataset = createNewDatasetDTO(undefined, undefined, undefined, '1-1-2020'); await runValidateExpectingFieldValidationError( testNewDataset, 'timePeriodCoveredStart', @@ -173,13 +173,13 @@ describe('validate', () => { }); test('should not raise a date format validation error when a date field has a valid format', async () => { - const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, '2020-01-01'); + const testNewDataset = createNewDatasetDTO(undefined, undefined, undefined, '2020-01-01'); const sut = new NewDatasetValidator(); await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); test('should raise a controlled vocabulary error when a controlled vocabulary field has an invalid format', async () => { - const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, undefined, 'Wrong Value'); + const testNewDataset = createNewDatasetDTO(undefined, undefined, undefined, undefined, 'Wrong Value'); await runValidateExpectingFieldValidationError( testNewDataset, 'contributorType', @@ -190,7 +190,7 @@ describe('validate', () => { }); test('should not raise a controlled vocabulary error when the value for a controlled vocabulary field is correct', async () => { - const testNewDataset = createNewDatasetModel(undefined, undefined, undefined, undefined, 'Project Member'); + const testNewDataset = createNewDatasetDTO(undefined, undefined, undefined, undefined, 'Project Member'); const sut = new NewDatasetValidator(); await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); diff --git a/test/unit/datasets/newDatasetTransformers.test.ts b/test/unit/datasets/newDatasetTransformers.test.ts index 6804b736..93e19d9b 100644 --- a/test/unit/datasets/newDatasetTransformers.test.ts +++ b/test/unit/datasets/newDatasetTransformers.test.ts @@ -1,14 +1,14 @@ import { assert } from 'sinon'; import { createNewDatasetMetadataBlockModel, - createNewDatasetModel, + createNewDatasetDTO, createNewDatasetRequestPayload, } from '../../testHelpers/datasets/newDatasetHelper'; import { transformNewDatasetModelToRequestPayload } from '../../../src/datasets/infra/repositories/transformers/newDatasetTransformers'; describe('transformNewDatasetModelToRequestPayload', () => { test('should correctly transform a new dataset model to a new dataset request payload', async () => { - const testNewDataset = createNewDatasetModel(); + const testNewDataset = createNewDatasetDTO(); const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; const expectedNewDatasetRequestPayload = createNewDatasetRequestPayload(); const actual = transformNewDatasetModelToRequestPayload(testNewDataset, testMetadataBlocks); From 53bc05f3d90865af84d713fae756be2bb8e8a792 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 22 Jan 2024 15:11:39 +0000 Subject: [PATCH 32/37] Fixed: CreateDataset dependency calls --- .../repositories/IDatasetsRepository.ts | 6 +- src/datasets/domain/useCases/CreateDataset.ts | 17 ++--- .../validators/NewDatasetValidator.ts | 4 +- .../transformers/newDatasetTransformers.ts | 3 +- test/unit/datasets/CreateDataset.test.ts | 65 ++++++++++++++----- 5 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 41c7ca78..fa3c697d 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -15,5 +15,9 @@ export interface IDatasetsRepository { getDatasetUserPermissions(datasetId: number | string): Promise; getDatasetLocks(datasetId: number | string): Promise; getAllDatasetPreviews(limit?: number, offset?: number): Promise; - createDataset(newDataset: NewDatasetDTO, datasetMetadataBlocks: MetadataBlock[], collectionId: string): Promise; + createDataset( + newDataset: NewDatasetDTO, + datasetMetadataBlocks: MetadataBlock[], + collectionId: string, + ): Promise; } diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts index da73e517..ca352d82 100644 --- a/src/datasets/domain/useCases/CreateDataset.ts +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -23,20 +23,17 @@ export class CreateDataset implements UseCase { async execute(newDataset: NewDatasetDTO, collectionId: string = 'root'): Promise { const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset); - return await this.newDatasetValidator.validate(newDataset, metadataBlocks).then(async () => { - return await this.datasetsRepository.createDataset(newDataset, metadataBlocks, collectionId); - }); + this.newDatasetValidator.validate(newDataset, metadataBlocks); + return this.datasetsRepository.createDataset(newDataset, metadataBlocks, collectionId); } async getNewDatasetMetadataBlocks(newDataset: NewDatasetDTO): Promise { let metadataBlocks: MetadataBlock[] = []; - for (const metadataBlockValue in newDataset.metadataBlockValues) { - metadataBlocks.push( - await this.metadataBlocksRepository.getMetadataBlockByName( - (metadataBlockValue as unknown as NewDatasetMetadataBlockValuesDTO).name, - ), - ); - } + await Promise.all( + newDataset.metadataBlockValues.map(async (metadataBlockValue: NewDatasetMetadataBlockValuesDTO) => { + metadataBlocks.push(await this.metadataBlocksRepository.getMetadataBlockByName(metadataBlockValue.name)); + }), + ); return metadataBlocks; } } diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index d5f63692..6c969610 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -203,7 +203,9 @@ export class NewDatasetValidator implements NewResourceValidator { } private isEmptyArray(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean { - return Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0; + return ( + Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0 + ); } private isValidArrayType( diff --git a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts index b4ff1f37..5ae104b5 100644 --- a/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts +++ b/src/datasets/infra/repositories/transformers/newDatasetTransformers.ts @@ -71,7 +71,8 @@ export const transformMetadataFieldModelsToRequestPayload = ( ): MetadataFieldRequestPayload[] => { let metadataFieldsRequestPayload: MetadataFieldRequestPayload[] = []; for (const metadataFieldKey of Object.keys(newDatasetMetadataFields)) { - const newDatasetMetadataChildFieldValue: NewDatasetMetadataFieldValueDTO = newDatasetMetadataFields[metadataFieldKey]; + const newDatasetMetadataChildFieldValue: NewDatasetMetadataFieldValueDTO = + newDatasetMetadataFields[metadataFieldKey]; metadataFieldsRequestPayload.push({ value: transformMetadataFieldValueToRequestPayload( newDatasetMetadataChildFieldValue, diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index 6cfd4544..f3f91388 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -5,7 +5,7 @@ import { assert, createSandbox, SinonSandbox } from 'sinon'; import { NewResourceValidator } from '../../../src/core/domain/useCases/validators/NewResourceValidator'; import { createNewDatasetDTO, createNewDatasetMetadataBlockModel } from '../../testHelpers/datasets/newDatasetHelper'; import { ResourceValidationError } from '../../../src/core/domain/useCases/validators/errors/ResourceValidationError'; -import { WriteError } from '../../../src'; +import { WriteError, ReadError } from '../../../src'; import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository'; describe('execute', () => { @@ -17,13 +17,6 @@ describe('execute', () => { sandbox.restore(); }); - function setupMetadataBlocksRepositoryStub(): IMetadataBlocksRepository { - const metadataBlocksRepositoryStub = {}; - const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlocks[0]); - metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; - return metadataBlocksRepositoryStub; - } - test('should return new dataset identifiers when validation is successful and repository call is successful', async () => { const testCreatedDatasetIdentifiers: CreatedDatasetIdentifiers = { persistentId: 'test', @@ -38,12 +31,17 @@ describe('execute', () => { const validateStub = sandbox.stub().resolves(); newDatasetValidatorStub.validate = validateStub; - const sut = new CreateDataset(datasetsRepositoryStub, setupMetadataBlocksRepositoryStub(), newDatasetValidatorStub); + const metadataBlocksRepositoryStub = {}; + const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlocks[0]); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + + const sut = new CreateDataset(datasetsRepositoryStub, metadataBlocksRepositoryStub, newDatasetValidatorStub); const actual = await sut.execute(testDataset); assert.match(actual, testCreatedDatasetIdentifiers); + assert.calledWithExactly(getMetadataBlockByNameStub, testMetadataBlocks[0].name); assert.calledWithExactly(validateStub, testDataset, testMetadataBlocks); assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks, 'root'); @@ -51,22 +49,27 @@ describe('execute', () => { }); test('should throw ResourceValidationError and not call repository when validation is unsuccessful', async () => { - const datasetsRepositoryStub = {}; - const createDatasetStub = sandbox.stub(); - datasetsRepositoryStub.createDataset = createDatasetStub; + const datasetsRepositoryMock = {}; + const createDatasetMock = sandbox.stub(); + datasetsRepositoryMock.createDataset = createDatasetMock; const newDatasetValidatorStub = {}; const testValidationError = new ResourceValidationError('Test error'); const validateStub = sandbox.stub().throwsException(testValidationError); newDatasetValidatorStub.validate = validateStub; - const sut = new CreateDataset(datasetsRepositoryStub, setupMetadataBlocksRepositoryStub(), newDatasetValidatorStub); + const metadataBlocksRepositoryStub = {}; + const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlocks[0]); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + + const sut = new CreateDataset(datasetsRepositoryMock, metadataBlocksRepositoryStub, newDatasetValidatorStub); let actualError: ResourceValidationError = undefined; await sut.execute(testDataset).catch((e) => (actualError = e)); assert.match(actualError, testValidationError); + assert.calledWithExactly(getMetadataBlockByNameStub, testMetadataBlocks[0].name); assert.calledWithExactly(validateStub, testDataset, testMetadataBlocks); - assert.notCalled(createDatasetStub); + assert.notCalled(createDatasetMock); }); test('should throw WriteError when validation is successful and repository raises an error', async () => { @@ -79,14 +82,44 @@ describe('execute', () => { const validateMock = sandbox.stub().resolves(); newDatasetValidatorStub.validate = validateMock; - const sut = new CreateDataset(datasetsRepositoryStub, setupMetadataBlocksRepositoryStub(), newDatasetValidatorStub); - let actualError: ResourceValidationError = undefined; + const metadataBlocksRepositoryStub = {}; + const getMetadataBlockByNameStub = sandbox.stub().resolves(testMetadataBlocks[0]); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + + const sut = new CreateDataset(datasetsRepositoryStub, metadataBlocksRepositoryStub, newDatasetValidatorStub); + let actualError: WriteError = undefined; await sut.execute(testDataset).catch((e) => (actualError = e)); assert.match(actualError, testWriteError); + assert.calledWithExactly(getMetadataBlockByNameStub, testMetadataBlocks[0].name); assert.calledWithExactly(validateMock, testDataset, testMetadataBlocks); assert.calledWithExactly(createDatasetStub, testDataset, testMetadataBlocks, 'root'); assert.callOrder(validateMock, createDatasetStub); }); + + test('should throw ReadError when metadata blocks repository raises an error', async () => { + const datasetsRepositoryMock = {}; + const createDatasetMock = sandbox.stub(); + datasetsRepositoryMock.createDataset = createDatasetMock; + + const newDatasetValidatorMock = {}; + const validateMock = sandbox.stub().resolves(); + newDatasetValidatorMock.validate = validateMock; + + const metadataBlocksRepositoryStub = {}; + const testReadError = new ReadError('Test error'); + const getMetadataBlockByNameStub = sandbox.stub().throwsException(testReadError); + metadataBlocksRepositoryStub.getMetadataBlockByName = getMetadataBlockByNameStub; + + const sut = new CreateDataset(datasetsRepositoryMock, metadataBlocksRepositoryStub, newDatasetValidatorMock); + let actualError: ReadError = undefined; + await sut.execute(testDataset).catch((e) => (actualError = e)); + assert.match(actualError, testReadError); + + assert.notCalled(validateMock); + assert.notCalled(createDatasetMock); + + assert.calledWithExactly(getMetadataBlockByNameStub, testMetadataBlocks[0].name); + }); }); From bf8fc087d9a5ee091f737fb6dc1eea578bb17496 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 22 Jan 2024 16:00:12 +0000 Subject: [PATCH 33/37] Refactor: metadata field validators --- .../validators/BaseMetadataFieldValidator.ts | 101 +++++++++ .../validators/MetadataFieldValidator.ts | 50 +++++ .../MultipleMetadataFieldValidator.ts | 53 +++++ .../validators/NewDatasetValidator.ts | 207 +----------------- .../SingleMetadataFieldValidator.ts | 30 +++ 5 files changed, 238 insertions(+), 203 deletions(-) create mode 100644 src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts create mode 100644 src/datasets/domain/useCases/validators/MetadataFieldValidator.ts create mode 100644 src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts create mode 100644 src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts diff --git a/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts new file mode 100644 index 00000000..1160b086 --- /dev/null +++ b/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts @@ -0,0 +1,101 @@ +import { ControlledVocabularyFieldError } from './errors/ControlledVocabularyFieldError'; +import { DateFormatFieldError } from './errors/DateFormatFieldError'; +import { NewDatasetMetadataChildFieldValueDTO, NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO'; +import { FieldValidationError } from './errors/FieldValidationError'; +import { MetadataFieldValidator } from './MetadataFieldValidator'; +import { MetadataFieldInfo } from '../../../../metadataBlocks'; + +export interface NewDatasetMetadataFieldAndValueInfo { + metadataFieldInfo: MetadataFieldInfo; + metadataFieldKey: string; + metadataFieldValue: NewDatasetMetadataFieldValueDTO; + metadataBlockName: string; + metadataParentFieldKey?: string; + metadataFieldPosition?: number; +} + +export abstract class BaseMetadataFieldValidator { + abstract validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void; + + protected executeMetadataFieldValidator( + metadataFieldValidator: BaseMetadataFieldValidator, + newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, + ) { + metadataFieldValidator.validate(newDatasetMetadataFieldAndValueInfo); + } + + protected validateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + if (metadataFieldInfo.isControlledVocabulary) { + this.validateControlledVocabularyFieldValue(newDatasetMetadataFieldAndValueInfo); + } + + if (metadataFieldInfo.type == 'DATE') { + this.validateDateFieldValue(newDatasetMetadataFieldAndValueInfo); + } + + if (metadataFieldInfo.childMetadataFields != undefined) { + this.validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo); + } + } + + protected validateControlledVocabularyFieldValue( + newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, + ) { + if ( + !newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.controlledVocabularyValues.includes( + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string, + ) + ) { + throw new ControlledVocabularyFieldError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + ); + } + } + + protected validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateFormatRegex.test(newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string)) { + throw new DateFormatFieldError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + ); + } + } + + protected validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); + for (const childMetadataFieldKey of childMetadataFieldKeys) { + const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; + this.executeMetadataFieldValidator(new MetadataFieldValidator(), { + metadataFieldInfo: childMetadataFieldInfo, + metadataFieldKey: childMetadataFieldKey, + metadataFieldValue: ( + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO + )[childMetadataFieldKey], + metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, + metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + }); + } + } + + protected createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, + reason: string, + ): FieldValidationError { + return new FieldValidationError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + reason, + ); + } +} diff --git a/src/datasets/domain/useCases/validators/MetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/MetadataFieldValidator.ts new file mode 100644 index 00000000..3ee1ed17 --- /dev/null +++ b/src/datasets/domain/useCases/validators/MetadataFieldValidator.ts @@ -0,0 +1,50 @@ +import { + BaseMetadataFieldValidator, + NewDatasetMetadataFieldAndValueInfo, +} from './BaseMetadataFieldValidator'; +import { MultipleMetadataFieldValidator } from './MultipleMetadataFieldValidator'; +import { SingleMetadataFieldValidator } from './SingleMetadataFieldValidator'; +import { EmptyFieldError } from './errors/EmptyFieldError'; +import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO'; + +export class MetadataFieldValidator extends BaseMetadataFieldValidator { + validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { + const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + if ( + metadataFieldValue == undefined || + metadataFieldValue == null || + this.isEmptyString(metadataFieldValue) || + this.isEmptyArray(metadataFieldValue) + ) { + if (metadataFieldInfo.isRequired) { + throw new EmptyFieldError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + ); + } else { + return; + } + } + if (newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.multiple) { + this.executeMetadataFieldValidator( + new MultipleMetadataFieldValidator(), + newDatasetMetadataFieldAndValueInfo, + ); + } else { + this.executeMetadataFieldValidator(new SingleMetadataFieldValidator(), newDatasetMetadataFieldAndValueInfo); + } + } + + private isEmptyString(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean { + return typeof metadataFieldValue == 'string' && metadataFieldValue.trim() === ''; + } + + private isEmptyArray(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean { + return ( + Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0 + ); + } +} diff --git a/src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts new file mode 100644 index 00000000..bf5b3b25 --- /dev/null +++ b/src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts @@ -0,0 +1,53 @@ +import { + BaseMetadataFieldValidator, + NewDatasetMetadataFieldAndValueInfo, +} from './BaseMetadataFieldValidator'; +import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO'; + +export class MultipleMetadataFieldValidator extends BaseMetadataFieldValidator { + validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { + const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + if (!Array.isArray(metadataFieldValue)) { + throw this.createGeneralValidationError(newDatasetMetadataFieldAndValueInfo, 'Expecting an array of values.'); + } + if (this.isValidArrayType(metadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { + throw this.createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo, + 'Expecting an array of child fields, not strings.', + ); + } else if (this.isValidArrayType(metadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { + throw this.createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo, + 'Expecting an array of strings, not child fields.', + ); + } else if ( + !this.isValidArrayType(metadataFieldValue, 'object') && + !this.isValidArrayType(metadataFieldValue, 'string') + ) { + throw this.createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo, + 'The provided array of values is not valid.', + ); + } + + const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValueDTO[]; + fieldValues.forEach((value, metadataFieldPosition) => { + this.validateFieldValue({ + metadataFieldInfo: metadataFieldInfo, + metadataFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldValue: value, + metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, + metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldPosition: metadataFieldPosition, + }); + }); + } + + private isValidArrayType( + metadataFieldValue: Array, + expectedType: 'string' | 'object', + ): boolean { + return metadataFieldValue.every((item: string | NewDatasetMetadataFieldValueDTO) => typeof item === expectedType); + } +} diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 6c969610..3bfe5622 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -1,25 +1,8 @@ -import { - NewDatasetDTO, - NewDatasetMetadataFieldValueDTO, - NewDatasetMetadataChildFieldValueDTO, - NewDatasetMetadataBlockValuesDTO, -} from '../../dtos/NewDatasetDTO'; +import { NewDatasetDTO, NewDatasetMetadataBlockValuesDTO } from '../../dtos/NewDatasetDTO'; import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; -import { MetadataFieldInfo, MetadataBlock } from '../../../../metadataBlocks'; +import { MetadataBlock } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; -import { EmptyFieldError } from './errors/EmptyFieldError'; -import { FieldValidationError } from './errors/FieldValidationError'; -import { ControlledVocabularyFieldError } from './errors/ControlledVocabularyFieldError'; -import { DateFormatFieldError } from './errors/DateFormatFieldError'; - -export interface NewDatasetMetadataFieldAndValueInfo { - metadataFieldInfo: MetadataFieldInfo; - metadataFieldKey: string; - metadataFieldValue: NewDatasetMetadataFieldValueDTO; - metadataBlockName: string; - metadataParentFieldKey?: string; - metadataFieldPosition?: number; -} +import { MetadataFieldValidator } from './MetadataFieldValidator'; export class NewDatasetValidator implements NewResourceValidator { async validate(resource: NewDatasetDTO, metadataBlocks: MetadataBlock[]): Promise { @@ -37,7 +20,7 @@ export class NewDatasetValidator implements NewResourceValidator { (metadataBlock) => metadataBlock.name === metadataBlockName, ); for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { - this.validateMetadataField({ + new MetadataFieldValidator().validate({ metadataFieldInfo: metadataBlock.metadataFields[metadataFieldKey], metadataFieldKey: metadataFieldKey, metadataFieldValue: metadataBlockValues.fields[metadataFieldKey], @@ -45,186 +28,4 @@ export class NewDatasetValidator implements NewResourceValidator { }); } } - - private validateMetadataField(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { - const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - if ( - metadataFieldValue == undefined || - metadataFieldValue == null || - this.isEmptyString(metadataFieldValue) || - this.isEmptyArray(metadataFieldValue) - ) { - if (metadataFieldInfo.isRequired) { - throw new EmptyFieldError( - newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataBlockName, - newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - ); - } else { - return; - } - } - if (metadataFieldInfo.multiple) { - this.validateMultipleMetadataField(newDatasetMetadataFieldAndValueInfo); - } else { - this.validateSingleMetadataField(newDatasetMetadataFieldAndValueInfo); - } - } - - private validateMultipleMetadataField(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - if (!Array.isArray(metadataFieldValue)) { - throw this.createGeneralValidationError(newDatasetMetadataFieldAndValueInfo, 'Expecting an array of values.'); - } - if (this.isValidArrayType(metadataFieldValue, 'string') && metadataFieldInfo.type === 'NONE') { - throw this.createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo, - 'Expecting an array of child fields, not strings.', - ); - } else if (this.isValidArrayType(metadataFieldValue, 'object') && metadataFieldInfo.type !== 'NONE') { - throw this.createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo, - 'Expecting an array of strings, not child fields.', - ); - } else if ( - !this.isValidArrayType(metadataFieldValue, 'object') && - !this.isValidArrayType(metadataFieldValue, 'string') - ) { - throw this.createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo, - 'The provided array of values is not valid.', - ); - } - - const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValueDTO[]; - fieldValues.forEach((value, metadataFieldPosition) => { - this.validateFieldValue({ - metadataFieldInfo: metadataFieldInfo, - metadataFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - metadataFieldValue: value, - metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, - metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - metadataFieldPosition: metadataFieldPosition, - }); - }); - } - - private validateSingleMetadataField(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - if (Array.isArray(metadataFieldValue)) { - throw this.createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo, - 'Expecting a single field, not an array.', - ); - } - if (typeof metadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { - throw this.createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo, - 'Expecting a string, not child fields.', - ); - } - if (typeof metadataFieldValue === 'string' && metadataFieldInfo.type === 'NONE') { - throw this.createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo, - 'Expecting child fields, not a string.', - ); - } - this.validateFieldValue(newDatasetMetadataFieldAndValueInfo); - } - - private validateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - if (metadataFieldInfo.isControlledVocabulary) { - this.validateControlledVocabularyFieldValue(newDatasetMetadataFieldAndValueInfo); - } - - if (metadataFieldInfo.type == 'DATE') { - this.validateDateFieldValue(newDatasetMetadataFieldAndValueInfo); - } - - if (metadataFieldInfo.childMetadataFields != undefined) { - this.validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo); - } - } - - private validateControlledVocabularyFieldValue( - newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, - ) { - if ( - !newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.controlledVocabularyValues.includes( - newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string, - ) - ) { - throw new ControlledVocabularyFieldError( - newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataBlockName, - newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - ); - } - } - - private validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; - if (!dateFormatRegex.test(newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string)) { - throw new DateFormatFieldError( - newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataBlockName, - newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - ); - } - } - - private validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); - for (const childMetadataFieldKey of childMetadataFieldKeys) { - const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; - this.validateMetadataField({ - metadataFieldInfo: childMetadataFieldInfo, - metadataFieldKey: childMetadataFieldKey, - metadataFieldValue: ( - newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO - )[childMetadataFieldKey], - metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, - metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - }); - } - } - - private isEmptyString(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean { - return typeof metadataFieldValue == 'string' && metadataFieldValue.trim() === ''; - } - - private isEmptyArray(metadataFieldValue: NewDatasetMetadataFieldValueDTO): boolean { - return ( - Array.isArray(metadataFieldValue) && (metadataFieldValue as Array).length == 0 - ); - } - - private isValidArrayType( - metadataFieldValue: Array, - expectedType: 'string' | 'object', - ): boolean { - return metadataFieldValue.every((item: string | NewDatasetMetadataFieldValueDTO) => typeof item === expectedType); - } - - private createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, - reason: string, - ): FieldValidationError { - return new FieldValidationError( - newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataBlockName, - newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - reason, - ); - } } diff --git a/src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts new file mode 100644 index 00000000..11197607 --- /dev/null +++ b/src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts @@ -0,0 +1,30 @@ +import { + BaseMetadataFieldValidator, + NewDatasetMetadataFieldAndValueInfo, +} from './BaseMetadataFieldValidator'; + +export class SingleMetadataFieldValidator extends BaseMetadataFieldValidator { + validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { + const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + if (Array.isArray(metadataFieldValue)) { + throw this.createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo, + 'Expecting a single field, not an array.', + ); + } + if (typeof metadataFieldValue === 'object' && metadataFieldInfo.type !== 'NONE') { + throw this.createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo, + 'Expecting a string, not child fields.', + ); + } + if (typeof metadataFieldValue === 'string' && metadataFieldInfo.type === 'NONE') { + throw this.createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo, + 'Expecting child fields, not a string.', + ); + } + this.validateFieldValue(newDatasetMetadataFieldAndValueInfo); + } +} From 2860270c7a8d3502d9fdade91082ff002db3c5a7 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 22 Jan 2024 16:03:33 +0000 Subject: [PATCH 34/37] Changed: protected methods now private --- .../validators/BaseMetadataFieldValidator.ts | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts index 1160b086..9a04f0e6 100644 --- a/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts +++ b/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts @@ -24,6 +24,37 @@ export abstract class BaseMetadataFieldValidator { metadataFieldValidator.validate(newDatasetMetadataFieldAndValueInfo); } + protected validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); + for (const childMetadataFieldKey of childMetadataFieldKeys) { + const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; + this.executeMetadataFieldValidator(new MetadataFieldValidator(), { + metadataFieldInfo: childMetadataFieldInfo, + metadataFieldKey: childMetadataFieldKey, + metadataFieldValue: ( + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO + )[childMetadataFieldKey], + metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, + metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + }); + } + } + + protected createGeneralValidationError( + newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, + reason: string, + ): FieldValidationError { + return new FieldValidationError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + reason, + ); + } + protected validateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; if (metadataFieldInfo.isControlledVocabulary) { @@ -39,7 +70,7 @@ export abstract class BaseMetadataFieldValidator { } } - protected validateControlledVocabularyFieldValue( + private validateControlledVocabularyFieldValue( newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, ) { if ( @@ -56,7 +87,7 @@ export abstract class BaseMetadataFieldValidator { } } - protected validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + private validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateFormatRegex.test(newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string)) { throw new DateFormatFieldError( @@ -67,35 +98,4 @@ export abstract class BaseMetadataFieldValidator { ); } } - - protected validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); - for (const childMetadataFieldKey of childMetadataFieldKeys) { - const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; - this.executeMetadataFieldValidator(new MetadataFieldValidator(), { - metadataFieldInfo: childMetadataFieldInfo, - metadataFieldKey: childMetadataFieldKey, - metadataFieldValue: ( - newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO - )[childMetadataFieldKey], - metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, - metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - }); - } - } - - protected createGeneralValidationError( - newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, - reason: string, - ): FieldValidationError { - return new FieldValidationError( - newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataBlockName, - newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - reason, - ); - } } From ce8e391fe1050ed15eaf8c336b219808783c3b79 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 22 Jan 2024 16:07:53 +0000 Subject: [PATCH 35/37] Changed: docker env vars to use docker.io and unstable --- test/integration/environment/.env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/environment/.env b/test/integration/environment/.env index 2141e353..80e9a14e 100644 --- a/test/integration/environment/.env +++ b/test/integration/environment/.env @@ -1,6 +1,6 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse SOLR_VERSION=9.3.0 -DATAVERSE_IMAGE_REGISTRY=ghcr.io -DATAVERSE_IMAGE_TAG=10216-metadatablocks-payload-extension +DATAVERSE_IMAGE_REGISTRY=docker.io +DATAVERSE_IMAGE_TAG=unstable DATAVERSE_BOOTSTRAP_TIMEOUT=5m From 938ab8fa95b50dfd34248fefd7ddc171f1ff93fa Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 23 Jan 2024 15:50:17 +0000 Subject: [PATCH 36/37] Refactor: replaced inheritance with composition in metadata field validators --- .../validators/BaseMetadataFieldValidator.ts | 74 +------------------ .../validators/MetadataFieldValidator.ts | 19 ++--- .../MultipleMetadataFieldValidator.ts | 12 +-- .../validators/NewDatasetValidator.ts | 6 +- .../SingleMetadataFieldValidator.ts | 73 +++++++++++++++++- src/datasets/index.ts | 14 +++- .../unit/datasets/NewDatasetValidator.test.ts | 21 +++--- 7 files changed, 114 insertions(+), 105 deletions(-) diff --git a/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts index 9a04f0e6..717f2b4c 100644 --- a/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts +++ b/src/datasets/domain/useCases/validators/BaseMetadataFieldValidator.ts @@ -1,8 +1,5 @@ -import { ControlledVocabularyFieldError } from './errors/ControlledVocabularyFieldError'; -import { DateFormatFieldError } from './errors/DateFormatFieldError'; -import { NewDatasetMetadataChildFieldValueDTO, NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO'; +import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO'; import { FieldValidationError } from './errors/FieldValidationError'; -import { MetadataFieldValidator } from './MetadataFieldValidator'; import { MetadataFieldInfo } from '../../../../metadataBlocks'; export interface NewDatasetMetadataFieldAndValueInfo { @@ -17,31 +14,6 @@ export interface NewDatasetMetadataFieldAndValueInfo { export abstract class BaseMetadataFieldValidator { abstract validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void; - protected executeMetadataFieldValidator( - metadataFieldValidator: BaseMetadataFieldValidator, - newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, - ) { - metadataFieldValidator.validate(newDatasetMetadataFieldAndValueInfo); - } - - protected validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); - for (const childMetadataFieldKey of childMetadataFieldKeys) { - const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; - this.executeMetadataFieldValidator(new MetadataFieldValidator(), { - metadataFieldInfo: childMetadataFieldInfo, - metadataFieldKey: childMetadataFieldKey, - metadataFieldValue: ( - newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO - )[childMetadataFieldKey], - metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, - metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - }); - } - } - protected createGeneralValidationError( newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, reason: string, @@ -54,48 +26,4 @@ export abstract class BaseMetadataFieldValidator { reason, ); } - - protected validateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; - if (metadataFieldInfo.isControlledVocabulary) { - this.validateControlledVocabularyFieldValue(newDatasetMetadataFieldAndValueInfo); - } - - if (metadataFieldInfo.type == 'DATE') { - this.validateDateFieldValue(newDatasetMetadataFieldAndValueInfo); - } - - if (metadataFieldInfo.childMetadataFields != undefined) { - this.validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo); - } - } - - private validateControlledVocabularyFieldValue( - newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, - ) { - if ( - !newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.controlledVocabularyValues.includes( - newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string, - ) - ) { - throw new ControlledVocabularyFieldError( - newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataBlockName, - newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - ); - } - } - - private validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { - const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; - if (!dateFormatRegex.test(newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string)) { - throw new DateFormatFieldError( - newDatasetMetadataFieldAndValueInfo.metadataFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataBlockName, - newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, - newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, - ); - } - } } diff --git a/src/datasets/domain/useCases/validators/MetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/MetadataFieldValidator.ts index 3ee1ed17..35111901 100644 --- a/src/datasets/domain/useCases/validators/MetadataFieldValidator.ts +++ b/src/datasets/domain/useCases/validators/MetadataFieldValidator.ts @@ -1,13 +1,17 @@ -import { - BaseMetadataFieldValidator, - NewDatasetMetadataFieldAndValueInfo, -} from './BaseMetadataFieldValidator'; +import { BaseMetadataFieldValidator, NewDatasetMetadataFieldAndValueInfo } from './BaseMetadataFieldValidator'; import { MultipleMetadataFieldValidator } from './MultipleMetadataFieldValidator'; import { SingleMetadataFieldValidator } from './SingleMetadataFieldValidator'; import { EmptyFieldError } from './errors/EmptyFieldError'; import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO'; export class MetadataFieldValidator extends BaseMetadataFieldValidator { + constructor( + private singleMetadataFieldValidator: SingleMetadataFieldValidator, + private multipleMetadataFieldValidator: MultipleMetadataFieldValidator, + ) { + super(); + } + validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; @@ -29,12 +33,9 @@ export class MetadataFieldValidator extends BaseMetadataFieldValidator { } } if (newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.multiple) { - this.executeMetadataFieldValidator( - new MultipleMetadataFieldValidator(), - newDatasetMetadataFieldAndValueInfo, - ); + this.multipleMetadataFieldValidator.validate(newDatasetMetadataFieldAndValueInfo); } else { - this.executeMetadataFieldValidator(new SingleMetadataFieldValidator(), newDatasetMetadataFieldAndValueInfo); + this.singleMetadataFieldValidator.validate(newDatasetMetadataFieldAndValueInfo); } } diff --git a/src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts index bf5b3b25..ae0be7fa 100644 --- a/src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts +++ b/src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator.ts @@ -1,10 +1,12 @@ -import { - BaseMetadataFieldValidator, - NewDatasetMetadataFieldAndValueInfo, -} from './BaseMetadataFieldValidator'; +import { BaseMetadataFieldValidator, NewDatasetMetadataFieldAndValueInfo } from './BaseMetadataFieldValidator'; import { NewDatasetMetadataFieldValueDTO } from '../../dtos/NewDatasetDTO'; +import { SingleMetadataFieldValidator } from './SingleMetadataFieldValidator'; export class MultipleMetadataFieldValidator extends BaseMetadataFieldValidator { + constructor(private singleMetadataFieldValidator: SingleMetadataFieldValidator) { + super(); + } + validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { const metadataFieldValue = newDatasetMetadataFieldAndValueInfo.metadataFieldValue; const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; @@ -33,7 +35,7 @@ export class MultipleMetadataFieldValidator extends BaseMetadataFieldValidator { const fieldValues = metadataFieldValue as NewDatasetMetadataFieldValueDTO[]; fieldValues.forEach((value, metadataFieldPosition) => { - this.validateFieldValue({ + this.singleMetadataFieldValidator.validate({ metadataFieldInfo: metadataFieldInfo, metadataFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, metadataFieldValue: value, diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts index 3bfe5622..7911211a 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetValidator.ts @@ -2,9 +2,11 @@ import { NewDatasetDTO, NewDatasetMetadataBlockValuesDTO } from '../../dtos/NewD import { NewResourceValidator } from '../../../../core/domain/useCases/validators/NewResourceValidator'; import { MetadataBlock } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; -import { MetadataFieldValidator } from './MetadataFieldValidator'; +import { BaseMetadataFieldValidator } from './BaseMetadataFieldValidator'; export class NewDatasetValidator implements NewResourceValidator { + constructor(private metadataFieldValidator: BaseMetadataFieldValidator) {} + async validate(resource: NewDatasetDTO, metadataBlocks: MetadataBlock[]): Promise { for (const metadataBlockValues of resource.metadataBlockValues) { await this.validateMetadataBlock(metadataBlockValues, metadataBlocks); @@ -20,7 +22,7 @@ export class NewDatasetValidator implements NewResourceValidator { (metadataBlock) => metadataBlock.name === metadataBlockName, ); for (const metadataFieldKey of Object.keys(metadataBlock.metadataFields)) { - new MetadataFieldValidator().validate({ + this.metadataFieldValidator.validate({ metadataFieldInfo: metadataBlock.metadataFields[metadataFieldKey], metadataFieldKey: metadataFieldKey, metadataFieldValue: metadataBlockValues.fields[metadataFieldKey], diff --git a/src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts b/src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts index 11197607..e338457c 100644 --- a/src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts +++ b/src/datasets/domain/useCases/validators/SingleMetadataFieldValidator.ts @@ -1,7 +1,9 @@ -import { - BaseMetadataFieldValidator, - NewDatasetMetadataFieldAndValueInfo, -} from './BaseMetadataFieldValidator'; +import { BaseMetadataFieldValidator, NewDatasetMetadataFieldAndValueInfo } from './BaseMetadataFieldValidator'; +import { ControlledVocabularyFieldError } from './errors/ControlledVocabularyFieldError'; +import { DateFormatFieldError } from './errors/DateFormatFieldError'; +import { MetadataFieldValidator } from './MetadataFieldValidator'; +import { NewDatasetMetadataChildFieldValueDTO } from '../../dtos/NewDatasetDTO'; +import { MultipleMetadataFieldValidator } from './MultipleMetadataFieldValidator'; export class SingleMetadataFieldValidator extends BaseMetadataFieldValidator { validate(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo): void { @@ -27,4 +29,67 @@ export class SingleMetadataFieldValidator extends BaseMetadataFieldValidator { } this.validateFieldValue(newDatasetMetadataFieldAndValueInfo); } + + private validateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + if (metadataFieldInfo.isControlledVocabulary) { + this.validateControlledVocabularyFieldValue(newDatasetMetadataFieldAndValueInfo); + } + + if (metadataFieldInfo.type == 'DATE') { + this.validateDateFieldValue(newDatasetMetadataFieldAndValueInfo); + } + + if (metadataFieldInfo.childMetadataFields != undefined) { + this.validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo); + } + } + + private validateControlledVocabularyFieldValue( + newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo, + ) { + if ( + !newDatasetMetadataFieldAndValueInfo.metadataFieldInfo.controlledVocabularyValues.includes( + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string, + ) + ) { + throw new ControlledVocabularyFieldError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + ); + } + } + + private validateDateFieldValue(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const dateFormatRegex = /^\d{4}-\d{2}-\d{2}$/; + if (!dateFormatRegex.test(newDatasetMetadataFieldAndValueInfo.metadataFieldValue as string)) { + throw new DateFormatFieldError( + newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataBlockName, + newDatasetMetadataFieldAndValueInfo.metadataParentFieldKey, + newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + ); + } + } + + private validateChildMetadataFieldValues(newDatasetMetadataFieldAndValueInfo: NewDatasetMetadataFieldAndValueInfo) { + const metadataFieldInfo = newDatasetMetadataFieldAndValueInfo.metadataFieldInfo; + const childMetadataFieldKeys = Object.keys(metadataFieldInfo.childMetadataFields); + const metadataFieldValidator = new MetadataFieldValidator(this, new MultipleMetadataFieldValidator(this)); + for (const childMetadataFieldKey of childMetadataFieldKeys) { + const childMetadataFieldInfo = metadataFieldInfo.childMetadataFields[childMetadataFieldKey]; + metadataFieldValidator.validate({ + metadataFieldInfo: childMetadataFieldInfo, + metadataFieldKey: childMetadataFieldKey, + metadataFieldValue: ( + newDatasetMetadataFieldAndValueInfo.metadataFieldValue as NewDatasetMetadataChildFieldValueDTO + )[childMetadataFieldKey], + metadataBlockName: newDatasetMetadataFieldAndValueInfo.metadataBlockName, + metadataParentFieldKey: newDatasetMetadataFieldAndValueInfo.metadataFieldKey, + metadataFieldPosition: newDatasetMetadataFieldAndValueInfo.metadataFieldPosition, + }); + } + } } diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 5028cf1f..24b7c9a2 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -10,6 +10,9 @@ import { GetAllDatasetPreviews } from './domain/useCases/GetAllDatasetPreviews'; import { NewDatasetValidator } from './domain/useCases/validators/NewDatasetValidator'; import { MetadataBlocksRepository } from '../metadataBlocks/infra/repositories/MetadataBlocksRepository'; import { CreateDataset } from './domain/useCases/CreateDataset'; +import { MetadataFieldValidator } from './domain/useCases/validators/MetadataFieldValidator'; +import { SingleMetadataFieldValidator } from './domain/useCases/validators/SingleMetadataFieldValidator'; +import { MultipleMetadataFieldValidator } from './domain/useCases/validators/MultipleMetadataFieldValidator'; const datasetsRepository = new DatasetsRepository(); @@ -21,7 +24,16 @@ const getPrivateUrlDatasetCitation = new GetPrivateUrlDatasetCitation(datasetsRe const getDatasetUserPermissions = new GetDatasetUserPermissions(datasetsRepository); const getDatasetLocks = new GetDatasetLocks(datasetsRepository); const getAllDatasetPreviews = new GetAllDatasetPreviews(datasetsRepository); -const createDataset = new CreateDataset(datasetsRepository, new MetadataBlocksRepository(), new NewDatasetValidator()); +const singleMetadataFieldValidator = new SingleMetadataFieldValidator(); +const metadataFieldValidator = new MetadataFieldValidator( + new SingleMetadataFieldValidator(), + new MultipleMetadataFieldValidator(singleMetadataFieldValidator), +); +const createDataset = new CreateDataset( + datasetsRepository, + new MetadataBlocksRepository(), + new NewDatasetValidator(metadataFieldValidator), +); export { getDatasetSummaryFieldNames, diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetValidator.test.ts index 82ca5274..b3be73ab 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetValidator.test.ts @@ -1,5 +1,5 @@ import { NewDatasetValidator } from '../../../src/datasets/domain/useCases/validators/NewDatasetValidator'; -import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { assert } from 'sinon'; import { createNewDatasetDTO, createNewDatasetMetadataBlockModel, @@ -9,14 +9,19 @@ import { fail } from 'assert'; import { EmptyFieldError } from '../../../src/datasets/domain/useCases/validators/errors/EmptyFieldError'; import { FieldValidationError } from '../../../src/datasets/domain/useCases/validators/errors/FieldValidationError'; import { NewDatasetDTO, NewDatasetMetadataFieldValueDTO } from '../../../src/datasets/domain/dtos/NewDatasetDTO'; +import { SingleMetadataFieldValidator } from '../../../src/datasets/domain/useCases/validators/SingleMetadataFieldValidator'; +import { MetadataFieldValidator } from '../../../src/datasets/domain/useCases/validators/MetadataFieldValidator'; +import { MultipleMetadataFieldValidator } from '../../../src/datasets/domain/useCases/validators/MultipleMetadataFieldValidator'; describe('validate', () => { - const sandbox: SinonSandbox = createSandbox(); const testMetadataBlocks = [createNewDatasetMetadataBlockModel()]; - afterEach(() => { - sandbox.restore(); - }); + const singleMetadataFieldValidator = new SingleMetadataFieldValidator(); + const metadataFieldValidator = new MetadataFieldValidator( + new SingleMetadataFieldValidator(), + new MultipleMetadataFieldValidator(singleMetadataFieldValidator), + ); + const sut = new NewDatasetValidator(metadataFieldValidator); async function runValidateExpectingFieldValidationError( newDataset: NewDatasetDTO, @@ -25,7 +30,6 @@ describe('validate', () => { expectedParentMetadataFieldName?: string, expectedPosition?: number, ): Promise { - const sut = new NewDatasetValidator(); await sut .validate(newDataset, testMetadataBlocks) .then(() => { @@ -43,8 +47,6 @@ describe('validate', () => { test('should not raise a validation error when a new dataset with only the required fields is valid', async () => { const testNewDataset = createNewDatasetDTO(); - const sut = new NewDatasetValidator(); - await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); @@ -159,7 +161,6 @@ describe('validate', () => { }, ]; const testNewDataset = createNewDatasetDTO(undefined, authorFieldValue, undefined); - const sut = new NewDatasetValidator(); await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); @@ -174,7 +175,6 @@ describe('validate', () => { test('should not raise a date format validation error when a date field has a valid format', async () => { const testNewDataset = createNewDatasetDTO(undefined, undefined, undefined, '2020-01-01'); - const sut = new NewDatasetValidator(); await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); @@ -191,7 +191,6 @@ describe('validate', () => { test('should not raise a controlled vocabulary error when the value for a controlled vocabulary field is correct', async () => { const testNewDataset = createNewDatasetDTO(undefined, undefined, undefined, undefined, 'Project Member'); - const sut = new NewDatasetValidator(); await sut.validate(testNewDataset, testMetadataBlocks).catch((e) => fail(e)); }); }); From 3c5f1900b653042a303d9ad6a930253122ef6b80 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 23 Jan 2024 15:54:12 +0000 Subject: [PATCH 37/37] Changed: NewDatasetValidator renamed to NewDatasetResourceValidator --- ...{NewDatasetValidator.ts => NewDatasetResourceValidator.ts} | 2 +- src/datasets/index.ts | 4 ++-- ...tValidator.test.ts => NewDatasetResourceValidator.test.ts} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/datasets/domain/useCases/validators/{NewDatasetValidator.ts => NewDatasetResourceValidator.ts} (95%) rename test/unit/datasets/{NewDatasetValidator.test.ts => NewDatasetResourceValidator.test.ts} (97%) diff --git a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts b/src/datasets/domain/useCases/validators/NewDatasetResourceValidator.ts similarity index 95% rename from src/datasets/domain/useCases/validators/NewDatasetValidator.ts rename to src/datasets/domain/useCases/validators/NewDatasetResourceValidator.ts index 7911211a..bb07efe6 100644 --- a/src/datasets/domain/useCases/validators/NewDatasetValidator.ts +++ b/src/datasets/domain/useCases/validators/NewDatasetResourceValidator.ts @@ -4,7 +4,7 @@ import { MetadataBlock } from '../../../../metadataBlocks'; import { ResourceValidationError } from '../../../../core/domain/useCases/validators/errors/ResourceValidationError'; import { BaseMetadataFieldValidator } from './BaseMetadataFieldValidator'; -export class NewDatasetValidator implements NewResourceValidator { +export class NewDatasetResourceValidator implements NewResourceValidator { constructor(private metadataFieldValidator: BaseMetadataFieldValidator) {} async validate(resource: NewDatasetDTO, metadataBlocks: MetadataBlock[]): Promise { diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 24b7c9a2..b75784b6 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -7,7 +7,7 @@ import { GetPrivateUrlDatasetCitation } from './domain/useCases/GetPrivateUrlDat import { GetDatasetUserPermissions } from './domain/useCases/GetDatasetUserPermissions'; import { GetDatasetLocks } from './domain/useCases/GetDatasetLocks'; import { GetAllDatasetPreviews } from './domain/useCases/GetAllDatasetPreviews'; -import { NewDatasetValidator } from './domain/useCases/validators/NewDatasetValidator'; +import { NewDatasetResourceValidator } from './domain/useCases/validators/NewDatasetResourceValidator'; import { MetadataBlocksRepository } from '../metadataBlocks/infra/repositories/MetadataBlocksRepository'; import { CreateDataset } from './domain/useCases/CreateDataset'; import { MetadataFieldValidator } from './domain/useCases/validators/MetadataFieldValidator'; @@ -32,7 +32,7 @@ const metadataFieldValidator = new MetadataFieldValidator( const createDataset = new CreateDataset( datasetsRepository, new MetadataBlocksRepository(), - new NewDatasetValidator(metadataFieldValidator), + new NewDatasetResourceValidator(metadataFieldValidator), ); export { diff --git a/test/unit/datasets/NewDatasetValidator.test.ts b/test/unit/datasets/NewDatasetResourceValidator.test.ts similarity index 97% rename from test/unit/datasets/NewDatasetValidator.test.ts rename to test/unit/datasets/NewDatasetResourceValidator.test.ts index b3be73ab..3c0e1c59 100644 --- a/test/unit/datasets/NewDatasetValidator.test.ts +++ b/test/unit/datasets/NewDatasetResourceValidator.test.ts @@ -1,4 +1,4 @@ -import { NewDatasetValidator } from '../../../src/datasets/domain/useCases/validators/NewDatasetValidator'; +import { NewDatasetResourceValidator } from '../../../src/datasets/domain/useCases/validators/NewDatasetResourceValidator'; import { assert } from 'sinon'; import { createNewDatasetDTO, @@ -21,7 +21,7 @@ describe('validate', () => { new SingleMetadataFieldValidator(), new MultipleMetadataFieldValidator(singleMetadataFieldValidator), ); - const sut = new NewDatasetValidator(metadataFieldValidator); + const sut = new NewDatasetResourceValidator(metadataFieldValidator); async function runValidateExpectingFieldValidationError( newDataset: NewDatasetDTO,