diff --git a/docs/useCases.md b/docs/useCases.md index f255c84e..d7b7ac2b 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -39,6 +39,7 @@ The different use cases currently available in the package are classified below, - [Update a Dataset](#update-a-dataset) - [Publish a Dataset](#publish-a-dataset) - [Deaccession a Dataset](#deaccession-a-dataset) + - [Delete a Draft Dataset](#delete-a-draft-dataset) - [Files](#Files) - [Files read use cases](#files-read-use-cases) - [Get a File](#get-a-file) @@ -837,6 +838,30 @@ The `version` parameter should be a string or a [DatasetNotNumberedVersion](../s You cannot deaccession a dataset more than once. If you call this endpoint twice for the same dataset version, you will get a not found error on the second call, since the dataset you are looking for will no longer be published since it is already deaccessioned. +#### Delete a Draft Dataset + +Delete a Draft Dataset, given its identifier. + +##### Example call: + +```typescript +import { deleteDatasetDraft } from '@iqss/dataverse-client-javascript' + +/* ... */ + +const datasetId = 1 + +deleteDatasetDraft.execute(datasetId) + +/* ... */ +``` + +_See [use case](../src/datasets/domain/useCases/DeleteDatasetDraft.ts) implementation_. + +The `datasetId` parameter is a number for numeric identifiers or string for persistent identifiers. + +If you try to delete a dataset without draft version, you will get a not found error. + #### Get Download Count of a Dataset Total number of downloads requested for a dataset, given a dataset numeric identifier, diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 4b00ee4b..66fa4587 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -60,4 +60,5 @@ export interface IDatasetsRepository { includeMDC?: boolean ): Promise getDatasetVersionsSummaries(datasetId: number | string): Promise + deleteDatasetDraft(datasetId: number | string): Promise } diff --git a/src/datasets/domain/useCases/DeleteDatasetDraft.ts b/src/datasets/domain/useCases/DeleteDatasetDraft.ts new file mode 100644 index 00000000..86f66a99 --- /dev/null +++ b/src/datasets/domain/useCases/DeleteDatasetDraft.ts @@ -0,0 +1,19 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { IDatasetsRepository } from '../repositories/IDatasetsRepository' + +export class DeleteDatasetDraft implements UseCase { + private datasetsRepository: IDatasetsRepository + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository + } + + /** + * Delete a Draft Dataset + * + * @param {number | string} [datasetId] - The dataset identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers). + */ + async execute(datasetId: number | string): Promise { + return this.datasetsRepository.deleteDatasetDraft(datasetId) + } +} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index bd5592b4..ba1fe5d5 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -19,6 +19,7 @@ import { GetDatasetVersionDiff } from './domain/useCases/GetDatasetVersionDiff' import { DeaccessionDataset } from './domain/useCases/DeaccessionDataset' import { GetDatasetDownloadCount } from './domain/useCases/GetDatasetDownloadCount' import { GetDatasetVersionsSummaries } from './domain/useCases/GetDatasetVersionsSummaries' +import { DeleteDatasetDraft } from './domain/useCases/DeleteDatasetDraft' const datasetsRepository = new DatasetsRepository() @@ -52,6 +53,7 @@ const updateDataset = new UpdateDataset( const deaccessionDataset = new DeaccessionDataset(datasetsRepository) const getDatasetDownloadCount = new GetDatasetDownloadCount(datasetsRepository) const getDatasetVersionsSummaries = new GetDatasetVersionsSummaries(datasetsRepository) +const deleteDatasetDraft = new DeleteDatasetDraft(datasetsRepository) export { getDataset, @@ -68,7 +70,8 @@ export { updateDataset, deaccessionDataset, getDatasetDownloadCount, - getDatasetVersionsSummaries + getDatasetVersionsSummaries, + deleteDatasetDraft } export { DatasetNotNumberedVersion } from './domain/models/DatasetNotNumberedVersion' export { DatasetUserPermissions } from './domain/models/DatasetUserPermissions' diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 4c069134..036872d6 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -277,4 +277,14 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error }) } + + public async deleteDatasetDraft(datasetId: string | number): Promise { + return this.doDelete( + this.buildApiEndpoint(this.datasetsResourceName, 'versions/:draft', datasetId) + ) + .then(() => undefined) + .catch((error) => { + throw error + }) + } } diff --git a/test/functional/datasets/DeleteDatasetDraft.test.ts b/test/functional/datasets/DeleteDatasetDraft.test.ts new file mode 100644 index 00000000..70135c80 --- /dev/null +++ b/test/functional/datasets/DeleteDatasetDraft.test.ts @@ -0,0 +1,64 @@ +import { createDataset, deleteDatasetDraft } from '../../../src/datasets' +import { ApiConfig, WriteError } from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' + +const testDataset = { + license: { + name: 'CC0 1.0', + uri: 'http://creativecommons.org/publicdomain/zero/1.0', + iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' + }, + metadataBlockValues: [ + { + name: 'citation', + fields: { + title: 'Dataset created using the createDataset use case', + author: [ + { + authorName: 'Admin, Dataverse', + authorAffiliation: 'Dataverse.org' + }, + { + authorName: 'Owner, Dataverse', + authorAffiliation: 'Dataversedemo.org' + } + ], + datasetContact: [ + { + datasetContactEmail: 'finch@mailinator.com', + datasetContactName: 'Finch, Fiona' + } + ], + dsDescription: [ + { + dsDescriptionValue: 'This is the description of the dataset.' + } + ], + subject: ['Medicine, Health and Life Sciences'] + } + } + ] +} + +describe('execute', () => { + beforeEach(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should delete a dataset when it is draft successfully', async () => { + const createdDatasetIdentifiers = await createDataset.execute(testDataset) + + const actual = await deleteDatasetDraft.execute(createdDatasetIdentifiers.numericId) + + expect(actual).toBeUndefined() + }) + + test('should throw an error when the dataset id is incorrect', async () => { + await expect(deleteDatasetDraft.execute(1111)).rejects.toBeInstanceOf(WriteError) + }) +}) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index bd71503b..d497c0a0 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -1336,4 +1336,32 @@ describe('DatasetsRepository', () => { ) }) }) + + describe('deleteDatasetDraft', () => { + test('should delete a draft dataset', async () => { + const testDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO) + + await waitForNoLocks(testDatasetIds.numericId, 10) + + const actual = await sut.deleteDatasetDraft(testDatasetIds.numericId) + + expect(actual).toBeUndefined() + + const expectedError = new ReadError( + `[404] Dataset with ID ${testDatasetIds.numericId} not found.` + ) + + await expect( + sut.getDataset(testDatasetIds.numericId, DatasetNotNumberedVersion.LATEST, false, false) + ).rejects.toThrow(expectedError) + }) + + test('should return error when dataset does not exist', async () => { + const expectedError = new WriteError( + `[404] Dataset with ID ${nonExistentTestDatasetId} not found.` + ) + + await expect(sut.deleteDatasetDraft(nonExistentTestDatasetId)).rejects.toThrow(expectedError) + }) + }) }) diff --git a/test/unit/datasets/DeleteDatasetDraft.test.ts b/test/unit/datasets/DeleteDatasetDraft.test.ts new file mode 100644 index 00000000..4a3b0f77 --- /dev/null +++ b/test/unit/datasets/DeleteDatasetDraft.test.ts @@ -0,0 +1,23 @@ +import { DeleteDatasetDraft } from '../../../src/datasets/domain/useCases/DeleteDatasetDraft' +import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository' +import { WriteError } from '../../../src' + +describe('execute', () => { + test('should return undefined on delete success', async () => { + const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + datasetsRepositoryStub.deleteDatasetDraft = jest.fn().mockResolvedValue(undefined) + const sut = new DeleteDatasetDraft(datasetsRepositoryStub) + + const actual = await sut.execute(1) + expect(actual).toEqual(undefined) + }) + + test('should return error result on delete error', async () => { + const datasetsRepositoryStub: IDatasetsRepository = {} as IDatasetsRepository + datasetsRepositoryStub.deleteDatasetDraft = jest.fn().mockRejectedValue(new WriteError()) + const sut = new DeleteDatasetDraft(datasetsRepositoryStub) + + const nonExistentDatasetId = 111 + await expect(sut.execute(nonExistentDatasetId)).rejects.toThrow(WriteError) + }) +})