diff --git a/docs/useCases.md b/docs/useCases.md index 6faa4ca7..26317d1c 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -14,10 +14,13 @@ The different use cases currently available in the package are classified below, - [Get Collection Facets](#get-collection-facets) - [Get User Permissions on a Collection](#get-user-permissions-on-a-collection) - [List All Collection Items](#list-all-collection-items) + - [Get Collection Featured Items](#get-collection-featured-items) - [Collections write use cases](#collections-write-use-cases) - [Create a Collection](#create-a-collection) - [Update a Collection](#update-a-collection) - [Publish a Collection](#publish-a-collection) + - [Update Collection Featured Items](#update-collection-featured-items) + - [Delete Collection Featured Items](#delete-collection-featured-items) - [Datasets](#Datasets) - [Datasets read use cases](#datasets-read-use-cases) - [Get a Dataset](#get-a-dataset) @@ -202,6 +205,33 @@ This use case supports the following optional parameters depending on the search - **offset**: (number) Offset for pagination. - **collectionSearchCriteria**: ([CollectionSearchCriteria](../src/collections/domain/models/CollectionSearchCriteria.ts)) Supports filtering the collection items by different properties. +#### Get Collection Featured Items + +Returns a [CollectionFeaturedItem](../src/collections/domain/models/CollectionFeaturedItem.ts) array containing the featured items of the requested collection, given the collection identifier or alias. + +##### Example call: + +```typescript +import { getCollectionFeaturedItems } from '@iqss/dataverse-client-javascript' + +const collectionIdOrAlias = 12345 + +getCollectionFeaturedItems + .execute(collectionId) + .then((featuredItems: CollectionFeaturedItem[]) => { + /* ... */ + }) + .catch((error: Error) => { + /* ... */ + }) +``` + +_See [use case](../src/collections/domain/useCases/GetCollectionFeaturedItems.ts)_ definition. + +The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId). + +If no collection identifier is specified, the default collection identifier; `:root` will be used. If you want to search for a different collection, you must add the collection identifier as a parameter in the use case call. + ### Collections Write Use Cases #### Create a Collection @@ -285,6 +315,54 @@ The `collectionIdOrAlias` is a generic collection identifier, which can be eithe _See [use case](../src/collections/domain/useCases/PublishCollection.ts)_ definition. +#### Update Collection Featured Items + +Updates all featured items, given a collection identifier and a CollectionFeaturedItemsDTO. + +##### Example call: + +```typescript +import { updateCollectionFeaturedItems } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const collectionIdOrAlias = 12345 + +updateCollectionFeaturedItems + .execute(collectionIdOrAlias) + .then((collectionFeaturedItems: CollectionFeaturedItem[]) => { + /* ... */ + }) + +/* ... */ +``` + +The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId). + +_See [use case](../src/collections/domain/useCases/UpdateCollectionFeaturedItems.ts)_ definition. + +#### Delete Collection Featured Items + +Deletes all featured items from a collection, given a collection identifier. + +##### Example call: + +```typescript +import { deleteCollectionFeaturedItems } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const collectionIdOrAlias = 12345 + +deleteCollectionFeaturedItems.execute(collectionIdOrAlias) + +/* ... */ +``` + +The `collectionIdOrAlias` is a generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId). + +_See [use case](../src/collections/domain/useCases/DeleteCollectionFeaturedItems.ts)_ definition. + ## Datasets ### Datasets Read Use Cases diff --git a/src/collections/domain/dtos/CollectionFeaturedItemsDTO.ts b/src/collections/domain/dtos/CollectionFeaturedItemsDTO.ts new file mode 100644 index 00000000..7a60052d --- /dev/null +++ b/src/collections/domain/dtos/CollectionFeaturedItemsDTO.ts @@ -0,0 +1,9 @@ +export type CollectionFeaturedItemsDTO = CollectionFeaturedItemDTO[] + +export interface CollectionFeaturedItemDTO { + id?: number + content: string + displayOrder: number + file?: File + keepFile: boolean +} diff --git a/src/collections/domain/models/CollectionFeaturedItem.ts b/src/collections/domain/models/CollectionFeaturedItem.ts new file mode 100644 index 00000000..88a3777d --- /dev/null +++ b/src/collections/domain/models/CollectionFeaturedItem.ts @@ -0,0 +1,7 @@ +export interface CollectionFeaturedItem { + id: number + content: string + imageFileName?: string + imageFileUrl?: string + displayOrder: number +} diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index c024779a..85f6c42f 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -1,6 +1,8 @@ import { CollectionDTO } from '../dtos/CollectionDTO' +import { CollectionFeaturedItemsDTO } from '../dtos/CollectionFeaturedItemsDTO' import { Collection } from '../models/Collection' import { CollectionFacet } from '../models/CollectionFacet' +import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem' import { CollectionItemSubset } from '../models/CollectionItemSubset' import { CollectionSearchCriteria } from '../models/CollectionSearchCriteria' import { CollectionUserPermissions } from '../models/CollectionUserPermissions' @@ -26,4 +28,12 @@ export interface ICollectionsRepository { collectionIdOrAlias: number | string, updatedCollection: CollectionDTO ): Promise + getCollectionFeaturedItems( + collectionIdOrAlias: number | string + ): Promise + updateCollectionFeaturedItems( + collectionIdOrAlias: number | string, + featuredItemDTOs: CollectionFeaturedItemsDTO + ): Promise + deleteCollectionFeaturedItems(collectionIdOrAlias: number | string): Promise } diff --git a/src/collections/domain/useCases/DeleteCollectionFeaturedItems.ts b/src/collections/domain/useCases/DeleteCollectionFeaturedItems.ts new file mode 100644 index 00000000..d8c84ce6 --- /dev/null +++ b/src/collections/domain/useCases/DeleteCollectionFeaturedItems.ts @@ -0,0 +1,23 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ROOT_COLLECTION_ID } from '../models/Collection' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' + +export class DeleteCollectionFeaturedItems implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Deletes all featured items from a collection, given a collection identifier. + * + * @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId) + * If this parameter is not set, the default value is: ':root' + * @returns {Promise} - This method does not return anything upon successful completion. + * @throws {WriteError} - If there are errors while writing data. + */ + async execute(collectionIdOrAlias: number | string = ROOT_COLLECTION_ID): Promise { + return await this.collectionsRepository.deleteCollectionFeaturedItems(collectionIdOrAlias) + } +} diff --git a/src/collections/domain/useCases/GetCollectionFeaturedItems.ts b/src/collections/domain/useCases/GetCollectionFeaturedItems.ts new file mode 100644 index 00000000..7a2a0adc --- /dev/null +++ b/src/collections/domain/useCases/GetCollectionFeaturedItems.ts @@ -0,0 +1,25 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { ROOT_COLLECTION_ID } from '../models/Collection' +import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem' + +export class GetCollectionFeaturedItems implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Returns a CollectionFeaturedItem array containing the featured items of the requested collection, given the collection identifier or alias. + * + * @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId) + * If this parameter is not set, the default value is: ':root' + * @returns {Promise} + */ + async execute( + collectionIdOrAlias: number | string = ROOT_COLLECTION_ID + ): Promise { + return await this.collectionsRepository.getCollectionFeaturedItems(collectionIdOrAlias) + } +} diff --git a/src/collections/domain/useCases/UpdateCollectionFeaturedItems.ts b/src/collections/domain/useCases/UpdateCollectionFeaturedItems.ts new file mode 100644 index 00000000..10d22b5a --- /dev/null +++ b/src/collections/domain/useCases/UpdateCollectionFeaturedItems.ts @@ -0,0 +1,32 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { CollectionFeaturedItemsDTO } from '../dtos/CollectionFeaturedItemsDTO' +import { ROOT_COLLECTION_ID } from '../models/Collection' +import { CollectionFeaturedItem } from '../models/CollectionFeaturedItem' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' + +export class UpdateCollectionFeaturedItems implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Updates all featured items, given a collection identifier and a CollectionFeaturedItemsDTO. + * + * @param {number | string} [collectionIdOrAlias = ':root'] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId) + * If this parameter is not set, the default value is: ':root' + * @param {CollectionFeaturedItemsDTO} [newCollectionFeaturedItems] - CollectionFeaturedItemsDTO object including the updated collection featured items data. + * @returns {Promise} -This method returns the updated collection featured items upon successful completion. + * @throws {WriteError} - If there are errors while writing data. + */ + async execute( + collectionIdOrAlias: number | string = ROOT_COLLECTION_ID, + featuredItemsDTO: CollectionFeaturedItemsDTO + ): Promise { + return await this.collectionsRepository.updateCollectionFeaturedItems( + collectionIdOrAlias, + featuredItemsDTO + ) + } +} diff --git a/src/collections/index.ts b/src/collections/index.ts index b148b55d..e3a50dce 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -5,8 +5,10 @@ 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' +import { UpdateCollectionFeaturedItems } from './domain/useCases/UpdateCollectionFeaturedItems' +import { DeleteCollectionFeaturedItems } from './domain/useCases/DeleteCollectionFeaturedItems' const collectionsRepository = new CollectionsRepository() @@ -17,6 +19,9 @@ 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) +const updateCollectionFeaturedItems = new UpdateCollectionFeaturedItems(collectionsRepository) +const deleteCollectionFeaturedItems = new DeleteCollectionFeaturedItems(collectionsRepository) export { getCollection, @@ -25,7 +30,10 @@ export { getCollectionUserPermissions, getCollectionItems, publishCollection, - updateCollection + updateCollection, + getCollectionFeaturedItems, + updateCollectionFeaturedItems, + deleteCollectionFeaturedItems } export { Collection, CollectionInputLevel } from './domain/models/Collection' export { CollectionFacet } from './domain/models/CollectionFacet' @@ -34,3 +42,5 @@ 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' +export { CollectionFeaturedItemsDTO } from './domain/dtos/CollectionFeaturedItemsDTO' diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index 98a335b9..dbfb587c 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -17,6 +17,10 @@ import { SortType } from '../../domain/models/CollectionSearchCriteria' import { CollectionItemType } from '../../domain/models/CollectionItemType' +import { CollectionFeaturedItem } from '../../domain/models/CollectionFeaturedItem' +import { transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems } from './transformers/collectionFeaturedItemsTransformer' +import { CollectionFeaturedItemsDTO } from '../../domain/dtos/CollectionFeaturedItemsDTO' +import { ApiConstants } from '../../../core/infra/repositories/ApiConstants' export interface NewCollectionRequestPayload { alias: string @@ -240,4 +244,67 @@ export class CollectionsRepository extends ApiRepository implements ICollections }) } } + + public async getCollectionFeaturedItems( + collectionIdOrAlias: number | string + ): Promise { + return this.doGet(`/${this.collectionsResourceName}/${collectionIdOrAlias}/featuredItems`, true) + .then((response) => + transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems(response.data.data) + ) + .catch((error) => { + throw error + }) + } + + public async updateCollectionFeaturedItems( + collectionIdOrAlias: number | string, + featuredItemsDTO: CollectionFeaturedItemsDTO + ): Promise { + const featuredItemsFormData = this.toFeaturedItemsFormData(featuredItemsDTO) + + return this.doPut( + `/${this.collectionsResourceName}/${collectionIdOrAlias}/featuredItems`, + featuredItemsFormData, + undefined, + ApiConstants.CONTENT_TYPE_MULTIPART_FORM_DATA + ) + .then((response) => + transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems(response.data.data) + ) + .catch((error) => { + throw error + }) + } + + private toFeaturedItemsFormData(featuredItemsDTO: CollectionFeaturedItemsDTO): FormData { + // This is not really necessary because we are sending displayOrder property anyways, but I wanted to keep the order of the items in the form data + const orderedFeaturedItemsDTO = featuredItemsDTO.sort((a, b) => a.displayOrder - b.displayOrder) + + const formData = new FormData() + + orderedFeaturedItemsDTO.forEach((item) => { + const { id, content, displayOrder, file, keepFile } = item + const fileName = file ? file.name : '' + + formData.append('id', id ? id.toString() : '0') + formData.append('content', content) + formData.append('displayOrder', displayOrder.toString()) + formData.append('keepFile', keepFile.toString()) + formData.append('fileName', fileName) + if (file) { + formData.append('file', file) + } + }) + + return formData + } + + public async deleteCollectionFeaturedItems(collectionIdOrAlias: number | string): Promise { + return this.doDelete(`/${this.collectionsResourceName}/${collectionIdOrAlias}/featuredItems`) + .then(() => undefined) + .catch((error) => { + throw error + }) + } } diff --git a/src/collections/infra/repositories/transformers/CollectionFeaturedItemPayload.ts b/src/collections/infra/repositories/transformers/CollectionFeaturedItemPayload.ts new file mode 100644 index 00000000..34ba0dcf --- /dev/null +++ b/src/collections/infra/repositories/transformers/CollectionFeaturedItemPayload.ts @@ -0,0 +1,7 @@ +export interface CollectionFeaturedItemPayload { + id: number + content: string + imageFileName: string | null + imageFileUrl: string | null + displayOrder: number +} diff --git a/src/collections/infra/repositories/transformers/collectionFeaturedItemsTransformer.ts b/src/collections/infra/repositories/transformers/collectionFeaturedItemsTransformer.ts new file mode 100644 index 00000000..f3bfb135 --- /dev/null +++ b/src/collections/infra/repositories/transformers/collectionFeaturedItemsTransformer.ts @@ -0,0 +1,16 @@ +import { CollectionFeaturedItem } from '../../../domain/models/CollectionFeaturedItem' +import { CollectionFeaturedItemPayload } from './CollectionFeaturedItemPayload' + +export const transformCollectionFeaturedItemsPayloadToCollectionFeaturedItems = ( + collectionFeaturedItemsPayload: CollectionFeaturedItemPayload[] +): CollectionFeaturedItem[] => { + return collectionFeaturedItemsPayload + .map((collectionFeaturedItemPayload) => ({ + id: collectionFeaturedItemPayload.id, + content: collectionFeaturedItemPayload.content, + imageFileUrl: collectionFeaturedItemPayload.imageFileUrl || undefined, + imageFileName: collectionFeaturedItemPayload.imageFileName || undefined, + displayOrder: collectionFeaturedItemPayload.displayOrder + })) + .sort((a, b) => a.displayOrder - b.displayOrder) +} diff --git a/src/core/infra/repositories/ApiRepository.ts b/src/core/infra/repositories/ApiRepository.ts index 66dac213..d9be61f4 100644 --- a/src/core/infra/repositories/ApiRepository.ts +++ b/src/core/infra/repositories/ApiRepository.ts @@ -30,9 +30,10 @@ export abstract class ApiRepository { public async doPut( apiEndpoint: string, data: string | object, - queryParams: object = {} + queryParams: object = {}, + contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON ): Promise { - return await this.doRequest('put', apiEndpoint, data, queryParams) + return await this.doRequest('put', apiEndpoint, data, queryParams, contentType) } public async doDelete(apiEndpoint: string, queryParams: object = {}): Promise { diff --git a/src/core/infra/repositories/apiConfigBuilders.ts b/src/core/infra/repositories/apiConfigBuilders.ts index 6c7ed764..3952cd49 100644 --- a/src/core/infra/repositories/apiConfigBuilders.ts +++ b/src/core/infra/repositories/apiConfigBuilders.ts @@ -16,6 +16,11 @@ export const buildRequestConfig = ( ...(abortSignal && { signal: abortSignal }) } + // When using multipart/form-data for axios to work properly its better to avoid setting the content-type and let the browser manage it + if (contentType === ApiConstants.CONTENT_TYPE_MULTIPART_FORM_DATA) { + requestConfig.headers['Content-Type'] = undefined + } + if (!authRequired) { return requestConfig } diff --git a/test/functional/collections/DeleteCollectionFeaturedItems.test.ts b/test/functional/collections/DeleteCollectionFeaturedItems.test.ts new file mode 100644 index 00000000..3f66a0e8 --- /dev/null +++ b/test/functional/collections/DeleteCollectionFeaturedItems.test.ts @@ -0,0 +1,82 @@ +import { ApiConfig, deleteCollectionFeaturedItems, WriteError } from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { + createCollectionViaApi, + deleteCollectionViaApi +} from '../../testHelpers/collections/collectionHelper' +import { + createCollectionFeaturedItemViaApi, + deleteCollectionFeaturedItemsViaApi +} from '../../testHelpers/collections/collectionFeaturedItemsHelper' + +describe('execute', () => { + const testCollectionAlias = 'deleteCollectionFeaturedItemsTest' + + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + beforeAll(async () => { + try { + await createCollectionViaApi(testCollectionAlias) + await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content

', + displayOrder: 1, + withFile: true, + fileName: 'featured-item-test-image.png' + }) + await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content 2

', + displayOrder: 2, + withFile: false + }) + await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content 3

', + displayOrder: 3, + withFile: false + }) + } catch (error) { + throw new Error( + `Tests beforeAll(): Error while creating test collection: ${testCollectionAlias}` + ) + } + }) + + afterAll(async () => { + try { + await deleteCollectionFeaturedItemsViaApi(testCollectionAlias) + await deleteCollectionViaApi(testCollectionAlias) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while deleting test collection: ${testCollectionAlias}` + ) + } + }) + + test('should succesfully delete all featured items from a collection', async () => { + const actual = await deleteCollectionFeaturedItems.execute(testCollectionAlias) + + expect(actual).toBeUndefined() + }) + + test('should throw an error when collection does not exist', async () => { + const invalidCollectionAlias = 'invalid-collection-alias' + let writeError: WriteError | undefined + + try { + await deleteCollectionFeaturedItems.execute(invalidCollectionAlias) + } catch (error) { + writeError = error as WriteError + } finally { + expect(writeError).toBeInstanceOf(WriteError) + expect((writeError as WriteError).message).toEqual( + `There was an error when writing the resource. Reason was: [404] Can't find dataverse with identifier='${invalidCollectionAlias}'` + ) + } + }) +}) diff --git a/test/functional/collections/GetCollectionFeaturedItems.test.ts b/test/functional/collections/GetCollectionFeaturedItems.test.ts new file mode 100644 index 00000000..a2f75a70 --- /dev/null +++ b/test/functional/collections/GetCollectionFeaturedItems.test.ts @@ -0,0 +1,106 @@ +import { ApiConfig, ReadError, getCollectionFeaturedItems } from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { + createCollectionFeaturedItemViaApi, + deleteCollectionFeaturedItemViaApi +} from '../../testHelpers/collections/collectionFeaturedItemsHelper' +import { + createCollectionViaApi, + deleteCollectionViaApi +} from '../../testHelpers/collections/collectionHelper' +import { ROOT_COLLECTION_ID } from '../../../src/collections/domain/models/Collection' + +describe('execute', () => { + const testCollectionAlias = 'getCollectionsFeaturedItemsTest' + let testFeaturedItemId: number + + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + beforeAll(async () => { + try { + await createCollectionViaApi(testCollectionAlias) + + const featuredItemCreated = await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content

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

Test content

') + expect(featuredItemsResponse[0].imageFileUrl).toBe( + `http://localhost:8080/api/access/dataverseFeaturedItemImage/${featuredItemsResponse[0].id}` + ) + expect(featuredItemsResponse[0].imageFileName).toBe('featured-item-test-image.png') + }) + + it('should return imageFileUrl and imageFileName as undefined when featured item does not have an image', async () => { + const featuredItemCreated = await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content

', + displayOrder: 2 + }) + + const featuredItemsResponse = await getCollectionFeaturedItems.execute(testCollectionAlias) + + expect(featuredItemsResponse.length).toBe(2) + expect(featuredItemsResponse[1].id).toBe(featuredItemCreated.id) + expect(featuredItemsResponse[1].displayOrder).toBe(2) + expect(featuredItemsResponse[1].content).toBe('

Test content

') + expect(featuredItemsResponse[1].imageFileUrl).toBeUndefined() + expect(featuredItemsResponse[1].imageFileName).toBeUndefined() + + await deleteCollectionFeaturedItemViaApi(featuredItemCreated.id) + }) + + test('should return empty featured items array given a valid collection alias that has no featured items', async () => { + const featuredItemsResponse = await getCollectionFeaturedItems.execute(ROOT_COLLECTION_ID) + + expect(featuredItemsResponse).toStrictEqual([]) + }) + + test('should throw an error when collection does not exist', async () => { + const invalidCollectionAlias = 'invalid-collection-alias' + let readError: ReadError | undefined + + try { + await getCollectionFeaturedItems.execute(invalidCollectionAlias) + } catch (error) { + readError = error as ReadError + } finally { + expect(readError).toBeInstanceOf(ReadError) + expect((readError as ReadError).message).toEqual( + `There was an error when reading the resource. Reason was: [404] Can't find dataverse with identifier='${invalidCollectionAlias}'` + ) + } + }) +}) diff --git a/test/functional/collections/UpdateCollectionFeaturedItems.test.ts b/test/functional/collections/UpdateCollectionFeaturedItems.test.ts new file mode 100644 index 00000000..2c196c63 --- /dev/null +++ b/test/functional/collections/UpdateCollectionFeaturedItems.test.ts @@ -0,0 +1,295 @@ +import { + ApiConfig, + CollectionFeaturedItemsDTO, + updateCollectionFeaturedItems, + WriteError +} from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { + createCollectionFeaturedItemViaApi, + createImageFile, + deleteCollectionFeaturedItemViaApi +} from '../../testHelpers/collections/collectionFeaturedItemsHelper' +import { + CONTENT_FIELD_WITH_ALL_TAGS, + createCollectionViaApi, + deleteCollectionViaApi, + EXPECTED_CONTENT_FIELD_WITH_ALL_TAGS +} from '../../testHelpers/collections/collectionHelper' + +describe('execute', () => { + const testCollectionAlias = 'updateCollectionFeaturedItemsTest' + + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + beforeAll(async () => { + try { + await createCollectionViaApi(testCollectionAlias) + } catch (error) { + throw new Error( + `Tests beforeAll(): Error while creating test collection: ${testCollectionAlias}` + ) + } + }) + + afterAll(async () => { + try { + await deleteCollectionViaApi(testCollectionAlias) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while deleting test collection: ${testCollectionAlias}` + ) + } + }) + + it('should successfully update the featured items of a collection', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + content: '

Test content 1

', + displayOrder: 0, + file: undefined, + keepFile: false + }, + { + content: '

Test content 2

', + displayOrder: 1, + file: undefined, + keepFile: false + }, + { + content: CONTENT_FIELD_WITH_ALL_TAGS, + displayOrder: 2, + file: createImageFile('featured-item-test-image-3.png'), + keepFile: false + } + ] + + const updatedFeaturedItemsResponse = await updateCollectionFeaturedItems.execute( + testCollectionAlias, + newFeaturedItems + ) + + expect(updatedFeaturedItemsResponse.length).toBe(3) + + expect(updatedFeaturedItemsResponse[0].content).toBe(newFeaturedItems[0].content) + expect(updatedFeaturedItemsResponse[0].displayOrder).toBe(newFeaturedItems[0].displayOrder) + expect(updatedFeaturedItemsResponse[0].imageFileUrl).toBeUndefined() + expect(updatedFeaturedItemsResponse[0].imageFileName).toBeUndefined() + + expect(updatedFeaturedItemsResponse[1].content).toBe(newFeaturedItems[1].content) + expect(updatedFeaturedItemsResponse[1].displayOrder).toBe(newFeaturedItems[1].displayOrder) + expect(updatedFeaturedItemsResponse[1].imageFileUrl).toBeUndefined() + expect(updatedFeaturedItemsResponse[1].imageFileName).toBeUndefined() + + expect(updatedFeaturedItemsResponse[2].content).toEqual(EXPECTED_CONTENT_FIELD_WITH_ALL_TAGS) + expect(updatedFeaturedItemsResponse[2].displayOrder).toBe(newFeaturedItems[2].displayOrder) + expect(updatedFeaturedItemsResponse[2].imageFileName).toEqual('featured-item-test-image-3.png') + expect(updatedFeaturedItemsResponse[2].imageFileUrl).toBe( + `http://localhost:8080/api/access/dataverseFeaturedItemImage/${updatedFeaturedItemsResponse[2].id}` + ) + }) + + test('should throw an error when collection does not exist', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + content: '

Test content 1

', + displayOrder: 0, + file: undefined, + keepFile: false + }, + { + content: '

Test content 2

', + displayOrder: 1, + file: undefined, + keepFile: false + }, + { + content: '

Test content 3

', + displayOrder: 2, + file: createImageFile('featured-item-test-image-3.png'), + keepFile: false + } + ] + const invalidCollectionAlias = 'invalid-collection-alias' + let writeError: WriteError | undefined + + try { + await updateCollectionFeaturedItems.execute(invalidCollectionAlias, newFeaturedItems) + } catch (error) { + writeError = error as WriteError + } finally { + expect(writeError).toBeInstanceOf(WriteError) + expect((writeError as WriteError).message).toEqual( + `There was an error when writing the resource. Reason was: [404] Can't find dataverse with identifier='${invalidCollectionAlias}'` + ) + } + }) + + test('should throw an error when featured item content is empty', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + content: '', + displayOrder: 0, + file: undefined, + keepFile: false + } + ] + let writeError: WriteError | undefined + + try { + await updateCollectionFeaturedItems.execute(testCollectionAlias, newFeaturedItems) + } catch (error) { + writeError = error as WriteError + } finally { + expect(writeError).toBeInstanceOf(WriteError) + expect((writeError as WriteError).message).toEqual( + "There was an error when writing the resource. Reason was: [400] Featured item 'content' property should be provided and not empty." + ) + } + }) + + describe('keepFile behaviour', () => { + let testFeaturedItemId: number + + const testFeaturedItemContent = '

Test content

' + const testFeaturedItemFilename = 'featured-item-test-image.png' + + beforeEach(async () => { + try { + const featuredItemCreated = await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: testFeaturedItemContent, + displayOrder: 1, + withFile: true, + fileName: testFeaturedItemFilename + }) + + testFeaturedItemId = featuredItemCreated.id + } catch (error) { + throw new Error(`Error while creating collection featured item in ${testCollectionAlias}`) + } + }) + + afterEach(async () => { + try { + await deleteCollectionFeaturedItemViaApi(testFeaturedItemId) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while deleting featured item with id ${testFeaturedItemId}` + ) + } + }) + + it('should keep existing file for a featured item if file is undefined and keepFile is true', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + id: testFeaturedItemId, + content: '

Test content Updated

', + displayOrder: 0, + file: undefined, + keepFile: true + } + ] + + const updatedFeaturedItemsResponse = await updateCollectionFeaturedItems.execute( + testCollectionAlias, + newFeaturedItems + ) + + expect(updatedFeaturedItemsResponse.length).toBe(1) + + expect(updatedFeaturedItemsResponse[0].content).toBe(newFeaturedItems[0].content) + expect(updatedFeaturedItemsResponse[0].displayOrder).toBe(newFeaturedItems[0].displayOrder) + // Should keep the existing file even if a file was not provided because keepFile is true + expect(updatedFeaturedItemsResponse[0].imageFileName).toEqual(testFeaturedItemFilename) + expect(updatedFeaturedItemsResponse[0].imageFileUrl).toBe( + `http://localhost:8080/api/access/dataverseFeaturedItemImage/${updatedFeaturedItemsResponse[0].id}` + ) + }) + + it('should remove existing file for a featured item if file is undefined and keepFile is false', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + id: testFeaturedItemId, + content: '

Test content Updated

', + displayOrder: 0, + file: undefined, + keepFile: false + } + ] + + const updatedFeaturedItemsResponse = await updateCollectionFeaturedItems.execute( + testCollectionAlias, + newFeaturedItems + ) + + expect(updatedFeaturedItemsResponse.length).toBe(1) + + expect(updatedFeaturedItemsResponse[0].content).toBe(newFeaturedItems[0].content) + expect(updatedFeaturedItemsResponse[0].displayOrder).toBe(newFeaturedItems[0].displayOrder) + expect(updatedFeaturedItemsResponse[0].imageFileUrl).toBeUndefined() + expect(updatedFeaturedItemsResponse[0].imageFileName).toBeUndefined() + }) + + it('should replace existing file for a featured item if a new file is provided and keepFile is false', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + id: testFeaturedItemId, + content: '

Test content Updated

', + displayOrder: 0, + file: createImageFile('featured-item-test-image-updated.png'), + keepFile: false + } + ] + + const updatedFeaturedItemsResponse = await updateCollectionFeaturedItems.execute( + testCollectionAlias, + newFeaturedItems + ) + + expect(updatedFeaturedItemsResponse.length).toBe(1) + + expect(updatedFeaturedItemsResponse[0].content).toBe(newFeaturedItems[0].content) + expect(updatedFeaturedItemsResponse[0].displayOrder).toBe(newFeaturedItems[0].displayOrder) + expect(updatedFeaturedItemsResponse[0].imageFileName).toEqual( + 'featured-item-test-image-updated.png' + ) + expect(updatedFeaturedItemsResponse[0].imageFileUrl).toBe( + `http://localhost:8080/api/access/dataverseFeaturedItemImage/${updatedFeaturedItemsResponse[0].id}` + ) + }) + + it('should not replace existing file for a featured item if a new file is provided but keepFile is true', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + id: testFeaturedItemId, + content: '

Test content Updated

', + displayOrder: 0, + file: createImageFile('featured-item-test-image-updated.png'), + keepFile: true + } + ] + + const updatedFeaturedItemsResponse = await updateCollectionFeaturedItems.execute( + testCollectionAlias, + newFeaturedItems + ) + + expect(updatedFeaturedItemsResponse.length).toBe(1) + + expect(updatedFeaturedItemsResponse[0].content).toBe(newFeaturedItems[0].content) + expect(updatedFeaturedItemsResponse[0].displayOrder).toBe(newFeaturedItems[0].displayOrder) + // Should keep the existing file even if a file was provided because keepFile is true + expect(updatedFeaturedItemsResponse[0].imageFileName).toEqual(testFeaturedItemFilename) + expect(updatedFeaturedItemsResponse[0].imageFileUrl).toBe( + `http://localhost:8080/api/access/dataverseFeaturedItemImage/${updatedFeaturedItemsResponse[0].id}` + ) + }) + }) +}) diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts index 042b5a26..2a7c9c9d 100644 --- a/test/integration/collections/CollectionsRepository.test.ts +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -1,6 +1,7 @@ import { CollectionsRepository } from '../../../src/collections/infra/repositories/CollectionsRepository' import { TestConstants } from '../../testHelpers/TestConstants' import { + CollectionFeaturedItemsDTO, CollectionItemType, CollectionPreview, CollectionSearchCriteria, @@ -32,6 +33,13 @@ import { OrderType, SortType } from '../../../src/collections/domain/models/CollectionSearchCriteria' +import { ROOT_COLLECTION_ID } from '../../../src/collections/domain/models/Collection' +import { + createCollectionFeaturedItemViaApi, + createImageFile, + deleteCollectionFeaturedItemsViaApi, + deleteCollectionFeaturedItemViaApi +} from '../../testHelpers/collections/collectionFeaturedItemsHelper' describe('CollectionsRepository', () => { const testCollectionAlias = 'collectionsRepositoryTestCollection' @@ -875,4 +883,175 @@ describe('CollectionsRepository', () => { ).rejects.toThrow(expectedError) }) }) + + describe('getCollectionFeaturedItems', () => { + let testFeaturedItemId: number + + beforeAll(async () => { + try { + const featuredItemCreated = await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content

', + displayOrder: 1, + withFile: true, + fileName: 'featured-item-test-image.png' + }) + + testFeaturedItemId = featuredItemCreated.id + } catch (error) { + throw new Error(`Error while creating collection featured item in ${testCollectionAlias}`) + } + }) + + afterAll(async () => { + try { + await deleteCollectionFeaturedItemViaApi(testFeaturedItemId) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while deleting featured item with id ${testFeaturedItemId}` + ) + } + }) + + 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) + + expect(featuredItemsResponse.length).toBe(1) + expect(featuredItemsResponse[0].id).toBe(testFeaturedItemId) + expect(featuredItemsResponse[0].displayOrder).toBe(1) + expect(featuredItemsResponse[0].content).toBe('

Test content

') + expect(featuredItemsResponse[0].imageFileUrl).toBe( + `http://localhost:8080/api/access/dataverseFeaturedItemImage/${featuredItemsResponse[0].id}` + ) + 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 + ) + }) + }) + + describe('updateCollectionFeaturedItems', () => { + afterAll(async () => { + try { + await deleteCollectionFeaturedItemsViaApi(testCollectionAlias) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while deleting all featured items from collection: ${testCollectionAlias}` + ) + } + }) + + it('should update collection featured items sending all new items', async () => { + const newFeaturedItems: CollectionFeaturedItemsDTO = [ + { + content: '

Test content 1

', + displayOrder: 0, + file: undefined, + keepFile: false + }, + { + content: '

Test content 2

', + displayOrder: 1, + file: undefined, + keepFile: false + }, + { + content: '

Test content 3

', + displayOrder: 2, + file: createImageFile('featured-item-test-image-3.png'), + keepFile: false + } + ] + + const response = await sut.updateCollectionFeaturedItems( + testCollectionAlias, + newFeaturedItems + ) + + expect(response).toHaveLength(3) + + expect(response[0].content).toEqual(newFeaturedItems[0].content) + expect(response[0].displayOrder).toEqual(newFeaturedItems[0].displayOrder) + expect(response[0].imageFileName).toEqual(undefined) + expect(response[0].imageFileUrl).toEqual(undefined) + + expect(response[1].content).toEqual(newFeaturedItems[1].content) + expect(response[1].displayOrder).toEqual(newFeaturedItems[1].displayOrder) + expect(response[1].imageFileName).toEqual(undefined) + expect(response[1].imageFileUrl).toEqual(undefined) + + expect(response[2].content).toEqual(newFeaturedItems[2].content) + expect(response[2].displayOrder).toEqual(newFeaturedItems[2].displayOrder) + expect(response[2].imageFileName).toEqual('featured-item-test-image-3.png') + expect(response[2].imageFileUrl).toBe( + `http://localhost:8080/api/access/dataverseFeaturedItemImage/${response[2].id}` + ) + }) + }) + + describe('deleteCollectionFeaturedItems', () => { + beforeAll(async () => { + try { + await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content

', + displayOrder: 1, + withFile: true, + fileName: 'featured-item-test-image.png' + }) + await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content 2

', + displayOrder: 2, + withFile: false + }) + await createCollectionFeaturedItemViaApi(testCollectionAlias, { + content: '

Test content 3

', + displayOrder: 3, + withFile: false + }) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while creating test featured items for collection: ${testCollectionAlias}` + ) + } + }) + + afterAll(async () => { + try { + await deleteCollectionFeaturedItemsViaApi(testCollectionAlias) + } catch (error) { + throw new Error( + `Tests afterAll(): Error while deleting test collection featured items: ${testCollectionAlias}` + ) + } + }) + + it('should delete all collection featured items', async () => { + const featuredItemsResponseBeforeDeletion = await sut.getCollectionFeaturedItems( + testCollectionAlias + ) + + expect(featuredItemsResponseBeforeDeletion).toHaveLength(3) + + await sut.deleteCollectionFeaturedItems(testCollectionAlias) + + const featuredItemsResponseAfterDeletion = await sut.getCollectionFeaturedItems( + testCollectionAlias + ) + + expect(featuredItemsResponseAfterDeletion).toStrictEqual([]) + }) + }) }) diff --git a/test/testHelpers/collections/collectionFeaturedItemsHelper.ts b/test/testHelpers/collections/collectionFeaturedItemsHelper.ts new file mode 100644 index 00000000..48032389 --- /dev/null +++ b/test/testHelpers/collections/collectionFeaturedItemsHelper.ts @@ -0,0 +1,134 @@ +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' +import { CollectionFeaturedItemsDTO } from '../../../src' + +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}/featuredItems`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + 'X-Dataverse-Key': process.env.TEST_API_KEY + } + }) + .then((response) => { + return 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.toString()}`, + { + 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 async function deleteCollectionFeaturedItemsViaApi(collectionAlias: string) { + try { + return await axios.delete( + `${TestConstants.TEST_API_URL}/dataverses/${collectionAlias}/featuredItems`, + { + headers: { 'Content-Type': 'application/json', 'X-Dataverse-Key': process.env.TEST_API_KEY } + } + ) + } catch (error) { + console.log(error) + throw new Error(`Error while deleting all featured items from collection: ${collectionAlias}`) + } +} + +export const createCollectionFeaturedItemsModel = (): CollectionFeaturedItem[] => { + return [ + { + id: 1, + content: 'This is a featured item', + displayOrder: 1, + imageFileName: 'test-image.png', + imageFileUrl: 'http://localhost:8080/api/access/dataverseFeaturedItemImage/1' + }, + { + id: 2, + content: 'This is another featured item', + displayOrder: 2, + imageFileName: undefined, + imageFileUrl: undefined + } + ] +} + +export const createCollectionFeaturedItemsDTO = (): CollectionFeaturedItemsDTO => { + return [ + { + id: 1, + content: 'This is a featured item', + displayOrder: 1, + file: createImageFile(), + keepFile: false + }, + { + id: 2, + content: 'This is another featured item', + displayOrder: 2, + file: undefined, + keepFile: false + } + ] +} + +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 +} diff --git a/test/testHelpers/collections/collectionHelper.ts b/test/testHelpers/collections/collectionHelper.ts index f2d2e5ae..6561329c 100644 --- a/test/testHelpers/collections/collectionHelper.ts +++ b/test/testHelpers/collections/collectionHelper.ts @@ -8,6 +8,8 @@ import { NewCollectionRequestPayload } from '../../../src/collections/infra/repo import { CollectionFacetPayload } from '../../../src/collections/infra/repositories/transformers/CollectionFacetPayload' import { CollectionType } from '../../../src/collections/domain/models/CollectionType' +export const ROOT_COLLECTION_ALIAS = 'root' + const COLLECTION_ID = 11111 const COLLECTION_IS_RELEASED = true const COLLECTION_ALIAS_STR = 'secondCollection' @@ -205,4 +207,25 @@ export const createCollectionFacetRequestPayload = (): CollectionFacetPayload => displayName: 'testDisplayName' } } -export const ROOT_COLLECTION_ALIAS = 'root' + +export const CONTENT_FIELD_WITH_ALL_TAGS = + '

A title

Esto es una oracion que contiene texto en negrita, italica, subrayada, tachado, de tipo code, este es un link que apunta a youtube.

Una lista desordenada:

  • Item

  • Item

Una lista ordenada:

  1. Item 1

  2. Item 2

Este es un blockquote.

Esto que viene es un bloque de codigo.

      <Controller name={`featuredItems.${itemIndex}.content`} control={control} rules={rules} render={({ field: { onChange, ref, value }, fieldState: { invalid, error } }) => { console.log({ value }) return ( <Col> <RichTextEditor initialValue={value as string} editorContentAriaLabelledBy={`featuredItems.${itemIndex}.content`} onChange={onChange} invalid={invalid} ariaRequired ref={ref} /> {invalid && <div className={styles["error-msg"]}>{error?.message}</div>} </Col> ) }} />
' + +export const EXPECTED_CONTENT_FIELD_WITH_ALL_TAGS = + '

A title

\n' + + '

Esto es una oracion que contiene texto en negrita, italica, subrayada, tachado, de tipo code, este es un link que apunta a youtube.

\n' + + '

Una lista desordenada:

\n' + + '
    \n' + + '
  • Item

  • \n' + + '
  • Item

  • \n' + + '
\n' + + '

Una lista ordenada:

\n' + + '
    \n' + + '
  1. Item 1

  2. \n' + + '
  3. Item 2

  4. \n' + + '
\n' + + '
\n' + + '

Este es un blockquote.

\n' + + '
\n' + + '

Esto que viene es un bloque de codigo.

\n' + + '
      <Controller name={`featuredItems.${itemIndex}.content`} control={control} rules={rules} render={({ field: { onChange, ref, value }, fieldState: { invalid, error } }) => { console.log({ value }) return ( <Col> <RichTextEditor initialValue={value as string} editorContentAriaLabelledBy={`featuredItems.${itemIndex}.content`} onChange={onChange} invalid={invalid} ariaRequired ref={ref} /> {invalid && <div className={styles["error-msg"]}>{error?.message}</div>} </Col> ) }} />
' diff --git a/test/unit/collections/DeleteCollectionFeaturedItems.test.ts b/test/unit/collections/DeleteCollectionFeaturedItems.test.ts new file mode 100644 index 00000000..4bff6aba --- /dev/null +++ b/test/unit/collections/DeleteCollectionFeaturedItems.test.ts @@ -0,0 +1,29 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { WriteError } from '../../../src' +import { DeleteCollectionFeaturedItems } from '../../../src/collections/domain/useCases/DeleteCollectionFeaturedItems' + +describe('execute', () => { + test('should return undefined on repository success', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.deleteCollectionFeaturedItems = jest.fn().mockResolvedValue(undefined) + const testDeleteCollectionFeaturedItems = new DeleteCollectionFeaturedItems( + collectionRepositoryStub + ) + + const actual = await testDeleteCollectionFeaturedItems.execute(1) + + expect(actual).toEqual(undefined) + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.deleteCollectionFeaturedItems = jest + .fn() + .mockRejectedValue(new WriteError()) + const testDeleteCollectionFeaturedItems = new DeleteCollectionFeaturedItems( + collectionRepositoryStub + ) + + await expect(testDeleteCollectionFeaturedItems.execute(1)).rejects.toThrow(WriteError) + }) +}) diff --git a/test/unit/collections/GetCollectionFeaturedItems.test.ts b/test/unit/collections/GetCollectionFeaturedItems.test.ts new file mode 100644 index 00000000..a8235051 --- /dev/null +++ b/test/unit/collections/GetCollectionFeaturedItems.test.ts @@ -0,0 +1,29 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { ReadError } from '../../../src' +import { createCollectionFeaturedItemsModel } from '../../testHelpers/collections/collectionFeaturedItemsHelper' +import { GetCollectionFeaturedItems } from '../../../src/collections/domain/useCases/GetCollectionFeaturedItems' + +describe('execute', () => { + test('should return collection featured items on repository success', async () => { + const testFeaturedItems = createCollectionFeaturedItemsModel() + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getCollectionFeaturedItems = jest + .fn() + .mockResolvedValue(testFeaturedItems) + const testGetCollectionFeaturedItems = new GetCollectionFeaturedItems(collectionRepositoryStub) + + const actual = await testGetCollectionFeaturedItems.execute(1) + + expect(actual).toEqual(testFeaturedItems) + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getCollectionFeaturedItems = jest + .fn() + .mockRejectedValue(new ReadError()) + const testGetCollectionFeaturedItems = new GetCollectionFeaturedItems(collectionRepositoryStub) + + await expect(testGetCollectionFeaturedItems.execute(1)).rejects.toThrow(ReadError) + }) +}) diff --git a/test/unit/collections/UpdateCollectionFeaturedItems.test.ts b/test/unit/collections/UpdateCollectionFeaturedItems.test.ts new file mode 100644 index 00000000..c21fb240 --- /dev/null +++ b/test/unit/collections/UpdateCollectionFeaturedItems.test.ts @@ -0,0 +1,41 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { ReadError } from '../../../src' +import { + createCollectionFeaturedItemsDTO, + createCollectionFeaturedItemsModel +} from '../../testHelpers/collections/collectionFeaturedItemsHelper' +import { UpdateCollectionFeaturedItems } from '../../../src/collections/domain/useCases/UpdateCollectionFeaturedItems' + +const testFeaturedItemsDTO = createCollectionFeaturedItemsDTO() + +describe('execute', () => { + test('should update collection featured items on repository success', async () => { + const testFeaturedItems = createCollectionFeaturedItemsModel() + + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.updateCollectionFeaturedItems = jest + .fn() + .mockResolvedValue(testFeaturedItems) + const testGetCollectionFeaturedItems = new UpdateCollectionFeaturedItems( + collectionRepositoryStub + ) + + const actual = await testGetCollectionFeaturedItems.execute(1, testFeaturedItemsDTO) + + expect(actual).toEqual(testFeaturedItems) + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.updateCollectionFeaturedItems = jest + .fn() + .mockRejectedValue(new ReadError()) + const testUpdateCollectionFeaturedItems = new UpdateCollectionFeaturedItems( + collectionRepositoryStub + ) + + await expect( + testUpdateCollectionFeaturedItems.execute(1, testFeaturedItemsDTO) + ).rejects.toThrow(ReadError) + }) +})