From d74bab3055c4e5bd99db2ebba6892d60b99d8453 Mon Sep 17 00:00:00 2001 From: German Gonzalo Saracca Date: Thu, 25 Sep 2025 13:31:44 +0200 Subject: [PATCH 1/2] Revert "Revert "Get Collections For Linking Use Case"" --- docs/useCases.md | 64 +++++++++++ .../repositories/ICollectionsRepository.ts | 8 ++ .../useCases/GetCollectionsForLinking.ts | 34 ++++++ src/collections/index.ts | 6 +- .../repositories/CollectionsRepository.ts | 45 +++++++- .../transformers/collectionTransformers.ts | 8 +- .../repositories/IDatasetsRepository.ts | 4 +- src/datasets/domain/useCases/LinkDataset.ts | 8 +- src/datasets/domain/useCases/UnlinkDataset.ts | 8 +- .../infra/repositories/DatasetsRepository.ts | 24 ++++- test/environment/.env | 4 +- .../collections/CollectionsRepository.test.ts | 100 +++++++++++++++++- .../datasets/DatasetsRepository.test.ts | 36 +++++++ .../collections/CollectionsRepository.test.ts | 49 +++++++++ .../GetCollectionsForLinking.test.ts | 33 ++++++ 15 files changed, 411 insertions(+), 20 deletions(-) create mode 100644 src/collections/domain/useCases/GetCollectionsForLinking.ts create mode 100644 test/unit/collections/GetCollectionsForLinking.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 94b36731..cb95e01f 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -16,6 +16,7 @@ The different use cases currently available in the package are classified below, - [List All Collection Items](#list-all-collection-items) - [List My Data Collection Items](#list-my-data-collection-items) - [Get Collection Featured Items](#get-collection-featured-items) + - [Get Collections for Linking](#get-collections-for-linking) - [Collections write use cases](#collections-write-use-cases) - [Create a Collection](#create-a-collection) - [Update a Collection](#update-a-collection) @@ -336,6 +337,69 @@ The `collectionIdOrAlias` is a generic collection identifier, which can be eithe 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. +#### Get Collections for Linking + +Returns an array of [CollectionSummary](../src/collections/domain/models/CollectionSummary.ts) (id, alias, displayName) representing the Dataverse collections to which a given Dataverse collection or Dataset may be linked. + +This use case supports an optional `searchTerm` to filter by collection name. + +##### Example calls: + +```typescript +import { getCollectionsForLinking } from '@iqss/dataverse-client-javascript' + +/* ... */ + +// Case 1: For a given Dataverse collection (by numeric id or alias) +const collectionIdOrAlias: number | string = 'collectionAlias' // or 123 +const searchTerm = 'searchOn' + +getCollectionsForLinking + .execute('collection', collectionIdOrAlias, searchTerm) + .then((collections) => { + // collections: CollectionSummary[] + /* ... */ + }) + .catch((error: Error) => { + /* ... */ + }) + +/* ... */ + +// Case 2: For a given Dataset (by persistent identifier) +const persistentId = 'doi:10.5072/FK2/J8SJZB' + +getCollectionsForLinking + .execute('dataset', persistentId, searchTerm) + .then((collections) => { + // collections: CollectionSummary[] + /* ... */ + }) + .catch((error: Error) => { + /* ... */ + }) + +// Case 3: [alreadyLinked] Optional flag. When true, returns collections currently linked (candidates to unlink). Defaults to false. +const alreadyLinked = true + +getCollectionsForLinking + .execute('dataset', persistentId, searchTerm, alreadyLinked) + .then((collections) => { + // collections: CollectionSummary[] + /* ... */ + }) + .catch((error: Error) => { + /* ... */ + }) +``` + +_See [use case](../src/collections/domain/useCases/GetCollectionsForLinking.ts) implementation_. + +Notes: + +- When the first argument is `'collection'`, the second argument can be a numeric collection id or a collection alias. +- When the first argument is `'dataset'`, the second argument must be the dataset persistent identifier string (e.g., `doi:...`). + ### Collections Write Use Cases #### Create a Collection diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index 820a1356..bc8960c8 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -10,6 +10,8 @@ import { CollectionUserPermissions } from '../models/CollectionUserPermissions' import { PublicationStatus } from '../../../core/domain/models/PublicationStatus' import { CollectionItemType } from '../../../collections/domain/models/CollectionItemType' import { CollectionLinks } from '../models/CollectionLinks' +import { CollectionSummary } from '../models/CollectionSummary' +import { LinkingObjectType } from '../useCases/GetCollectionsForLinking' export interface ICollectionsRepository { getCollection(collectionIdOrAlias: number | string): Promise @@ -60,4 +62,10 @@ export interface ICollectionsRepository { linkingCollectionIdOrAlias: number | string ): Promise getCollectionLinks(collectionIdOrAlias: number | string): Promise + getCollectionsForLinking( + objectType: LinkingObjectType, + id: number | string, + searchTerm: string, + alreadyLinked: boolean + ): Promise } diff --git a/src/collections/domain/useCases/GetCollectionsForLinking.ts b/src/collections/domain/useCases/GetCollectionsForLinking.ts new file mode 100644 index 00000000..e01e156e --- /dev/null +++ b/src/collections/domain/useCases/GetCollectionsForLinking.ts @@ -0,0 +1,34 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { CollectionSummary } from '../models/CollectionSummary' + +export type LinkingObjectType = 'collection' | 'dataset' + +export class GetCollectionsForLinking implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Returns an array of CollectionSummary (id, alias, displayName) to which the given Dataverse collection or Dataset may be linked. + * @param objectType - 'collection' when providing a collection identifier/alias; 'dataset' when providing a dataset persistentId. + * @param id - For objectType 'collection', a numeric id or alias string. For 'dataset', the persistentId string (e.g., doi:...) + * @param searchTerm - Optional search term to filter by collection name. Defaults to empty string (no filtering). + * @param alreadyLinked - Optional flag. When true, returns collections currently linked (candidates to unlink). Defaults to false. + */ + async execute( + objectType: LinkingObjectType, + id: number | string, + searchTerm = '', + alreadyLinked = false + ): Promise { + return await this.collectionsRepository.getCollectionsForLinking( + objectType, + id, + searchTerm, + alreadyLinked + ) + } +} diff --git a/src/collections/index.ts b/src/collections/index.ts index 05e49954..59e2e50b 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -15,6 +15,7 @@ import { DeleteCollectionFeaturedItem } from './domain/useCases/DeleteCollection import { LinkCollection } from './domain/useCases/LinkCollection' import { UnlinkCollection } from './domain/useCases/UnlinkCollection' import { GetCollectionLinks } from './domain/useCases/GetCollectionLinks' +import { GetCollectionsForLinking } from './domain/useCases/GetCollectionsForLinking' const collectionsRepository = new CollectionsRepository() @@ -34,6 +35,7 @@ const deleteCollectionFeaturedItem = new DeleteCollectionFeaturedItem(collection const linkCollection = new LinkCollection(collectionsRepository) const unlinkCollection = new UnlinkCollection(collectionsRepository) const getCollectionLinks = new GetCollectionLinks(collectionsRepository) +const getCollectionsForLinking = new GetCollectionsForLinking(collectionsRepository) export { getCollection, @@ -51,7 +53,8 @@ export { deleteCollectionFeaturedItem, linkCollection, unlinkCollection, - getCollectionLinks + getCollectionLinks, + getCollectionsForLinking } export { Collection, CollectionInputLevel } from './domain/models/Collection' export { CollectionFacet } from './domain/models/CollectionFacet' @@ -62,3 +65,4 @@ export { CollectionItemType } from './domain/models/CollectionItemType' export { CollectionSearchCriteria } from './domain/models/CollectionSearchCriteria' export { FeaturedItem } from './domain/models/FeaturedItem' export { FeaturedItemsDTO } from './domain/dtos/FeaturedItemsDTO' +export { CollectionSummary } from './domain/models/CollectionSummary' diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index 704367e2..e0e459b0 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -38,6 +38,8 @@ import { ApiConstants } from '../../../core/infra/repositories/ApiConstants' import { PublicationStatus } from '../../../core/domain/models/PublicationStatus' import { ReadError } from '../../../core/domain/repositories/ReadError' import { CollectionLinks } from '../../domain/models/CollectionLinks' +import { CollectionSummary } from '../../domain/models/CollectionSummary' +import { LinkingObjectType } from '../../domain/useCases/GetCollectionsForLinking' export interface NewCollectionRequestPayload { alias: string @@ -93,7 +95,6 @@ export enum GetMyDataCollectionItemsQueryParams { export class CollectionsRepository extends ApiRepository implements ICollectionsRepository { private readonly collectionsResourceName: string = 'dataverses' - public async getCollection( collectionIdOrAlias: number | string = ROOT_COLLECTION_ID ): Promise { @@ -485,4 +486,46 @@ export class CollectionsRepository extends ApiRepository implements ICollections throw error }) } + + public async getCollectionsForLinking( + objectType: LinkingObjectType, + id: number | string, + searchTerm: string, + alreadyLinked: boolean + ): Promise { + let path: string + const queryParams = new URLSearchParams() + if (objectType === 'collection') { + path = `/${this.collectionsResourceName}/${id}/dataverse/linkingDataverses` + } else { + path = `/${this.collectionsResourceName}/:persistentId/dataset/linkingDataverses` + queryParams.set('persistentId', String(id)) + } + + if (searchTerm) { + queryParams.set('searchTerm', searchTerm) + } + + if (alreadyLinked) { + queryParams.set('alreadyLinking', 'true') + } + + return this.doGet(path, true, queryParams) + .then((response) => { + const payload = response.data.data as { + id: number + alias: string + name: string + }[] + + return payload.map((item) => ({ + id: item.id, + alias: item.alias, + displayName: item.name + })) + }) + .catch((error) => { + throw error + }) + } } diff --git a/src/collections/infra/repositories/transformers/collectionTransformers.ts b/src/collections/infra/repositories/transformers/collectionTransformers.ts index a26c4718..fa23b8ed 100644 --- a/src/collections/infra/repositories/transformers/collectionTransformers.ts +++ b/src/collections/infra/repositories/transformers/collectionTransformers.ts @@ -159,7 +159,13 @@ export const transformCollectionLinksResponseToCollectionLinks = ( const responseDataPayload = response.data.data const linkedCollections = responseDataPayload.linkedDataverses const collectionsLinkingToThis = responseDataPayload.dataversesLinkingToThis - const linkedDatasets = responseDataPayload.linkedDatasets + const linkedDatasets = responseDataPayload.linkedDatasets.map( + (ld: { identifier: string; title: string }) => ({ + persistentId: ld.identifier, + title: ld.title + }) + ) + return { linkedCollections, collectionsLinkingToThis, diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index e78816c4..621a5a06 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -67,8 +67,8 @@ export interface IDatasetsRepository { ): Promise getDatasetVersionsSummaries(datasetId: number | string): Promise deleteDatasetDraft(datasetId: number | string): Promise - linkDataset(datasetId: number, collectionAlias: string): Promise - unlinkDataset(datasetId: number, collectionAlias: string): Promise + linkDataset(datasetId: number | string, collectionIdOrAlias: number | string): Promise + unlinkDataset(datasetId: number | string, collectionIdOrAlias: number | string): Promise getDatasetLinkedCollections(datasetId: number | string): Promise getDatasetAvailableCategories(datasetId: number | string): Promise getDatasetCitationInOtherFormats( diff --git a/src/datasets/domain/useCases/LinkDataset.ts b/src/datasets/domain/useCases/LinkDataset.ts index be7f732f..4e953b17 100644 --- a/src/datasets/domain/useCases/LinkDataset.ts +++ b/src/datasets/domain/useCases/LinkDataset.ts @@ -11,11 +11,11 @@ export class LinkDataset implements UseCase { /** * Creates a link between a Dataset and a Collection. * - * @param {number} [datasetId] - The dataset id. - * @param {string} [collectionAlias] - The collection alias. + * @param {number | string} [datasetId] - The dataset id (numeric) or persistent identifier string. + * @param {number | string} [collectionIdOrAlias] - The collection identifier (numeric id) or alias. * @returns {Promise} - This method does not return anything upon successful completion. */ - async execute(datasetId: number, collectionAlias: string): Promise { - return await this.datasetsRepository.linkDataset(datasetId, collectionAlias) + async execute(datasetId: number | string, collectionIdOrAlias: number | string): Promise { + return await this.datasetsRepository.linkDataset(datasetId, collectionIdOrAlias) } } diff --git a/src/datasets/domain/useCases/UnlinkDataset.ts b/src/datasets/domain/useCases/UnlinkDataset.ts index d2d8eff5..8b2142fb 100644 --- a/src/datasets/domain/useCases/UnlinkDataset.ts +++ b/src/datasets/domain/useCases/UnlinkDataset.ts @@ -11,11 +11,11 @@ export class UnlinkDataset implements UseCase { /** * Removes a link between a Dataset and a Collection. * - * @param {number} [datasetId] - The dataset id. - * @param {string} [collectionAlias] - The collection alias. + * @param {number | string} [datasetId] - The dataset id (numeric) or persistent identifier string. + * @param {number | string} [collectionIdOrAlias] - The collection identifier (numeric id) or alias. * @returns {Promise} - This method does not return anything upon successful completion. */ - async execute(datasetId: number, collectionAlias: string): Promise { - return await this.datasetsRepository.unlinkDataset(datasetId, collectionAlias) + async execute(datasetId: number | string, collectionIdOrAlias: number | string): Promise { + return await this.datasetsRepository.unlinkDataset(datasetId, collectionIdOrAlias) } } diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 1545a43d..326516a8 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -329,16 +329,32 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }) } - public async linkDataset(datasetId: number, collectionAlias: string): Promise { - return this.doPut(`/${this.datasetsResourceName}/${datasetId}/link/${collectionAlias}`, {}) + public async linkDataset( + datasetId: number | string, + collectionIdOrAlias: number | string + ): Promise { + const endpoint = this.buildApiEndpoint( + this.datasetsResourceName, + `link/${collectionIdOrAlias}`, + datasetId + ) + return this.doPut(endpoint, {}) .then(() => undefined) .catch((error) => { throw error }) } - public async unlinkDataset(datasetId: number, collectionAlias: string): Promise { - return this.doDelete(`/${this.datasetsResourceName}/${datasetId}/deleteLink/${collectionAlias}`) + public async unlinkDataset( + datasetId: number | string, + collectionIdOrAlias: number | string + ): Promise { + const endpoint = this.buildApiEndpoint( + this.datasetsResourceName, + `deleteLink/${collectionIdOrAlias}`, + datasetId + ) + return this.doDelete(endpoint) .then(() => undefined) .catch((error) => { throw error diff --git a/test/environment/.env b/test/environment/.env index e7b54bde..3a9a818d 100644 --- a/test/environment/.env +++ b/test/environment/.env @@ -1,6 +1,6 @@ POSTGRES_VERSION=17 DATAVERSE_DB_USER=dataverse SOLR_VERSION=9.8.0 -DATAVERSE_IMAGE_REGISTRY=docker.io -DATAVERSE_IMAGE_TAG=unstable +DATAVERSE_IMAGE_REGISTRY=ghcr.io +DATAVERSE_IMAGE_TAG=11710-find-dataverses-for-linking DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts index d1afd76d..116457e1 100644 --- a/test/integration/collections/CollectionsRepository.test.ts +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -804,6 +804,101 @@ describe('CollectionsRepository', () => { }) }) + describe('getCollectionsForLinking', () => { + const linkingParentCollection = 'collectionsRepositoryLinkingTestParentCollection' + const linkingTargetAlias = 'collectionsRepositoryLinkTarget' + + beforeAll(async () => { + await createCollectionViaApi(linkingParentCollection) + await createCollectionViaApi(linkingTargetAlias, linkingParentCollection) + }) + + afterAll(async () => { + await deleteCollectionViaApi(linkingTargetAlias) + await deleteCollectionViaApi(linkingParentCollection) + }) + + test('should list collections for linking for a given collection alias', async () => { + const results = await sut.getCollectionsForLinking( + 'collection', + linkingParentCollection, + 'Scientific', + false + ) + + expect(Array.isArray(results)).toBe(true) + // Should contain the newly created linking target collection among candidates + const found = results.find((c) => c.alias === linkingTargetAlias) + expect(found).toBeDefined() + expect(found?.id).toBeGreaterThan(0) + expect(found?.displayName).toBe('Scientific Research') + }) + + test('should list collections for linking for a given dataset persistentId', async () => { + // Create a temporary dataset to query linking candidates + const { persistentId, numericId } = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + linkingParentCollection + ) + + const results = await sut.getCollectionsForLinking( + 'dataset', + persistentId, + 'Scientific', + false + ) + + // Cleanup dataset (unpublished) + await deleteUnpublishedDatasetViaApi(numericId) + + expect(Array.isArray(results)).toBe(true) + const found = results.find((c) => c.alias === linkingTargetAlias) + expect(found).toBeDefined() + expect(found?.displayName).toBe('Scientific Research') + }) + + test('should return collections for unlinking when sending alreadyLinked param to true', async () => { + const collectionsForUnlinkingBefore = await sut.getCollectionsForLinking( + 'collection', + linkingParentCollection, + '', + true + ) + + // Link the test collection with the linking target collection + await sut.linkCollection(linkingParentCollection, linkingTargetAlias) + + const collectionsForUnlinkingAfter = await sut.getCollectionsForLinking( + 'collection', + linkingParentCollection, + '', + true + ) + + expect(collectionsForUnlinkingBefore.length).toBe(0) + expect(collectionsForUnlinkingAfter.length).toBeGreaterThan(0) + expect(collectionsForUnlinkingAfter[0].alias).toBe(linkingTargetAlias) + expect(collectionsForUnlinkingAfter[0].displayName).toBe('Scientific Research') + }) + + it('should return error when collection does not exist', async () => { + await expect( + sut.getCollectionsForLinking( + 'collection', + TestConstants.TEST_DUMMY_COLLECTION_ALIAS, + '', + false + ) + ).rejects.toThrow(ReadError) + }) + + it('should return error when dataset does not exist', async () => { + await expect( + sut.getCollectionsForLinking('dataset', TestConstants.TEST_DUMMY_PERSISTENT_ID, '', false) + ).rejects.toThrow(ReadError) + }) + }) + describe('getCollectionItems for published tabular file', () => { let testDatasetIds: CreatedDatasetIdentifiers const testTextFile4Name = 'test-file-4.tab' @@ -1998,16 +2093,18 @@ describe('CollectionsRepository', () => { const thirdCollectionAlias = 'getCollectionLinksThird' const fourthCollectionAlias = 'getCollectionLinksFourth' let childDatasetNumericId: number + let childDatasetPersistentId: string beforeAll(async () => { await createCollectionViaApi(firstCollectionAlias) await createCollectionViaApi(secondCollectionAlias) await createCollectionViaApi(thirdCollectionAlias) await createCollectionViaApi(fourthCollectionAlias) - const { numericId: createdId } = await createDataset.execute( + const { numericId: createdId, persistentId: createdPid } = await createDataset.execute( TestConstants.TEST_NEW_DATASET_DTO, fourthCollectionAlias ) childDatasetNumericId = createdId + childDatasetPersistentId = createdPid await sut.linkCollection(secondCollectionAlias, firstCollectionAlias) await sut.linkCollection(firstCollectionAlias, thirdCollectionAlias) await sut.linkCollection(firstCollectionAlias, fourthCollectionAlias) @@ -2036,6 +2133,7 @@ describe('CollectionsRepository', () => { expect(collectionLinks.linkedDatasets[0].title).toBe( 'Dataset created using the createDataset use case' ) + expect(collectionLinks.linkedDatasets[0].persistentId).toBe(childDatasetPersistentId) }) test('should return error when collection does not exist', async () => { diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index af669e7c..812fc43b 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1599,6 +1599,21 @@ describe('DatasetsRepository', () => { sut.linkDataset(testDatasetIds.numericId, 'nonExistentCollectionAlias') ).rejects.toThrow() }) + + test('should link a dataset to another collection using persistent id', async () => { + const persistentCollectionAlias = 'testLinkDatasetCollectionPersistent' + await createCollectionViaApi(persistentCollectionAlias) + + const actual = await sut.linkDataset(testDatasetIds.persistentId, persistentCollectionAlias) + + expect(actual).toBeUndefined() + + const linkedCollections = await sut.getDatasetLinkedCollections(testDatasetIds.numericId) + const aliases = linkedCollections.map((c) => c.alias) + expect(aliases).toContain(persistentCollectionAlias) + + await deleteCollectionViaApi(persistentCollectionAlias) + }) }) describe('unlinkDataset', () => { @@ -1644,6 +1659,27 @@ describe('DatasetsRepository', () => { sut.unlinkDataset(testDatasetIds.numericId, testCollectionAlias) ).rejects.toThrow() }) + + test('should unlink a dataset from a collection using persistent id', async () => { + const persistentCollectionAlias = 'testUnlinkDatasetCollectionPersistent' + await createCollectionViaApi(persistentCollectionAlias) + + await sut.linkDataset(testDatasetIds.persistentId, persistentCollectionAlias) + const linkedCollections = await sut.getDatasetLinkedCollections(testDatasetIds.numericId) + const aliases = linkedCollections.map((c) => c.alias) + expect(aliases).toContain(persistentCollectionAlias) + + const actual = await sut.unlinkDataset(testDatasetIds.persistentId, persistentCollectionAlias) + + expect(actual).toBeUndefined() + const updatedLinkedCollections = await sut.getDatasetLinkedCollections( + testDatasetIds.numericId + ) + const updatedAliases = updatedLinkedCollections.map((c) => c.alias) + expect(updatedAliases).not.toContain(persistentCollectionAlias) + + await deleteCollectionViaApi(persistentCollectionAlias) + }) }) describe('getDatasetLinkedCollections', () => { diff --git a/test/unit/collections/CollectionsRepository.test.ts b/test/unit/collections/CollectionsRepository.test.ts index 950a9cdf..d099acb1 100644 --- a/test/unit/collections/CollectionsRepository.test.ts +++ b/test/unit/collections/CollectionsRepository.test.ts @@ -567,6 +567,55 @@ describe('CollectionsRepository', () => { }) }) + describe('getCollectionsForLinking', () => { + test('should call dataverse variant with numeric id and search term', async () => { + const payload = { + data: { + status: 'OK', + data: [ + { id: 1, alias: 'dv1', name: 'DV 1' }, + { id: 2, alias: 'dv2', name: 'DV 2' } + ] + } + } + jest.spyOn(axios, 'get').mockResolvedValue(payload) + + const actual = await sut.getCollectionsForLinking('collection', 99, 'abc', false) + const expectedEndpoint = `${TestConstants.TEST_API_URL}/dataverses/99/dataverse/linkingDataverses` + const expectedParams = new URLSearchParams({ searchTerm: 'abc' }) + const expectedConfig = { + params: expectedParams, + headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers + } + expect(axios.get).toHaveBeenCalledWith(expectedEndpoint, expectedConfig) + expect(actual).toEqual([ + { id: 1, alias: 'dv1', displayName: 'DV 1' }, + { id: 2, alias: 'dv2', displayName: 'DV 2' } + ]) + }) + + test('should call dataset variant using persistentId and map results', async () => { + const payload = { + data: { + status: 'OK', + data: [{ id: 3, alias: 'dv3', name: 'DV 3' }] + } + } + jest.spyOn(axios, 'get').mockResolvedValue(payload) + + const pid = 'doi:10.5072/FK2/J8SJZB' + const actual = await sut.getCollectionsForLinking('dataset', pid, '', false) + const expectedEndpoint = `${TestConstants.TEST_API_URL}/dataverses/:persistentId/dataset/linkingDataverses` + const expectedParams = new URLSearchParams({ persistentId: pid }) + const expectedConfig = { + params: expectedParams, + headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers + } + expect(axios.get).toHaveBeenCalledWith(expectedEndpoint, expectedConfig) + expect(actual).toEqual([{ id: 3, alias: 'dv3', displayName: 'DV 3' }]) + }) + }) + describe('deleteCollection', () => { const deleteTestCollectionAlias = 'deleteCollection-unit-test' const deleteTestCollectionId = 123 diff --git a/test/unit/collections/GetCollectionsForLinking.test.ts b/test/unit/collections/GetCollectionsForLinking.test.ts new file mode 100644 index 00000000..79511fe8 --- /dev/null +++ b/test/unit/collections/GetCollectionsForLinking.test.ts @@ -0,0 +1,33 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { GetCollectionsForLinking } from '../../../src/collections/domain/useCases/GetCollectionsForLinking' +import { CollectionSummary, ReadError } from '../../../src' + +const sample: CollectionSummary[] = [ + { id: 1, alias: 'col1', displayName: 'Collection 1' }, + { id: 2, alias: 'col2', displayName: 'Collection 2' } +] + +describe('GetCollectionsForLinking', () => { + test('should return collections for linking on success', async () => { + const repo: ICollectionsRepository = {} as ICollectionsRepository + repo.getCollectionsForLinking = jest.fn().mockResolvedValue(sample) + + const uc = new GetCollectionsForLinking(repo) + await expect(uc.execute('collection', 123, 'foo')).resolves.toEqual(sample) + expect(repo.getCollectionsForLinking).toHaveBeenCalledWith('collection', 123, 'foo', false) + }) + + test('should return error result on repository error', async () => { + const repo: ICollectionsRepository = {} as ICollectionsRepository + repo.getCollectionsForLinking = jest.fn().mockRejectedValue(new ReadError('x')) + + const uc = new GetCollectionsForLinking(repo) + await expect(uc.execute('dataset', 'doi:10.123/ABC')).rejects.toThrow(ReadError) + expect(repo.getCollectionsForLinking).toHaveBeenCalledWith( + 'dataset', + 'doi:10.123/ABC', + '', + false + ) + }) +}) From 93da193f7953e42bd219d041619057745bf54c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Saracca?= Date: Fri, 3 Oct 2025 12:35:45 -0300 Subject: [PATCH 2/2] docs: add to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb116c44..57ea977c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel ### Added +- New Use Case: [Get Collections For Linking Use Case](./docs/useCases.md#get-collections-for-linking). + ### Changed ### Fixed