diff --git a/src/collections/index.ts b/src/collections/index.ts index b148b55d..7e149b53 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -5,7 +5,7 @@ import { GetCollectionUserPermissions } from './domain/useCases/GetCollectionUse import { GetCollectionItems } from './domain/useCases/GetCollectionItems' import { PublishCollection } from './domain/useCases/PublishCollection' import { UpdateCollection } from './domain/useCases/UpdateCollection' - +import { GetCollectionFeaturedItems } from './domain/useCases/GetCollectionFeaturedItems' import { CollectionsRepository } from './infra/repositories/CollectionsRepository' const collectionsRepository = new CollectionsRepository() @@ -17,6 +17,7 @@ const getCollectionUserPermissions = new GetCollectionUserPermissions(collection const getCollectionItems = new GetCollectionItems(collectionsRepository) const publishCollection = new PublishCollection(collectionsRepository) const updateCollection = new UpdateCollection(collectionsRepository) +const getCollectionFeaturedItems = new GetCollectionFeaturedItems(collectionsRepository) export { getCollection, @@ -25,7 +26,8 @@ export { getCollectionUserPermissions, getCollectionItems, publishCollection, - updateCollection + updateCollection, + getCollectionFeaturedItems } export { Collection, CollectionInputLevel } from './domain/models/Collection' export { CollectionFacet } from './domain/models/CollectionFacet' @@ -34,3 +36,4 @@ export { CollectionDTO, CollectionInputLevelDTO } from './domain/dtos/Collection export { CollectionPreview } from './domain/models/CollectionPreview' export { CollectionItemType } from './domain/models/CollectionItemType' export { CollectionSearchCriteria } from './domain/models/CollectionSearchCriteria' +export { CollectionFeaturedItem } from './domain/models/CollectionFeaturedItem' diff --git a/test/environment/.env b/test/environment/.env index 80e9a14e..bf08e36f 100644 --- a/test/environment/.env +++ b/test/environment/.env @@ -1,6 +1,6 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse SOLR_VERSION=9.3.0 -DATAVERSE_IMAGE_REGISTRY=docker.io -DATAVERSE_IMAGE_TAG=unstable +DATAVERSE_IMAGE_REGISTRY=ghcr.io +DATAVERSE_IMAGE_TAG=10943-featured-items DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts index e763e105..c67b75a3 100644 --- a/test/integration/collections/CollectionsRepository.test.ts +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -28,6 +28,11 @@ import { OrderType, SortType } from '../../../src/collections/domain/models/CollectionSearchCriteria' +import { ROOT_COLLECTION_ID } from '../../../src/collections/domain/models/Collection' +import { + createCollectionFeaturedItemViaApi, + deleteCollectionFeaturedItemViaApi +} from '../../testHelpers/collections/collectionFeaturedItemsHelper' describe('CollectionsRepository', () => { const testCollectionAlias = 'collectionsRepositoryTestCollection' @@ -293,7 +298,7 @@ describe('CollectionsRepository', () => { const expectedFileMd5 = '68b22040025784da775f55cfcb6dee2e' const expectedDatasetCitationFragment = - 'Admin, Dataverse; Owner, Dataverse, 2024, "Dataset created using the createDataset use case' + 'Admin, Dataverse; Owner, Dataverse, 2025, "Dataset created using the createDataset use case' const expectedDatasetDescription = 'Dataset created using the createDataset use case' const expectedFileName = 'test-file-1.txt' const expectedCollectionsName = 'Scientific Research' @@ -720,4 +725,64 @@ describe('CollectionsRepository', () => { ).rejects.toThrow(expectedError) }) }) + + describe('getCollectionFeaturedItems', () => { + let tetFeaturedItemId: number + + beforeAll(async () => { + try { + const featuredItemCreated = await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content

', + displayOrder: 1, + withFile: true, + fileName: 'featured-item-test-image.png' + }) + + tetFeaturedItemId = featuredItemCreated.id + } catch (error) { + throw new Error(`Error while creating collection featured item in ${testCollectionAlias}`) + } + }) + + afterAll(async () => { + try { + await deleteCollectionFeaturedItemViaApi(tetFeaturedItemId) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while deleting test dataset with id ${tetFeaturedItemId}` + ) + } + }) + + test('should return empty featured items array given a valid collection alias when collection has no featured items', async () => { + const featuredItemsResponse = await sut.getCollectionFeaturedItems(ROOT_COLLECTION_ID) + + expect(featuredItemsResponse).toStrictEqual([]) + }) + + test('should return featured items array given a valid collection alias when collection has featured items', async () => { + const featuredItemsResponse = await sut.getCollectionFeaturedItems(testCollectionAlias) + console.log({ featuredItemsResponse }) + + expect(featuredItemsResponse.length).toBe(1) + expect(featuredItemsResponse[0].id).toBe(tetFeaturedItemId) + expect(featuredItemsResponse[0].displayOrder).toBe(1) + expect(featuredItemsResponse[0].content).toBe('

Test content

') + expect(featuredItemsResponse[0].imageFileUrl).toBe( + 'http://localhost:8080/api/access/dataverseFeatureItemImage/1' + ) + expect(featuredItemsResponse[0].imageFileName).toBe('featured-item-test-image.png') + }) + + test('should return error when collection does not exist', async () => { + const invalidCollectionAlias = 'invalid-collection-alias' + const expectedError = new ReadError( + `[404] Can't find dataverse with identifier='${invalidCollectionAlias}'` + ) + + await expect(sut.getCollectionFeaturedItems(invalidCollectionAlias)).rejects.toThrow( + expectedError + ) + }) + }) }) diff --git a/test/testHelpers/collections/collectionFeaturedItemsHelper.ts b/test/testHelpers/collections/collectionFeaturedItemsHelper.ts new file mode 100644 index 00000000..1a20ce77 --- /dev/null +++ b/test/testHelpers/collections/collectionFeaturedItemsHelper.ts @@ -0,0 +1,79 @@ +import axios from 'axios' +import { File, Blob } from '@web-std/file' +import { CollectionFeaturedItem } from '../../../src/collections/domain/models/CollectionFeaturedItem' +import { ROOT_COLLECTION_ID } from '../../../src/collections/domain/models/Collection' +import { TestConstants } from '../TestConstants' + +interface CreateCollectionFeaturedItemData { + content: string + displayOrder?: number + withFile?: boolean + fileName?: string +} + +export async function createCollectionFeaturedItemViaApi( + collectionAlias: string, + { + content, + displayOrder = 1, + withFile = false, + fileName = 'test-image.png' + }: CreateCollectionFeaturedItemData +): Promise { + try { + if (collectionAlias == undefined) { + collectionAlias = ROOT_COLLECTION_ID + } + + const formData = new FormData() + formData.append('content', content) + formData.append('displayOrder', displayOrder.toString()) + + if (withFile) { + const file = createImageFile(fileName) + + formData.append('file', file) + } + + return await axios + .post(`${TestConstants.TEST_API_URL}/dataverses/${collectionAlias}/featuredItem`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + 'X-Dataverse-Key': process.env.TEST_API_KEY + } + }) + .then((response) => response.data.data) + } catch (error) { + console.log(error) + throw new Error(`Error while creating collection featured item in ${collectionAlias}`) + } +} + +export async function deleteCollectionFeaturedItemViaApi(featuredItemId: number): Promise { + try { + return await axios.delete( + `${TestConstants.TEST_API_URL}/dataverseFeaturedItems/${featuredItemId}`, + { + headers: { 'Content-Type': 'application/json', 'X-Dataverse-Key': process.env.TEST_API_KEY } + } + ) + } catch (error) { + throw new Error(`Error while deleting collection featured item with id ${featuredItemId}`) + } +} + +export function createImageFile(fileName = 'test-image.png'): File { + // Binary data for a 1x1 black pixel PNG image + const imageData = Uint8Array.from([ + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, + 0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0xe2, 0x21, 0xbc, 0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, + 0x42, 0x60, 0x82 + ]) + + const blob = new Blob([imageData], { type: 'image/png' }) + const imageFile = new File([blob], fileName, { type: 'image/png' }) + + return imageFile +}