Skip to content

Commit

Permalink
Merge pull request #175 from IQSS/165-get-collection-user-permissions
Browse files Browse the repository at this point in the history
Adds GetCollectionUserPermissions use case
  • Loading branch information
ekraffmiller authored Aug 15, 2024
2 parents 0ea8141 + 3f8ee0a commit 75c2f56
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 1 deletion.
29 changes: 29 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The different use cases currently available in the package are classified below,
- [Collections read use cases](#collections-read-use-cases)
- [Get a Collection](#get-a-collection)
- [Get Collection Facets](#get-collection-facets)
- [Get User Permissions on a Collection](#get-user-permissions-on-a-collection)
- [Collections write use cases](#collections-write-use-cases)
- [Create a Collection](#create-a-collection)
- [Datasets](#Datasets)
Expand Down Expand Up @@ -129,6 +130,34 @@ The `collectionIdOrAlias` is a generic collection identifier, which can be eithe

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.

#### Get User Permissions on a Collection

Returns an instance of [CollectionUserPermissions](../src/collections/domain/models/CollectionUserPermissions.ts) that includes the permissions that the calling user has on a particular Collection.

##### Example call:

```typescript
import { getCollectionUserPermissions } from '@iqss/dataverse-client-javascript'

/* ... */

const collectionIdOrAlias = 12345

getCollectionUserPermissions
.execute(collectionIdOrAlias)
.then((permissions: CollectionUserPermissions) => {
/* ... */
})

/* ... */
```

_See [use case](../src/collections/domain/useCases/GetCollectionUserPermissions.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. 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
Expand Down
9 changes: 9 additions & 0 deletions src/collections/domain/models/CollectionUserPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface CollectionUserPermissions {
canAddCollection: boolean
canAddDataset: boolean
canViewUnpublishedCollection: boolean
canEditCollection: boolean
canManageCollectionPermissions: boolean
canPublishCollection: boolean
canDeleteCollection: boolean
}
4 changes: 4 additions & 0 deletions src/collections/domain/repositories/ICollectionsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CollectionDTO } from '../dtos/CollectionDTO'
import { Collection } from '../models/Collection'
import { CollectionUserPermissions } from '../models/CollectionUserPermissions'

export interface ICollectionsRepository {
getCollection(collectionIdOrAlias: number | string): Promise<Collection>
Expand All @@ -8,4 +9,7 @@ export interface ICollectionsRepository {
parentCollectionId: number | string
): Promise<number>
getCollectionFacets(collectionIdOrAlias: number | string): Promise<string[]>
getCollectionUserPermissions(
collectionIdOrAlias: number | string
): Promise<CollectionUserPermissions>
}
22 changes: 22 additions & 0 deletions src/collections/domain/useCases/GetCollectionUserPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { CollectionUserPermissions } from '../models/CollectionUserPermissions'
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'

export class GetCollectionUserPermissions implements UseCase<CollectionUserPermissions> {
private collectionsRepository: ICollectionsRepository

constructor(collectionsRepository: ICollectionsRepository) {
this.collectionsRepository = collectionsRepository
}

/**
* Returns an instance of CollectionUserPermissions that includes the permissions that the calling user has on a particular 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<CollectionUserPermissions>}
*/
async execute(collectionIdOrAlias: number | string): Promise<CollectionUserPermissions> {
return await this.collectionsRepository.getCollectionUserPermissions(collectionIdOrAlias)
}
}
5 changes: 4 additions & 1 deletion src/collections/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CreateCollection } from './domain/useCases/CreateCollection'
import { GetCollection } from './domain/useCases/GetCollection'
import { GetCollectionFacets } from './domain/useCases/GetCollectionFacets'
import { GetCollectionUserPermissions } from './domain/useCases/GetCollectionUserPermissions'

import { CollectionsRepository } from './infra/repositories/CollectionsRepository'

Expand All @@ -9,7 +10,9 @@ const collectionsRepository = new CollectionsRepository()
const getCollection = new GetCollection(collectionsRepository)
const createCollection = new CreateCollection(collectionsRepository)
const getCollectionFacets = new GetCollectionFacets(collectionsRepository)
const getCollectionUserPermissions = new GetCollectionUserPermissions(collectionsRepository)

export { getCollection, createCollection, getCollectionFacets }
export { getCollection, createCollection, getCollectionFacets, getCollectionUserPermissions }
export { Collection, CollectionInputLevel } from './domain/models/Collection'
export { CollectionUserPermissions } from './domain/models/CollectionUserPermissions'
export { CollectionDTO, CollectionInputLevelDTO } from './domain/dtos/CollectionDTO'
17 changes: 17 additions & 0 deletions src/collections/infra/repositories/CollectionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ICollectionsRepository } from '../../domain/repositories/ICollectionsRe
import { transformCollectionResponseToCollection } from './transformers/collectionTransformers'
import { Collection, ROOT_COLLECTION_ALIAS } from '../../domain/models/Collection'
import { CollectionDTO } from '../../domain/dtos/CollectionDTO'
import { CollectionUserPermissions } from '../../domain/models/CollectionUserPermissions'
import { transformCollectionUserPermissionsResponseToCollectionUserPermissions } from './transformers/collectionUserPermissionsTransformers'

export interface NewCollectionRequestPayload {
alias: string
Expand Down Expand Up @@ -86,4 +88,19 @@ export class CollectionsRepository extends ApiRepository implements ICollections
throw error
})
}

public async getCollectionUserPermissions(
collectionIdOrAlias: number | string
): Promise<CollectionUserPermissions> {
return this.doGet(
`/${this.collectionsResourceName}/${collectionIdOrAlias}/userPermissions`,
true
)
.then((response) =>
transformCollectionUserPermissionsResponseToCollectionUserPermissions(response)
)
.catch((error) => {
throw error
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface CollectionUserPermissionsPayload {
canAddDataverse: boolean
canAddDataset: boolean
canViewUnpublishedDataverse: boolean
canEditDataverse: boolean
canManageDataversePermissions: boolean
canPublishDataverse: boolean
canDeleteDataverse: boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AxiosResponse } from 'axios'
import { CollectionUserPermissions } from '../../../domain/models/CollectionUserPermissions'

export const transformCollectionUserPermissionsResponseToCollectionUserPermissions = (
response: AxiosResponse
): CollectionUserPermissions => {
const collectionUserPermissionsPayload = response.data.data
return {
canAddCollection: collectionUserPermissionsPayload.canAddDataverse,
canAddDataset: collectionUserPermissionsPayload.canAddDataset,
canViewUnpublishedCollection: collectionUserPermissionsPayload.canViewUnpublishedDataverse,
canEditCollection: collectionUserPermissionsPayload.canEditDataverse,
canManageCollectionPermissions: collectionUserPermissionsPayload.canManageDataversePermissions,
canPublishCollection: collectionUserPermissionsPayload.canPublishDataverse,
canDeleteCollection: collectionUserPermissionsPayload.canDeleteDataverse
}
}
52 changes: 52 additions & 0 deletions test/functional/collections/GetCollectionUserPermissions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
ApiConfig,
CollectionUserPermissions,
ReadError,
getCollectionUserPermissions
} from '../../../src'
import { TestConstants } from '../../testHelpers/TestConstants'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
import { ROOT_COLLECTION_ALIAS } from '../../../src/collections/domain/models/Collection'

describe('execute', () => {
beforeEach(async () => {
ApiConfig.init(
TestConstants.TEST_API_URL,
DataverseApiAuthMechanism.API_KEY,
process.env.TEST_API_KEY
)
})

test('should return user permissions when a valid collection alias is provided', async () => {
let actual: CollectionUserPermissions
try {
actual = await getCollectionUserPermissions.execute(ROOT_COLLECTION_ALIAS)
} catch (error) {
throw new Error('Permissions should be retrieved')
} finally {
expect(actual.canAddDataset).toBe(true)
expect(actual.canAddCollection).toBe(true)
expect(actual.canDeleteCollection).toBe(true)
expect(actual.canEditCollection).toBe(true)
expect(actual.canManageCollectionPermissions).toBe(true)
expect(actual.canPublishCollection).toBe(true)
expect(actual.canViewUnpublishedCollection).toBe(true)
}
})

test('should throw an error when collection does not exist', async () => {
expect.assertions(2)
let readError: ReadError
try {
await getCollectionUserPermissions.execute(TestConstants.TEST_DUMMY_COLLECTION_ID)
throw new Error('Use case should throw an error')
} catch (error) {
readError = error
} finally {
expect(readError).toBeInstanceOf(ReadError)
expect(readError.message).toEqual(
`There was an error when reading the resource. Reason was: [404] Can't find dataverse with identifier='${TestConstants.TEST_DUMMY_COLLECTION_ID}'`
)
}
})
})
25 changes: 25 additions & 0 deletions test/integration/collections/CollectionsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,29 @@ describe('CollectionsRepository', () => {
).rejects.toThrow(expectedError)
})
})

describe('getCollectionUserPermissions', () => {
test('should return user permissions', async () => {
const actual = await sut.getCollectionUserPermissions('root')
expect(actual.canAddDataset).toBe(true)
expect(actual.canAddCollection).toBe(true)
expect(actual.canDeleteCollection).toBe(true)
expect(actual.canEditCollection).toBe(true)
expect(actual.canManageCollectionPermissions).toBe(true)
expect(actual.canPublishCollection).toBe(true)
expect(actual.canViewUnpublishedCollection).toBe(true)
})

test('should return error when collection does not exist', async () => {
const nonExistentCollectionAlias = 'nonExistentCollection'

const expectedError = new ReadError(
`[404] Can't find dataverse with identifier='${nonExistentCollectionAlias}'`
)

await expect(sut.getCollectionUserPermissions(nonExistentCollectionAlias)).rejects.toThrow(
expectedError
)
})
})
})
26 changes: 26 additions & 0 deletions test/testHelpers/collections/collectionUserPermissionsHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CollectionUserPermissions } from '../../../src/collections/domain/models/CollectionUserPermissions'
import { CollectionUserPermissionsPayload } from '../../../src/collections/infra/repositories/transformers/CollectionUserPermissionsPayload'

export const createCollectionUserPermissionsModel = (): CollectionUserPermissions => {
return {
canAddCollection: true,
canAddDataset: true,
canViewUnpublishedCollection: true,
canEditCollection: true,
canManageCollectionPermissions: true,
canPublishCollection: true,
canDeleteCollection: true
}
}

export const createCollectionUserPermissionsPayload = (): CollectionUserPermissionsPayload => {
return {
canAddDataverse: true,
canAddDataset: true,
canViewUnpublishedDataverse: true,
canEditDataverse: true,
canManageDataversePermissions: true,
canPublishDataverse: true,
canDeleteDataverse: true
}
}
55 changes: 55 additions & 0 deletions test/unit/collections/CollectionsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {
import { TestConstants } from '../../testHelpers/TestConstants'
import { ReadError, WriteError } from '../../../src'
import { ROOT_COLLECTION_ALIAS } from '../../../src/collections/domain/models/Collection'
import {
createCollectionUserPermissionsModel,
createCollectionUserPermissionsPayload
} from '../../testHelpers/collections/collectionUserPermissionsHelper'

describe('CollectionsRepository', () => {
const sut: CollectionsRepository = new CollectionsRepository()
Expand Down Expand Up @@ -228,4 +232,55 @@ describe('CollectionsRepository', () => {
})
})
})

describe('getCollectionUserPermissions', () => {
const testCollectionUserPermissions = createCollectionUserPermissionsModel()
const testCollectionUserPermissionsResponse = {
data: {
status: 'OK',
data: createCollectionUserPermissionsPayload()
}
}

describe('by numeric id', () => {
const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/dataverses/${testCollectionModel.id}/userPermissions`

test('should return dataset user permissions when providing id and response is successful', async () => {
jest.spyOn(axios, 'get').mockResolvedValue(testCollectionUserPermissionsResponse)

// API Key auth
let actual = await sut.getCollectionUserPermissions(testCollectionModel.id)

expect(axios.get).toHaveBeenCalledWith(
expectedApiEndpoint,
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY
)
expect(actual).toStrictEqual(testCollectionUserPermissions)

// Session cookie auth
ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE)

actual = await sut.getCollectionUserPermissions(testCollectionModel.id)

expect(axios.get).toHaveBeenCalledWith(
expectedApiEndpoint,
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE
)
expect(actual).toStrictEqual(testCollectionUserPermissions)
})

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.getCollectionUserPermissions(testCollectionModel.id).catch((e) => (error = e))

expect(axios.get).toHaveBeenCalledWith(
expectedApiEndpoint,
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY
)
expect(error).toBeInstanceOf(Error)
})
})
})
})
34 changes: 34 additions & 0 deletions test/unit/collections/GetCollectionUserPermissions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { GetCollectionUserPermissions } from '../../../src/collections/domain/useCases/GetCollectionUserPermissions'
import { ReadError } from '../../../src/core/domain/repositories/ReadError'
import { ICollectionsRepository } from '../../../src/collections/domain/repositories/ICollectionsRepository'
import { createCollectionUserPermissionsModel } from '../../testHelpers/collections/collectionUserPermissionsHelper'

describe('execute', () => {
const testCollectionAlias = 'test'

test('should return collection user permissions on repository success', async () => {
const testCollectionUserPermissions = createCollectionUserPermissionsModel()
const collectionsRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository
collectionsRepositoryStub.getCollectionUserPermissions = jest
.fn()
.mockResolvedValue(testCollectionUserPermissions)
const sut = new GetCollectionUserPermissions(collectionsRepositoryStub)

const actual = await sut.execute(testCollectionAlias)

expect(actual).toEqual(testCollectionUserPermissions)
expect(collectionsRepositoryStub.getCollectionUserPermissions).toHaveBeenCalledWith(
testCollectionAlias
)
})

test('should return error result on repository error', async () => {
const collectionsRepositoryStub: ICollectionsRepository = {} as ICollectionsRepository
collectionsRepositoryStub.getCollectionUserPermissions = jest
.fn()
.mockRejectedValue(new ReadError())
const sut = new GetCollectionUserPermissions(collectionsRepositoryStub)

await expect(sut.execute(testCollectionAlias)).rejects.toThrow(ReadError)
})
})

0 comments on commit 75c2f56

Please sign in to comment.