Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds GetCollectionUserPermissions use case #175

Merged
merged 2 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -128,6 +129,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
}
}
4 changes: 2 additions & 2 deletions test/environment/.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
POSTGRES_VERSION=13
DATAVERSE_DB_USER=dataverse
SOLR_VERSION=9.3.0
DATAVERSE_IMAGE_REGISTRY=docker.io
DATAVERSE_IMAGE_TAG=unstable
DATAVERSE_IMAGE_REGISTRY=ghcr.io
DATAVERSE_IMAGE_TAG=10749-dataverse-user-permissions-api
DATAVERSE_BOOTSTRAP_TIMEOUT=5m
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)
})
})
Loading