From f199086480a3fe4d0ce0c4cd79822afb3410f801 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 4 Aug 2025 18:15:12 -0400 Subject: [PATCH 01/14] add linkCollection and unlinkCollection use cases --- .DS_Store | Bin 0 -> 8196 bytes .../repositories/ICollectionsRepository.ts | 8 ++ .../domain/useCases/LinkCollection.ts | 27 ++++++ .../domain/useCases/UnLinkCollection.ts | 27 ++++++ .../repositories/CollectionsRepository.ts | 26 ++++++ test/.DS_Store | Bin 0 -> 6148 bytes test/integration/.DS_Store | Bin 0 -> 6148 bytes .../collections/CollectionsRepository.test.ts | 80 ++++++++++++++++++ 8 files changed, 168 insertions(+) create mode 100644 .DS_Store create mode 100644 src/collections/domain/useCases/LinkCollection.ts create mode 100644 src/collections/domain/useCases/UnLinkCollection.ts create mode 100644 test/.DS_Store create mode 100644 test/integration/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4f3b130854e695abc9ec4e7c35549d4183cb6733 GIT binary patch literal 8196 zcmeHMJ!lj`6n>MtkVPY=JkiKPQisIS#xtB2HVQ#R6!d;V61j7aBxhr@Bnpa!jaG`t zfk-$JixeV=f~cj~Sy>1Q7D6m61Pi}6Gj}_ay*ok*QG5e4Z*RW$_WSsDZue%7h}3kW zGDValq5z%a$W9Dh8spqkZNScqBP!HWb#7{=9M$TnOqmb|!~tWgJbrUi3#d#HT>}3i6>&voJr$#e)}bPm<7zqn;^d9N`)}BjE_sQfetDj*u}Pc? z>!Si+mNu=A;Ml5*xjJ5r2L~^#YaN~1T9oj2!+qY3Hg5z^mOLNSfd`gXV=nKPtCz30 z_SfZI_CF{=M8ZA=Ub1U0ClOV39Yhhe`p3|CaovR>?x_q=-^JQt7 zUD>oQa^IeTv4gpsM?dCHJ!)ScTkEoR!#&=y4g4_BO`iY82N}hI?K+S#g*o2;AL;%6 zf4irbI&nZ8_y-(N*}_a=8ba-}H6@vUiihau(YbM6Y@#l~z$rYhx8sPNKMZjkS}bp4 TY$6aec@dy(kU<>yqYnHA<%1>d literal 0 HcmV?d00001 diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index a9ec708d..af37b091 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -50,4 +50,12 @@ export interface ICollectionsRepository { ): Promise deleteCollectionFeaturedItems(collectionIdOrAlias: number | string): Promise deleteCollectionFeaturedItem(featuredItemId: number): Promise + linkCollection( + linkedCollectionIdOrAlias: number | string, + linkingCollectionIdOrAlias: number | string + ): Promise + unlinkCollection( + linkedCollectionIdOrAlias: number | string, + linkingCollectionIdOrAlias: number | string + ): Promise } diff --git a/src/collections/domain/useCases/LinkCollection.ts b/src/collections/domain/useCases/LinkCollection.ts new file mode 100644 index 00000000..e2882af1 --- /dev/null +++ b/src/collections/domain/useCases/LinkCollection.ts @@ -0,0 +1,27 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' + +export class LinkCollection implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Deletes the Dataverse collection whose database ID or alias is given: + * + * @param {number| string} [linkedCollectionIdOrAlias] - The collection to be linked. Can be either a string (collection alias), or a number (collection id) + * @param { number | string} [linkingCollectionIdOrAlias] - The collection that will be linking to the linked collection. Can be either a string (collection alias), or a number (collection id) + * @returns {Promise} -This method does not return anything upon successful completion. + */ + async execute( + linkedCollectionIdOrAlias: string, + linkingCollectionIdOrAlias: string + ): Promise { + return await this.collectionsRepository.linkCollection( + linkedCollectionIdOrAlias, + linkingCollectionIdOrAlias + ) + } +} diff --git a/src/collections/domain/useCases/UnLinkCollection.ts b/src/collections/domain/useCases/UnLinkCollection.ts new file mode 100644 index 00000000..d36ad051 --- /dev/null +++ b/src/collections/domain/useCases/UnLinkCollection.ts @@ -0,0 +1,27 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' + +export class UnLinkCollection implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Unlinks a collection from the collection that links to it + * + * @param {number| string} [linkedCollectionIdOrAlias] - The collection that is linked. Can be either a string (collection alias), or a number (collection id) + * @param { number | string} [linkingCollectionIdOrAlias] - The collection that links to the linked collection. Can be either a string (collection alias), or a number (collection id) + * @returns {Promise} -This method does not return anything upon successful completion. + */ + async execute( + linkedCollectionIdOrAlias: string, + linkingCollectionIdOrAlias: string + ): Promise { + return await this.collectionsRepository.unlinkCollection( + linkedCollectionIdOrAlias, + linkingCollectionIdOrAlias + ) + } +} diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index b200fa37..b0537de3 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -446,4 +446,30 @@ export class CollectionsRepository extends ApiRepository implements ICollections throw error }) } + public async linkCollection( + linkedCollectionIdOrAlias: number | string, + linkingCollectionIdOrAlias: number | string + ): Promise { + console.log(linkedCollectionIdOrAlias, linkingCollectionIdOrAlias) + return this.doPut( + `/dataverses/${linkedCollectionIdOrAlias}` + `/link/${linkingCollectionIdOrAlias}`, + {} // No data is needed for this operation + ) + .then(() => undefined) + .catch((error) => { + throw error + }) + } + public async unlinkCollection( + linkedCollectionIdOrAlias: number | string, + linkingCollectionIdOrAlias: number | string + ): Promise { + return this.doDelete( + `/dataverses/${linkedCollectionIdOrAlias}` + `/deleteLink/${linkingCollectionIdOrAlias}` + ) + .then(() => undefined) + .catch((error) => { + throw error + }) + } } diff --git a/test/.DS_Store b/test/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..fa0df19f23a6f5ff26a75ce0debda8b375f7ecb5 GIT binary patch literal 6148 zcmeHK!Ab)$5S_6-EcDW&$NWP7AeQwD`U6@kRcO1V=zS5d;)i(i7d-e|zR8TGi>n}l zh`fR1O)@jd?1N1*BBImV#YAK*A{CmUn$jaS-D^4u=OIwb8cW&9x!lOLG(8Lb#VN~u zgnT#1`ce+_->jCC*+sM7mb3>CkDq(4o4TH_nmIhG`fDDjK80TnY#lvZG>Q+6o!^TWExTj=RNO_gBYQGH2D%Ikd^ooI{}jJW zZ;{^(;fM^7fq%w;4(nMx#Ye^6`r-5Ju1#oXXeQ>@ngW5|c?4i!=g4s}x;>c=zZ}>) U+A20*!hw7U6hhdMfnQ+Y4Zx~3pa1{> literal 0 HcmV?d00001 diff --git a/test/integration/.DS_Store b/test/integration/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..032a606244d1fb0ee4b6585872591be9845f009c GIT binary patch literal 6148 zcmeHKy-EW?5T4N#2We7Tu8)v6IKxR=Auk|FM1_kR(DaK>U~ge9mcD_FujMyCh8&)0 z1QD4zcD~u&nZ3CWZg+=>c(PfJiAF?Jp$W1WJtD)sQwQ!m0J5%eK?}O0Ysz%fv(R50 zlH7;LI#Y{hH~-uAay(n))mqc$>C^X~tK2m6HqYTv9p~q_*Ytj}&wIJ`P7jawFMGY$ zSG!r9X)>XLfnXpQ2nK?IA25J3TcjEqh7JaTfnZ?Efb0(mO|W(>hPrh?X$b(7YqSb% zsU;*PIo6KF5Hk?AP@sjfml$l}7*Fn3I~GF=C-&ll{mq}n3+vmleo}Yh+Awr55Dat~ zIJE9a&i_;VGQCB9Hzaz&Krryn7~nxOYbN+8KU=?io}9G_?Ho-+{E8S5*quuNI7>mkvEf%c7DHJ@#x)!m7Xc+CR50)h4154-@-%k< literal 0 HcmV?d00001 diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts index b215f681..d3c0d82a 100644 --- a/test/integration/collections/CollectionsRepository.test.ts +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -1911,4 +1911,84 @@ describe('CollectionsRepository', () => { ).rejects.toThrow(expectedError) }) }) + describe('linkCollection', () => { + const firstCollectionAlias = 'linkCollectionFirst' + const secondCollectionAlias = 'linkCollectionSecond' + + beforeAll(async () => { + await createCollectionViaApi(firstCollectionAlias) + await createCollectionViaApi(secondCollectionAlias) + }) + + afterAll(async () => { + await deleteCollectionViaApi(firstCollectionAlias) + await deleteCollectionViaApi(secondCollectionAlias) + }) + + test('should link a collection successfully', async () => { + const firstCollection = await sut.getCollection(firstCollectionAlias) + await sut.getCollection(secondCollectionAlias) + + await sut.linkCollection(secondCollectionAlias, firstCollectionAlias) + + await sut.getCollection(secondCollectionAlias) + await new Promise((res) => setTimeout(res, 2000)) + const collectionItemSubset = await sut.getCollectionItems(firstCollection.alias) + expect(collectionItemSubset.items.length).toBe(1) + }) + + test('should throw error when linking a non-existent collection', async () => { + const invalidCollectionId = 99999 + const firstCollection = await sut.getCollection(firstCollectionAlias) + + const expectedError = new WriteError("[404] Can't find dataverse with identifier='99999'") + + await expect(sut.linkCollection(invalidCollectionId, firstCollection.id)).rejects.toThrow( + expectedError + ) + }) + }) + + describe('unlinkCollection', () => { + const firstCollectionAlias = 'unlinkCollectionFirst' + const secondCollectionAlias = 'unlinkCollectionSecond' + + beforeAll(async () => { + await createCollectionViaApi(firstCollectionAlias) + await createCollectionViaApi(secondCollectionAlias) + + const firstCollection = await sut.getCollection(firstCollectionAlias) + const secondCollection = await sut.getCollection(secondCollectionAlias) + + await sut.linkCollection(secondCollection.id, firstCollection.id) + }) + + afterAll(async () => { + await deleteCollectionViaApi(firstCollectionAlias) + await deleteCollectionViaApi(secondCollectionAlias) + }) + + test('should unlink a collection successfully', async () => { + const firstCollection = await sut.getCollection(firstCollectionAlias) + const secondCollection = await sut.getCollection(secondCollectionAlias) + + await sut.unlinkCollection(secondCollection.id, firstCollection.id) + await new Promise((res) => setTimeout(res, 2000)) + + await sut.getCollection(secondCollectionAlias) + const collectionItemSubset = await sut.getCollectionItems(firstCollection.alias) + expect(collectionItemSubset.items).toStrictEqual([]) + }) + + test('should throw error when unlinking a non-existent collection', async () => { + const invalidCollectionId = 99999 + const firstCollection = await sut.getCollection(firstCollectionAlias) + + const expectedError = new WriteError("[404] Can't find dataverse with identifier='99999'") + + await expect(sut.unlinkCollection(invalidCollectionId, firstCollection.id)).rejects.toThrow( + expectedError + ) + }) + }) }) From 548ca2c9ac573b32ca60d41380a09a3ec6e879b9 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 5 Aug 2025 14:56:04 -0400 Subject: [PATCH 02/14] add functional tests --- .DS_Store | Bin 8196 -> 8196 bytes .../domain/useCases/LinkCollection.ts | 4 +- .../domain/useCases/UnLinkCollection.ts | 4 +- src/collections/index.ts | 8 +- .../repositories/CollectionsRepository.ts | 1 - .../collections/LinkCollection.test.ts | 79 ++++++++++++++++++ .../collections/UnLinkCollection.test.ts | 79 ++++++++++++++++++ 7 files changed, 169 insertions(+), 6 deletions(-) create mode 100644 test/functional/collections/LinkCollection.test.ts create mode 100644 test/functional/collections/UnLinkCollection.test.ts diff --git a/.DS_Store b/.DS_Store index 4f3b130854e695abc9ec4e7c35549d4183cb6733..a1aeee6b5b38a1556a51ae2f3089f47fdb418bc2 100644 GIT binary patch delta 169 zcmZp1XmQw}DiF6gXBGnk0}F#5LpnnyLrHGFi%U{YeiBfOL*-KPB3H%Zj;Qh}c;yQ+ z41<&Na|?ia7??O2CN~SLRhnN2#m7}~bCBRHCZ@$XlivyF VGYc>IFgZaal1a8;^KKDOZU9)EEV%#x delta 169 zcmZp1XmQw}DiF8m;!g$!1{MZAhIEEZhLYTT7nh`*{3M_lM@!E6M=KSMJEF>`;FT}P zFbq!4&n*DzVPIlcnA|L|R%zZ-AcqxbLncECLn=cevK@1#ENWV { * @returns {Promise} -This method does not return anything upon successful completion. */ async execute( - linkedCollectionIdOrAlias: string, - linkingCollectionIdOrAlias: string + linkedCollectionIdOrAlias: number | string, + linkingCollectionIdOrAlias: number | string ): Promise { return await this.collectionsRepository.linkCollection( linkedCollectionIdOrAlias, diff --git a/src/collections/domain/useCases/UnLinkCollection.ts b/src/collections/domain/useCases/UnLinkCollection.ts index d36ad051..909c9424 100644 --- a/src/collections/domain/useCases/UnLinkCollection.ts +++ b/src/collections/domain/useCases/UnLinkCollection.ts @@ -16,8 +16,8 @@ export class UnLinkCollection implements UseCase { * @returns {Promise} -This method does not return anything upon successful completion. */ async execute( - linkedCollectionIdOrAlias: string, - linkingCollectionIdOrAlias: string + linkedCollectionIdOrAlias: number | string, + linkingCollectionIdOrAlias: number | string ): Promise { return await this.collectionsRepository.unlinkCollection( linkedCollectionIdOrAlias, diff --git a/src/collections/index.ts b/src/collections/index.ts index c275402e..31a038b5 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -12,6 +12,8 @@ import { DeleteCollectionFeaturedItems } from './domain/useCases/DeleteCollectio import { DeleteCollection } from './domain/useCases/DeleteCollection' import { GetMyDataCollectionItems } from './domain/useCases/GetMyDataCollectionItems' import { DeleteCollectionFeaturedItem } from './domain/useCases/DeleteCollectionFeaturedItem' +import { LinkCollection } from './domain/useCases/LinkCollection' +import { UnLinkCollection } from './domain/useCases/UnLinkCollection' const collectionsRepository = new CollectionsRepository() @@ -28,6 +30,8 @@ const updateCollectionFeaturedItems = new UpdateCollectionFeaturedItems(collecti const deleteCollectionFeaturedItems = new DeleteCollectionFeaturedItems(collectionsRepository) const deleteCollection = new DeleteCollection(collectionsRepository) const deleteCollectionFeaturedItem = new DeleteCollectionFeaturedItem(collectionsRepository) +const linkCollection = new LinkCollection(collectionsRepository) +const unlinkCollection = new UnLinkCollection(collectionsRepository) export { getCollection, @@ -42,7 +46,9 @@ export { updateCollectionFeaturedItems, deleteCollectionFeaturedItems, deleteCollection, - deleteCollectionFeaturedItem + deleteCollectionFeaturedItem, + linkCollection, + unlinkCollection } export { Collection, CollectionInputLevel } from './domain/models/Collection' export { CollectionFacet } from './domain/models/CollectionFacet' diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index b0537de3..e095920d 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -450,7 +450,6 @@ export class CollectionsRepository extends ApiRepository implements ICollections linkedCollectionIdOrAlias: number | string, linkingCollectionIdOrAlias: number | string ): Promise { - console.log(linkedCollectionIdOrAlias, linkingCollectionIdOrAlias) return this.doPut( `/dataverses/${linkedCollectionIdOrAlias}` + `/link/${linkingCollectionIdOrAlias}`, {} // No data is needed for this operation diff --git a/test/functional/collections/LinkCollection.test.ts b/test/functional/collections/LinkCollection.test.ts new file mode 100644 index 00000000..dd89d84d --- /dev/null +++ b/test/functional/collections/LinkCollection.test.ts @@ -0,0 +1,79 @@ +import { + ApiConfig, + WriteError, + createCollection, + getCollection, + linkCollection, + deleteCollection, + getCollectionItems +} from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { createCollectionDTO } from '../../testHelpers/collections/collectionHelper' + +describe('execute', () => { + const firstCollectionAlias = 'linkCollection-functional-test-first' + const secondCollectionAlias = 'linkCollection-functional-test-second' + + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + const firstCollection = createCollectionDTO(firstCollectionAlias) + const secondCollection = createCollectionDTO(secondCollectionAlias) + await createCollection.execute(firstCollection) + await createCollection.execute(secondCollection) + }) + + afterEach(async () => { + await Promise.all([ + getCollection + .execute(firstCollectionAlias) + .then((collection) => + collection && collection.id ? deleteCollection.execute(collection.id) : null + ), + getCollection + .execute(secondCollectionAlias) + .then((collection) => + collection && collection.id ? deleteCollection.execute(collection.id) : null + ) + ]) + }) + + test('should successfully link two collections', async () => { + const firstCollection = await getCollection.execute(firstCollectionAlias) + const secondCollection = await getCollection.execute(secondCollectionAlias) + expect.assertions(1) + try { + await linkCollection.execute(secondCollection.alias, firstCollection.alias) + } catch (error) { + throw new Error('Collections should be linked successfully') + } finally { + await new Promise((resolve) => setTimeout(resolve, 5000)) + const collectionItemSubset = await getCollectionItems.execute(firstCollectionAlias) + + expect(collectionItemSubset.items.length).toBe(1) + } + }) + + test('should throw an error when linking a non-existent collection', async () => { + const invalidCollectionId = 99999 + const firstCollection = await getCollection.execute(firstCollectionAlias) + + expect.assertions(2) + let writeError: WriteError | undefined = undefined + try { + await linkCollection.execute(invalidCollectionId, firstCollection.id) + throw new Error('Use case should throw an error') + } catch (error) { + writeError = error as WriteError + } finally { + expect(writeError).toBeInstanceOf(WriteError) + expect(writeError?.message).toEqual( + `There was an error when writing the resource. Reason was: [404] Can't find dataverse with identifier='${invalidCollectionId}'` + ) + } + }) +}) diff --git a/test/functional/collections/UnLinkCollection.test.ts b/test/functional/collections/UnLinkCollection.test.ts new file mode 100644 index 00000000..e8d3a0a8 --- /dev/null +++ b/test/functional/collections/UnLinkCollection.test.ts @@ -0,0 +1,79 @@ +import { + ApiConfig, + WriteError, + createCollection, + getCollection, + linkCollection, + deleteCollection, + getCollectionItems, + unlinkCollection +} from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { createCollectionDTO } from '../../testHelpers/collections/collectionHelper' + +describe('execute', () => { + const firstCollectionAlias = 'unlinkCollection-functional-test-first' + const secondCollectionAlias = 'unlinkCollection-functional-test-second' + + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + const firstCollection = createCollectionDTO(firstCollectionAlias) + const secondCollection = createCollectionDTO(secondCollectionAlias) + await createCollection.execute(firstCollection) + await createCollection.execute(secondCollection) + await linkCollection.execute(secondCollection.alias, firstCollection.alias) + }) + + afterEach(async () => { + await Promise.all([ + getCollection + .execute(firstCollectionAlias) + .then((collection) => + collection && collection.id ? deleteCollection.execute(collection.id) : null + ), + getCollection + .execute(secondCollectionAlias) + .then((collection) => + collection && collection.id ? deleteCollection.execute(collection.id) : null + ) + ]) + }) + + test('should successfully unlink two collections', async () => { + const firstCollection = await getCollection.execute(firstCollectionAlias) + const secondCollection = await getCollection.execute(secondCollectionAlias) + // Give enough time to Solr for indexing + await new Promise((resolve) => setTimeout(resolve, 5000)) + const collectionItemSubset = await getCollectionItems.execute(firstCollectionAlias) + expect(collectionItemSubset.items.length).toBe(1) + + await unlinkCollection.execute(secondCollection.alias, firstCollection.alias) + await new Promise((resolve) => setTimeout(resolve, 5000)) + const collectionItemSubset2 = await getCollectionItems.execute(firstCollectionAlias) + expect(collectionItemSubset2.items.length).toBe(0) + }) + + test('should throw an error when linking a non-existent collection', async () => { + const invalidCollectionId = 99999 + const firstCollection = await getCollection.execute(firstCollectionAlias) + + expect.assertions(2) + let writeError: WriteError | undefined = undefined + try { + await unlinkCollection.execute(invalidCollectionId, firstCollection.id) + throw new Error('Use case should throw an error') + } catch (error) { + writeError = error as WriteError + } finally { + expect(writeError).toBeInstanceOf(WriteError) + expect(writeError?.message).toEqual( + `There was an error when writing the resource. Reason was: [404] Can't find dataverse with identifier='${invalidCollectionId}'` + ) + } + }) +}) From 9db4c479ad24287648519ba7e3b4c03053582043 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 5 Aug 2025 15:19:58 -0400 Subject: [PATCH 03/14] use consistent names for UnlinkCollection --- .DS_Store | Bin 8196 -> 8196 bytes ...nLinkCollection.ts => UnlinkCollection.ts} | 2 +- src/collections/index.ts | 4 +-- ...ction.test.ts => UnlinkCollection.test.ts} | 0 test/unit/collections/LinkCollection.test.ts | 25 ++++++++++++++++++ .../unit/collections/UnlinkCollection.test.ts | 25 ++++++++++++++++++ 6 files changed, 53 insertions(+), 3 deletions(-) rename src/collections/domain/useCases/{UnLinkCollection.ts => UnlinkCollection.ts} (95%) rename test/functional/collections/{UnLinkCollection.test.ts => UnlinkCollection.test.ts} (100%) create mode 100644 test/unit/collections/LinkCollection.test.ts create mode 100644 test/unit/collections/UnlinkCollection.test.ts diff --git a/.DS_Store b/.DS_Store index a1aeee6b5b38a1556a51ae2f3089f47fdb418bc2..ddb8dee25d26d40029aba870d54886cd5f873410 100644 GIT binary patch delta 160 zcmZp1XmQw}CJ=kwoPmLXg+Y%YogtHru`=M@7vtUwzw8B!Qh84{802sB(-;ITPSkcXLB-&=8Vf=DEj LwZZ1yBA(m;fZ->c delta 160 zcmZp1XmQw}CJ>u5i-CcGg+Y%YogtHjpn+4V?&MyRVSb;WVGNdr1G9)6~;o#ij { +export class UnlinkCollection implements UseCase { private collectionsRepository: ICollectionsRepository constructor(collectionsRepository: ICollectionsRepository) { diff --git a/src/collections/index.ts b/src/collections/index.ts index 31a038b5..598a4e0f 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -13,7 +13,7 @@ import { DeleteCollection } from './domain/useCases/DeleteCollection' import { GetMyDataCollectionItems } from './domain/useCases/GetMyDataCollectionItems' import { DeleteCollectionFeaturedItem } from './domain/useCases/DeleteCollectionFeaturedItem' import { LinkCollection } from './domain/useCases/LinkCollection' -import { UnLinkCollection } from './domain/useCases/UnLinkCollection' +import { UnlinkCollection } from './domain/useCases/UnLinkCollection' const collectionsRepository = new CollectionsRepository() @@ -31,7 +31,7 @@ const deleteCollectionFeaturedItems = new DeleteCollectionFeaturedItems(collecti const deleteCollection = new DeleteCollection(collectionsRepository) const deleteCollectionFeaturedItem = new DeleteCollectionFeaturedItem(collectionsRepository) const linkCollection = new LinkCollection(collectionsRepository) -const unlinkCollection = new UnLinkCollection(collectionsRepository) +const unlinkCollection = new UnlinkCollection(collectionsRepository) export { getCollection, diff --git a/test/functional/collections/UnLinkCollection.test.ts b/test/functional/collections/UnlinkCollection.test.ts similarity index 100% rename from test/functional/collections/UnLinkCollection.test.ts rename to test/functional/collections/UnlinkCollection.test.ts diff --git a/test/unit/collections/LinkCollection.test.ts b/test/unit/collections/LinkCollection.test.ts new file mode 100644 index 00000000..7555caa2 --- /dev/null +++ b/test/unit/collections/LinkCollection.test.ts @@ -0,0 +1,25 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { WriteError } from '../../../src' +import { LinkCollection } from '../../../src/collections/domain/useCases/LinkCollection' + +describe('execute', () => { + test('should link collection successfully on repository success', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.linkCollection = jest.fn().mockResolvedValue(undefined) + + const testLinkCollection = new LinkCollection(collectionRepositoryStub) + + await expect(testLinkCollection.execute(1, 2)).resolves.toBeUndefined() + expect(collectionRepositoryStub.linkCollection).toHaveBeenCalledWith(1, 2) + }) + + test('should throw error on repository failure', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.linkCollection = jest.fn().mockRejectedValue(new WriteError()) + + const testLinkCollection = new LinkCollection(collectionRepositoryStub) + + await expect(testLinkCollection.execute(1, 2)).rejects.toThrow(WriteError) + expect(collectionRepositoryStub.linkCollection).toHaveBeenCalledWith(1, 2) + }) +}) diff --git a/test/unit/collections/UnlinkCollection.test.ts b/test/unit/collections/UnlinkCollection.test.ts new file mode 100644 index 00000000..907aa769 --- /dev/null +++ b/test/unit/collections/UnlinkCollection.test.ts @@ -0,0 +1,25 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { WriteError } from '../../../src' +import { UnlinkCollection } from '../../../src/collections/domain/useCases/UnlinkCollection' + +describe('execute', () => { + test('should unlink collection successfully on repository success', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.unlinkCollection = jest.fn().mockResolvedValue(undefined) + + const testUnlinkCollection = new UnlinkCollection(collectionRepositoryStub) + + await expect(testUnlinkCollection.execute(1, 2)).resolves.toBeUndefined() + expect(collectionRepositoryStub.unlinkCollection).toHaveBeenCalledWith(1, 2) + }) + + test('should throw error on repository failure', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.unlinkCollection = jest.fn().mockRejectedValue(new WriteError()) + + const testUnlinkCollection = new UnlinkCollection(collectionRepositoryStub) + + await expect(testUnlinkCollection.execute(1, 2)).rejects.toThrow(WriteError) + expect(collectionRepositoryStub.unlinkCollection).toHaveBeenCalledWith(1, 2) + }) +}) From 380dedcd7de42192567e7c1a52ca198abbe85ef7 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 5 Aug 2025 15:34:24 -0400 Subject: [PATCH 04/14] fix import --- .DS_Store | Bin 8196 -> 8196 bytes src/collections/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.DS_Store b/.DS_Store index ddb8dee25d26d40029aba870d54886cd5f873410..7dcd531e3428528964e134752e77224cd05aa277 100644 GIT binary patch delta 144 zcmZp1XmQw}CJ_644FdxM3xgg*IzuKyNp8N2OHxjL5>Sj|w-1kkm*R0pRQVLV@&y@& v!O8i#1wcIvOgs#en+4V?&OQj_umWw!WJqC1Wk^J}<3miZve)K7!OeUC0N5j$ delta 144 zcmZp1XmQw}CJ=kwoPmLXg+Y%YogtHru`=M@7vtUwzw8B!Qh84{802sB(-;ITPSa5EnODz+pI diff --git a/src/collections/index.ts b/src/collections/index.ts index 598a4e0f..4589c5d3 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -13,7 +13,7 @@ import { DeleteCollection } from './domain/useCases/DeleteCollection' import { GetMyDataCollectionItems } from './domain/useCases/GetMyDataCollectionItems' import { DeleteCollectionFeaturedItem } from './domain/useCases/DeleteCollectionFeaturedItem' import { LinkCollection } from './domain/useCases/LinkCollection' -import { UnlinkCollection } from './domain/useCases/UnLinkCollection' +import { UnlinkCollection } from './domain/useCases/UnlinkCollection' const collectionsRepository = new CollectionsRepository() From ed2ebf365e1152634616389a63ae8a4bc745115e Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 5 Aug 2025 16:23:07 -0400 Subject: [PATCH 05/14] fix roles test data --- .DS_Store | Bin 8196 -> 8196 bytes test/testHelpers/roles/roleHelper.ts | 5 +---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.DS_Store b/.DS_Store index 7dcd531e3428528964e134752e77224cd05aa277..609b1bae2563c40e4e04e3d02aa49c05b5583912 100644 GIT binary patch delta 156 zcmZp1XmQw}CJ_6P0SH(a^cd0^G8sy8^Icq$a`KaaVjMm@BsIMhk2|8ur{I+@$S@2} z&d)6X8Op?CFu7S^oxBu~%?h+2lOcs6l_3$?iWi}KKW+{XSj|w-1kkm*R0pRQVLV@&y@& z!O8i#1wcIvOgs#en+4X%9|W>lffi&kq%fp1BqCe!A*NS(bATWpGn2RC> diff --git a/test/testHelpers/roles/roleHelper.ts b/test/testHelpers/roles/roleHelper.ts index 3e641cda..cf48cc3c 100644 --- a/test/testHelpers/roles/roleHelper.ts +++ b/test/testHelpers/roles/roleHelper.ts @@ -42,9 +42,7 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataverse', - 'LinkDataverse', 'PublishDataset', - 'LinkDataset', 'DeleteDataverse', 'DeleteDatasetDraft' ], @@ -101,11 +99,10 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataset', - 'LinkDataset', 'DeleteDatasetDraft' ], description: - 'For datasets, a person who can edit License + Terms, edit Permissions, and publish and link datasets.', + 'For datasets, a person who can edit License + Terms, edit Permissions, and publish datasets.', id: 7 }, { From 2f24d326bf1338742e50f88180ceb7f63ccf68ec Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Mon, 11 Aug 2025 16:45:07 -0400 Subject: [PATCH 06/14] add GetCollectionLinks use case --- .DS_Store | Bin 8196 -> 8196 bytes .../domain/models/CollectionLinks.ts | 8 +++ .../domain/models/CollectionSummary.ts | 5 ++ .../repositories/ICollectionsRepository.ts | 2 + .../domain/useCases/GetCollectionLinks.ts | 22 +++++++ .../repositories/CollectionsRepository.ts | 12 ++++ .../transformers/collectionTransformers.ts | 18 +++++- src/datasets/domain/models/DatasetSummary.ts | 4 ++ test/.DS_Store | Bin 6148 -> 6148 bytes test/environment/.env | 4 +- test/integration/.DS_Store | Bin 6148 -> 6148 bytes .../collections/CollectionsRepository.test.ts | 56 +++++++++++++++++- 12 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 src/collections/domain/models/CollectionLinks.ts create mode 100644 src/collections/domain/models/CollectionSummary.ts create mode 100644 src/collections/domain/useCases/GetCollectionLinks.ts create mode 100644 src/datasets/domain/models/DatasetSummary.ts diff --git a/.DS_Store b/.DS_Store index 609b1bae2563c40e4e04e3d02aa49c05b5583912..7d0139850f8734b6fcee3de98b3d38b34a82c22f 100644 GIT binary patch delta 265 zcmZp1XmQw}DiFIgYZU_n0}F#5LpnnyLrHGFi%U{YeiBfOU;i*sPY9FhQZ1CxdlKy3`~5Jn*`RSEcy;)vjQ#1WJqC1Wk^J}BCKyKpNlHk2ox0v zOSI1Nv%5eXfusW28U{uIhRF{EMK-SxbYWszA2j)$aIn090LTR_3?)Dtiy2CC(h<&L Tkc{n{oFEd(sIqyNh!-~iN<~1s delta 265 zcmZp1XmQw}DiFK)BLfhyFz7L)Gh{N9cKsGDTf=q@KhE#?`WGh~T?)?b00!0PFk|w>WE1PA&jzCg@ zYz+e=1HVe{{YAZEDR+;8;coAa?%mbV$k=xI5|NilF@SW IE)g$o0HRAlsQ>@~ diff --git a/src/collections/domain/models/CollectionLinks.ts b/src/collections/domain/models/CollectionLinks.ts new file mode 100644 index 00000000..40b7799b --- /dev/null +++ b/src/collections/domain/models/CollectionLinks.ts @@ -0,0 +1,8 @@ +import { CollectionSummary } from './CollectionSummary' +import { DatasetSummary } from '../../../datasets/domain/models/DatasetSummary' + +export interface CollectionLinks { + linkedCollections: CollectionSummary[] + collectionsLinkingToThis: CollectionSummary[] + linkedDatasets: DatasetSummary[] +} diff --git a/src/collections/domain/models/CollectionSummary.ts b/src/collections/domain/models/CollectionSummary.ts new file mode 100644 index 00000000..bb4ee24d --- /dev/null +++ b/src/collections/domain/models/CollectionSummary.ts @@ -0,0 +1,5 @@ +export interface CollectionSummary { + id: number + alias: string + displayName: string +} diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index af37b091..820a1356 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -9,6 +9,7 @@ import { CollectionSearchCriteria } from '../models/CollectionSearchCriteria' import { CollectionUserPermissions } from '../models/CollectionUserPermissions' import { PublicationStatus } from '../../../core/domain/models/PublicationStatus' import { CollectionItemType } from '../../../collections/domain/models/CollectionItemType' +import { CollectionLinks } from '../models/CollectionLinks' export interface ICollectionsRepository { getCollection(collectionIdOrAlias: number | string): Promise @@ -58,4 +59,5 @@ export interface ICollectionsRepository { linkedCollectionIdOrAlias: number | string, linkingCollectionIdOrAlias: number | string ): Promise + getCollectionLinks(collectionIdOrAlias: number | string): Promise } diff --git a/src/collections/domain/useCases/GetCollectionLinks.ts b/src/collections/domain/useCases/GetCollectionLinks.ts new file mode 100644 index 00000000..3028e779 --- /dev/null +++ b/src/collections/domain/useCases/GetCollectionLinks.ts @@ -0,0 +1,22 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { CollectionLinks } from '../models/CollectionLinks' + +export class GetCollectionItems implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Returns a CollectionLinks object containing other collections this collection is linked to, the other collections linking to this collection, and datasets linked to this collection, given the collection identifier or alias. + * + * @param {number | string} [collectionIdOrAlias] - 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(collectionId: number | string): Promise { + return await this.collectionsRepository.getCollectionLinks(collectionId) + } +} diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index e095920d..0f96f8ba 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -3,6 +3,7 @@ import { ICollectionsRepository } from '../../domain/repositories/ICollectionsRe import { transformCollectionFacetsResponseToCollectionFacets, transformCollectionItemsResponseToCollectionItemSubset, + transformCollectionLinksResponseToCollectionLinks, transformCollectionResponseToCollection, transformMyDataResponseToCollectionItemSubset } from './transformers/collectionTransformers' @@ -36,6 +37,7 @@ import { 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' export interface NewCollectionRequestPayload { alias: string @@ -471,4 +473,14 @@ export class CollectionsRepository extends ApiRepository implements ICollections throw error }) } + public async getCollectionLinks(collectionIdOrAlias: number | string): Promise { + return this.doGet(`/${this.collectionsResourceName}/${collectionIdOrAlias}/links`, true) + .then((response) => { + console.log('getCollectionLinks response:', response.data.data) // Print the response + return transformCollectionLinksResponseToCollectionLinks(response) + }) + .catch((error) => { + throw error + }) + } } diff --git a/src/collections/infra/repositories/transformers/collectionTransformers.ts b/src/collections/infra/repositories/transformers/collectionTransformers.ts index 43802706..56b152d8 100644 --- a/src/collections/infra/repositories/transformers/collectionTransformers.ts +++ b/src/collections/infra/repositories/transformers/collectionTransformers.ts @@ -44,6 +44,7 @@ import { PublicationStatusCount } from '../../../domain/models/MyDataCollectionItemSubset' import { PublicationStatus } from '../../../../core/domain/models/PublicationStatus' +import { CollectionLinks } from '../../../domain/models/CollectionLinks' export const transformCollectionResponseToCollection = (response: AxiosResponse): Collection => { const collectionPayload = response.data.data @@ -152,7 +153,22 @@ export const transformCollectionItemsResponseToCollectionItemSubset = ( ...(countPerObjectType && { countPerObjectType }) } } - +export const transformCollectionLinksResponseToCollectionLinks = ( + response: AxiosResponse +): CollectionLinks => { + const responseDataPayload = response.data.data + const linkedCollections = responseDataPayload.linkedDataverses + const collectionsLinkingToThis = responseDataPayload.dataversesLinkingToThis + const linkedDatasets = responseDataPayload.linkedDatasets + console.log('linkedCollections', linkedCollections) + console.log('collectionsLinkedToThis', collectionsLinkingToThis) + console.log('linkedDatasets', linkedDatasets) + return { + linkedCollections, + collectionsLinkingToThis, + linkedDatasets + } +} export const transformMyDataResponseToCollectionItemSubset = ( response: AxiosResponse ): MyDataCollectionItemSubset => { diff --git a/src/datasets/domain/models/DatasetSummary.ts b/src/datasets/domain/models/DatasetSummary.ts new file mode 100644 index 00000000..a868ad32 --- /dev/null +++ b/src/datasets/domain/models/DatasetSummary.ts @@ -0,0 +1,4 @@ +export interface DatasetSummary { + persistentId: string + title: string +} diff --git a/test/.DS_Store b/test/.DS_Store index fa0df19f23a6f5ff26a75ce0debda8b375f7ecb5..0ecc76d5bde691dd63753fa1a15d6fd87890c7bd 100644 GIT binary patch delta 33 pcmZoMXffDe!pJmz_GBJLsmTG1S&Rak4>G#3O>AJ>%+B$b9{{g{3PAt> delta 31 ncmZoMXffDe!pPLyKbeP7YH|Q$*5-qZZfp}9*f+Ct{N)D#p_d99 diff --git a/test/environment/.env b/test/environment/.env index e7b54bde..92b485ab 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=11724-extend-list-dataverse-collection-links DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/integration/.DS_Store b/test/integration/.DS_Store index 032a606244d1fb0ee4b6585872591be9845f009c..ee0e96a4e69d35a729cb53a9ae7da53d297a89e8 100644 GIT binary patch delta 59 zcmZoMXffDe!pIc3dNL2A)Z_q09yVvg+4J30Cr@CMnOq0t{&uj*cK~s<7!x)hWOQSj M*ub`#o#QV*01@;O82|tP delta 59 zcmZoMXffDe!pP)4e=-lF)Z_q09yZ1Nm4`MfOrF3fGr11PRhTflX*Gze#Tc;pAfp@G M#0Iv_>>Pjj0V`7y^#A|> diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts index d3c0d82a..d1afd76d 100644 --- a/test/integration/collections/CollectionsRepository.test.ts +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -15,7 +15,8 @@ import { createCollection, getDatasetFiles, restrictFile, - deleteFile + deleteFile, + linkDataset } from '../../../src' import { ApiConfig } from '../../../src' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' @@ -1991,4 +1992,57 @@ describe('CollectionsRepository', () => { ) }) }) + describe('getCollectionLinks', () => { + const firstCollectionAlias = 'getCollectionLinksFirst' + const secondCollectionAlias = 'getCollectionLinksSecond' + const thirdCollectionAlias = 'getCollectionLinksThird' + const fourthCollectionAlias = 'getCollectionLinksFourth' + let childDatasetNumericId: number + beforeAll(async () => { + await createCollectionViaApi(firstCollectionAlias) + await createCollectionViaApi(secondCollectionAlias) + await createCollectionViaApi(thirdCollectionAlias) + await createCollectionViaApi(fourthCollectionAlias) + const { numericId: createdId } = await createDataset.execute( + TestConstants.TEST_NEW_DATASET_DTO, + fourthCollectionAlias + ) + childDatasetNumericId = createdId + await sut.linkCollection(secondCollectionAlias, firstCollectionAlias) + await sut.linkCollection(firstCollectionAlias, thirdCollectionAlias) + await sut.linkCollection(firstCollectionAlias, fourthCollectionAlias) + await linkDataset.execute(childDatasetNumericId, firstCollectionAlias) + }) + + afterAll(async () => { + await deleteUnpublishedDatasetViaApi(childDatasetNumericId) + await deleteCollectionViaApi(firstCollectionAlias) + await deleteCollectionViaApi(secondCollectionAlias) + await deleteCollectionViaApi(thirdCollectionAlias) + await deleteCollectionViaApi(fourthCollectionAlias) + }) + + test('should return collection links successfully', async () => { + const firstCollection = await sut.getCollection(firstCollectionAlias) + const collectionLinks = await sut.getCollectionLinks(firstCollection.id) + + expect(collectionLinks.linkedCollections).toHaveLength(1) + + expect(collectionLinks.linkedCollections[0].alias).toBe(secondCollectionAlias) + expect(collectionLinks.collectionsLinkingToThis).toHaveLength(2) + expect(collectionLinks.collectionsLinkingToThis[0].alias).toBe(thirdCollectionAlias) + expect(collectionLinks.collectionsLinkingToThis[1].alias).toBe(fourthCollectionAlias) + expect(collectionLinks.linkedDatasets).toHaveLength(1) + expect(collectionLinks.linkedDatasets[0].title).toBe( + 'Dataset created using the createDataset use case' + ) + }) + + test('should return error when collection does not exist', async () => { + const invalidCollectionId = 99999 + const expectedError = new ReadError("[404] Can't find dataverse with identifier='99999'") + + await expect(sut.getCollectionLinks(invalidCollectionId)).rejects.toThrow(expectedError) + }) + }) }) From c2564a1fd00564ebf3867796a8f41543b990eb96 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 12 Aug 2025 14:14:04 -0400 Subject: [PATCH 07/14] remove console.log() --- .DS_Store | Bin 8196 -> 8196 bytes .../repositories/CollectionsRepository.ts | 1 - .../transformers/collectionTransformers.ts | 3 --- 3 files changed, 4 deletions(-) diff --git a/.DS_Store b/.DS_Store index 7d0139850f8734b6fcee3de98b3d38b34a82c22f..f5fea97756ffd74e77ac6f33202d1aee9fb2b100 100644 GIT binary patch delta 200 zcmZp1XmQw}DiF7fQ-^_pfrUYjA)O(Up(Hoo#U&{xKM5$tq5o6y+g;V;j;Qh}c;yQ+ z41<&Na|?ia7?=beOl}rft2mbj$YBNAkjaq3kjjvVY{#0V2Ls$E2MWrnfmsmSkklfZ b$H2(IF!_O?$mSJ-E=)}8yf?oS7UKZ`sU;i*sPY9FhQZ1CxdlKy3`~3mlbZ$BDlYmCE1%2cKtWkG mt+V{>E)d(0)FPY5z$m~l`GKIw<`sf2Oib&8Hop@V;{gEXpfXAT diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index 0f96f8ba..135ffecc 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -476,7 +476,6 @@ export class CollectionsRepository extends ApiRepository implements ICollections public async getCollectionLinks(collectionIdOrAlias: number | string): Promise { return this.doGet(`/${this.collectionsResourceName}/${collectionIdOrAlias}/links`, true) .then((response) => { - console.log('getCollectionLinks response:', response.data.data) // Print the response return transformCollectionLinksResponseToCollectionLinks(response) }) .catch((error) => { diff --git a/src/collections/infra/repositories/transformers/collectionTransformers.ts b/src/collections/infra/repositories/transformers/collectionTransformers.ts index 56b152d8..a26c4718 100644 --- a/src/collections/infra/repositories/transformers/collectionTransformers.ts +++ b/src/collections/infra/repositories/transformers/collectionTransformers.ts @@ -160,9 +160,6 @@ export const transformCollectionLinksResponseToCollectionLinks = ( const linkedCollections = responseDataPayload.linkedDataverses const collectionsLinkingToThis = responseDataPayload.dataversesLinkingToThis const linkedDatasets = responseDataPayload.linkedDatasets - console.log('linkedCollections', linkedCollections) - console.log('collectionsLinkedToThis', collectionsLinkingToThis) - console.log('linkedDatasets', linkedDatasets) return { linkedCollections, collectionsLinkingToThis, From 1d78fbb7d3b392cf9f7ace737f6bf7a0b4a7c7c8 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 15 Aug 2025 10:58:41 -0400 Subject: [PATCH 08/14] remove .DS_Store --- .DS_Store | Bin 8196 -> 8196 bytes .gitignore | 3 +++ test/.DS_Store | Bin 6148 -> 0 bytes test/environment/.env | 4 ++-- test/integration/.DS_Store | Bin 6148 -> 0 bytes 5 files changed, 5 insertions(+), 2 deletions(-) delete mode 100644 test/.DS_Store delete mode 100644 test/integration/.DS_Store diff --git a/.DS_Store b/.DS_Store index f5fea97756ffd74e77ac6f33202d1aee9fb2b100..e9ecac09f4c5bb3b1b643dcf5b8336bf3ac7e742 100644 GIT binary patch delta 181 zcmZp1XmQw}DiF6^Xchwl0}F#5LpnnyLrHGFi%U{YeiBfO`;FT}P zFbq!4&n*DzVPFysnA|L|PN59QW(8W1$&kX3%8-a`MZM##nn#-h1$mj7bW{CF*#LGeshxGA10>QzR3w95^Qew aB*NFoO|BD>f-&cbNHeJzY~C&6$qfK0=`N1| diff --git a/.gitignore b/.gitignore index dbc1d777..e8782206 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ node_modules # unit tests coverage +# macOS +.DS_Store + # ignore npm lock package-json.lock .npmrc \ No newline at end of file diff --git a/test/.DS_Store b/test/.DS_Store deleted file mode 100644 index 0ecc76d5bde691dd63753fa1a15d6fd87890c7bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKOG*Pl5UomPF_2A`F8d0(K^w*sZkqg@g=>Uf9E~_f%oR-Ad5=A zQ%Eon3VaYO!9Xw&416#k`$Iw#%#OuSw+?iA1pvx1S_QhEQGrPe z!0cEIVS%uP0xgvNioq5Rdvd?*SPU(kSf7k_{K@Z^7xoh}Cv_*zhS3KD!N4&CeH#v? z{$Jo%>b&G1hr}ot2nJ4!0j`=!Gsa7KZT<9mQfm|1C7Ou%6;UA2wG;!j6k{O!$eB*s bd=ee=*|8YPDq`1gU>pRLkm!PeKVaYu8(KBS diff --git a/test/environment/.env b/test/environment/.env index 92b485ab..e7b54bde 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=ghcr.io -DATAVERSE_IMAGE_TAG=11724-extend-list-dataverse-collection-links +DATAVERSE_IMAGE_REGISTRY=docker.io +DATAVERSE_IMAGE_TAG=unstable DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/integration/.DS_Store b/test/integration/.DS_Store deleted file mode 100644 index ee0e96a4e69d35a729cb53a9ae7da53d297a89e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKy-ve05Wa&Bk-Btb^uhyVXD}tOqrO1Z76DRPR|59D2LlhlgYZx+eD_1uh|mQI zA#@kn-`T#qTs|qbkBE5sw5o|FL{y;(vM2*0(}PP#Zaf0A#<-y+J<>fD+6)Bxi&K*O z7+Du;@$BdS(B9UwWm#`EZN7f}-f~@@fEUJt8`Q_^L?fj7SYG?hP&q_XMy}jGd zVvevO!9Xw&3gTfnJrR{4WkbRf`MRQ$AIh)2~99Nc80ojpwkinDA#Be=u%6F zPjbwTogr2rY_33aWiK(<+%ccrE<1LH=1%Oz2m6~pix<|nBYskI;%pdwFc1v%8927# zM9%+9{4#?@e%~cV!9XzZ&luoQGi#>!C_h`je4d=O32lxhB6gi95a_*200y#;oamyh bC(&V-9XmsbBJ=7FjEjI05?wIx3k-Y!0S7ai From 3a969cbaec1a0a95b93f2cd8974fd8f8bdf9f0d6 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 15 Aug 2025 11:04:18 -0400 Subject: [PATCH 09/14] Remove remaining .DS_Store file --- .DS_Store | Bin 8196 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index e9ecac09f4c5bb3b1b643dcf5b8336bf3ac7e742..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHM&ubGw82zT%;-VE6K}gZcN-Lg})}t5G5)t%Zpm-1jO|qq}ByMP%UKCsqLGj>G zPy`W550&Df2QN~qsQ4d<#~uVhP!HZb_~u8tGub4R9z^jQn0cG|zHi^-WwN`oAtEu+ zjK+xaMC7AO4EA8?&}3ZpOzSfJMgzSfP1!V zewX{cj`GM3umk_41M>P1qDw3oOf;%X2L`nT0D72K4ab2z0EPw&1`~~lpb1?H)TP2~ zF@!G1d}#6t1{00CoP^nY2*WJQ4n-(-oIh0QBnldNWCz%RtOIiI?$L3Yrde8W_V3I4 z9@?>bTr5^-O%dj7HflAyTg_`k`l*BATMItL5<`Sgfe66pQ;qoBKYU1jBjMhQ89FC4JtA@wwCkj7w+{xc_%&Tke4dzmFMUh9>uA! zJ|g%s-?l#dv$yP+t>evzfAZ>@(y>)rk5m3mahLadi#LWR^NtVV#FF7vF`M`E?dx|} z4w-cs!AsQIy!XEhzQ`SP;|{M->vDjup;oSg%T!lKO{(FOqhojD(NE~;nrrJC{WY+B zX-M(*$WQ&*PQI;s@AFsEuN=OkIjTBtd8_Brh^^^Pn3P}Ul_pbXJC%*vd=vzd&!YwD zO53^^JQmq8oAdCu2b=4y>xQYdSv$oY-r)cb2CDJ>U;Dr#JFrs+a=I`t_y4E6zyIIq z>A8*_U Date: Fri, 15 Aug 2025 11:48:55 -0400 Subject: [PATCH 10/14] add GetCollectionLinks to index.ts --- src/collections/domain/useCases/GetCollectionLinks.ts | 2 +- src/collections/domain/useCases/LinkCollection.ts | 2 +- src/collections/index.ts | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/collections/domain/useCases/GetCollectionLinks.ts b/src/collections/domain/useCases/GetCollectionLinks.ts index 3028e779..fa0b6c92 100644 --- a/src/collections/domain/useCases/GetCollectionLinks.ts +++ b/src/collections/domain/useCases/GetCollectionLinks.ts @@ -2,7 +2,7 @@ import { UseCase } from '../../../core/domain/useCases/UseCase' import { ICollectionsRepository } from '../repositories/ICollectionsRepository' import { CollectionLinks } from '../models/CollectionLinks' -export class GetCollectionItems implements UseCase { +export class GetCollectionLinks implements UseCase { private collectionsRepository: ICollectionsRepository constructor(collectionsRepository: ICollectionsRepository) { diff --git a/src/collections/domain/useCases/LinkCollection.ts b/src/collections/domain/useCases/LinkCollection.ts index 4e5c5ad3..4ea0b2b4 100644 --- a/src/collections/domain/useCases/LinkCollection.ts +++ b/src/collections/domain/useCases/LinkCollection.ts @@ -9,7 +9,7 @@ export class LinkCollection implements UseCase { } /** - * Deletes the Dataverse collection whose database ID or alias is given: + * Creates a link between two collections. The linked collection will be linked to the linking collection.: * * @param {number| string} [linkedCollectionIdOrAlias] - The collection to be linked. Can be either a string (collection alias), or a number (collection id) * @param { number | string} [linkingCollectionIdOrAlias] - The collection that will be linking to the linked collection. Can be either a string (collection alias), or a number (collection id) diff --git a/src/collections/index.ts b/src/collections/index.ts index 4589c5d3..05e49954 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -14,6 +14,7 @@ import { GetMyDataCollectionItems } from './domain/useCases/GetMyDataCollectionI import { DeleteCollectionFeaturedItem } from './domain/useCases/DeleteCollectionFeaturedItem' import { LinkCollection } from './domain/useCases/LinkCollection' import { UnlinkCollection } from './domain/useCases/UnlinkCollection' +import { GetCollectionLinks } from './domain/useCases/GetCollectionLinks' const collectionsRepository = new CollectionsRepository() @@ -32,6 +33,7 @@ const deleteCollection = new DeleteCollection(collectionsRepository) const deleteCollectionFeaturedItem = new DeleteCollectionFeaturedItem(collectionsRepository) const linkCollection = new LinkCollection(collectionsRepository) const unlinkCollection = new UnlinkCollection(collectionsRepository) +const getCollectionLinks = new GetCollectionLinks(collectionsRepository) export { getCollection, @@ -48,7 +50,8 @@ export { deleteCollection, deleteCollectionFeaturedItem, linkCollection, - unlinkCollection + unlinkCollection, + getCollectionLinks } export { Collection, CollectionInputLevel } from './domain/models/Collection' export { CollectionFacet } from './domain/models/CollectionFacet' From 8d47d048444c10209081834cfb073f3bb4b057aa Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 15 Aug 2025 12:25:59 -0400 Subject: [PATCH 11/14] fix string for API url, update unit UnlinkCollection test --- .../repositories/CollectionsRepository.ts | 4 +- .../collections/UnlinkCollection.test.ts | 38 ++++++++----------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index 135ffecc..18e3c474 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -453,7 +453,7 @@ export class CollectionsRepository extends ApiRepository implements ICollections linkingCollectionIdOrAlias: number | string ): Promise { return this.doPut( - `/dataverses/${linkedCollectionIdOrAlias}` + `/link/${linkingCollectionIdOrAlias}`, + `/dataverses/${linkedCollectionIdOrAlias}/link/${linkingCollectionIdOrAlias}`, {} // No data is needed for this operation ) .then(() => undefined) @@ -466,7 +466,7 @@ export class CollectionsRepository extends ApiRepository implements ICollections linkingCollectionIdOrAlias: number | string ): Promise { return this.doDelete( - `/dataverses/${linkedCollectionIdOrAlias}` + `/deleteLink/${linkingCollectionIdOrAlias}` + `/dataverses/${linkedCollectionIdOrAlias}/deleteLink/${linkingCollectionIdOrAlias}` ) .then(() => undefined) .catch((error) => { diff --git a/test/functional/collections/UnlinkCollection.test.ts b/test/functional/collections/UnlinkCollection.test.ts index e8d3a0a8..c1c1b4e3 100644 --- a/test/functional/collections/UnlinkCollection.test.ts +++ b/test/functional/collections/UnlinkCollection.test.ts @@ -2,7 +2,6 @@ import { ApiConfig, WriteError, createCollection, - getCollection, linkCollection, deleteCollection, getCollectionItems, @@ -16,43 +15,37 @@ describe('execute', () => { const firstCollectionAlias = 'unlinkCollection-functional-test-first' const secondCollectionAlias = 'unlinkCollection-functional-test-second' + let firstCollectionId: number + let secondCollectionId: number beforeEach(async () => { ApiConfig.init( TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, process.env.TEST_API_KEY ) - const firstCollection = createCollectionDTO(firstCollectionAlias) - const secondCollection = createCollectionDTO(secondCollectionAlias) - await createCollection.execute(firstCollection) - await createCollection.execute(secondCollection) - await linkCollection.execute(secondCollection.alias, firstCollection.alias) + const firstCollectionDTO = createCollectionDTO(firstCollectionAlias) + const secondCollectionDTO = createCollectionDTO(secondCollectionAlias) + firstCollectionId = await createCollection.execute(firstCollectionDTO) + secondCollectionId = await createCollection.execute(secondCollectionDTO) + await linkCollection.execute(secondCollectionAlias, firstCollectionAlias) + // Give enough time to Solr for indexing + await new Promise((resolve) => setTimeout(resolve, 5000)) }) afterEach(async () => { await Promise.all([ - getCollection - .execute(firstCollectionAlias) - .then((collection) => - collection && collection.id ? deleteCollection.execute(collection.id) : null - ), - getCollection - .execute(secondCollectionAlias) - .then((collection) => - collection && collection.id ? deleteCollection.execute(collection.id) : null - ) + deleteCollection.execute(firstCollectionId), + deleteCollection.execute(secondCollectionId) ]) }) test('should successfully unlink two collections', async () => { - const firstCollection = await getCollection.execute(firstCollectionAlias) - const secondCollection = await getCollection.execute(secondCollectionAlias) - // Give enough time to Solr for indexing - await new Promise((resolve) => setTimeout(resolve, 5000)) + // Verify that the collections are linked const collectionItemSubset = await getCollectionItems.execute(firstCollectionAlias) expect(collectionItemSubset.items.length).toBe(1) - await unlinkCollection.execute(secondCollection.alias, firstCollection.alias) + await unlinkCollection.execute(secondCollectionAlias, firstCollectionAlias) + // Wait for the unlinking to be processed by Solr await new Promise((resolve) => setTimeout(resolve, 5000)) const collectionItemSubset2 = await getCollectionItems.execute(firstCollectionAlias) expect(collectionItemSubset2.items.length).toBe(0) @@ -60,12 +53,11 @@ describe('execute', () => { test('should throw an error when linking a non-existent collection', async () => { const invalidCollectionId = 99999 - const firstCollection = await getCollection.execute(firstCollectionAlias) expect.assertions(2) let writeError: WriteError | undefined = undefined try { - await unlinkCollection.execute(invalidCollectionId, firstCollection.id) + await unlinkCollection.execute(invalidCollectionId, firstCollectionId) throw new Error('Use case should throw an error') } catch (error) { writeError = error as WriteError From b9d0422d28f318dde5cc28ce9430b74467a264ce Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 15 Aug 2025 12:33:06 -0400 Subject: [PATCH 12/14] Update LinkCollection.test.ts --- .../collections/LinkCollection.test.ts | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/test/functional/collections/LinkCollection.test.ts b/test/functional/collections/LinkCollection.test.ts index dd89d84d..eff7550d 100644 --- a/test/functional/collections/LinkCollection.test.ts +++ b/test/functional/collections/LinkCollection.test.ts @@ -14,7 +14,8 @@ import { createCollectionDTO } from '../../testHelpers/collections/collectionHel describe('execute', () => { const firstCollectionAlias = 'linkCollection-functional-test-first' const secondCollectionAlias = 'linkCollection-functional-test-second' - + let firstCollectionId: number + let secondCollectionId: number beforeEach(async () => { ApiConfig.init( TestConstants.TEST_API_URL, @@ -23,34 +24,25 @@ describe('execute', () => { ) const firstCollection = createCollectionDTO(firstCollectionAlias) const secondCollection = createCollectionDTO(secondCollectionAlias) - await createCollection.execute(firstCollection) - await createCollection.execute(secondCollection) + firstCollectionId = await createCollection.execute(firstCollection) + secondCollectionId = await createCollection.execute(secondCollection) }) afterEach(async () => { await Promise.all([ - getCollection - .execute(firstCollectionAlias) - .then((collection) => - collection && collection.id ? deleteCollection.execute(collection.id) : null - ), - getCollection - .execute(secondCollectionAlias) - .then((collection) => - collection && collection.id ? deleteCollection.execute(collection.id) : null - ) + deleteCollection.execute(firstCollectionId), + deleteCollection.execute(secondCollectionId) ]) }) test('should successfully link two collections', async () => { - const firstCollection = await getCollection.execute(firstCollectionAlias) - const secondCollection = await getCollection.execute(secondCollectionAlias) expect.assertions(1) try { - await linkCollection.execute(secondCollection.alias, firstCollection.alias) + await linkCollection.execute(secondCollectionAlias, firstCollectionAlias) } catch (error) { throw new Error('Collections should be linked successfully') } finally { + // Wait for the linking to be processed by Solr await new Promise((resolve) => setTimeout(resolve, 5000)) const collectionItemSubset = await getCollectionItems.execute(firstCollectionAlias) From 14e796898acec6fcf3298dd58b9579000d46ef6b Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 15 Aug 2025 12:55:27 -0400 Subject: [PATCH 13/14] fix test description --- test/functional/collections/UnlinkCollection.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/collections/UnlinkCollection.test.ts b/test/functional/collections/UnlinkCollection.test.ts index c1c1b4e3..0b20b455 100644 --- a/test/functional/collections/UnlinkCollection.test.ts +++ b/test/functional/collections/UnlinkCollection.test.ts @@ -51,7 +51,7 @@ describe('execute', () => { expect(collectionItemSubset2.items.length).toBe(0) }) - test('should throw an error when linking a non-existent collection', async () => { + test('should throw an error when unlinking a non-existent collection', async () => { const invalidCollectionId = 99999 expect.assertions(2) From 396c348620d7083fad35ddd225e62b3e14edd225 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Fri, 15 Aug 2025 13:36:10 -0400 Subject: [PATCH 14/14] fix integrations --- test/testHelpers/roles/roleHelper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/testHelpers/roles/roleHelper.ts b/test/testHelpers/roles/roleHelper.ts index be63c0f1..d792b377 100644 --- a/test/testHelpers/roles/roleHelper.ts +++ b/test/testHelpers/roles/roleHelper.ts @@ -42,7 +42,6 @@ export const createSuperAdminRoleArray = (): Role[] => { 'ManageDatasetPermissions', 'ManageFilePermissions', 'PublishDataverse', - 'LinkDataverse', 'PublishDataset', 'LinkDataverse', 'LinkDataset',