diff --git a/.gitignore b/.gitignore index ce7d01e5..a26c4941 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ node_modules coverage # ignore npm lock -package-json.lock \ No newline at end of file +package-json.lock +.npmrc \ No newline at end of file diff --git a/docs/useCases.md b/docs/useCases.md index 413a7b88..4025b9d6 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -8,6 +8,9 @@ The different use cases currently available in the package are classified below, ## Table of Contents +- [Collections](#Collections) + - [Collections read use cases](#collections-read-use-cases) + - [Get a Collection](#get-a-collection) - [Datasets](#Datasets) - [Datasets read use cases](#datasets-read-use-cases) - [Get a Dataset](#get-a-dataset) @@ -42,6 +45,54 @@ The different use cases currently available in the package are classified below, - [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months) - [Get ZIP Download Limit](#get-zip-download-limit) +## Collections + +### Collections Read Use Cases + +#### Get a Collection + +Returns a [Collection](../src/collections/domain/models/Collection.ts) instance, given the search parameters to identify it. + +##### Example call: + +```typescript +import { getCollection } from '@iqss/dataverse-client-javascript' + +/* ... */ +// Case 1: Fetch Collection by its numerical ID +const collectionIdOrAlias = 12345 + +getCollection + .execute(collectionId) + .then((collection: Collection) => { + /* ... */ + }) + .catch((error: Error) => { + /* ... */ + }) + +/* ... */ + +// Case 2: Fetch Collection by its alias +const collectionIdOrAlias = 'classicLiterature' +getCollection + .execute(collectionAlias) + .then((collection: Collection) => { + /* ... */ + }) + .catch((error: Error) => { + /* ... */ + }) + +/* ... */ +``` + +_See [use case](../src/collections/domain/useCases/GetCollection.ts)_ definition. + +The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId). + +If no collection identifier is specified, the default collection identifier; `root` will be used. If you want to search for a different collection, you must add the collection identifier as a parameter in the use case call. + ## Datasets ### Datasets Read Use Cases diff --git a/src/collections/domain/models/Collection.ts b/src/collections/domain/models/Collection.ts new file mode 100644 index 00000000..c418fca4 --- /dev/null +++ b/src/collections/domain/models/Collection.ts @@ -0,0 +1,11 @@ +import { DvObjectOwnerNode } from '../../../core' +export interface Collection { + id: number + alias: string + name: string + affiliation?: string + description?: string + isPartOf: DvObjectOwnerNode +} + +export const ROOT_COLLECTION_ALIAS = 'root' diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts new file mode 100644 index 00000000..bd4f5d6a --- /dev/null +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -0,0 +1,4 @@ +import { Collection } from '../models/Collection' +export interface ICollectionsRepository { + getCollection(collectionIdOrAlias: number | string): Promise +} diff --git a/src/collections/domain/useCases/GetCollection.ts b/src/collections/domain/useCases/GetCollection.ts new file mode 100644 index 00000000..f17de63e --- /dev/null +++ b/src/collections/domain/useCases/GetCollection.ts @@ -0,0 +1,22 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { Collection, ROOT_COLLECTION_ALIAS } from '../models/Collection' + +export class GetCollection implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Returns a Collection instance, given the search parameters to identify it. + * + * @param {number | string} [collectionIdOrAlias = 'root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId) + * If this parameter is not set, the default value is: 'root' + * @returns {Promise} + */ + async execute(collectionIdOrAlias: number | string = ROOT_COLLECTION_ALIAS): Promise { + return await this.collectionsRepository.getCollection(collectionIdOrAlias) + } +} diff --git a/src/collections/index.ts b/src/collections/index.ts new file mode 100644 index 00000000..89b6296e --- /dev/null +++ b/src/collections/index.ts @@ -0,0 +1,10 @@ +import { GetCollection } from './domain/useCases/GetCollection' + +import { CollectionsRepository } from './infra/repositories/CollectionsRepository' + +const collectionsRepository = new CollectionsRepository() + +const getCollection = new GetCollection(collectionsRepository) + +export { getCollection } +export { Collection } from './domain/models/Collection' diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts new file mode 100644 index 00000000..12443ba6 --- /dev/null +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -0,0 +1,26 @@ +import { ApiRepository } from '../../../core/infra/repositories/ApiRepository' +import { ICollectionsRepository } from '../../domain/repositories/ICollectionsRepository' +import { transformCollectionResponseToCollection } from './transformers/collectionTransformers' +import { Collection, ROOT_COLLECTION_ALIAS } from '../../domain/models/Collection' +export class CollectionsRepository extends ApiRepository implements ICollectionsRepository { + private readonly collectionsResourceName: string = 'dataverses' + private readonly collectionsDefaultOperationType: string = 'get' + + public async getCollection( + collectionIdOrAlias: number | string = ROOT_COLLECTION_ALIAS + ): Promise { + return this.doGet( + this.buildApiEndpoint( + this.collectionsResourceName, + this.collectionsDefaultOperationType, + collectionIdOrAlias + ), + true, + { returnOwners: true } + ) + .then((response) => transformCollectionResponseToCollection(response)) + .catch((error) => { + throw error + }) + } +} diff --git a/src/collections/infra/repositories/transformers/CollectionPayload.ts b/src/collections/infra/repositories/transformers/CollectionPayload.ts new file mode 100644 index 00000000..f1354f88 --- /dev/null +++ b/src/collections/infra/repositories/transformers/CollectionPayload.ts @@ -0,0 +1,9 @@ +import { OwnerNodePayload } from '../../../../core/infra/repositories/transformers/OwnerNodePayload' +export interface CollectionPayload { + id: number + alias: string + name: string + affiliation?: string + description?: string + isPartOf: OwnerNodePayload +} diff --git a/src/collections/infra/repositories/transformers/collectionTransformers.ts b/src/collections/infra/repositories/transformers/collectionTransformers.ts new file mode 100644 index 00000000..177a87eb --- /dev/null +++ b/src/collections/infra/repositories/transformers/collectionTransformers.ts @@ -0,0 +1,23 @@ +import { Collection } from '../../../domain/models/Collection' +import { AxiosResponse } from 'axios' +import { CollectionPayload } from './CollectionPayload' +import { transformPayloadToOwnerNode } from '../../../../core/infra/repositories/transformers/dvObjectOwnerNodeTransformer' + +export const transformCollectionResponseToCollection = (response: AxiosResponse): Collection => { + const collectionPayload = response.data.data + return transformPayloadToCollection(collectionPayload) +} + +const transformPayloadToCollection = (collectionPayload: CollectionPayload): Collection => { + const collectionModel: Collection = { + id: collectionPayload.id, + alias: collectionPayload.alias, + name: collectionPayload.name, + affiliation: collectionPayload.affiliation, + description: collectionPayload.description, + ...(collectionPayload.isPartOf && { + isPartOf: transformPayloadToOwnerNode(collectionPayload.isPartOf) + }) + } + return collectionModel +} diff --git a/src/core/infra/repositories/ApiRepository.ts b/src/core/infra/repositories/ApiRepository.ts index 9a29464c..2379bb75 100644 --- a/src/core/infra/repositories/ApiRepository.ts +++ b/src/core/infra/repositories/ApiRepository.ts @@ -39,15 +39,15 @@ export abstract class ApiRepository { operation: string, resourceId: number | string = undefined ) { - let endpoint - if (typeof resourceId === 'number') { - endpoint = `/${resourceName}/${resourceId}/${operation}` - } else if (typeof resourceId === 'string') { - endpoint = `/${resourceName}/:persistentId/${operation}?persistentId=${resourceId}` - } else { - endpoint = `/${resourceName}/${operation}` + if (resourceName === 'dataverses') { + return `/${resourceName}/${resourceId}` } - return endpoint + + return typeof resourceId === 'number' + ? `/${resourceName}/${resourceId}/${operation}` + : typeof resourceId === 'string' + ? `/${resourceName}/:persistentId/${operation}?persistentId=${resourceId}` + : `/${resourceName}/${operation}` } private buildRequestConfig(authRequired: boolean, queryParams: object): AxiosRequestConfig { @@ -61,7 +61,7 @@ export abstract class ApiRepository { switch (ApiConfig.dataverseApiAuthMechanism) { case DataverseApiAuthMechanism.SESSION_COOKIE: /* - We set { withCredentials: true } to send the JSESSIONID cookie in the requests for API authentication. + We set { withCredentials: true } to send the JSESSIONID cookie in the requests for API authentication. This is required, along with the session auth feature flag enabled in the backend, to be able to authenticate using the JSESSIONID cookie. Auth mechanisms like this are configurable to set the one that fits the particular use case of js-dataverse. (For the SPA MVP, it is the session cookie API auth). */ diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 5337263d..78c12d66 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -1,32 +1,32 @@ import { Dataset } from '../models/Dataset' -import { DatasetUserPermissions } from '../models/DatasetUserPermissions' import { DatasetLock } from '../models/DatasetLock' import { DatasetPreviewSubset } from '../models/DatasetPreviewSubset' +import { DatasetUserPermissions } from '../models/DatasetUserPermissions' +import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers' import { NewDatasetDTO } from '../dtos/NewDatasetDTO' import { MetadataBlock } from '../../../metadataBlocks' -import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers' export interface IDatasetsRepository { - getDatasetSummaryFieldNames(): Promise getDataset( datasetId: number | string, datasetVersionId: string, includeDeaccessioned: boolean ): Promise - getPrivateUrlDataset(token: string): Promise + getDatasetLocks(datasetId: number | string): Promise getDatasetCitation( datasetId: number, datasetVersionId: string, includeDeaccessioned: boolean ): Promise - getPrivateUrlDatasetCitation(token: string): Promise - getDatasetUserPermissions(datasetId: number | string): Promise - getDatasetLocks(datasetId: number | string): Promise + getPrivateUrlDataset(token: string): Promise getAllDatasetPreviews( limit?: number, offset?: number, collectionId?: string ): Promise + getDatasetSummaryFieldNames(): Promise + getPrivateUrlDatasetCitation(token: string): Promise + getDatasetUserPermissions(datasetId: number | string): Promise createDataset( newDataset: NewDatasetDTO, datasetMetadataBlocks: MetadataBlock[], diff --git a/src/datasets/domain/useCases/CreateDataset.ts b/src/datasets/domain/useCases/CreateDataset.ts index aae563e1..60f28fdc 100644 --- a/src/datasets/domain/useCases/CreateDataset.ts +++ b/src/datasets/domain/useCases/CreateDataset.ts @@ -5,6 +5,7 @@ import { NewResourceValidator } from '../../../core/domain/useCases/validators/N import { IMetadataBlocksRepository } from '../../../metadataBlocks/domain/repositories/IMetadataBlocksRepository' import { MetadataBlock } from '../../../metadataBlocks' import { CreatedDatasetIdentifiers } from '../models/CreatedDatasetIdentifiers' +import { ROOT_COLLECTION_ALIAS } from '../../../collections/domain/models/Collection' export class CreateDataset implements UseCase { private datasetsRepository: IDatasetsRepository @@ -33,7 +34,7 @@ export class CreateDataset implements UseCase { */ async execute( newDataset: NewDatasetDTO, - collectionId = 'root' + collectionId = ROOT_COLLECTION_ALIAS ): Promise { const metadataBlocks = await this.getNewDatasetMetadataBlocks(newDataset) diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 27ffccca..d37323ad 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -1,29 +1,29 @@ import { DatasetsRepository } from './infra/repositories/DatasetsRepository' -import { GetDatasetSummaryFieldNames } from './domain/useCases/GetDatasetSummaryFieldNames' +import { MetadataBlocksRepository } from '../metadataBlocks/infra/repositories/MetadataBlocksRepository' import { GetDataset } from './domain/useCases/GetDataset' -import { GetPrivateUrlDataset } from './domain/useCases/GetPrivateUrlDataset' -import { GetDatasetCitation } from './domain/useCases/GetDatasetCitation' -import { GetPrivateUrlDatasetCitation } from './domain/useCases/GetPrivateUrlDatasetCitation' -import { GetDatasetUserPermissions } from './domain/useCases/GetDatasetUserPermissions' +import { CreateDataset } from './domain/useCases/CreateDataset' import { GetDatasetLocks } from './domain/useCases/GetDatasetLocks' +import { GetDatasetCitation } from './domain/useCases/GetDatasetCitation' +import { GetPrivateUrlDataset } from './domain/useCases/GetPrivateUrlDataset' import { GetAllDatasetPreviews } from './domain/useCases/GetAllDatasetPreviews' -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' +import { GetDatasetUserPermissions } from './domain/useCases/GetDatasetUserPermissions' +import { NewDatasetResourceValidator } from './domain/useCases/validators/NewDatasetResourceValidator' +import { GetDatasetSummaryFieldNames } from './domain/useCases/GetDatasetSummaryFieldNames' +import { GetPrivateUrlDatasetCitation } from './domain/useCases/GetPrivateUrlDatasetCitation' import { SingleMetadataFieldValidator } from './domain/useCases/validators/SingleMetadataFieldValidator' import { MultipleMetadataFieldValidator } from './domain/useCases/validators/MultipleMetadataFieldValidator' const datasetsRepository = new DatasetsRepository() -const getDatasetSummaryFieldNames = new GetDatasetSummaryFieldNames(datasetsRepository) const getDataset = new GetDataset(datasetsRepository) -const getPrivateUrlDataset = new GetPrivateUrlDataset(datasetsRepository) -const getDatasetCitation = new GetDatasetCitation(datasetsRepository) -const getPrivateUrlDatasetCitation = new GetPrivateUrlDatasetCitation(datasetsRepository) -const getDatasetUserPermissions = new GetDatasetUserPermissions(datasetsRepository) const getDatasetLocks = new GetDatasetLocks(datasetsRepository) +const getDatasetCitation = new GetDatasetCitation(datasetsRepository) +const getPrivateUrlDataset = new GetPrivateUrlDataset(datasetsRepository) const getAllDatasetPreviews = new GetAllDatasetPreviews(datasetsRepository) +const getDatasetUserPermissions = new GetDatasetUserPermissions(datasetsRepository) +const getDatasetSummaryFieldNames = new GetDatasetSummaryFieldNames(datasetsRepository) +const getPrivateUrlDatasetCitation = new GetPrivateUrlDatasetCitation(datasetsRepository) const singleMetadataFieldValidator = new SingleMetadataFieldValidator() const metadataFieldValidator = new MetadataFieldValidator( new SingleMetadataFieldValidator(), @@ -36,14 +36,14 @@ const createDataset = new CreateDataset( ) export { - getDatasetSummaryFieldNames, getDataset, - getPrivateUrlDataset, - getDatasetCitation, - getPrivateUrlDatasetCitation, - getDatasetUserPermissions, getDatasetLocks, + getDatasetCitation, + getPrivateUrlDataset, getAllDatasetPreviews, + getDatasetUserPermissions, + getDatasetSummaryFieldNames, + getPrivateUrlDatasetCitation, createDataset } export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion' @@ -51,22 +51,22 @@ export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions' export { DatasetLock, DatasetLockType } from './domain/models/DatasetLock' export { Dataset, + DatasetLicense, DatasetVersionInfo, DatasetVersionState, - DatasetLicense, - DatasetMetadataBlocks, DatasetMetadataBlock, + DatasetMetadataBlocks, DatasetMetadataFields, - DatasetMetadataFieldValue, - DatasetMetadataSubField + DatasetMetadataSubField, + DatasetMetadataFieldValue } from './domain/models/Dataset' export { DatasetPreview } from './domain/models/DatasetPreview' export { DatasetPreviewSubset } from './domain/models/DatasetPreviewSubset' export { NewDatasetDTO as NewDataset, - NewDatasetMetadataBlockValuesDTO as NewDatasetMetadataBlockValues, NewDatasetMetadataFieldsDTO as NewDatasetMetadataFields, NewDatasetMetadataFieldValueDTO as NewDatasetMetadataFieldValue, + NewDatasetMetadataBlockValuesDTO as NewDatasetMetadataBlockValues, 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 c393493b..80fffc25 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -1,17 +1,17 @@ import { ApiRepository } from '../../../core/infra/repositories/ApiRepository' import { IDatasetsRepository } from '../../domain/repositories/IDatasetsRepository' import { Dataset } from '../../domain/models/Dataset' -import { transformVersionResponseToDataset } from './transformers/datasetTransformers' -import { DatasetUserPermissions } from '../../domain/models/DatasetUserPermissions' -import { transformDatasetUserPermissionsResponseToDatasetUserPermissions } from './transformers/datasetUserPermissionsTransformers' import { DatasetLock } from '../../domain/models/DatasetLock' -import { transformDatasetLocksResponseToDatasetLocks } from './transformers/datasetLocksTransformers' -import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transformers/datasetPreviewsTransformers' +import { DatasetUserPermissions } from '../../domain/models/DatasetUserPermissions' +import { CreatedDatasetIdentifiers } from '../../domain/models/CreatedDatasetIdentifiers' import { DatasetPreviewSubset } from '../../domain/models/DatasetPreviewSubset' import { NewDatasetDTO } from '../../domain/dtos/NewDatasetDTO' import { MetadataBlock } from '../../../metadataBlocks' +import { transformVersionResponseToDataset } from './transformers/datasetTransformers' import { transformNewDatasetModelToRequestPayload } from './transformers/newDatasetTransformers' -import { CreatedDatasetIdentifiers } from '../../domain/models/CreatedDatasetIdentifiers' +import { transformDatasetLocksResponseToDatasetLocks } from './transformers/datasetLocksTransformers' +import { transformDatasetPreviewsResponseToDatasetPreviewSubset } from './transformers/datasetPreviewsTransformers' +import { transformDatasetUserPermissionsResponseToDatasetUserPermissions } from './transformers/datasetUserPermissionsTransformers' export interface GetAllDatasetPreviewsQueryParams { per_page?: number diff --git a/src/datasets/infra/repositories/transformers/datasetTransformers.ts b/src/datasets/infra/repositories/transformers/datasetTransformers.ts index d08f6627..49076c4a 100644 --- a/src/datasets/infra/repositories/transformers/datasetTransformers.ts +++ b/src/datasets/infra/repositories/transformers/datasetTransformers.ts @@ -1,21 +1,21 @@ import { Dataset, + DatasetLicense, DatasetVersionState, + DatasetMetadataBlocks, DatasetMetadataFields, DatasetMetadataSubField, DatasetMetadataFieldValue, - DatasetLicense, - DatasetMetadataBlocks, ANONYMIZED_FIELD_VALUE } from '../../../domain/models/Dataset' import { AxiosResponse } from 'axios' import { DatasetPayload, LicensePayload, - MetadataBlocksPayload, - MetadataSubfieldValuePayload, MetadataFieldPayload, - MetadataFieldValuePayload + MetadataBlocksPayload, + MetadataFieldValuePayload, + MetadataSubfieldValuePayload } from './DatasetPayload' import { transformPayloadToOwnerNode } from '../../../../core/infra/repositories/transformers/dvObjectOwnerNodeTransformer' import TurndownService from 'turndown' @@ -30,8 +30,8 @@ export const transformVersionResponseToDataset = (response: AxiosResponse): Data export const transformVersionPayloadToDataset = (versionPayload: DatasetPayload): Dataset => { const datasetModel: Dataset = { id: versionPayload.datasetId, - persistentId: versionPayload.datasetPersistentId, versionId: versionPayload.id, + persistentId: versionPayload.datasetPersistentId, versionInfo: { majorNumber: versionPayload.versionNumber, minorNumber: versionPayload.versionMinorNumber, diff --git a/src/index.ts b/src/index.ts index 4d702cb5..63562fda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,5 +3,6 @@ export * from './info' export * from './users' export * from './auth' export * from './datasets' +export * from './collections' export * from './metadataBlocks' export * from './files' diff --git a/test/environment/setup.ts b/test/environment/setup.ts index 7f34a7e0..0acefc9f 100644 --- a/test/environment/setup.ts +++ b/test/environment/setup.ts @@ -6,6 +6,7 @@ import datasetJson1 from '../testHelpers/datasets/test-dataset-1.json' import datasetJson2 from '../testHelpers/datasets/test-dataset-2.json' import datasetJson3 from '../testHelpers/datasets/test-dataset-3.json' import collectionJson from '../testHelpers/collections/test-collection-1.json' +import { ROOT_COLLECTION_ALIAS } from '../../src/collections/domain/models/Collection' const COMPOSE_FILE = 'docker-compose.yml' @@ -86,7 +87,10 @@ async function createCollectionViaApi(collectionJson: any): Promise { } /* eslint-disable @typescript-eslint/no-explicit-any */ -async function createDatasetViaApi(datasetJson: any, collectionId = 'root'): Promise { +async function createDatasetViaApi( + datasetJson: any, + collectionId = ROOT_COLLECTION_ALIAS +): Promise { return await axios.post( `${TestConstants.TEST_API_URL}/dataverses/${collectionId}/datasets`, datasetJson, diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts new file mode 100644 index 00000000..31a14043 --- /dev/null +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -0,0 +1,71 @@ +import { CollectionsRepository } from '../../../src/collections/infra/repositories/CollectionsRepository' +import { TestConstants } from '../../testHelpers/TestConstants' +import { ReadError } from '../../../src' +import { ApiConfig } from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' + +describe('CollectionsRepository', () => { + const testGetCollection: CollectionsRepository = new CollectionsRepository() + + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + afterEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + describe('getCollection', () => { + describe('by default `root` Id', () => { + test('should return the root collection of the Dataverse installation if no parameter is passed AS `root`', async () => { + const actual = await testGetCollection.getCollection() + expect(actual.alias).toBe(TestConstants.TEST_CREATED_COLLECTION_1_ROOT) + }) + }) + + describe('by string alias', () => { + test('should return collection when it exists filtering by id AS (alias)', async () => { + const actual = await testGetCollection.getCollection( + TestConstants.TEST_CREATED_COLLECTION_1_ALIAS + ) + expect(actual.alias).toBe(TestConstants.TEST_CREATED_COLLECTION_1_ALIAS) + }) + + test('should return error when collection does not exist', async () => { + const expectedError = new ReadError( + `[404] Can't find dataverse with identifier='${TestConstants.TEST_DUMMY_COLLECTION_ALIAS}'` + ) + + await expect( + testGetCollection.getCollection(TestConstants.TEST_DUMMY_COLLECTION_ALIAS) + ).rejects.toThrow(expectedError) + }) + }) + describe('by numeric id', () => { + test('should return collection when it exists filtering by id AS (id)', async () => { + const actual = await testGetCollection.getCollection( + TestConstants.TEST_CREATED_COLLECTION_1_ID + ) + expect(actual.id).toBe(TestConstants.TEST_CREATED_COLLECTION_1_ID) + }) + + test('should return error when collection does not exist', async () => { + const expectedError = new ReadError( + `[404] Can't find dataverse with identifier='${TestConstants.TEST_DUMMY_COLLECTION_ID}'` + ) + + await expect( + testGetCollection.getCollection(TestConstants.TEST_DUMMY_COLLECTION_ID) + ).rejects.toThrow(expectedError) + }) + }) + }) +}) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index ceccd49a..15eecb6c 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -20,6 +20,7 @@ import { DatasetContact, DatasetDescription } from '../../../src/datasets/domain/models/Dataset' +import { ROOT_COLLECTION_ALIAS } from '../../../src/collections/domain/models/Collection' describe('DatasetsRepository', () => { const sut: DatasetsRepository = new DatasetsRepository() @@ -336,7 +337,7 @@ describe('DatasetsRepository', () => { const createdDataset = await sut.createDataset( testNewDataset, [citationMetadataBlock], - 'root' + ROOT_COLLECTION_ALIAS ) const actualCreatedDataset = await sut.getDataset( createdDataset.numericId, diff --git a/test/testHelpers/TestConstants.ts b/test/testHelpers/TestConstants.ts index 9d331efa..57540ed3 100644 --- a/test/testHelpers/TestConstants.ts +++ b/test/testHelpers/TestConstants.ts @@ -1,3 +1,5 @@ +import { ROOT_COLLECTION_ALIAS } from '../../src/collections/domain/models/Collection' + export class TestConstants { static readonly TEST_API_URL = 'http://localhost:8080/api/v1' static readonly TEST_DUMMY_API_KEY = 'dummyApiKey' @@ -46,4 +48,9 @@ export class TestConstants { static readonly TEST_CREATED_DATASET_1_ID = 2 static readonly TEST_CREATED_DATASET_2_ID = 3 static readonly TEST_CREATED_DATASET_3_ID = 4 + static readonly TEST_DUMMY_COLLECTION_ID = 10001 + static readonly TEST_DUMMY_COLLECTION_ALIAS = 'dummyCollectionId' + static readonly TEST_CREATED_COLLECTION_1_ID = 4 + static readonly TEST_CREATED_COLLECTION_1_ALIAS = 'firstCollection' + static readonly TEST_CREATED_COLLECTION_1_ROOT = ROOT_COLLECTION_ALIAS } diff --git a/test/testHelpers/collections/collectionHelper.ts b/test/testHelpers/collections/collectionHelper.ts new file mode 100644 index 00000000..d18c66b3 --- /dev/null +++ b/test/testHelpers/collections/collectionHelper.ts @@ -0,0 +1,33 @@ +import { Collection } from '../../../src/collections' +import { DvObjectType } from '../../../src' +import { CollectionPayload } from '../../../src/collections/infra/repositories/transformers/CollectionPayload' + +const COLLECTION_ID = 11111 +const COLLECTION_ALIAS_STR = 'secondCollection' +const COLLECTION_NAME_STR = 'Laboratory Research' +const COLLECTION_AFFILIATION_STR = 'Laboratory Research Corporation' +const COLLECTION_DESCRIPTION_STR = 'This is an example collection used for testing.' + +export const createCollectionModel = (): Collection => { + const collectionModel: Collection = { + id: COLLECTION_ID, + alias: COLLECTION_ALIAS_STR, + name: COLLECTION_NAME_STR, + affiliation: COLLECTION_AFFILIATION_STR, + description: COLLECTION_DESCRIPTION_STR, + isPartOf: { type: DvObjectType.DATAVERSE, identifier: 'root', displayName: 'Root' } + } + return collectionModel +} + +export const createCollectionPayload = (): CollectionPayload => { + const collectionPayload: CollectionPayload = { + id: COLLECTION_ID, + alias: COLLECTION_ALIAS_STR, + name: COLLECTION_NAME_STR, + affiliation: COLLECTION_AFFILIATION_STR, + description: COLLECTION_DESCRIPTION_STR, + isPartOf: { type: DvObjectType.DATAVERSE, identifier: 'root', displayName: 'Root' } + } + return collectionPayload +} diff --git a/test/testHelpers/collections/test-collection-1.json b/test/testHelpers/collections/test-collection-1.json index eaa4bfa8..f23d819c 100644 --- a/test/testHelpers/collections/test-collection-1.json +++ b/test/testHelpers/collections/test-collection-1.json @@ -1,6 +1,7 @@ { - "name": "Scientific Research", + "id": 4, "alias": "firstCollection", + "name": "Scientific Research", "dataverseContacts": [ { "contactEmail": "pi@example.edu" diff --git a/test/unit/collections/CollectionsRepository.test.ts b/test/unit/collections/CollectionsRepository.test.ts new file mode 100644 index 00000000..eab8e8a0 --- /dev/null +++ b/test/unit/collections/CollectionsRepository.test.ts @@ -0,0 +1,110 @@ +import { CollectionsRepository } from '../../../src/collections/infra/repositories/CollectionsRepository' +import axios from 'axios' +import { + ApiConfig, + DataverseApiAuthMechanism +} from '../../../src/core/infra/repositories/ApiConfig' +import { + createCollectionModel, + createCollectionPayload +} from '../../testHelpers/collections/collectionHelper' +import { TestConstants } from '../../testHelpers/TestConstants' +import { ReadError } from '../../../src' + +describe('CollectionsRepository', () => { + const sut: CollectionsRepository = new CollectionsRepository() + const testCollectionSuccessfulResponse = { + data: { + status: 'OK', + data: createCollectionPayload() + } + } + const testCollectionModel = createCollectionModel() + + beforeEach(() => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + TestConstants.TEST_DUMMY_API_KEY + ) + }) + describe('getCollection', () => { + const expectedRequestConfigApiKey = { + params: { + returnOwners: true + }, + headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers + } + + describe('by numeric id', () => { + test('should return Collection when providing a numeric id', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(testCollectionSuccessfulResponse) + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionModel.id}` + + // API Key auth + const actual = await sut.getCollection(testCollectionModel.id) + + expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey) + expect(actual).toStrictEqual(testCollectionModel) + }) + + test('should return error on repository read error', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionModel.id}` + let error = undefined as unknown as ReadError + + await sut.getCollection(testCollectionModel.id).catch((e) => (error = e)) + + expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey) + expect(error).toBeInstanceOf(Error) + }) + }) + describe('by alias id', () => { + test('should return a Collection when providing the Collection alias is successful', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(testCollectionSuccessfulResponse) + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionModel.alias}` + + // API Key auth + const actual = await sut.getCollection(testCollectionModel.alias) + + expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey) + expect(actual).toStrictEqual(testCollectionModel) + }) + + test('should return error on repository read error', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionModel.alias}` + let error = undefined as unknown as ReadError + + await sut.getCollection(testCollectionModel.alias).catch((e) => (error = e)) + + expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey) + expect(error).toBeInstanceOf(Error) + }) + }) + describe('by default root id', () => { + test('should return a Collection when no collection id, using ROOT instead is successful', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(testCollectionSuccessfulResponse) + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${TestConstants.TEST_CREATED_COLLECTION_1_ROOT}` + + // API Key auth + const actual = await sut.getCollection() + + expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey) + expect(actual).toStrictEqual(testCollectionModel) + }) + + test('should return error on repository read error', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${TestConstants.TEST_CREATED_COLLECTION_1_ROOT}` + + let error = undefined as unknown as ReadError + + await sut.getCollection().catch((e) => (error = e)) + + expect(axios.get).toHaveBeenCalledWith(expectedApiEndpoint, expectedRequestConfigApiKey) + expect(error).toBeInstanceOf(Error) + }) + }) + }) +}) diff --git a/test/unit/collections/GetCollection.test.ts b/test/unit/collections/GetCollection.test.ts new file mode 100644 index 00000000..697fe610 --- /dev/null +++ b/test/unit/collections/GetCollection.test.ts @@ -0,0 +1,25 @@ +import { GetCollection } from '../../../src/collections/domain/useCases/GetCollection' +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { ReadError } from '../../../src' +import { createCollectionModel } from '../../testHelpers/collections/collectionHelper' + +describe('execute', () => { + test('should return collection on repository success', async () => { + const testCollection = createCollectionModel() + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getCollection = jest.fn().mockResolvedValue(testCollection) + const testGetCollection = new GetCollection(collectionRepositoryStub) + + const actual = await testGetCollection.execute(1) + + expect(actual).toEqual(testCollection) + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getCollection = jest.fn().mockRejectedValue(new ReadError()) + const testGetCollection = new GetCollection(collectionRepositoryStub) + + await expect(testGetCollection.execute(1)).rejects.toThrow(ReadError) + }) +}) diff --git a/test/unit/datasets/CreateDataset.test.ts b/test/unit/datasets/CreateDataset.test.ts index c7996bd9..5e826b38 100644 --- a/test/unit/datasets/CreateDataset.test.ts +++ b/test/unit/datasets/CreateDataset.test.ts @@ -9,6 +9,7 @@ import { import { ResourceValidationError } from '../../../src/core/domain/useCases/validators/errors/ResourceValidationError' import { WriteError, ReadError } from '../../../src' import { IMetadataBlocksRepository } from '../../../src/metadataBlocks/domain/repositories/IMetadataBlocksRepository' +import { ROOT_COLLECTION_ALIAS } from '../../../src/collections/domain/models/Collection' describe('execute', () => { const testDataset = createNewDatasetDTO() @@ -50,7 +51,7 @@ describe('execute', () => { expect(datasetsRepositoryStub.createDataset).toHaveBeenCalledWith( testDataset, testMetadataBlocks, - 'root' + ROOT_COLLECTION_ALIAS ) }) @@ -110,7 +111,7 @@ describe('execute', () => { expect(datasetsRepositoryStub.createDataset).toHaveBeenCalledWith( testDataset, testMetadataBlocks, - 'root' + ROOT_COLLECTION_ALIAS ) }) diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 1aee7ed6..7a7e0e58 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -80,7 +80,7 @@ describe('DatasetsRepository', () => { test('should return error result on error response', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut.getDatasetSummaryFieldNames().catch((e) => (error = e)) expect(axios.get).toHaveBeenCalledWith( @@ -212,7 +212,7 @@ describe('DatasetsRepository', () => { test('should return error on repository read error', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut .getDataset(testDatasetModel.id, testVersionId, testIncludeDeaccessioned) .catch((e) => (error = e)) @@ -258,7 +258,7 @@ describe('DatasetsRepository', () => { test('should return error on repository read error', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut .getDataset(testDatasetModel.persistentId, testVersionId, testIncludeDeaccessioned) .catch((e) => (error = e)) @@ -292,7 +292,7 @@ describe('DatasetsRepository', () => { test('should return error on repository read error', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut.getPrivateUrlDataset(testPrivateUrlToken).catch((e) => (error = e)) expect(axios.get).toHaveBeenCalledWith( @@ -341,7 +341,7 @@ describe('DatasetsRepository', () => { test('should return error on repository read error', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut .getDatasetCitation(1, testVersionId, testIncludeDeaccessioned) .catch((e) => (error = e)) @@ -370,7 +370,7 @@ describe('DatasetsRepository', () => { test('should return error on repository read error', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut.getPrivateUrlDatasetCitation(testPrivateUrlToken).catch((e) => (error = e)) expect(axios.get).toHaveBeenCalledWith( @@ -420,7 +420,7 @@ describe('DatasetsRepository', () => { test('should return error result on error response', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut.getDatasetUserPermissions(testDatasetModel.id).catch((e) => (error = e)) expect(axios.get).toHaveBeenCalledWith( @@ -460,7 +460,7 @@ describe('DatasetsRepository', () => { test('should return error result on error response', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut .getDatasetUserPermissions(TestConstants.TEST_DUMMY_PERSISTENT_ID) .catch((e) => (error = e)) @@ -513,7 +513,7 @@ describe('DatasetsRepository', () => { test('should return error result on error response', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut.getDatasetLocks(testDatasetModel.id).catch((e) => (error = e)) expect(axios.get).toHaveBeenCalledWith( @@ -553,7 +553,7 @@ describe('DatasetsRepository', () => { test('should return error result on error response', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut.getDatasetLocks(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)) expect(axios.get).toHaveBeenCalledWith( @@ -699,7 +699,7 @@ describe('DatasetsRepository', () => { test('should return error result on error response', async () => { jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: ReadError = undefined + let error = undefined as unknown as ReadError await sut.getAllDatasetPreviews().catch((e) => (error = e)) expect(axios.get).toHaveBeenCalledWith( @@ -762,7 +762,7 @@ describe('DatasetsRepository', () => { test('should return error result on error response', async () => { jest.spyOn(axios, 'post').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) - let error: WriteError = undefined + let error = undefined as unknown as WriteError await sut .createDataset(testNewDataset, testMetadataBlocks, testCollectionName) .catch((e) => (error = e))