diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index fc9b6dda..efd38b2d 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -39,4 +39,5 @@ export interface IFilesRepository { getFileDataTables(fileId: number | string): Promise; getFile(fileId: number | string, datasetVersionId: string): Promise; + getFileCitation(fileId: number | string, datasetVersionId: string, includeDeaccessioned: boolean): Promise; } diff --git a/src/files/domain/useCases/GetFileCitation.ts b/src/files/domain/useCases/GetFileCitation.ts new file mode 100644 index 00000000..2daccc1d --- /dev/null +++ b/src/files/domain/useCases/GetFileCitation.ts @@ -0,0 +1,19 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IFilesRepository } from '../repositories/IFilesRepository'; +import { DatasetNotNumberedVersion } from '../../../datasets'; + +export class GetFileCitation implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute( + fileId: number, + datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST, + includeDeaccessioned = false, + ): Promise { + return await this.filesRepository.getFileCitation(fileId, datasetVersionId, includeDeaccessioned); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index 45b8f270..c50c3785 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -6,6 +6,7 @@ import { GetFileUserPermissions } from './domain/useCases/GetFileUserPermissions import { GetFileDataTables } from './domain/useCases/GetFileDataTables'; import { GetDatasetFilesTotalDownloadSize } from './domain/useCases/GetDatasetFilesTotalDownloadSize'; import { GetFile } from './domain/useCases/GetFile'; +import { GetFileCitation } from './domain/useCases/GetFileCitation'; const filesRepository = new FilesRepository(); @@ -16,6 +17,7 @@ const getFileUserPermissions = new GetFileUserPermissions(filesRepository); const getFileDataTables = new GetFileDataTables(filesRepository); const getDatasetFilesTotalDownloadSize = new GetDatasetFilesTotalDownloadSize(filesRepository); const getFile = new GetFile(filesRepository); +const getFileCitation = new GetFileCitation(filesRepository); export { getDatasetFiles, @@ -25,6 +27,7 @@ export { getDatasetFileCounts, getDatasetFilesTotalDownloadSize, getFile, + getFileCitation, }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index dff4031b..25a54296 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -153,6 +153,22 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { }); } + public async getFileCitation( + fileId: number | string, + datasetVersionId: string, + includeDeaccessioned: boolean, + ): Promise { + return this.doGet( + this.buildApiEndpoint(this.filesResourceName, `versions/${datasetVersionId}/citation`, fileId), + true, + { includeDeaccessioned: includeDeaccessioned }, + ) + .then((response) => response.data.data.message) + .catch((error) => { + throw error; + }); + } + private getFileEndpoint(fileId: number | string, datasetVersionId: string): string { if (datasetVersionId === DatasetNotNumberedVersion.DRAFT) { return this.buildApiEndpoint(this.filesResourceName, 'draft', fileId); diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 3ce4fe82..ff14e769 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -81,11 +81,6 @@ describe('DatasetsRepository', () => { expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_1_ID); }); - test('should return dataset when it exists filtering by id and version id', async () => { - const actual = await sut.getDataset(TestConstants.TEST_CREATED_DATASET_1_ID, latestVersionId, false); - expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_1_ID); - }); - test('should return dataset when it is deaccessioned and includeDeaccessioned param is set', async () => { await publishDatasetViaApi(TestConstants.TEST_CREATED_DATASET_2_ID) .then() @@ -238,7 +233,7 @@ describe('DatasetsRepository', () => { assert.match(actual.length, 1); assert.match(actual[0].lockType, DatasetLockType.FINALIZE_PUBLICATION); assert.match(actual[0].userId, 'dataverseAdmin'); - assert.match(actual[0].message, 'Publishing the dataset; Validating Datafiles Asynchronously'); + assert.match(actual[0].message, 'Publishing the dataset; Registering PIDs for Datafiles; Validating Datafiles Asynchronously'); }); test('should return error when dataset does not exist', async () => { diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index e0d9897c..7241f163 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -11,6 +11,7 @@ import { DatasetNotNumberedVersion } from '../../../src/datasets'; import { FileCounts } from '../../../src/files/domain/models/FileCounts'; import { FileDownloadSizeMode } from '../../../src'; import { fail } from 'assert'; +import {deaccessionDatasetViaApi, publishDatasetViaApi, waitForNoLocks} from "../../testHelpers/datasets/datasetHelper"; describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository(); @@ -519,4 +520,51 @@ describe('FilesRepository', () => { }); }); }); + describe('getFileCitation', () => { + test('should return citation when file exists', async () => { + const actualFileCitation = await sut.getFileCitation( + testFileId, + DatasetNotNumberedVersion.LATEST, + false, + ); + expect(typeof actualFileCitation).to.be.a('string'); + }); + + test('should return citation when dataset is deaccessioned', async () => { + await publishDatasetViaApi(TestConstants.TEST_CREATED_DATASET_1_ID) + .then() + .catch(() => { + assert.fail('Error while publishing test Dataset'); + }); + + await waitForNoLocks(TestConstants.TEST_CREATED_DATASET_1_ID, 10) + .then() + .catch(() => { + assert.fail('Error while waiting for no locks'); + }); + + await deaccessionDatasetViaApi(TestConstants.TEST_CREATED_DATASET_1_ID, '1.0') + .then() + .catch(() => { + assert.fail('Error while deaccessioning test Dataset'); + }); + + const actualFileCitation = await sut.getFileCitation( + testFileId, + DatasetNotNumberedVersion.LATEST, + true, + ); + expect(typeof actualFileCitation).to.be.a('string'); + }); + + test('should return error when file does not exist', async () => { + let error: ReadError = undefined; + await sut.getFileCitation(nonExistentFiledId, DatasetNotNumberedVersion.LATEST, false).catch((e) => (error = e)); + + assert.match( + error.message, + `There was an error when reading the resource. Reason was: [404] File with ID ${nonExistentFiledId} not found.`, + ); + }); + }); }); diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index 4348d1d6..38cd4d92 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -874,4 +874,57 @@ describe('FilesRepository', () => { }); }); }); + + describe('getFileCitation', () => { + const testIncludeDeaccessioned = true; + const testCitation = 'test citation'; + const testCitationSuccessfulResponse = { + data: { + status: 'OK', + data: { + message: testCitation, + }, + }, + }; + test('should return citation when response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testCitationSuccessfulResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/versions/${DatasetNotNumberedVersion.LATEST}/citation`; + + // API Key auth + let actual = await sut.getFileCitation(testFile.id, DatasetNotNumberedVersion.LATEST, testIncludeDeaccessioned); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY_INCLUDE_DEACCESSIONED, + ); + assert.match(actual, testCitation); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileCitation(testFile.id, DatasetNotNumberedVersion.LATEST, testIncludeDeaccessioned); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE_INCLUDE_DEACCESSIONED, + ); + assert.match(actual, testCitation); + }); + + test('should return error on repository read error', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileCitation(1, DatasetNotNumberedVersion.LATEST, testIncludeDeaccessioned).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/${testFile.id}/versions/${DatasetNotNumberedVersion.LATEST}/citation`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY_INCLUDE_DEACCESSIONED, + ); + expect(error).to.be.instanceOf(Error); + }); + }); }); diff --git a/test/unit/files/GetFileCitation.test.ts b/test/unit/files/GetFileCitation.test.ts new file mode 100644 index 00000000..ac40b547 --- /dev/null +++ b/test/unit/files/GetFileCitation.test.ts @@ -0,0 +1,39 @@ +import {assert, createSandbox, SinonSandbox} from "sinon"; +import {DatasetNotNumberedVersion, ReadError} from "../../../src"; +import {IFilesRepository} from "../../../src/files/domain/repositories/IFilesRepository"; +import {GetFileCitation} from "../../../src/files/domain/useCases/GetFileCitation"; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + const testId = 1; + + afterEach(() => { + sandbox.restore(); + }); + + test('should return successful result with file citation on repository success', async () => { + const testCitation = 'test citation'; + const filesRepositoryStub = {}; + const getFileCitation = sandbox.stub().returns(testCitation); + filesRepositoryStub.getFileCitation = getFileCitation; + + const sut = new GetFileCitation(filesRepositoryStub); + + const actual = await sut.execute(testId); + + assert.match(actual, testCitation); + assert.calledWithExactly(getFileCitation, testId, DatasetNotNumberedVersion.LATEST, false); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.getFileCitation = sandbox.stub().throwsException(testReadError); + const sut = new GetFileCitation(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute(testId).catch((e) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +}) \ No newline at end of file