diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4a912c..650ee39b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel - Datasets: Added `exportDatasetMetadata` use case, repository method, and `ExportedDatasetMetadata` response type to support exporting dataset metadata by numeric id or persistent id through Dataverse endpoint `GET /datasets/export`. - Collections: Added `allowedDatasetTypes` field to the [Collection](./src/collections/domain/models/Collection.ts) model. This field is optional and only populated the feature is enabled on the installation and configured on the collection. - Collections: Added theme information when retrieving a collection using `getCollection`. +- Collections: Added `setDefaultContributorRole` use case. ### Changed diff --git a/docs/useCases.md b/docs/useCases.md index 2df0b987..79b7e853 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -29,6 +29,7 @@ The different use cases currently available in the package are classified below, - [Update Collection Featured Items](#update-collection-featured-items) - [Delete Collection Featured Items](#delete-collection-featured-items) - [Delete a Collection Featured Item](#delete-a-collection-featured-item) + - [Set Default Contributor Role](#set-default-contributor-role) - [Templates](#Templates) - [Templates read use cases](#templates-read-use-cases) - [Get a Template](#get-a-template) @@ -732,6 +733,27 @@ deleteCollectionFeaturedItem.execute(featuredItemId) _See [use case](../src/collections/domain/useCases/DeleteCollectionFeaturedItem.ts)_ definition. +#### Set Default Contributor Role + +Sets the default contributor role of a collection, given a collection identifier and a role alias. + +##### Example call: + +```typescript +import { setDefaultContributorRole } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const collectionIdOrAlias = 12345 +const roleAlias = 'curator' + +setDefaultContributorRole.execute(collectionIdOrAlias, roleAlias) + +/* ... */ +``` + +_See [use case](../src/collections/domain/useCases/SetDefaultContributorRole.ts)_ definition. + ## Templates ### Templates Read Use Cases diff --git a/src/collections/domain/repositories/ICollectionsRepository.ts b/src/collections/domain/repositories/ICollectionsRepository.ts index ed6cf4ff..cf1fad5b 100644 --- a/src/collections/domain/repositories/ICollectionsRepository.ts +++ b/src/collections/domain/repositories/ICollectionsRepository.ts @@ -14,6 +14,7 @@ import { CollectionSummary } from '../models/CollectionSummary' import { AllowedStorageDrivers } from '../models/AllowedStorageDrivers' import { StorageDriver } from '../../../core/domain/models/StorageDriver' import { LinkingObjectType } from '../useCases/GetCollectionsForLinking' +import { RoleAlias } from '../../../roles/domain/models/RoleAlias' export interface ICollectionsRepository { getCollection(collectionIdOrAlias: number | string): Promise @@ -39,6 +40,10 @@ export interface ICollectionsRepository { getCollectionUserPermissions( collectionIdOrAlias: number | string ): Promise + setDefaultContributorRole( + collectionIdOrAlias: number | string, + roleAlias: RoleAlias | string + ): Promise getCollectionItems( collectionId?: string, limit?: number, diff --git a/src/collections/domain/useCases/SetDefaultContributorRole.ts b/src/collections/domain/useCases/SetDefaultContributorRole.ts new file mode 100644 index 00000000..e2aa9beb --- /dev/null +++ b/src/collections/domain/useCases/SetDefaultContributorRole.ts @@ -0,0 +1,30 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ICollectionsRepository } from '../repositories/ICollectionsRepository' +import { ROOT_COLLECTION_ID } from '../models/Collection' +import { RoleAlias } from '../../../roles/domain/models/RoleAlias' + +export class SetDefaultContributorRole implements UseCase { + private collectionsRepository: ICollectionsRepository + + constructor(collectionsRepository: ICollectionsRepository) { + this.collectionsRepository = collectionsRepository + } + + /** + * Sets 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' + * @param {RoleAlias | string} [roleAlias] - The alias of the role to be assigned + * @returns {Promise} + */ + async execute( + collectionIdOrAlias: number | string = ROOT_COLLECTION_ID, + roleAlias: RoleAlias | string + ): Promise { + return await this.collectionsRepository.setDefaultContributorRole( + collectionIdOrAlias, + roleAlias + ) + } +} diff --git a/src/collections/index.ts b/src/collections/index.ts index b62ed9e8..efee7d13 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 { SetDefaultContributorRole } from './domain/useCases/SetDefaultContributorRole' const collectionsRepository = new CollectionsRepository() @@ -46,6 +47,7 @@ const deleteCollectionStorageDriver = new DeleteCollectionStorageDriver(collecti const getAllowedCollectionStorageDrivers = new GetAllowedCollectionStorageDrivers( collectionsRepository ) +const setDefaultContributorRole = new SetDefaultContributorRole(collectionsRepository) export { getCollection, @@ -68,7 +70,8 @@ export { getCollectionsForLinking, setCollectionStorageDriver, deleteCollectionStorageDriver, - getAllowedCollectionStorageDrivers + getAllowedCollectionStorageDrivers, + setDefaultContributorRole } export { Collection, CollectionInputLevel, CollectionTheme } 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 ccf22729..99dddaf3 100644 --- a/src/collections/infra/repositories/CollectionsRepository.ts +++ b/src/collections/infra/repositories/CollectionsRepository.ts @@ -42,6 +42,7 @@ import { CollectionSummary } from '../../domain/models/CollectionSummary' 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' export interface NewCollectionRequestPayload { alias: string @@ -225,6 +226,20 @@ export class CollectionsRepository extends ApiRepository implements ICollections }) } + public async setDefaultContributorRole( + collectionIdOrAlias: number | string, + roleAlias: RoleAlias | string + ): Promise { + return this.doPut( + `/${this.collectionsResourceName}/${collectionIdOrAlias}/defaultContributorRole/${roleAlias}`, + {} + ) + .then(() => undefined) + .catch((error) => { + throw error + }) + } + public async getCollectionItems( collectionId?: string, limit?: number, diff --git a/src/roles/domain/models/RoleAlias.ts b/src/roles/domain/models/RoleAlias.ts new file mode 100644 index 00000000..23e35fff --- /dev/null +++ b/src/roles/domain/models/RoleAlias.ts @@ -0,0 +1,12 @@ +// Aliases of built-in roles +export enum RoleAlias { + ADMIN = 'admin', + FILE_DOWNLOADER = 'fileDownloader', + FULL_CONTRIBUTOR = 'fullContributor', + DV_CONTRIBUTOR = 'dvContributor', + DS_CONTRIBUTOR = 'dsContributor', + EDITOR = 'contributor', + MANAGER = 'manager', + CURATOR = 'curator', + MEMBER = 'member' +} diff --git a/src/roles/index.ts b/src/roles/index.ts index 8bd9e276..51617c3d 100644 --- a/src/roles/index.ts +++ b/src/roles/index.ts @@ -8,3 +8,4 @@ const getUserSelectableRoles = new GetUserSelectableRoles(rolesRepository) export { getUserSelectableRoles } export { Role } from './domain/models/Role' +export { RoleAlias } from './domain/models/RoleAlias' diff --git a/test/integration/collections/SetDefaultContributorRole.test.ts b/test/integration/collections/SetDefaultContributorRole.test.ts new file mode 100644 index 00000000..79cc9be2 --- /dev/null +++ b/test/integration/collections/SetDefaultContributorRole.test.ts @@ -0,0 +1,57 @@ +import { SetDefaultContributorRole } from '../../../src/collections/domain/useCases/SetDefaultContributorRole' +import { CollectionsRepository } from '../../../src/collections/infra/repositories/CollectionsRepository' +import { ApiConfig } from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { + createCollectionViaApi, + deleteCollectionViaApi +} from '../../testHelpers/collections/collectionHelper' +import { RoleAlias } from '../../../src/roles/domain/models/RoleAlias' +import { WriteError } from '../../../src/core/domain/repositories/WriteError' + +describe('SetDefaultContributorRole', () => { + const collectionsRepository = new CollectionsRepository() + const useCase = new SetDefaultContributorRole(collectionsRepository) + const testCollectionAlias = 'setDefaultContributorRoleTestCollection' + + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + await createCollectionViaApi(testCollectionAlias) + }) + + afterAll(async () => { + await deleteCollectionViaApi(testCollectionAlias) + }) + + test('should successfully set the default contributor role', async () => { + const roleAlias = RoleAlias.CURATOR + + await expect(useCase.execute(testCollectionAlias, roleAlias)).resolves.toBeUndefined() + }) + + test('should successfully set the default contributor role for the root collection', async () => { + const roleAlias = RoleAlias.CURATOR + + await expect(useCase.execute(undefined, roleAlias)).resolves.toBeUndefined() + }) + + test('should throw an error when the collection does not exist', async () => { + const nonExistentCollection = 'nonExistentCollection' + const roleAlias = RoleAlias.CURATOR + + await expect(useCase.execute(nonExistentCollection, roleAlias)).rejects.toThrow(WriteError) + }) + + test('should throw an error when the role alias does not exist', async () => { + const nonExistentRoleAlias = 'invalidRoleAlias' + + await expect(useCase.execute(testCollectionAlias, nonExistentRoleAlias)).rejects.toThrow( + WriteError + ) + }) +}) diff --git a/test/unit/collections/CollectionsRepository.test.ts b/test/unit/collections/CollectionsRepository.test.ts index adcdf8cf..075034cc 100644 --- a/test/unit/collections/CollectionsRepository.test.ts +++ b/test/unit/collections/CollectionsRepository.test.ts @@ -48,6 +48,7 @@ import { OrderType, SortType } from '../../../src/collections/domain/models/CollectionSearchCriteria' +import { RoleAlias } from '../../../src/roles/domain/models/RoleAlias' describe('CollectionsRepository', () => { const sut: CollectionsRepository = new CollectionsRepository() @@ -97,6 +98,14 @@ describe('CollectionsRepository', () => { data: createCollectionPayload() } } + const testSetDefaultContributorRoleResponse = { + data: { + status: 'OK', + data: { + message: 'Default contributor role has been set' + } + } + } const testCollectionModel = createCollectionModel() beforeEach(() => { @@ -893,4 +902,32 @@ describe('CollectionsRepository', () => { }) }) }) + + describe('setDefaultContributorRole', () => { + const testRoleAlias = RoleAlias.CURATOR + + test('should call the API', async () => { + jest.spyOn(axios, 'put').mockResolvedValue(testSetDefaultContributorRoleResponse) + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/test-collection/defaultContributorRole/${testRoleAlias}` + + await sut.setDefaultContributorRole('test-collection', testRoleAlias) + + expect(axios.put).toHaveBeenCalledWith( + expectedApiEndpoint, + '{}', + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY + ) + }) + + test('should return error result on error response', async () => { + jest.spyOn(axios, 'put').mockRejectedValue(TestConstants.TEST_ERROR_RESPONSE) + let error = undefined as unknown as WriteError + + await sut + .setDefaultContributorRole('test-collection', testRoleAlias) + .catch((e) => (error = e)) + + expect(error).toBeInstanceOf(Error) + }) + }) }) diff --git a/test/unit/collections/SetDefaultContributorRole.test.ts b/test/unit/collections/SetDefaultContributorRole.test.ts new file mode 100644 index 00000000..f3587b6e --- /dev/null +++ b/test/unit/collections/SetDefaultContributorRole.test.ts @@ -0,0 +1,30 @@ +import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository' +import { WriteError } from '../../../src' +import { SetDefaultContributorRole } from '../../../src/collections/domain/useCases/SetDefaultContributorRole' +import { RoleAlias } from '../../../src/roles/domain/models/RoleAlias' + +describe('execute', () => { + test('should set default contributor role on repository success', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.setDefaultContributorRole = jest.fn().mockResolvedValue(undefined) + const testSetDefaultContributorRole = new SetDefaultContributorRole(collectionRepositoryStub) + + await expect( + testSetDefaultContributorRole.execute(1, RoleAlias.CURATOR) + ).resolves.toBeUndefined() + expect(collectionRepositoryStub.setDefaultContributorRole).toHaveBeenCalledWith(1, 'curator') + }) + + test('should return error result on repository error', async () => { + const collectionRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository + collectionRepositoryStub.setDefaultContributorRole = jest + .fn() + .mockRejectedValue(new WriteError()) + const testSetDefaultContributorRole = new SetDefaultContributorRole(collectionRepositoryStub) + + await expect(testSetDefaultContributorRole.execute(1, RoleAlias.CURATOR)).rejects.toThrow( + WriteError + ) + expect(collectionRepositoryStub.setDefaultContributorRole).toHaveBeenCalledWith(1, 'curator') + }) +})