diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d822c2..efd219f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel ### Added +- Collections: Added `getDefaultContributorRole` use case. + ### Changed ### Fixed diff --git a/docs/useCases.md b/docs/useCases.md index 64a2c8d6..26e4f908 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -19,6 +19,7 @@ The different use cases currently available in the package are classified below, - [List My Data Collection Items](#list-my-data-collection-items) - [Get Collection Featured Items](#get-collection-featured-items) - [Get Collections for Linking](#get-collections-for-linking) + - [Get Default Contributor Role](#get-default-contributor-role) - [Collections write use cases](#collections-write-use-cases) - [Create a Collection](#create-a-collection) - [Set Collection Storage Driver](#set-collection-storage-driver) @@ -516,6 +517,38 @@ Notes: - When the first argument is `'collection'`, the second argument can be a numeric collection id or a collection alias. - When the first argument is `'dataset'`, the second argument must be the dataset persistent identifier string (e.g., `doi:...`). +#### Get Default Contributor Role + +Returns the default [Role](../src/roles/domain/models/Role.ts) assigned to a user who creates a dataset in the collection. + +Requires the `ManageDataversePermissions` permission on the collection. + +##### Example call: + +```typescript +import { getDefaultContributorRole } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const collectionIdOrAlias = 'classicLiterature' + +getDefaultContributorRole + .execute(collectionIdOrAlias) + .then((role) => { + /* ... */ + }) + +/* ... */ +``` + +_See [use case](../src/collections/domain/useCases/GetDefaultContributorRole.ts)_ implementation. + +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. + +The call returns a `Promise` and rejects with a `ReadError` if the collection cannot be read, including when it does not exist or the caller lacks permission. + ### Collections Write Use Cases #### Create a Collection diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index cf1fad5b..6b3b0540 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -15,6 +15,7 @@ import { AllowedStorageDrivers } from '../models/AllowedStorageDrivers' import { StorageDriver } from '../../../core/domain/models/StorageDriver' import { LinkingObjectType } from '../useCases/GetCollectionsForLinking' import { RoleAlias } from '../../../roles/domain/models/RoleAlias' +import { Role } from '../../../roles/domain/models/Role' export interface ICollectionsRepository { getCollection(collectionIdOrAlias: number | string): Promise @@ -40,6 +41,7 @@ export interface ICollectionsRepository { getCollectionUserPermissions( collectionIdOrAlias: number | string ): Promise + getDefaultContributorRole(collectionIdOrAlias: number | string): Promise setDefaultContributorRole( collectionIdOrAlias: number | string, roleAlias: RoleAlias | string diff --git a/src/collections/domain/useCases/GetDefaultContributorRole.ts b/src/collections/domain/useCases/GetDefaultContributorRole.ts new file mode 100644 index 00000000..c6167796 --- /dev/null +++ b/src/collections/domain/useCases/GetDefaultContributorRole.ts @@ -0,0 +1,23 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { ROOT_COLLECTION_ID } from '../models/Collection' +import { Role } from '../../../roles/domain/models/Role' + +export class GetDefaultContributorRole implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Returns the default Role that is assigned to contributors in the given collection. + * + * @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.getDefaultContributorRole(collectionIdOrAlias) + } +} diff --git a/src/collections/index.ts b/src/collections/index.ts index efee7d13..6f639683 100644 --- a/src/collections/index.ts +++ b/src/collections/index.ts @@ -20,6 +20,7 @@ import { GetCollectionsForLinking } from './domain/useCases/GetCollectionsForLin import { SetCollectionStorageDriver } from './domain/useCases/SetCollectionStorageDriver' import { DeleteCollectionStorageDriver } from './domain/useCases/DeleteCollectionStorageDriver' import { GetAllowedCollectionStorageDrivers } from './domain/useCases/GetAllowedCollectionStorageDrivers' +import { GetDefaultContributorRole } from './domain/useCases/GetDefaultContributorRole' import { SetDefaultContributorRole } from './domain/useCases/SetDefaultContributorRole' const collectionsRepository = new CollectionsRepository() @@ -47,6 +48,7 @@ const deleteCollectionStorageDriver = new DeleteCollectionStorageDriver(collecti const getAllowedCollectionStorageDrivers = new GetAllowedCollectionStorageDrivers( collectionsRepository ) +const getDefaultContributorRole = new GetDefaultContributorRole(collectionsRepository) const setDefaultContributorRole = new SetDefaultContributorRole(collectionsRepository) export { @@ -71,6 +73,7 @@ export { setCollectionStorageDriver, deleteCollectionStorageDriver, getAllowedCollectionStorageDrivers, + getDefaultContributorRole, setDefaultContributorRole } export { Collection, CollectionInputLevel, CollectionTheme } from './domain/models/Collection' diff --git a/src/collections/infra/repositories/CollectionsRepository.ts b/src/collections/infra/repositories/CollectionsRepository.ts index 99dddaf3..c186f6e0 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -43,6 +43,8 @@ import { AllowedStorageDrivers } from '../../domain/models/AllowedStorageDrivers import { StorageDriver } from '../../../core/domain/models/StorageDriver' import { LinkingObjectType } from '../../domain/useCases/GetCollectionsForLinking' import { RoleAlias } from '../../../roles/domain/models/RoleAlias' +import { Role } from '../../../roles/domain/models/Role' +import { transformCollectionDefaultContributorRoleToRole } from './transformers/collectionDefaultContributorRoleTransformers' export interface NewCollectionRequestPayload { alias: string @@ -226,6 +228,17 @@ export class CollectionsRepository extends ApiRepository implements ICollections }) } + public async getDefaultContributorRole(collectionIdOrAlias: number | string): Promise { + return this.doGet( + `/${this.collectionsResourceName}/${collectionIdOrAlias}/defaultContributorRole`, + true + ) + .then((response) => transformCollectionDefaultContributorRoleToRole(response)) + .catch((error) => { + throw error + }) + } + public async setDefaultContributorRole( collectionIdOrAlias: number | string, roleAlias: RoleAlias | string diff --git a/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts b/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts new file mode 100644 index 00000000..84e885bb --- /dev/null +++ b/src/collections/infra/repositories/transformers/CollectionDefaultContributorRolePayload.ts @@ -0,0 +1,7 @@ +export interface CollectionDefaultContributorRolePayload { + alias: string + name: string + permissions: Array + description: string + id: number +} diff --git a/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts b/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts new file mode 100644 index 00000000..36868451 --- /dev/null +++ b/src/collections/infra/repositories/transformers/collectionDefaultContributorRoleTransformers.ts @@ -0,0 +1,13 @@ +import { AxiosResponse } from 'axios' +import { Role } from '../../../../roles/domain/models/Role' + +export const transformCollectionDefaultContributorRoleToRole = (response: AxiosResponse): Role => { + const defaultContributorRolePayload = response.data.data + return { + alias: defaultContributorRolePayload.alias, + name: defaultContributorRolePayload.name, + permissions: defaultContributorRolePayload.permissions, + description: defaultContributorRolePayload.description, + id: defaultContributorRolePayload.id + } +} diff --git a/test/integration/collections/CollectionsRepository.test.ts b/test/integration/collections/CollectionsRepository.test.ts index 0e30d5f2..a8d44dbb 100644 --- a/test/integration/collections/CollectionsRepository.test.ts +++ b/test/integration/collections/CollectionsRepository.test.ts @@ -16,7 +16,10 @@ import { getDatasetFiles, restrictFile, deleteFile, - linkDataset + linkDataset, + setDefaultContributorRole, + RoleAlias, + deleteCollection } from '../../../src' import { ApiConfig } from '../../../src' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' @@ -59,6 +62,7 @@ import { DvObjectFeaturedItemDTO, FeaturedItemsDTO } from '../../../src/collections/domain/dtos/FeaturedItemsDTO' +import { createSuperAdminRoleArray } from '../../testHelpers/roles/roleHelper' describe('CollectionsRepository', () => { const testCollectionAlias = 'collectionsRepositoryTestCollection' @@ -1738,6 +1742,51 @@ describe('CollectionsRepository', () => { }) }) + describe('getDefaultContributorRole', () => { + const sut = new CollectionsRepository() + const collectionAlias = 'getDefaultContributorRoleIntegration' + let collectionId: number | undefined + + beforeAll(async () => { + collectionId = await createCollection.execute(createCollectionDTO(collectionAlias)) + await setDefaultContributorRole.execute(collectionId, RoleAlias.CURATOR) + }) + + afterAll(async () => { + if (collectionId !== undefined) { + await deleteCollection.execute(collectionId) + } + }) + + test.each(['numeric id', 'alias'])( + 'should return the configured default role by %s', + async (identifierType) => { + if (collectionId === undefined) { + throw new Error('Test collection was not created') + } + const identifier = identifierType === 'numeric id' ? collectionId : collectionAlias + const expectedRole = createSuperAdminRoleArray().find((role) => role.alias === 'curator') + + const actual = await sut.getDefaultContributorRole(identifier) + + expect(actual).toStrictEqual({ + ...expectedRole + }) + } + ) + + 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.getDefaultContributorRole(invalidCollectionAlias)).rejects.toThrow( + expectedError + ) + }) + }) + describe('getMyDataCollectionItems', () => { let testDatasetIds: CreatedDatasetIdentifiers let testSubCollectionCreated = false diff --git a/test/unit/collections/CollectionsRepository.test.ts b/test/unit/collections/CollectionsRepository.test.ts index 075034cc..ae9d8ac1 100644 --- a/test/unit/collections/CollectionsRepository.test.ts +++ b/test/unit/collections/CollectionsRepository.test.ts @@ -903,6 +903,41 @@ describe('CollectionsRepository', () => { }) }) + describe('getDefaultContributorRole', () => { + test.each([123, 'test-collection', ':root'])( + 'should return the default role for %s', + async (identifier) => { + const role = { + id: 7, + alias: 'curator', + name: 'Curator', + description: 'Can edit and publish datasets.', + permissions: ['EditDataset', 'PublishDataset'] + } + jest.spyOn(axios, 'get').mockResolvedValue({ + data: { status: 'OK', data: { ...role } } + }) + + const actual = await sut.getDefaultContributorRole(identifier) + + expect(axios.get).toHaveBeenCalledWith( + `${TestConstants.TEST_API_URL}/dataverses/${identifier}/defaultContributorRole`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY + ) + expect(actual).toStrictEqual(role) + } + ) + + test('should return error result on error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) + let error = undefined as unknown as ReadError + + await sut.getDefaultContributorRole('missing').catch((e) => (error = e)) + + expect(error).toBeInstanceOf(ReadError) + }) + }) + describe('setDefaultContributorRole', () => { const testRoleAlias = RoleAlias.CURATOR diff --git a/test/unit/collections/GetDefaultContributorRole.test.ts b/test/unit/collections/GetDefaultContributorRole.test.ts new file mode 100644 index 00000000..b410699a --- /dev/null +++ b/test/unit/collections/GetDefaultContributorRole.test.ts @@ -0,0 +1,27 @@ +import { GetDefaultContributorRole } from '../../../src/collections/domain/useCases/GetDefaultContributorRole' +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { ReadError } from '../../../src' +import { createRoleModel } from '../../testHelpers/roles/roleHelper' + +describe('GetDefaultContributorRole', () => { + test('should return the default role on repository success', async () => { + const role = createRoleModel() + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getDefaultContributorRole = jest.fn().mockResolvedValue(role) + const testGetDefaultContributorRole = new GetDefaultContributorRole(collectionRepositoryStub) + + const actual = await testGetDefaultContributorRole.execute(1) + + expect(actual).toEqual(role) + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.getDefaultContributorRole = jest + .fn() + .mockRejectedValue(new ReadError()) + const testGetDefaultContributorRole = new GetDefaultContributorRole(collectionRepositoryStub) + + await expect(testGetDefaultContributorRole.execute(1)).rejects.toThrow(ReadError) + }) +})