Skip to content
Merged
26 changes: 26 additions & 0 deletions .github/workflows/copy_labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Copy labels from issue to pull request

on:
pull_request:
types: [opened]

jobs:
copy-labels:
runs-on: ubuntu-latest
name: Copy labels from linked issues
steps:
- name: copy-labels
uses: michalvankodev/[email protected]
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
labels-to-exclude: |
Size: 3
Size: 10
Size: 20
Size: 33
Size: 80
Original size: 3
Original size: 10
Original size: 20
Original size: 33
Original size: 80
19 changes: 19 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The different use cases currently available in the package are classified below,
- [Create a Collection](#create-a-collection)
- [Update a Collection](#update-a-collection)
- [Publish a Collection](#publish-a-collection)
- [Delete a Collection](#delete-a-collection)
- [Update Collection Featured Items](#update-collection-featured-items)
- [Delete Collection Featured Items](#delete-collection-featured-items)
- [Datasets](#Datasets)
Expand Down Expand Up @@ -320,6 +321,24 @@ The `collectionIdOrAlias` is a generic collection identifier, which can be eithe

_See [use case](../src/collections/domain/useCases/PublishCollection.ts)_ definition.

### Delete a Collection

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

/* ... */

const collectionIdOrAlias = 12345

deleteCollection.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/DeleteCollection.ts)_ definition.

#### Update Collection Featured Items

Updates all featured items, given a collection identifier and a CollectionFeaturedItemsDTO.
Expand Down
1 change: 1 addition & 0 deletions src/collections/domain/models/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface Collection {
contacts?: CollectionContact[]
isMetadataBlockRoot: boolean
isFacetRoot: boolean
childCount: number
}

export interface CollectionInputLevel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface ICollectionsRepository {
parentCollectionId: number | string
): Promise<number>
publishCollection(collectionIdOrAlias: number | string): Promise<void>
deleteCollection(collectionIdOrAlias: number | string): Promise<void>
getCollectionFacets(collectionIdOrAlias: number | string): Promise<CollectionFacet[]>
getCollectionUserPermissions(
collectionIdOrAlias: number | string
Expand Down
20 changes: 20 additions & 0 deletions src/collections/domain/useCases/DeleteCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { UseCase } from '../../../core/domain/useCases/UseCase'
import { ICollectionsRepository } from '../repositories/ICollectionsRepository'

export class DeleteCollection implements UseCase<void> {
private collectionsRepository: ICollectionsRepository

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

/**
* Deletes the Dataverse collection whose database ID or alias is given:
*
* @param {number | string} [collectionIdOrAlias] - A generic collection identifier, which can be either a string (for queries by CollectionAlias), or a number (for queries by CollectionId)
* @returns {Promise<void>} -This method does not return anything upon successful completion.
*/
async execute(collectionIdOrAlias: number | string): Promise<void> {
return await this.collectionsRepository.deleteCollection(collectionIdOrAlias)
}
}
5 changes: 4 additions & 1 deletion src/collections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { GetCollectionFeaturedItems } from './domain/useCases/GetCollectionFeatu
import { CollectionsRepository } from './infra/repositories/CollectionsRepository'
import { UpdateCollectionFeaturedItems } from './domain/useCases/UpdateCollectionFeaturedItems'
import { DeleteCollectionFeaturedItems } from './domain/useCases/DeleteCollectionFeaturedItems'
import { DeleteCollection } from './domain/useCases/DeleteCollection'

const collectionsRepository = new CollectionsRepository()

Expand All @@ -22,6 +23,7 @@ const updateCollection = new UpdateCollection(collectionsRepository)
const getCollectionFeaturedItems = new GetCollectionFeaturedItems(collectionsRepository)
const updateCollectionFeaturedItems = new UpdateCollectionFeaturedItems(collectionsRepository)
const deleteCollectionFeaturedItems = new DeleteCollectionFeaturedItems(collectionsRepository)
const deleteCollection = new DeleteCollection(collectionsRepository)

export {
getCollection,
Expand All @@ -33,7 +35,8 @@ export {
updateCollection,
getCollectionFeaturedItems,
updateCollectionFeaturedItems,
deleteCollectionFeaturedItems
deleteCollectionFeaturedItems,
deleteCollection
}
export { Collection, CollectionInputLevel } from './domain/models/Collection'
export { CollectionFacet } from './domain/models/CollectionFacet'
Expand Down
12 changes: 11 additions & 1 deletion src/collections/infra/repositories/CollectionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export class CollectionsRepository extends ApiRepository implements ICollections
collectionIdOrAlias: number | string = ROOT_COLLECTION_ID
): Promise<Collection> {
return this.doGet(`/${this.collectionsResourceName}/${collectionIdOrAlias}`, true, {
returnOwners: true
returnOwners: true,
returnChildCount: true
})
.then((response) => transformCollectionResponseToCollection(response))
.catch((error) => {
Expand Down Expand Up @@ -102,6 +103,7 @@ export class CollectionsRepository extends ApiRepository implements ICollections
throw error
})
}

public async publishCollection(collectionIdOrAlias: string | number): Promise<void> {
return this.doPost(
`/${this.collectionsResourceName}/${collectionIdOrAlias}/actions/:publish`,
Expand All @@ -113,6 +115,14 @@ export class CollectionsRepository extends ApiRepository implements ICollections
})
}

public async deleteCollection(collectionIdOrAlias: number | string): Promise<void> {
return this.doDelete(`/${this.collectionsResourceName}/${collectionIdOrAlias}`)
.then(() => undefined)
.catch((error) => {
throw error
})
}

public async getCollectionUserPermissions(
collectionIdOrAlias: number | string
): Promise<CollectionUserPermissions> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface CollectionPayload {
dataverseType: string
isMetadataBlockRoot: boolean
isFacetRoot: boolean
childCount: number
}

export interface CollectionInputLevelPayload {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const transformPayloadToCollection = (collectionPayload: CollectionPayload): Col
isMetadataBlockRoot: collectionPayload.isMetadataBlockRoot,
isFacetRoot: collectionPayload.isFacetRoot,
description: collectionPayload.description,
childCount: collectionPayload.childCount,
...(collectionPayload.isPartOf && {
isPartOf: transformPayloadToOwnerNode(collectionPayload.isPartOf)
}),
Expand Down
55 changes: 55 additions & 0 deletions test/functional/collections/DeleteCollection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
ApiConfig,
ReadError,
WriteError,
createCollection,
deleteCollection,
getCollection
} from '../../../src'
import { TestConstants } from '../../testHelpers/TestConstants'
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
import { createCollectionDTO } from '../../testHelpers/collections/collectionHelper'

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

test('should successfully delete a collection', async () => {
const testCollectionAlias = 'deleteCollection-functional-test'
const testNewCollection = createCollectionDTO(testCollectionAlias)
await createCollection.execute(testNewCollection)

expect.assertions(1)
try {
await deleteCollection.execute(testCollectionAlias)
} catch (error) {
throw new Error('Collection should be deleted')
} finally {
const expectedError = new ReadError(
`[404] Can't find dataverse with identifier='${testCollectionAlias}'`
)
await expect(getCollection.execute(testCollectionAlias)).rejects.toThrow(expectedError)
}
})

test('should throw an error when the collection does not exist', async () => {
expect.assertions(2)
let writeError: WriteError | undefined = undefined
try {
await deleteCollection.execute(TestConstants.TEST_DUMMY_COLLECTION_ID)
throw new Error('Use case should throw an error')
} catch (error) {
writeError = error as WriteError
} finally {
expect(writeError).toBeInstanceOf(WriteError)
expect(writeError?.message).toEqual(
`There was an error when writing the resource. Reason was: [404] Can't find dataverse with identifier='${TestConstants.TEST_DUMMY_COLLECTION_ID}'`
)
}
})
})
59 changes: 59 additions & 0 deletions test/integration/collections/CollectionsRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('CollectionsRepository', () => {
const sut: CollectionsRepository = new CollectionsRepository()
let testCollectionId: number
const currentYear = new Date().getFullYear()

beforeAll(async () => {
ApiConfig.init(
TestConstants.TEST_API_URL,
Expand Down Expand Up @@ -127,6 +128,34 @@ describe('CollectionsRepository', () => {
)
})
})

test('should return childCount correctly', async () => {
const parentCollectionAlias = 'childCountTestCollection'
const childCollectionAlias = 'childCountTestChildCollection'

await createCollectionViaApi(parentCollectionAlias, ROOT_COLLECTION_ALIAS)
await createCollectionViaApi(childCollectionAlias, parentCollectionAlias)
const { numericId: childDatasetNumericId } = await createDataset.execute(
TestConstants.TEST_NEW_DATASET_DTO,
parentCollectionAlias
)

const actual = await sut.getCollection(parentCollectionAlias)

expect(actual.childCount).toBe(2)

await deleteCollectionViaApi(childCollectionAlias)

const actualAfterDeletion = await sut.getCollection(parentCollectionAlias)

expect(actualAfterDeletion.childCount).toBe(1)

await deleteUnpublishedDatasetViaApi(childDatasetNumericId)

const actualAfterDatasetDeletion = await sut.getCollection(parentCollectionAlias)

expect(actualAfterDatasetDeletion.childCount).toBe(0)
})
})

describe('publishCollection', () => {
Expand All @@ -146,6 +175,7 @@ describe('CollectionsRepository', () => {
expect(createdCollection.name).toBe(newCollectionDTO.name)
})
})

describe('createCollection', () => {
const testCreateCollectionAlias1 = 'createCollection-test-1'
const testCreateCollectionAlias2 = 'createCollection-test-2'
Expand Down Expand Up @@ -247,6 +277,35 @@ describe('CollectionsRepository', () => {
})
})

describe('deleteCollection', () => {
test('should delete collection successfully', async () => {
const collectionAlias = 'deleteCollection-test'
const collectionDTO = createCollectionDTO(collectionAlias)
await sut.createCollection(collectionDTO)

const createdCollection = await sut.getCollection(collectionAlias)

expect(createdCollection.alias).toBe(collectionAlias)

const deleteResult = await sut.deleteCollection(collectionAlias)
const expectedError = new ReadError(
`[404] Can't find dataverse with identifier='${collectionAlias}'`
)

expect(deleteResult).toBeUndefined()
await expect(sut.getCollection(collectionAlias)).rejects.toThrow(expectedError)
})

test('should return error when collection does not exist', async () => {
const expectedError = new WriteError(
`[404] Can't find dataverse with identifier='${TestConstants.TEST_DUMMY_COLLECTION_ID}'`
)
await expect(sut.deleteCollection(TestConstants.TEST_DUMMY_COLLECTION_ID)).rejects.toThrow(
expectedError
)
})
})

describe('getCollectionFacets', () => {
test('should return collection facets given a valid collection alias', async () => {
const actual = await sut.getCollectionFacets(testCollectionAlias)
Expand Down
6 changes: 4 additions & 2 deletions test/testHelpers/collections/collectionHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export const createCollectionModel = (): Collection => {
}
],
isMetadataBlockRoot: true,
isFacetRoot: true
isFacetRoot: true,
childCount: 0
}
return collectionModel
}
Expand Down Expand Up @@ -75,7 +76,8 @@ export const createCollectionPayload = (): CollectionPayload => {
}
],
isMetadataBlockRoot: true,
isFacetRoot: true
isFacetRoot: true,
childCount: 0
}
return collectionPayload
}
Expand Down
3 changes: 2 additions & 1 deletion test/unit/auth/BearerTokenMechanism.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ describe('BearerTokenMechanism', () => {
const expectedRequestConfigBearerToken = {
headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_BEARER_TOKEN.headers,
params: {
returnOwners: true
returnOwners: true,
returnChildCount: true
}
}

Expand Down
Loading