From 4cee74848b4333e53d3a41a5ee5a66c6e5f80c5f Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 10 Jul 2023 14:44:41 +0100 Subject: [PATCH 01/25] Stash: getDatasetFiles use case WIP --- .../repositories/DatasetFileOrderCriteria.ts | 8 +++++++ .../repositories/IDatasetsRepository.ts | 9 ++++++++ .../domain/useCases/GetDatasetFiles.ts | 22 +++++++++++++++++++ .../infra/repositories/DatasetsRepository.ts | 14 ++++++++++++ src/files/domain/models/File.ts | 4 ++++ src/files/index.ts | 1 + 6 files changed, 58 insertions(+) create mode 100644 src/datasets/domain/repositories/DatasetFileOrderCriteria.ts create mode 100644 src/datasets/domain/useCases/GetDatasetFiles.ts create mode 100644 src/files/domain/models/File.ts create mode 100644 src/files/index.ts diff --git a/src/datasets/domain/repositories/DatasetFileOrderCriteria.ts b/src/datasets/domain/repositories/DatasetFileOrderCriteria.ts new file mode 100644 index 00000000..42c0cc22 --- /dev/null +++ b/src/datasets/domain/repositories/DatasetFileOrderCriteria.ts @@ -0,0 +1,8 @@ +export enum DatasetFileOrderCriteria { + NAME_AZ = 'NameAZ', + NAME_ZA = 'NameZA', + NEWEST = 'Newest', + OLDEST = 'Oldest', + SIZE = 'Size', + TYPE = 'Type', +} diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 59aae717..c4bf8f57 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -1,4 +1,6 @@ import { Dataset } from '../models/Dataset'; +import { DatasetFileOrderCriteria } from './DatasetFileOrderCriteria'; +import { File } from '../../../files'; export interface IDatasetsRepository { getDatasetSummaryFieldNames(): Promise; @@ -7,4 +9,11 @@ export interface IDatasetsRepository { getPrivateUrlDataset(token: string): Promise; getDatasetCitation(datasetId: number, datasetVersionId?: string): Promise; getPrivateUrlDatasetCitation(token: string): Promise; + getDatasetFiles( + datasetId: string, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: DatasetFileOrderCriteria, + ): Promise; } diff --git a/src/datasets/domain/useCases/GetDatasetFiles.ts b/src/datasets/domain/useCases/GetDatasetFiles.ts new file mode 100644 index 00000000..d41205dd --- /dev/null +++ b/src/datasets/domain/useCases/GetDatasetFiles.ts @@ -0,0 +1,22 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IDatasetsRepository } from '../repositories/IDatasetsRepository'; +import { File } from '../../../files'; +import { DatasetFileOrderCriteria } from '../repositories/DatasetFileOrderCriteria'; + +export class GetDatasetFiles implements UseCase { + private datasetsRepository: IDatasetsRepository; + + constructor(datasetsRepository: IDatasetsRepository) { + this.datasetsRepository = datasetsRepository; + } + + async execute( + datasetId: string, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: DatasetFileOrderCriteria, + ): Promise { + return await this.datasetsRepository.getDatasetFiles(datasetId, datasetVersionId, limit, offset, orderCriteria); + } +} diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 56328fcb..fb1e7416 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -2,6 +2,8 @@ import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'; import { IDatasetsRepository } from '../../domain/repositories/IDatasetsRepository'; import { Dataset } from '../../domain/models/Dataset'; import { transformVersionResponseToDataset } from './transformers/datasetTransformers'; +import { File } from '../../../files'; +import { DatasetFileOrderCriteria } from '../../domain/repositories/DatasetFileOrderCriteria'; export class DatasetsRepository extends ApiRepository implements IDatasetsRepository { DATASET_VERSION_LATEST = ':latest'; @@ -64,4 +66,16 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error; }); } + + public async getDatasetFiles( + datasetId: string, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: DatasetFileOrderCriteria, + ): Promise { + throw new Error( + `Method not implemented. Params: ${datasetId} ${datasetVersionId} ${limit} ${offset} ${orderCriteria}`, + ); + } } diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts new file mode 100644 index 00000000..ba9b1311 --- /dev/null +++ b/src/files/domain/models/File.ts @@ -0,0 +1,4 @@ +export interface File { + id: number; + // TODO +} diff --git a/src/files/index.ts b/src/files/index.ts new file mode 100644 index 00000000..38fa019a --- /dev/null +++ b/src/files/index.ts @@ -0,0 +1 @@ +export { File } from './domain/models/File'; From c9c6fbe8d213d5cb25f6cffa6f15f01023b79107 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 11 Jul 2023 10:14:16 +0100 Subject: [PATCH 02/25] Added: getFilesByDatasetId use case (pending repository impl) --- .../repositories/IDatasetsRepository.ts | 9 ----- .../domain/useCases/GetDatasetFiles.ts | 22 ----------- .../infra/repositories/DatasetsRepository.ts | 14 ------- .../domain/models/FileOrderCriteria.ts} | 2 +- .../domain/repositories/IFilesRepository.ts | 12 ++++++ .../domain/useCases/GetFilesByDatasetId.ts | 22 +++++++++++ src/files/index.ts | 9 +++++ .../infra/repositories/FilesRepository.ts | 18 +++++++++ test/unit/files/GetFilesByDatasetId.test.ts | 38 +++++++++++++++++++ 9 files changed, 100 insertions(+), 46 deletions(-) delete mode 100644 src/datasets/domain/useCases/GetDatasetFiles.ts rename src/{datasets/domain/repositories/DatasetFileOrderCriteria.ts => files/domain/models/FileOrderCriteria.ts} (75%) create mode 100644 src/files/domain/repositories/IFilesRepository.ts create mode 100644 src/files/domain/useCases/GetFilesByDatasetId.ts create mode 100644 src/files/infra/repositories/FilesRepository.ts create mode 100644 test/unit/files/GetFilesByDatasetId.test.ts diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index c4bf8f57..59aae717 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -1,6 +1,4 @@ import { Dataset } from '../models/Dataset'; -import { DatasetFileOrderCriteria } from './DatasetFileOrderCriteria'; -import { File } from '../../../files'; export interface IDatasetsRepository { getDatasetSummaryFieldNames(): Promise; @@ -9,11 +7,4 @@ export interface IDatasetsRepository { getPrivateUrlDataset(token: string): Promise; getDatasetCitation(datasetId: number, datasetVersionId?: string): Promise; getPrivateUrlDatasetCitation(token: string): Promise; - getDatasetFiles( - datasetId: string, - datasetVersionId?: string, - limit?: number, - offset?: number, - orderCriteria?: DatasetFileOrderCriteria, - ): Promise; } diff --git a/src/datasets/domain/useCases/GetDatasetFiles.ts b/src/datasets/domain/useCases/GetDatasetFiles.ts deleted file mode 100644 index d41205dd..00000000 --- a/src/datasets/domain/useCases/GetDatasetFiles.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { UseCase } from '../../../core/domain/useCases/UseCase'; -import { IDatasetsRepository } from '../repositories/IDatasetsRepository'; -import { File } from '../../../files'; -import { DatasetFileOrderCriteria } from '../repositories/DatasetFileOrderCriteria'; - -export class GetDatasetFiles implements UseCase { - private datasetsRepository: IDatasetsRepository; - - constructor(datasetsRepository: IDatasetsRepository) { - this.datasetsRepository = datasetsRepository; - } - - async execute( - datasetId: string, - datasetVersionId?: string, - limit?: number, - offset?: number, - orderCriteria?: DatasetFileOrderCriteria, - ): Promise { - return await this.datasetsRepository.getDatasetFiles(datasetId, datasetVersionId, limit, offset, orderCriteria); - } -} diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index fb1e7416..56328fcb 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -2,8 +2,6 @@ import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'; import { IDatasetsRepository } from '../../domain/repositories/IDatasetsRepository'; import { Dataset } from '../../domain/models/Dataset'; import { transformVersionResponseToDataset } from './transformers/datasetTransformers'; -import { File } from '../../../files'; -import { DatasetFileOrderCriteria } from '../../domain/repositories/DatasetFileOrderCriteria'; export class DatasetsRepository extends ApiRepository implements IDatasetsRepository { DATASET_VERSION_LATEST = ':latest'; @@ -66,16 +64,4 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi throw error; }); } - - public async getDatasetFiles( - datasetId: string, - datasetVersionId?: string, - limit?: number, - offset?: number, - orderCriteria?: DatasetFileOrderCriteria, - ): Promise { - throw new Error( - `Method not implemented. Params: ${datasetId} ${datasetVersionId} ${limit} ${offset} ${orderCriteria}`, - ); - } } diff --git a/src/datasets/domain/repositories/DatasetFileOrderCriteria.ts b/src/files/domain/models/FileOrderCriteria.ts similarity index 75% rename from src/datasets/domain/repositories/DatasetFileOrderCriteria.ts rename to src/files/domain/models/FileOrderCriteria.ts index 42c0cc22..49dc2b73 100644 --- a/src/datasets/domain/repositories/DatasetFileOrderCriteria.ts +++ b/src/files/domain/models/FileOrderCriteria.ts @@ -1,4 +1,4 @@ -export enum DatasetFileOrderCriteria { +export enum FileOrderCriteria { NAME_AZ = 'NameAZ', NAME_ZA = 'NameZA', NEWEST = 'Newest', diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts new file mode 100644 index 00000000..44d04357 --- /dev/null +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -0,0 +1,12 @@ +import { FileOrderCriteria } from '../models/FileOrderCriteria'; +import { File } from '../models/File'; + +export interface IFilesRepository { + getFilesByDatasetId( + datasetId: number, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: FileOrderCriteria, + ): Promise; +} diff --git a/src/files/domain/useCases/GetFilesByDatasetId.ts b/src/files/domain/useCases/GetFilesByDatasetId.ts new file mode 100644 index 00000000..bd79ad36 --- /dev/null +++ b/src/files/domain/useCases/GetFilesByDatasetId.ts @@ -0,0 +1,22 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { FileOrderCriteria } from '../models/FileOrderCriteria'; +import { IFilesRepository } from '../repositories/IFilesRepository'; +import { File } from '../models/File'; + +export class GetFilesByDatasetId implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute( + datasetId: number, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: FileOrderCriteria, + ): Promise { + return await this.filesRepository.getFilesByDatasetId(datasetId, datasetVersionId, limit, offset, orderCriteria); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index 38fa019a..d196117f 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1 +1,10 @@ +import { GetFilesByDatasetId } from './domain/useCases/GetFilesByDatasetId'; +import { FilesRepository } from './infra/repositories/FilesRepository'; + +const filesRepository = new FilesRepository(); +const getFilesByDatasetId = new GetFilesByDatasetId(filesRepository); + +export { getFilesByDatasetId }; + export { File } from './domain/models/File'; +export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts new file mode 100644 index 00000000..d2f7bc09 --- /dev/null +++ b/src/files/infra/repositories/FilesRepository.ts @@ -0,0 +1,18 @@ +import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'; +import { IFilesRepository } from '../../domain/repositories/IFilesRepository'; +import { FileOrderCriteria } from '../../domain/models/FileOrderCriteria'; +import { File } from '../../domain/models/File'; + +export class FilesRepository extends ApiRepository implements IFilesRepository { + public async getFilesByDatasetId( + datasetId: number, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: FileOrderCriteria, + ): Promise { + throw new Error( + `Method not implemented. Params: ${datasetId} ${datasetVersionId} ${limit} ${offset} ${orderCriteria}`, + ); + } +} diff --git a/test/unit/files/GetFilesByDatasetId.test.ts b/test/unit/files/GetFilesByDatasetId.test.ts new file mode 100644 index 00000000..50f18143 --- /dev/null +++ b/test/unit/files/GetFilesByDatasetId.test.ts @@ -0,0 +1,38 @@ +import { GetFilesByDatasetId } from '../../../src/files/domain/useCases/GetFilesByDatasetId'; +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { File } from '../../../src/files/domain/models/File'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + test('should return files on repository success', async () => { + const testFiles : File[] = []; + const filesRepositoryStub = {}; + const getFilesByDatasetIdStub = sandbox.stub().returns(testFiles); + filesRepositoryStub.getFilesByDatasetId = getFilesByDatasetIdStub; + const sut = new GetFilesByDatasetId(filesRepositoryStub); + + const actual = await sut.execute(1); + + assert.match(actual, testFiles); + assert.calledWithExactly(getFilesByDatasetIdStub, 1, undefined, undefined, undefined, undefined); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.getFilesByDatasetId = sandbox.stub().throwsException(testReadError); + const sut = new GetFilesByDatasetId(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute(1).catch((e) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +}); From cd2039c5099a33e3b04df0236b43e1d96e9336d5 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 11 Jul 2023 15:59:26 +0100 Subject: [PATCH 03/25] Stash: FilesRepository getFilesByDatasetId impl WIP --- src/core/infra/repositories/ApiRepository.ts | 11 ++- .../infra/repositories/FilesRepository.ts | 39 +++++++- test/testHelpers/TestConstants.ts | 3 + test/unit/files/FilesRepository.test.ts | 91 +++++++++++++++++++ 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 test/unit/files/FilesRepository.test.ts diff --git a/src/core/infra/repositories/ApiRepository.ts b/src/core/infra/repositories/ApiRepository.ts index 56d1d1bf..a1d1e0e4 100644 --- a/src/core/infra/repositories/ApiRepository.ts +++ b/src/core/infra/repositories/ApiRepository.ts @@ -4,9 +4,9 @@ import { ReadError } from '../../domain/repositories/ReadError'; import { WriteError } from '../../domain/repositories/WriteError'; export abstract class ApiRepository { - public async doGet(apiEndpoint: string, authRequired = false): Promise { + public async doGet(apiEndpoint: string, authRequired = false, queryParams: object = {}): Promise { return await axios - .get(this.buildRequestUrl(apiEndpoint), this.buildRequestConfig(authRequired)) + .get(this.buildRequestUrl(apiEndpoint), this.buildRequestConfig(authRequired, queryParams)) .then((response) => response) .catch((error) => { throw new ReadError( @@ -15,9 +15,9 @@ export abstract class ApiRepository { }); } - public async doPost(apiEndpoint: string, data: string | object): Promise { + public async doPost(apiEndpoint: string, data: string | object, queryParams: object = {}): Promise { return await axios - .post(this.buildRequestUrl(apiEndpoint), JSON.stringify(data), this.buildRequestConfig(true)) + .post(this.buildRequestUrl(apiEndpoint), JSON.stringify(data), this.buildRequestConfig(true, queryParams)) .then((response) => response) .catch((error) => { throw new WriteError( @@ -26,8 +26,9 @@ export abstract class ApiRepository { }); } - private buildRequestConfig(authRequired: boolean): AxiosRequestConfig { + private buildRequestConfig(authRequired: boolean, queryParams: object): AxiosRequestConfig { const requestConfig: AxiosRequestConfig = { + params: queryParams, headers: { 'Content-Type': 'application/json' }, }; if (!authRequired) { diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index d2f7bc09..365e371f 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -3,7 +3,16 @@ import { IFilesRepository } from '../../domain/repositories/IFilesRepository'; import { FileOrderCriteria } from '../../domain/models/FileOrderCriteria'; import { File } from '../../domain/models/File'; +export interface GetFilesQueryParams { + limit?: number; + offset?: number; + orderCriteria?: string; +} + export class FilesRepository extends ApiRepository implements IFilesRepository { + // TODO Shared with DatasetsRepository so move somewhere + DATASET_VERSION_LATEST = ':latest'; + public async getFilesByDatasetId( datasetId: number, datasetVersionId?: string, @@ -11,8 +20,32 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { offset?: number, orderCriteria?: FileOrderCriteria, ): Promise { - throw new Error( - `Method not implemented. Params: ${datasetId} ${datasetVersionId} ${limit} ${offset} ${orderCriteria}`, - ); + if (datasetVersionId === undefined) { + datasetVersionId = this.DATASET_VERSION_LATEST; + } + return this.getFiles(`/datasets/${datasetId}/versions/${datasetVersionId}/files`, limit, offset, orderCriteria); + } + + private async getFiles( + endpoint: string, + limit?: number, + offset?: number, + orderCriteria?: FileOrderCriteria, + ): Promise { + const queryParams: GetFilesQueryParams = {}; + if (limit !== undefined) { + queryParams.limit = limit; + } + if (offset !== undefined) { + queryParams.offset = offset; + } + if (orderCriteria !== undefined) { + queryParams.orderCriteria = orderCriteria.toString(); + } + return this.doGet(endpoint, true, queryParams) + .then(() => []) + .catch((error) => { + throw error; + }); } } diff --git a/test/testHelpers/TestConstants.ts b/test/testHelpers/TestConstants.ts index 82c792c9..3d4c27a7 100644 --- a/test/testHelpers/TestConstants.ts +++ b/test/testHelpers/TestConstants.ts @@ -8,6 +8,7 @@ export class TestConstants { }, }; static readonly TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY = { + params: {}, headers: { 'Content-Type': 'application/json', 'X-Dataverse-Key': TestConstants.TEST_DUMMY_API_KEY, @@ -15,11 +16,13 @@ export class TestConstants { }; static readonly TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE = { withCredentials: true, + params: {}, headers: { 'Content-Type': 'application/json', }, }; static readonly TEST_EXPECTED_UNAUTHENTICATED_REQUEST_CONFIG = { + params: {}, headers: { 'Content-Type': 'application/json', }, diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts new file mode 100644 index 00000000..73a4c61e --- /dev/null +++ b/test/unit/files/FilesRepository.test.ts @@ -0,0 +1,91 @@ +import { FilesRepository } from '../../../src/files/infra/repositories/FilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import axios from 'axios'; +import { expect } from 'chai'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { ApiConfig, DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'; +import { TestConstants } from '../../testHelpers/TestConstants'; +import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; + +describe('FilesRepository', () => { + const sandbox: SinonSandbox = createSandbox(); + const sut: FilesRepository = new FilesRepository(); + // TODO: Add actual file payload + const testFilesSuccessfulResponse = { + data: { + status: 'OK', + data: [{}], + }, + }; + const testDatasetId = 1; + + beforeEach(() => { + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, TestConstants.TEST_DUMMY_API_KEY); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getFilesByDatasetId', () => { + test('should return files on successful response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); + + const actual = await sut.getFilesByDatasetId(testDatasetId); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, []); + }); + + test('should return files when providing id, optional params, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); + + const testVersionId = ':draft'; + const testLimit = 10; + const testOffset = 20; + const testFileOrderCriteria = FileOrderCriteria.NEWEST; + + const actual = await sut.getFilesByDatasetId( + testDatasetId, + testVersionId, + testLimit, + testOffset, + testFileOrderCriteria, + ); + + const expectedRequestConfig = { + params: { + limit: testLimit, + offset: testOffset, + orderCriteria: testFileOrderCriteria.toString(), + }, + headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers, + }; + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/${testVersionId}/files`, + expectedRequestConfig, + ); + assert.match(actual, []); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFilesByDatasetId(testDatasetId).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); +}); From a835cc32c67122dc4f97d1380e095841ade69fbf Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 11 Jul 2023 19:06:48 +0100 Subject: [PATCH 04/25] Added: file model attributes --- src/files/domain/models/File.ts | 37 ++++++++++++++++++++- test/testHelpers/files/filesHelper.ts | 18 ++++++++++ test/unit/files/GetFilesByDatasetId.test.ts | 3 +- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/testHelpers/files/filesHelper.ts diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index ba9b1311..496515fa 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -1,4 +1,39 @@ export interface File { id: number; - // TODO + persistentId: string; + name: string; + pidURL?: string; + sizeBytes: number; + version: number; + description?: string; + restricted: boolean; + directoryLabel?: string; + datasetVersionId?: number; + categories?: string[]; + contentType: string; + embargo?: FileEmbargo; + storageIdentifier?: string; + originalFormat?: string; + originalFormatLabel?: string; + originalSize?: number; + originalName?: string; + UNF?: string; + rootDataFileId?: number; + previousDataFileId?: number; + md5?: string; + checksum?: FileChecksum; + metadataId?: number; + tabularTags?: string[]; + creationDate?: Date; + publicationDate?: Date; +} + +export interface FileEmbargo { + dateAvailable: Date; + reason?: string; +} + +export interface FileChecksum { + type: string; + value: string; } diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts new file mode 100644 index 00000000..d556eabf --- /dev/null +++ b/test/testHelpers/files/filesHelper.ts @@ -0,0 +1,18 @@ +import { File } from '../../../src/files/domain/models/File'; + +export const createFileModel = (): File => { + return { + id: 1, + persistentId: '', + name: 'test', + sizeBytes: 127426, + version: 1, + restricted: false, + contentType: 'image/png', + }; +}; + +export const createFilePayload = (): any => { + // TODO + return {}; +}; diff --git a/test/unit/files/GetFilesByDatasetId.test.ts b/test/unit/files/GetFilesByDatasetId.test.ts index 50f18143..fb79c0b9 100644 --- a/test/unit/files/GetFilesByDatasetId.test.ts +++ b/test/unit/files/GetFilesByDatasetId.test.ts @@ -3,6 +3,7 @@ import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesR import { assert, createSandbox, SinonSandbox } from 'sinon'; import { ReadError } from '../../../src/core/domain/repositories/ReadError'; import { File } from '../../../src/files/domain/models/File'; +import { createFileModel } from '../../testHelpers/files/filesHelper'; describe('execute', () => { const sandbox: SinonSandbox = createSandbox(); @@ -12,7 +13,7 @@ describe('execute', () => { }); test('should return files on repository success', async () => { - const testFiles : File[] = []; + const testFiles : File[] = [createFileModel()]; const filesRepositoryStub = {}; const getFilesByDatasetIdStub = sandbox.stub().returns(testFiles); filesRepositoryStub.getFilesByDatasetId = getFilesByDatasetIdStub; From d1f24451d5ebdbf1487669ad1925a9d7585461c0 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 12 Jul 2023 14:44:40 +0100 Subject: [PATCH 05/25] Stash: files response transforming WIP --- .../infra/repositories/FilesRepository.ts | 3 +- .../transformers/fileTransformers.ts | 94 +++++++++++++++++++ test/testHelpers/files/filesHelper.ts | 37 +++++++- test/unit/files/FilesRepository.test.ts | 11 ++- 4 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 src/files/infra/repositories/transformers/fileTransformers.ts diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 365e371f..a8495297 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -2,6 +2,7 @@ import { ApiRepository } from '../../../core/infra/repositories/ApiRepository'; import { IFilesRepository } from '../../domain/repositories/IFilesRepository'; import { FileOrderCriteria } from '../../domain/models/FileOrderCriteria'; import { File } from '../../domain/models/File'; +import { transformFilesResponseToFiles } from './transformers/fileTransformers'; export interface GetFilesQueryParams { limit?: number; @@ -43,7 +44,7 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { queryParams.orderCriteria = orderCriteria.toString(); } return this.doGet(endpoint, true, queryParams) - .then(() => []) + .then((response) => transformFilesResponseToFiles(response)) .catch((error) => { throw error; }); diff --git a/src/files/infra/repositories/transformers/fileTransformers.ts b/src/files/infra/repositories/transformers/fileTransformers.ts new file mode 100644 index 00000000..6ed33aa6 --- /dev/null +++ b/src/files/infra/repositories/transformers/fileTransformers.ts @@ -0,0 +1,94 @@ +import { File } from '../../../domain/models/File'; +import { AxiosResponse } from 'axios'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const transformFilesResponseToFiles = (response: AxiosResponse): File[] => { + const files: File[] = []; + const filesPayload = response.data.data; + filesPayload.forEach(function (filePayload: any) { + files.push(transformFilePayloadToFile(filePayload)); + }); + return files; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformFilePayloadToFile = (filePayload: any): File => { + return { + id: filePayload.dataFile.id, + persistentId: filePayload.dataFile.persistentId, + name: filePayload.dataFile.filename, + pidURL: filePayload.dataFile.pidURL, + sizeBytes: filePayload.dataFile.filesize, + version: 1, + description: filePayload.dataFile.description, + // + restricted: false, + contentType: 'image/png', + storageIdentifier: 'local://18945a85439-9fa52783e5cb', + rootDataFileId: 4, + previousDataFileId: 4, + md5: '29e413e0c881e17314ce8116fed4d1a7', + checksum: { + type: 'md5', + value: '29e413e0c881e17314ce8116fed4d1a7', + }, + metadataId: 4, + creationDate: new Date('2023-07-11'), + }; +}; + +// MODEL +// id: number; +// persistentId: string; +// name: string; +// pidURL?: string; +// sizeBytes: number; +// version: number; +// description?: string; +// restricted: boolean; +// directoryLabel?: string; +// datasetVersionId?: number; +// categories?: string[]; +// contentType: string; +// embargo?: FileEmbargo; +// storageIdentifier?: string; +// originalFormat?: string; +// originalFormatLabel?: string; +// originalSize?: number; +// originalName?: string; +// UNF?: string; +// rootDataFileId?: number; +// previousDataFileId?: number; +// md5?: string; +// checksum?: FileChecksum; +// metadataId?: number; +// tabularTags?: string[]; +// creationDate?: Date; +// publicationDate?: Date; + +// PAYLOAD +// { +// label: 'test', +// restricted: false, +// version: 1, +// datasetVersionId: 2, +// dataFile: { +// id: 5, +// persistentId: '', +// filename: 'test', +// contentType: 'image/png', +// filesize: 127426, +// restricted: false, +// storageIdentifier: 'local://18945a85439-9fa52783e5cb', +// rootDataFileId: 4, +// previousDataFileId: 4, +// md5: '29e413e0c881e17314ce8116fed4d1a7', +// checksum: { +// type: 'MD5', +// value: '29e413e0c881e17314ce8116fed4d1a7', +// }, +// fileMetadataId: 4, +// creationDate: '2023-07-11', +// varGroups: [], +// }, +// }; diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index d556eabf..644c6585 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -9,10 +9,43 @@ export const createFileModel = (): File => { version: 1, restricted: false, contentType: 'image/png', + storageIdentifier: 'local://18945a85439-9fa52783e5cb', + rootDataFileId: 4, + previousDataFileId: 4, + md5: '29e413e0c881e17314ce8116fed4d1a7', + checksum: { + type: 'md5', + value: '29e413e0c881e17314ce8116fed4d1a7', + }, + metadataId: 4, + creationDate: new Date('2023-07-11'), }; }; export const createFilePayload = (): any => { - // TODO - return {}; + return { + label: 'test', + restricted: false, + version: 1, + datasetVersionId: 2, + dataFile: { + id: 5, + persistentId: '', + filename: 'test', + contentType: 'image/png', + filesize: 127426, + restricted: false, + storageIdentifier: 'local://18945a85439-9fa52783e5cb', + rootDataFileId: 4, + previousDataFileId: 4, + md5: '29e413e0c881e17314ce8116fed4d1a7', + checksum: { + type: 'MD5', + value: '29e413e0c881e17314ce8116fed4d1a7', + }, + fileMetadataId: 4, + creationDate: '2023-07-11', + varGroups: [], + }, + }; }; diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index 73a4c61e..b7d9589f 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -6,18 +6,19 @@ import { ReadError } from '../../../src/core/domain/repositories/ReadError'; import { ApiConfig, DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'; import { TestConstants } from '../../testHelpers/TestConstants'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; +import { createFilePayload, createFileModel } from '../../testHelpers/files/filesHelper'; describe('FilesRepository', () => { const sandbox: SinonSandbox = createSandbox(); const sut: FilesRepository = new FilesRepository(); - // TODO: Add actual file payload const testFilesSuccessfulResponse = { data: { status: 'OK', - data: [{}], + data: [createFilePayload()], }, }; const testDatasetId = 1; + const testFile = createFileModel(); beforeEach(() => { ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, TestConstants.TEST_DUMMY_API_KEY); @@ -38,7 +39,7 @@ describe('FilesRepository', () => { `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, ); - assert.match(actual, []); + assert.match(actual, [testFile]); }); test('should return files when providing id, optional params, and response is successful', async () => { @@ -48,7 +49,7 @@ describe('FilesRepository', () => { const testLimit = 10; const testOffset = 20; const testFileOrderCriteria = FileOrderCriteria.NEWEST; - + const actual = await sut.getFilesByDatasetId( testDatasetId, testVersionId, @@ -71,7 +72,7 @@ describe('FilesRepository', () => { `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/${testVersionId}/files`, expectedRequestConfig, ); - assert.match(actual, []); + assert.match(actual, [testFile]); }); test('should return error result on error response', async () => { From 0ef3b9033008222152d012397b8d011b881dc9db Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 17 Jul 2023 11:03:11 +0100 Subject: [PATCH 06/25] Added: files response transforming --- src/files/index.ts | 2 +- .../transformers/fileTransformers.ts | 113 +++++++----------- .../datasets/DatasetsRepository.test.ts | 3 +- test/testHelpers/files/filesHelper.ts | 22 ++-- test/unit/files/FilesRepository.test.ts | 22 +++- 5 files changed, 77 insertions(+), 85 deletions(-) diff --git a/src/files/index.ts b/src/files/index.ts index d196117f..bddb3fcc 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -6,5 +6,5 @@ const getFilesByDatasetId = new GetFilesByDatasetId(filesRepository); export { getFilesByDatasetId }; -export { File } from './domain/models/File'; +export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/transformers/fileTransformers.ts b/src/files/infra/repositories/transformers/fileTransformers.ts index 6ed33aa6..e042f7f4 100644 --- a/src/files/infra/repositories/transformers/fileTransformers.ts +++ b/src/files/infra/repositories/transformers/fileTransformers.ts @@ -1,10 +1,10 @@ -import { File } from '../../../domain/models/File'; +import { File, FileEmbargo, FileChecksum } from '../../../domain/models/File'; import { AxiosResponse } from 'axios'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any export const transformFilesResponseToFiles = (response: AxiosResponse): File[] => { const files: File[] = []; const filesPayload = response.data.data; + // eslint-disable-next-line @typescript-eslint/no-explicit-any filesPayload.forEach(function (filePayload: any) { files.push(transformFilePayloadToFile(filePayload)); }); @@ -17,78 +17,47 @@ const transformFilePayloadToFile = (filePayload: any): File => { id: filePayload.dataFile.id, persistentId: filePayload.dataFile.persistentId, name: filePayload.dataFile.filename, - pidURL: filePayload.dataFile.pidURL, + ...(filePayload.dataFile.pidURL && { pidURL: filePayload.dataFile.pidURL }), sizeBytes: filePayload.dataFile.filesize, - version: 1, - description: filePayload.dataFile.description, - // - restricted: false, - contentType: 'image/png', - storageIdentifier: 'local://18945a85439-9fa52783e5cb', - rootDataFileId: 4, - previousDataFileId: 4, - md5: '29e413e0c881e17314ce8116fed4d1a7', - checksum: { - type: 'md5', - value: '29e413e0c881e17314ce8116fed4d1a7', - }, - metadataId: 4, - creationDate: new Date('2023-07-11'), + version: filePayload.version, + ...(filePayload.dataFile.description && { description: filePayload.dataFile.description }), + restricted: filePayload.dataFile.restricted, + ...(filePayload.dataFile.directoryLabel && { directoryLabel: filePayload.dataFile.directoryLabel }), + ...(filePayload.dataFile.datasetVersionId && { datasetVersionId: filePayload.dataFile.datasetVersionId }), + ...(filePayload.dataFile.categories && { categories: filePayload.dataFile.categories }), + contentType: filePayload.dataFile.contentType, + ...(filePayload.dataFile.embargo && { embargo: transformEmbargoPayloadToEmbargo(filePayload.dataFile.embargo) }), + ...(filePayload.dataFile.storageIdentifier && { storageIdentifier: filePayload.dataFile.storageIdentifier }), + ...(filePayload.dataFile.originalFormat && { originalFormat: filePayload.dataFile.originalFormat }), + ...(filePayload.dataFile.originalFormatLabel && { originalFormatLabel: filePayload.dataFile.originalFormatLabel }), + ...(filePayload.dataFile.originalSize && { originalSize: filePayload.dataFile.originalSize }), + ...(filePayload.dataFile.originalName && { originalName: filePayload.dataFile.originalName }), + ...(filePayload.dataFile.UNF && { UNF: filePayload.dataFile.UNF }), + ...(filePayload.dataFile.rootDataFileId && { rootDataFileId: filePayload.dataFile.rootDataFileId }), + ...(filePayload.dataFile.previousDataFileId && { previousDataFileId: filePayload.dataFile.previousDataFileId }), + ...(filePayload.dataFile.md5 && { md5: filePayload.dataFile.md5 }), + ...(filePayload.dataFile.checksum && { + checksum: transformChecksumPayloadToChecksum(filePayload.dataFile.checksum), + }), + ...(filePayload.dataFile.fileMetadataId && { metadataId: filePayload.dataFile.fileMetadataId }), + ...(filePayload.dataFile.tabularTags && { tabularTags: filePayload.dataFile.tabularTags }), + ...(filePayload.dataFile.creationDate && { creationDate: new Date(filePayload.dataFile.creationDate) }), + ...(filePayload.dataFile.publicationDate && { publicationDate: new Date(filePayload.dataFile.publicationDate) }), }; }; -// MODEL -// id: number; -// persistentId: string; -// name: string; -// pidURL?: string; -// sizeBytes: number; -// version: number; -// description?: string; -// restricted: boolean; -// directoryLabel?: string; -// datasetVersionId?: number; -// categories?: string[]; -// contentType: string; -// embargo?: FileEmbargo; -// storageIdentifier?: string; -// originalFormat?: string; -// originalFormatLabel?: string; -// originalSize?: number; -// originalName?: string; -// UNF?: string; -// rootDataFileId?: number; -// previousDataFileId?: number; -// md5?: string; -// checksum?: FileChecksum; -// metadataId?: number; -// tabularTags?: string[]; -// creationDate?: Date; -// publicationDate?: Date; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformEmbargoPayloadToEmbargo = (embargoPayload: any): FileEmbargo => { + return { + dateAvailable: new Date(embargoPayload.dateAvailable), + ...(embargoPayload.reason && { reason: embargoPayload.reason }), + }; +}; -// PAYLOAD -// { -// label: 'test', -// restricted: false, -// version: 1, -// datasetVersionId: 2, -// dataFile: { -// id: 5, -// persistentId: '', -// filename: 'test', -// contentType: 'image/png', -// filesize: 127426, -// restricted: false, -// storageIdentifier: 'local://18945a85439-9fa52783e5cb', -// rootDataFileId: 4, -// previousDataFileId: 4, -// md5: '29e413e0c881e17314ce8116fed4d1a7', -// checksum: { -// type: 'MD5', -// value: '29e413e0c881e17314ce8116fed4d1a7', -// }, -// fileMetadataId: 4, -// creationDate: '2023-07-11', -// varGroups: [], -// }, -// }; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformChecksumPayloadToChecksum = (checksumPayload: any): FileChecksum => { + return { + type: checksumPayload.type, + value: checksumPayload.value, + }; +}; diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 37d6a1fb..ca6bb012 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -10,9 +10,8 @@ describe('DatasetsRepository', () => { const createdTestDatasetId = 2; const nonExistentTestDatasetId = 100; - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, process.env.TEST_API_KEY); - beforeAll(async () => { + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, process.env.TEST_API_KEY); // We update timeout due to experienced timeout errors jest.setTimeout(10000); await createDatasetViaApi() diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index 644c6585..b2626a44 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -13,12 +13,16 @@ export const createFileModel = (): File => { rootDataFileId: 4, previousDataFileId: 4, md5: '29e413e0c881e17314ce8116fed4d1a7', + metadataId: 4, + creationDate: new Date('2023-07-11'), + embargo: { + dateAvailable: new Date('2023-07-11'), + reason: 'test', + }, checksum: { - type: 'md5', + type: 'MD5', value: '29e413e0c881e17314ce8116fed4d1a7', }, - metadataId: 4, - creationDate: new Date('2023-07-11'), }; }; @@ -29,7 +33,7 @@ export const createFilePayload = (): any => { version: 1, datasetVersionId: 2, dataFile: { - id: 5, + id: 1, persistentId: '', filename: 'test', contentType: 'image/png', @@ -39,13 +43,17 @@ export const createFilePayload = (): any => { rootDataFileId: 4, previousDataFileId: 4, md5: '29e413e0c881e17314ce8116fed4d1a7', + fileMetadataId: 4, + creationDate: '2023-07-11', + varGroups: [], + embargo: { + dateAvailable: '2023-07-11', + reason: 'test', + }, checksum: { type: 'MD5', value: '29e413e0c881e17314ce8116fed4d1a7', }, - fileMetadataId: 4, - creationDate: '2023-07-11', - varGroups: [], }, }; }; diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index b7d9589f..c836eaa0 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -32,14 +32,30 @@ describe('FilesRepository', () => { test('should return files on successful response', async () => { const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - const actual = await sut.getFilesByDatasetId(testDatasetId); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`; + const expectedFiles = [testFile]; + + // API Key auth + let actual = await sut.getFilesByDatasetId(testDatasetId); assert.calledWithExactly( axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`, + expectedApiEndpoint, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, ); - assert.match(actual, [testFile]); + assert.match(actual, expectedFiles); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFilesByDatasetId(testDatasetId); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedFiles); }); test('should return files when providing id, optional params, and response is successful', async () => { From 7f6d55af941ebb12182a6214b4d2a8a6cf23e3d9 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 17 Jul 2023 11:33:15 +0100 Subject: [PATCH 07/25] Added: getFilesByDatasetPersistentId use case --- src/core/infra/repositories/ApiRepository.ts | 2 + .../infra/repositories/DatasetsRepository.ts | 2 - .../domain/repositories/IFilesRepository.ts | 8 ++ .../useCases/GetFilesByDatasetPersistentId.ts | 28 +++++++ src/files/index.ts | 5 +- .../infra/repositories/FilesRepository.ts | 21 ++++- test/unit/files/FilesRepository.test.ts | 82 ++++++++++++++++++- .../GetFilesByDatasetPersistentId.test.ts | 39 +++++++++ 8 files changed, 180 insertions(+), 7 deletions(-) create mode 100644 src/files/domain/useCases/GetFilesByDatasetPersistentId.ts create mode 100644 test/unit/files/GetFilesByDatasetPersistentId.test.ts diff --git a/src/core/infra/repositories/ApiRepository.ts b/src/core/infra/repositories/ApiRepository.ts index a1d1e0e4..6730e712 100644 --- a/src/core/infra/repositories/ApiRepository.ts +++ b/src/core/infra/repositories/ApiRepository.ts @@ -4,6 +4,8 @@ import { ReadError } from '../../domain/repositories/ReadError'; import { WriteError } from '../../domain/repositories/WriteError'; export abstract class ApiRepository { + DATASET_VERSION_LATEST = ':latest'; + public async doGet(apiEndpoint: string, authRequired = false, queryParams: object = {}): Promise { return await axios .get(this.buildRequestUrl(apiEndpoint), this.buildRequestConfig(authRequired, queryParams)) diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index 56328fcb..cbec5c19 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -4,8 +4,6 @@ import { Dataset } from '../../domain/models/Dataset'; import { transformVersionResponseToDataset } from './transformers/datasetTransformers'; export class DatasetsRepository extends ApiRepository implements IDatasetsRepository { - DATASET_VERSION_LATEST = ':latest'; - public async getDatasetSummaryFieldNames(): Promise { return this.doGet('/datasets/summaryFieldNames') .then((response) => response.data.data) diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index 44d04357..0e416fc9 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -9,4 +9,12 @@ export interface IFilesRepository { offset?: number, orderCriteria?: FileOrderCriteria, ): Promise; + + getFilesByDatasetPersistentId( + datasetPersistentId: string, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: FileOrderCriteria, + ): Promise; } diff --git a/src/files/domain/useCases/GetFilesByDatasetPersistentId.ts b/src/files/domain/useCases/GetFilesByDatasetPersistentId.ts new file mode 100644 index 00000000..4b1619b9 --- /dev/null +++ b/src/files/domain/useCases/GetFilesByDatasetPersistentId.ts @@ -0,0 +1,28 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { FileOrderCriteria } from '../models/FileOrderCriteria'; +import { IFilesRepository } from '../repositories/IFilesRepository'; +import { File } from '../models/File'; + +export class GetFilesByDatasetPersistentId implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute( + datasetPersistentId: string, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: FileOrderCriteria, + ): Promise { + return await this.filesRepository.getFilesByDatasetPersistentId( + datasetPersistentId, + datasetVersionId, + limit, + offset, + orderCriteria, + ); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index bddb3fcc..e673c357 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1,10 +1,13 @@ import { GetFilesByDatasetId } from './domain/useCases/GetFilesByDatasetId'; +import { GetFilesByDatasetPersistentId } from './domain/useCases/GetFilesByDatasetPersistentId'; import { FilesRepository } from './infra/repositories/FilesRepository'; const filesRepository = new FilesRepository(); + const getFilesByDatasetId = new GetFilesByDatasetId(filesRepository); +const getFilesByDatasetPersistentId = new GetFilesByDatasetPersistentId(filesRepository); -export { getFilesByDatasetId }; +export { getFilesByDatasetId, getFilesByDatasetPersistentId }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index a8495297..d60f1f64 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -11,9 +11,6 @@ export interface GetFilesQueryParams { } export class FilesRepository extends ApiRepository implements IFilesRepository { - // TODO Shared with DatasetsRepository so move somewhere - DATASET_VERSION_LATEST = ':latest'; - public async getFilesByDatasetId( datasetId: number, datasetVersionId?: string, @@ -27,6 +24,24 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { return this.getFiles(`/datasets/${datasetId}/versions/${datasetVersionId}/files`, limit, offset, orderCriteria); } + public async getFilesByDatasetPersistentId( + datasetPersistentId: string, + datasetVersionId?: string, + limit?: number, + offset?: number, + orderCriteria?: FileOrderCriteria, + ): Promise { + if (datasetVersionId === undefined) { + datasetVersionId = this.DATASET_VERSION_LATEST; + } + return this.getFiles( + `/datasets/:persistentId/versions/${datasetVersionId}/files?persistentId=${datasetPersistentId}`, + limit, + offset, + orderCriteria, + ); + } + private async getFiles( endpoint: string, limit?: number, diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index c836eaa0..1e7081bb 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -17,7 +17,6 @@ describe('FilesRepository', () => { data: [createFilePayload()], }, }; - const testDatasetId = 1; const testFile = createFileModel(); beforeEach(() => { @@ -29,6 +28,8 @@ describe('FilesRepository', () => { }); describe('getFilesByDatasetId', () => { + const testDatasetId = 1; + test('should return files on successful response', async () => { const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); @@ -105,4 +106,83 @@ describe('FilesRepository', () => { expect(error).to.be.instanceOf(Error); }); }); + + describe('getFilesByDatasetPersistentId', () => { + const testDatasetPersistentId = 'doi:11.1111/AA1/1AAAAA'; + + test('should return files on successful response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${testDatasetPersistentId}`; + const expectedFiles = [testFile]; + + // API Key auth + let actual = await sut.getFilesByDatasetPersistentId(testDatasetPersistentId); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedFiles); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFilesByDatasetPersistentId(testDatasetPersistentId); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedFiles); + }); + + test('should return files when providing persistent id, optional params, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); + + const testVersionId = ':draft'; + const testLimit = 10; + const testOffset = 20; + const testFileOrderCriteria = FileOrderCriteria.NEWEST; + + const actual = await sut.getFilesByDatasetPersistentId( + testDatasetPersistentId, + testVersionId, + testLimit, + testOffset, + testFileOrderCriteria, + ); + + const expectedRequestConfig = { + params: { + limit: testLimit, + offset: testOffset, + orderCriteria: testFileOrderCriteria.toString(), + }, + headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers, + }; + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/${testVersionId}/files?persistentId=${testDatasetPersistentId}`, + expectedRequestConfig, + ); + assert.match(actual, [testFile]); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFilesByDatasetPersistentId(testDatasetPersistentId).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${testDatasetPersistentId}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); }); diff --git a/test/unit/files/GetFilesByDatasetPersistentId.test.ts b/test/unit/files/GetFilesByDatasetPersistentId.test.ts new file mode 100644 index 00000000..3a710c74 --- /dev/null +++ b/test/unit/files/GetFilesByDatasetPersistentId.test.ts @@ -0,0 +1,39 @@ +import { GetFilesByDatasetPersistentId } from '../../../src/files/domain/useCases/GetFilesByDatasetPersistentId'; +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { File } from '../../../src/files/domain/models/File'; +import { createFileModel } from '../../testHelpers/files/filesHelper'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + test('should return files on repository success', async () => { + const testFiles: File[] = [createFileModel()]; + const filesRepositoryStub = {}; + const getFilesByDatasetPersistentIdStub = sandbox.stub().returns(testFiles); + filesRepositoryStub.getFilesByDatasetPersistentId = getFilesByDatasetPersistentIdStub; + const sut = new GetFilesByDatasetPersistentId(filesRepositoryStub); + + const actual = await sut.execute('test'); + + assert.match(actual, testFiles); + assert.calledWithExactly(getFilesByDatasetPersistentIdStub, 'test', undefined, undefined, undefined, undefined); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.getFilesByDatasetPersistentId = sandbox.stub().throwsException(testReadError); + const sut = new GetFilesByDatasetPersistentId(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute('test').catch((e) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +}); From 7cbf2f4e0b473302e44546f0cbd04396000fb46e Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 17 Jul 2023 13:08:43 +0100 Subject: [PATCH 08/25] Changed: restricted field payload transform --- src/files/infra/repositories/transformers/fileTransformers.ts | 2 +- test/testHelpers/files/filesHelper.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/files/infra/repositories/transformers/fileTransformers.ts b/src/files/infra/repositories/transformers/fileTransformers.ts index e042f7f4..28319592 100644 --- a/src/files/infra/repositories/transformers/fileTransformers.ts +++ b/src/files/infra/repositories/transformers/fileTransformers.ts @@ -21,7 +21,7 @@ const transformFilePayloadToFile = (filePayload: any): File => { sizeBytes: filePayload.dataFile.filesize, version: filePayload.version, ...(filePayload.dataFile.description && { description: filePayload.dataFile.description }), - restricted: filePayload.dataFile.restricted, + restricted: filePayload.restricted, ...(filePayload.dataFile.directoryLabel && { directoryLabel: filePayload.dataFile.directoryLabel }), ...(filePayload.dataFile.datasetVersionId && { datasetVersionId: filePayload.dataFile.datasetVersionId }), ...(filePayload.dataFile.categories && { categories: filePayload.dataFile.categories }), diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index b2626a44..be84b7d8 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -38,7 +38,6 @@ export const createFilePayload = (): any => { filename: 'test', contentType: 'image/png', filesize: 127426, - restricted: false, storageIdentifier: 'local://18945a85439-9fa52783e5cb', rootDataFileId: 4, previousDataFileId: 4, From 9b305eab36c31de90bf3a9b933fee8c7bfdb1f50 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 17 Jul 2023 14:52:56 +0100 Subject: [PATCH 09/25] Stash: files repository integration test WIP (now failing) --- .../datasets/DatasetsRepository.test.ts | 23 +++++++------- .../integration/files/FilesRepository.test.ts | 30 +++++++++++++++++++ test/testHelpers/TestConstants.ts | 1 + test/testHelpers/files/filesHelper.ts | 10 +++++++ test/testHelpers/files/test-file-1.txt | 1 + 5 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 test/integration/files/FilesRepository.test.ts create mode 100644 test/testHelpers/files/test-file-1.txt diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index ca6bb012..a577ae45 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -7,7 +7,6 @@ import { ReadError } from '../../../src/core/domain/repositories/ReadError'; describe('DatasetsRepository', () => { const sut: DatasetsRepository = new DatasetsRepository(); - const createdTestDatasetId = 2; const nonExistentTestDatasetId = 100; beforeAll(async () => { @@ -36,13 +35,13 @@ describe('DatasetsRepository', () => { describe('getDatasetById', () => { test('should return dataset when it exists filtering by id', async () => { - const actual = await sut.getDatasetById(createdTestDatasetId); - expect(actual.id).toBe(createdTestDatasetId); + const actual = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); }); test('should return dataset when it exists filtering by id and version id', async () => { - const actual = await sut.getDatasetById(createdTestDatasetId, ':draft'); - expect(actual.id).toBe(createdTestDatasetId); + const actual = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID, ':draft'); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); }); test('should return error when dataset does not exist', async () => { @@ -58,15 +57,15 @@ describe('DatasetsRepository', () => { describe('getDatasetByPersistentId', () => { test('should return dataset when it exists filtering by persistent id', async () => { - const createdDataset = await sut.getDatasetById(createdTestDatasetId); + const createdDataset = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); const actual = await sut.getDatasetByPersistentId(createdDataset.persistentId); - expect(actual.id).toBe(createdTestDatasetId); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); }); test('should return dataset when it exists filtering by persistent id and version id', async () => { - const createdDataset = await sut.getDatasetById(createdTestDatasetId); + const createdDataset = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); const actual = await sut.getDatasetByPersistentId(createdDataset.persistentId, ':draft'); - expect(actual.id).toBe(createdTestDatasetId); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); }); test('should return error when dataset does not exist', async () => { @@ -84,7 +83,7 @@ describe('DatasetsRepository', () => { describe('getDatasetCitation', () => { test('should return citation when dataset exists', async () => { - const actualDatasetCitation = await sut.getDatasetCitation(createdTestDatasetId); + const actualDatasetCitation = await sut.getDatasetCitation(TestConstants.TEST_CREATED_DATASET_ID); expect(typeof actualDatasetCitation).toBe('string'); }); @@ -106,7 +105,7 @@ describe('DatasetsRepository', () => { let privateUrlToken: string = undefined; beforeAll(async () => { - await createPrivateUrlViaApi(createdTestDatasetId) + await createPrivateUrlViaApi(TestConstants.TEST_CREATED_DATASET_ID) .then((response) => { privateUrlToken = response.data.data.token; }) @@ -119,7 +118,7 @@ describe('DatasetsRepository', () => { test('should return dataset when token is valid', async () => { const actual = await sut.getPrivateUrlDataset(privateUrlToken); - expect(actual.id).toBe(createdTestDatasetId); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); }); test('should return error when token is not valid', async () => { diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts new file mode 100644 index 00000000..6fa9407d --- /dev/null +++ b/test/integration/files/FilesRepository.test.ts @@ -0,0 +1,30 @@ +import { FilesRepository } from '../../../src/files/infra/repositories/FilesRepository'; +import { ApiConfig, DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'; +import { assert } from 'sinon'; +import { TestConstants } from '../../testHelpers/TestConstants'; +import { createDatasetViaApi } from '../../testHelpers/datasets/datasetHelper'; +import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'; + +describe('getDataverseVersion', () => { + const sut: FilesRepository = new FilesRepository(); + + beforeAll(async () => { + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, process.env.TEST_API_KEY); + await createDatasetViaApi() + .then() + .catch(() => { + fail('Tesgooglts beforeAll(): Error while creating test Dataset'); + }); + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, 'test-file-1.txt') + .then() + .catch((e) => { + console.log(e); + fail('Tests beforeAll(): Error while creating test file'); + }); + }); + + test('should return files when exist filtering by dataset id', async () => { + const actual = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID); + assert.match(actual, []); + }); +}); diff --git a/test/testHelpers/TestConstants.ts b/test/testHelpers/TestConstants.ts index 3d4c27a7..b2ff2017 100644 --- a/test/testHelpers/TestConstants.ts +++ b/test/testHelpers/TestConstants.ts @@ -27,4 +27,5 @@ export class TestConstants { 'Content-Type': 'application/json', }, }; + static readonly TEST_CREATED_DATASET_ID = 2; } diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index be84b7d8..17cd9cfc 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -1,4 +1,6 @@ import { File } from '../../../src/files/domain/models/File'; +import axios, { AxiosResponse } from 'axios'; +import { TestConstants } from '../TestConstants'; export const createFileModel = (): File => { return { @@ -56,3 +58,11 @@ export const createFilePayload = (): any => { }, }; }; + +export const uploadFileViaApi = async (datasetId: number, fileName: string): Promise => { + let formData = new FormData(); + formData.append('file', fileName); + return await axios.post(`${TestConstants.TEST_API_URL}/datasets/${datasetId}/add`, formData, { + headers: { 'Content-Type': 'multipart/form-data', 'X-Dataverse-Key': process.env.TEST_API_KEY }, + }); +}; diff --git a/test/testHelpers/files/test-file-1.txt b/test/testHelpers/files/test-file-1.txt new file mode 100644 index 00000000..75342f57 --- /dev/null +++ b/test/testHelpers/files/test-file-1.txt @@ -0,0 +1 @@ +test file 1 From f4a6bdd7730ef96159ea0d61d1644231c0d35bfd Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 19 Jul 2023 15:55:58 +0100 Subject: [PATCH 10/25] Fixed: file upload via API test helper --- .../integration/files/FilesRepository.test.ts | 25 ++++++++++++++++--- test/testHelpers/files/filesHelper.ts | 11 +++++--- test/testHelpers/files/test-file-2.txt | 1 + test/testHelpers/files/test-file-3.txt | 1 + 4 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 test/testHelpers/files/test-file-2.txt create mode 100644 test/testHelpers/files/test-file-3.txt diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 6fa9407d..6c73afc9 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -8,18 +8,37 @@ import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'; describe('getDataverseVersion', () => { const sut: FilesRepository = new FilesRepository(); + const testFile1Name = 'test-file-1.txt'; + const testFile2Name = 'test-file-2.txt'; + const testFile3Name = 'test-file-3.txt'; + beforeAll(async () => { ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, process.env.TEST_API_KEY); await createDatasetViaApi() .then() .catch(() => { - fail('Tesgooglts beforeAll(): Error while creating test Dataset'); + fail('Test beforeAll(): Error while creating test Dataset'); + }); + // Uploading test file 1 + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testFile1Name) + .then() + .catch((e) => { + console.log(e); + fail(`Tests beforeAll(): Error while uploading file ${testFile1Name}`); + }); + // Uploading test file 2 + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testFile2Name) + .then() + .catch((e) => { + console.log(e); + fail(`Tests beforeAll(): Error while uploading file ${testFile2Name}`); }); - await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, 'test-file-1.txt') + // Uploading test file 3 + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testFile3Name) .then() .catch((e) => { console.log(e); - fail('Tests beforeAll(): Error while creating test file'); + fail(`Tests beforeAll(): Error while uploading file ${testFile3Name}`); }); }); diff --git a/test/testHelpers/files/filesHelper.ts b/test/testHelpers/files/filesHelper.ts index 17cd9cfc..97dab816 100644 --- a/test/testHelpers/files/filesHelper.ts +++ b/test/testHelpers/files/filesHelper.ts @@ -1,6 +1,7 @@ import { File } from '../../../src/files/domain/models/File'; import axios, { AxiosResponse } from 'axios'; import { TestConstants } from '../TestConstants'; +import { readFile } from 'fs/promises'; export const createFileModel = (): File => { return { @@ -60,9 +61,13 @@ export const createFilePayload = (): any => { }; export const uploadFileViaApi = async (datasetId: number, fileName: string): Promise => { - let formData = new FormData(); - formData.append('file', fileName); + const formData = new FormData(); + const file = await readFile(`${__dirname}/${fileName}`); + formData.append('file', new Blob([file]), fileName); return await axios.post(`${TestConstants.TEST_API_URL}/datasets/${datasetId}/add`, formData, { - headers: { 'Content-Type': 'multipart/form-data', 'X-Dataverse-Key': process.env.TEST_API_KEY }, + headers: { + 'Content-Type': 'multipart/form-data', + 'X-Dataverse-Key': process.env.TEST_API_KEY, + }, }); }; diff --git a/test/testHelpers/files/test-file-2.txt b/test/testHelpers/files/test-file-2.txt new file mode 100644 index 00000000..8c95218f --- /dev/null +++ b/test/testHelpers/files/test-file-2.txt @@ -0,0 +1 @@ +test file 2 diff --git a/test/testHelpers/files/test-file-3.txt b/test/testHelpers/files/test-file-3.txt new file mode 100644 index 00000000..23ea6e58 --- /dev/null +++ b/test/testHelpers/files/test-file-3.txt @@ -0,0 +1 @@ +test file 3 From 0111e66be4696f23b968f915aa6d0ce8e217f080 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 20 Jul 2023 10:51:33 +0100 Subject: [PATCH 11/25] Added: integration tests for FilesRepository --- jest.config.js | 1 + jest.config.unit.js | 1 + .../datasets/DatasetsRepository.test.ts | 7 -- test/integration/environment/.env | 2 +- .../integration/files/FilesRepository.test.ts | 70 +++++++++++++++++-- 5 files changed, 69 insertions(+), 12 deletions(-) diff --git a/jest.config.js b/jest.config.js index 476ad23f..9823ab2f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,4 +7,5 @@ module.exports = { moduleFileExtensions: ['ts', 'js', 'json', 'node'], coveragePathIgnorePatterns: ['node_modules', 'testHelpers'], globalSetup: '/test/integration/environment/setup.js', + testTimeout: 15000, }; diff --git a/jest.config.unit.js b/jest.config.unit.js index 7144b835..845173e7 100644 --- a/jest.config.unit.js +++ b/jest.config.unit.js @@ -1,5 +1,6 @@ var config = require('./jest.config'); config.modulePathIgnorePatterns = ['/test/integration']; delete config.globalSetup; +delete config.testTimeout; console.log('RUNNING UNIT TESTS'); module.exports = config; diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index a577ae45..7a2b8b26 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -11,8 +11,6 @@ describe('DatasetsRepository', () => { beforeAll(async () => { ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, process.env.TEST_API_KEY); - // We update timeout due to experienced timeout errors - jest.setTimeout(10000); await createDatasetViaApi() .then() .catch(() => { @@ -20,11 +18,6 @@ describe('DatasetsRepository', () => { }); }); - afterAll(async () => { - // We update timeout back to original default value - jest.setTimeout(5000); - }); - describe('getDatasetSummaryFieldNames', () => { test('should return not empty field list on successful response', async () => { const actual = await sut.getDatasetSummaryFieldNames(); diff --git a/test/integration/environment/.env b/test/integration/environment/.env index c9e070b5..cb181a92 100644 --- a/test/integration/environment/.env +++ b/test/integration/environment/.env @@ -2,5 +2,5 @@ POSTGRES_VERSION=13 DATAVERSE_DB_USER=dataverse SOLR_VERSION=8.11.1 DATAVERSE_IMAGE_REGISTRY=ghcr.io -DATAVERSE_IMAGE_TAG=9667-metadatablocks-api-extension +DATAVERSE_IMAGE_TAG=9692-files-api-extension-display-data DATAVERSE_BOOTSTRAP_TIMEOUT=5m diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 6c73afc9..27cfb9d6 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -4,8 +4,10 @@ import { assert } from 'sinon'; import { TestConstants } from '../../testHelpers/TestConstants'; import { createDatasetViaApi } from '../../testHelpers/datasets/datasetHelper'; import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'; +import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; +import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'; -describe('getDataverseVersion', () => { +describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository(); const testFile1Name = 'test-file-1.txt'; @@ -42,8 +44,68 @@ describe('getDataverseVersion', () => { }); }); - test('should return files when exist filtering by dataset id', async () => { - const actual = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID); - assert.match(actual, []); + describe('getFilesByDatasetId', () => { + test('should return all files filtering by dataset id', async () => { + const actual = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile1Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile3Name); + }); + + test('should return correct files filtering by dataset id and paginating', async () => { + const actual = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID, undefined, 2, 2, undefined); + assert.match(actual.length, 1); + assert.match(actual[0].name, testFile3Name); + }); + + test('should return correct files filtering by dataset id and applying order criteria', async () => { + let actual = await sut.getFilesByDatasetId( + TestConstants.TEST_CREATED_DATASET_ID, + undefined, + undefined, + undefined, + FileOrderCriteria.NEWEST, + ); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile3Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile1Name); + }); + }); + + describe('getFilesByDatasetPersistentId', () => { + const datasetRepository = new DatasetsRepository(); + + test('should return all files filtering by persistent id', async () => { + const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + const actual = await sut.getFilesByDatasetPersistentId(testDataset.persistentId); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile1Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile3Name); + }); + + test('should return correct files filtering by persistent id and paginating', async () => { + const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + const actual = await sut.getFilesByDatasetPersistentId(testDataset.persistentId, undefined, 2, 2, undefined); + assert.match(actual.length, 1); + assert.match(actual[0].name, testFile3Name); + }); + + test('should return correct files filtering by persistent id and applying order criteria', async () => { + const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + let actual = await sut.getFilesByDatasetPersistentId( + testDataset.persistentId, + undefined, + undefined, + undefined, + FileOrderCriteria.NEWEST, + ); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile3Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile1Name); + }); }); }); From 7e7730c8b08e63b93899c1825af6d810706128d3 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 20 Jul 2023 14:29:16 +0100 Subject: [PATCH 12/25] Added: GetFileGuestbookResponsesCount use case with repository logic (pending tests and refactoring) --- .../domain/repositories/IFilesRepository.ts | 2 + .../GetFileGuestbookResponsesCount.ts | 14 +++ src/files/index.ts | 4 +- .../infra/repositories/FilesRepository.ts | 14 +++ .../integration/files/FilesRepository.test.ts | 15 +++ test/testHelpers/TestConstants.ts | 1 + test/unit/files/FilesRepository.test.ts | 114 ++++++++++++++++-- .../GetFileGuestbookResponsesCount.test.ts | 51 ++++++++ 8 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 src/files/domain/useCases/GetFileGuestbookResponsesCount.ts create mode 100644 test/unit/files/GetFileGuestbookResponsesCount.test.ts diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index 0e416fc9..14146e59 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -17,4 +17,6 @@ export interface IFilesRepository { offset?: number, orderCriteria?: FileOrderCriteria, ): Promise; + + getFileGuestbookResponsesCount(fileId: number | string): Promise; } diff --git a/src/files/domain/useCases/GetFileGuestbookResponsesCount.ts b/src/files/domain/useCases/GetFileGuestbookResponsesCount.ts new file mode 100644 index 00000000..9bdb7748 --- /dev/null +++ b/src/files/domain/useCases/GetFileGuestbookResponsesCount.ts @@ -0,0 +1,14 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IFilesRepository } from '../repositories/IFilesRepository'; + +export class GetFileGuestbookResponsesCount implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute(fileId: number | string): Promise { + return await this.filesRepository.getFileGuestbookResponsesCount(fileId); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index e673c357..389fb163 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1,13 +1,15 @@ import { GetFilesByDatasetId } from './domain/useCases/GetFilesByDatasetId'; import { GetFilesByDatasetPersistentId } from './domain/useCases/GetFilesByDatasetPersistentId'; import { FilesRepository } from './infra/repositories/FilesRepository'; +import { GetFileGuestbookResponsesCount } from './domain/useCases/GetFileGuestbookResponsesCount'; const filesRepository = new FilesRepository(); const getFilesByDatasetId = new GetFilesByDatasetId(filesRepository); const getFilesByDatasetPersistentId = new GetFilesByDatasetPersistentId(filesRepository); +const getFileGuestbookResponsesCount = new GetFileGuestbookResponsesCount(filesRepository); -export { getFilesByDatasetId, getFilesByDatasetPersistentId }; +export { getFilesByDatasetId, getFilesByDatasetPersistentId, getFileGuestbookResponsesCount }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index d60f1f64..2a836df0 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -42,6 +42,20 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { ); } + public async getFileGuestbookResponsesCount(fileId: number | string): Promise { + let endpoint; + if (typeof fileId === 'number') { + endpoint = `/files/${fileId}/guestbookResponses/count`; + } else { + endpoint = `/files/:persistentId/guestbookResponses/count?persistentId=${fileId}`; + } + return this.doGet(endpoint, true) + .then((response) => response.data.data.message as number) + .catch((error) => { + throw error; + }); + } + private async getFiles( endpoint: string, limit?: number, diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 27cfb9d6..49eaa663 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -72,6 +72,8 @@ describe('FilesRepository', () => { assert.match(actual[1].name, testFile2Name); assert.match(actual[2].name, testFile1Name); }); + + // TODO: Error test }); describe('getFilesByDatasetPersistentId', () => { @@ -107,5 +109,18 @@ describe('FilesRepository', () => { assert.match(actual[1].name, testFile2Name); assert.match(actual[2].name, testFile1Name); }); + + // TODO: Error test + }); + + describe('getFileGuestbookResponsesCount', () => { + test('should return count filtering by file id', async () => { + const currentTestFiles = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID); + const testFile = currentTestFiles[0]; + const actual = await sut.getFileGuestbookResponsesCount(testFile.id); + assert.match(actual, 0); + }); + + // TODO: Error test }); }); diff --git a/test/testHelpers/TestConstants.ts b/test/testHelpers/TestConstants.ts index b2ff2017..37dca855 100644 --- a/test/testHelpers/TestConstants.ts +++ b/test/testHelpers/TestConstants.ts @@ -1,6 +1,7 @@ export class TestConstants { static readonly TEST_API_URL = 'http://localhost:8080/api/v1'; static readonly TEST_DUMMY_API_KEY = 'dummyApiKey'; + static readonly TEST_DUMMY_PERSISTENT_ID = 'doi:11.1111/AA1/AA1AAA'; static readonly TEST_ERROR_RESPONSE = { response: { status: 'ERROR', diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index 1e7081bb..8ff53502 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -108,15 +108,13 @@ describe('FilesRepository', () => { }); describe('getFilesByDatasetPersistentId', () => { - const testDatasetPersistentId = 'doi:11.1111/AA1/1AAAAA'; - test('should return files on successful response', async () => { const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${testDatasetPersistentId}`; + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; const expectedFiles = [testFile]; // API Key auth - let actual = await sut.getFilesByDatasetPersistentId(testDatasetPersistentId); + let actual = await sut.getFilesByDatasetPersistentId(TestConstants.TEST_DUMMY_PERSISTENT_ID); assert.calledWithExactly( axiosGetStub, @@ -128,7 +126,7 @@ describe('FilesRepository', () => { // Session cookie auth ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - actual = await sut.getFilesByDatasetPersistentId(testDatasetPersistentId); + actual = await sut.getFilesByDatasetPersistentId(TestConstants.TEST_DUMMY_PERSISTENT_ID); assert.calledWithExactly( axiosGetStub, @@ -147,7 +145,7 @@ describe('FilesRepository', () => { const testFileOrderCriteria = FileOrderCriteria.NEWEST; const actual = await sut.getFilesByDatasetPersistentId( - testDatasetPersistentId, + TestConstants.TEST_DUMMY_PERSISTENT_ID, testVersionId, testLimit, testOffset, @@ -165,7 +163,7 @@ describe('FilesRepository', () => { assert.calledWithExactly( axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/${testVersionId}/files?persistentId=${testDatasetPersistentId}`, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/${testVersionId}/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, expectedRequestConfig, ); assert.match(actual, [testFile]); @@ -175,14 +173,112 @@ describe('FilesRepository', () => { const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); let error: ReadError = undefined; - await sut.getFilesByDatasetPersistentId(testDatasetPersistentId).catch((e) => (error = e)); + await sut.getFilesByDatasetPersistentId(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); assert.calledWithExactly( axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${testDatasetPersistentId}`, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, ); expect(error).to.be.instanceOf(Error); }); }); + + describe('getFileGuestbookResponsesCount', () => { + const testCount = 1; + const testFileGuestbookResponseCountResponse = { + data: { + status: 'OK', + data: { + message: `${testCount}`, + }, + }, + }; + + describe('by numeric id', () => { + test('should return count when providing id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileGuestbookResponseCountResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/guestbookResponses/count`; + + // API Key auth + let actual = await sut.getFileGuestbookResponsesCount(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testCount); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileGuestbookResponsesCount(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, testCount); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileGuestbookResponsesCount(testFile.id).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/${testFile.id}/guestbookResponses/count`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + + describe('by persistent id', () => { + test('should return count when providing persistent id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileGuestbookResponseCountResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/guestbookResponses/count?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; + + // API Key auth + let actual = await sut.getFileGuestbookResponsesCount(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testCount); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileGuestbookResponsesCount(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, testCount); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileGuestbookResponsesCount(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/:persistentId/guestbookResponses/count?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + }); }); diff --git a/test/unit/files/GetFileGuestbookResponsesCount.test.ts b/test/unit/files/GetFileGuestbookResponsesCount.test.ts new file mode 100644 index 00000000..d1f4a575 --- /dev/null +++ b/test/unit/files/GetFileGuestbookResponsesCount.test.ts @@ -0,0 +1,51 @@ +import { GetFileGuestbookResponsesCount } from '../../../src/files/domain/useCases/GetFileGuestbookResponsesCount'; +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { TestConstants } from '../../testHelpers/TestConstants'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + const testFileId = 1; + const testCount = 10; + + afterEach(() => { + sandbox.restore(); + }); + + test('should return count on repository success filtering by id', async () => { + const filesRepositoryStub = {}; + const getFileGuestbookResponsesCountStub = sandbox.stub().returns(testCount); + filesRepositoryStub.getFileGuestbookResponsesCount = getFileGuestbookResponsesCountStub; + const sut = new GetFileGuestbookResponsesCount(filesRepositoryStub); + + const actual = await sut.execute(testFileId); + + assert.match(actual, testCount); + assert.calledWithExactly(getFileGuestbookResponsesCountStub, testFileId); + }); + + test('should return count on repository success filtering by persistent id', async () => { + const filesRepositoryStub = {}; + const getFileGuestbookResponsesCountStub = sandbox.stub().returns(testCount); + filesRepositoryStub.getFileGuestbookResponsesCount = getFileGuestbookResponsesCountStub; + const sut = new GetFileGuestbookResponsesCount(filesRepositoryStub); + + const actual = await sut.execute(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.match(actual, testCount); + assert.calledWithExactly(getFileGuestbookResponsesCountStub, TestConstants.TEST_DUMMY_PERSISTENT_ID); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.getFileGuestbookResponsesCount = sandbox.stub().throwsException(testReadError); + const sut = new GetFileGuestbookResponsesCount(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute(testFileId).catch((e) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +}); From 448909b9624ccea8b2e52389145597a93562b86b Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 20 Jul 2023 18:21:19 +0100 Subject: [PATCH 13/25] Refactor: single GetDatasetFiles use case managing numeric and PID ids --- .../domain/repositories/IFilesRepository.ts | 12 +- ...FilesByDatasetId.ts => GetDatasetFiles.ts} | 6 +- .../useCases/GetFilesByDatasetPersistentId.ts | 28 -- src/files/index.ts | 8 +- .../infra/repositories/FilesRepository.ts | 28 +- .../integration/files/FilesRepository.test.ts | 153 ++++++---- test/unit/files/FilesRepository.test.ts | 276 +++++++++--------- ...asetId.test.ts => GetDatasetFiles.test.ts} | 18 +- .../GetFilesByDatasetPersistentId.test.ts | 39 --- 9 files changed, 257 insertions(+), 311 deletions(-) rename src/files/domain/useCases/{GetFilesByDatasetId.ts => GetDatasetFiles.ts} (72%) delete mode 100644 src/files/domain/useCases/GetFilesByDatasetPersistentId.ts rename test/unit/files/{GetFilesByDatasetId.test.ts => GetDatasetFiles.test.ts} (59%) delete mode 100644 test/unit/files/GetFilesByDatasetPersistentId.test.ts diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index 14146e59..df1de02f 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -2,16 +2,8 @@ import { FileOrderCriteria } from '../models/FileOrderCriteria'; import { File } from '../models/File'; export interface IFilesRepository { - getFilesByDatasetId( - datasetId: number, - datasetVersionId?: string, - limit?: number, - offset?: number, - orderCriteria?: FileOrderCriteria, - ): Promise; - - getFilesByDatasetPersistentId( - datasetPersistentId: string, + getDatasetFiles( + datasetId: number | string, datasetVersionId?: string, limit?: number, offset?: number, diff --git a/src/files/domain/useCases/GetFilesByDatasetId.ts b/src/files/domain/useCases/GetDatasetFiles.ts similarity index 72% rename from src/files/domain/useCases/GetFilesByDatasetId.ts rename to src/files/domain/useCases/GetDatasetFiles.ts index bd79ad36..e8e62bf7 100644 --- a/src/files/domain/useCases/GetFilesByDatasetId.ts +++ b/src/files/domain/useCases/GetDatasetFiles.ts @@ -3,7 +3,7 @@ import { FileOrderCriteria } from '../models/FileOrderCriteria'; import { IFilesRepository } from '../repositories/IFilesRepository'; import { File } from '../models/File'; -export class GetFilesByDatasetId implements UseCase { +export class GetDatasetFiles implements UseCase { private filesRepository: IFilesRepository; constructor(filesRepository: IFilesRepository) { @@ -11,12 +11,12 @@ export class GetFilesByDatasetId implements UseCase { } async execute( - datasetId: number, + datasetId: number | string, datasetVersionId?: string, limit?: number, offset?: number, orderCriteria?: FileOrderCriteria, ): Promise { - return await this.filesRepository.getFilesByDatasetId(datasetId, datasetVersionId, limit, offset, orderCriteria); + return await this.filesRepository.getDatasetFiles(datasetId, datasetVersionId, limit, offset, orderCriteria); } } diff --git a/src/files/domain/useCases/GetFilesByDatasetPersistentId.ts b/src/files/domain/useCases/GetFilesByDatasetPersistentId.ts deleted file mode 100644 index 4b1619b9..00000000 --- a/src/files/domain/useCases/GetFilesByDatasetPersistentId.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { UseCase } from '../../../core/domain/useCases/UseCase'; -import { FileOrderCriteria } from '../models/FileOrderCriteria'; -import { IFilesRepository } from '../repositories/IFilesRepository'; -import { File } from '../models/File'; - -export class GetFilesByDatasetPersistentId implements UseCase { - private filesRepository: IFilesRepository; - - constructor(filesRepository: IFilesRepository) { - this.filesRepository = filesRepository; - } - - async execute( - datasetPersistentId: string, - datasetVersionId?: string, - limit?: number, - offset?: number, - orderCriteria?: FileOrderCriteria, - ): Promise { - return await this.filesRepository.getFilesByDatasetPersistentId( - datasetPersistentId, - datasetVersionId, - limit, - offset, - orderCriteria, - ); - } -} diff --git a/src/files/index.ts b/src/files/index.ts index 389fb163..c16f0ca1 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1,15 +1,13 @@ -import { GetFilesByDatasetId } from './domain/useCases/GetFilesByDatasetId'; -import { GetFilesByDatasetPersistentId } from './domain/useCases/GetFilesByDatasetPersistentId'; import { FilesRepository } from './infra/repositories/FilesRepository'; +import { GetDatasetFiles } from './domain/useCases/GetDatasetFiles'; import { GetFileGuestbookResponsesCount } from './domain/useCases/GetFileGuestbookResponsesCount'; const filesRepository = new FilesRepository(); -const getFilesByDatasetId = new GetFilesByDatasetId(filesRepository); -const getFilesByDatasetPersistentId = new GetFilesByDatasetPersistentId(filesRepository); +const getDatasetFiles = new GetDatasetFiles(filesRepository); const getFileGuestbookResponsesCount = new GetFileGuestbookResponsesCount(filesRepository); -export { getFilesByDatasetId, getFilesByDatasetPersistentId, getFileGuestbookResponsesCount }; +export { getDatasetFiles, getFileGuestbookResponsesCount }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 2a836df0..0b6c0321 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -11,8 +11,8 @@ export interface GetFilesQueryParams { } export class FilesRepository extends ApiRepository implements IFilesRepository { - public async getFilesByDatasetId( - datasetId: number, + public async getDatasetFiles( + datasetId: number | string, datasetVersionId?: string, limit?: number, offset?: number, @@ -21,25 +21,13 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { if (datasetVersionId === undefined) { datasetVersionId = this.DATASET_VERSION_LATEST; } - return this.getFiles(`/datasets/${datasetId}/versions/${datasetVersionId}/files`, limit, offset, orderCriteria); - } - - public async getFilesByDatasetPersistentId( - datasetPersistentId: string, - datasetVersionId?: string, - limit?: number, - offset?: number, - orderCriteria?: FileOrderCriteria, - ): Promise { - if (datasetVersionId === undefined) { - datasetVersionId = this.DATASET_VERSION_LATEST; + let endpoint; + if (typeof datasetId === 'number') { + endpoint = `/datasets/${datasetId}/versions/${datasetVersionId}/files`; + } else { + endpoint = `/datasets/:persistentId/versions/${datasetVersionId}/files?persistentId=${datasetId}`; } - return this.getFiles( - `/datasets/:persistentId/versions/${datasetVersionId}/files?persistentId=${datasetPersistentId}`, - limit, - offset, - orderCriteria, - ); + return this.getFiles(endpoint, limit, offset, orderCriteria); } public async getFileGuestbookResponsesCount(fileId: number | string): Promise { diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 49eaa663..551b6959 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -6,6 +6,7 @@ import { createDatasetViaApi } from '../../testHelpers/datasets/datasetHelper'; import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository(); @@ -44,83 +45,115 @@ describe('FilesRepository', () => { }); }); - describe('getFilesByDatasetId', () => { - test('should return all files filtering by dataset id', async () => { - const actual = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile1Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile3Name); - }); + describe('getDatasetFiles', () => { + describe('by numeric id', () => { + test('should return all files filtering by dataset id', async () => { + const actual = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile1Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile3Name); + }); - test('should return correct files filtering by dataset id and paginating', async () => { - const actual = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID, undefined, 2, 2, undefined); - assert.match(actual.length, 1); - assert.match(actual[0].name, testFile3Name); - }); + test('should return correct files filtering by dataset id and paginating', async () => { + const actual = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID, undefined, 2, 2, undefined); + assert.match(actual.length, 1); + assert.match(actual[0].name, testFile3Name); + }); - test('should return correct files filtering by dataset id and applying order criteria', async () => { - let actual = await sut.getFilesByDatasetId( - TestConstants.TEST_CREATED_DATASET_ID, - undefined, - undefined, - undefined, - FileOrderCriteria.NEWEST, - ); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile3Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile1Name); - }); + test('should return correct files filtering by dataset id and applying order criteria', async () => { + let actual = await sut.getDatasetFiles( + TestConstants.TEST_CREATED_DATASET_ID, + undefined, + undefined, + undefined, + FileOrderCriteria.NEWEST, + ); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile3Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile1Name); + }); - // TODO: Error test - }); + test('should return error when dataset does not exist', async () => { + let error: ReadError = undefined; - describe('getFilesByDatasetPersistentId', () => { - const datasetRepository = new DatasetsRepository(); + const nonExistentTestDatasetId = 100; + await sut.getDatasetFiles(nonExistentTestDatasetId).catch((e) => (error = e)); - test('should return all files filtering by persistent id', async () => { - const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); - const actual = await sut.getFilesByDatasetPersistentId(testDataset.persistentId); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile1Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile3Name); + assert.match( + error.message, + `There was an error when reading the resource. Reason was: [404] Dataset with ID ${nonExistentTestDatasetId} not found.`, + ); + }); }); - test('should return correct files filtering by persistent id and paginating', async () => { - const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); - const actual = await sut.getFilesByDatasetPersistentId(testDataset.persistentId, undefined, 2, 2, undefined); - assert.match(actual.length, 1); - assert.match(actual[0].name, testFile3Name); - }); + describe('by persistent id', () => { + const datasetRepository = new DatasetsRepository(); - test('should return correct files filtering by persistent id and applying order criteria', async () => { - const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); - let actual = await sut.getFilesByDatasetPersistentId( - testDataset.persistentId, - undefined, - undefined, - undefined, - FileOrderCriteria.NEWEST, - ); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile3Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile1Name); - }); + test('should return all files filtering by persistent id', async () => { + const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + const actual = await sut.getDatasetFiles(testDataset.persistentId); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile1Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile3Name); + }); - // TODO: Error test + test('should return correct files filtering by persistent id and paginating', async () => { + const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + const actual = await sut.getDatasetFiles(testDataset.persistentId, undefined, 2, 2, undefined); + assert.match(actual.length, 1); + assert.match(actual[0].name, testFile3Name); + }); + + test('should return correct files filtering by persistent id and applying order criteria', async () => { + const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + let actual = await sut.getDatasetFiles( + testDataset.persistentId, + undefined, + undefined, + undefined, + FileOrderCriteria.NEWEST, + ); + assert.match(actual.length, 3); + assert.match(actual[0].name, testFile3Name); + assert.match(actual[1].name, testFile2Name); + assert.match(actual[2].name, testFile1Name); + }); + + test('should return error when dataset does not exist', async () => { + let error: ReadError = undefined; + + const testWrongPersistentId = 'wrongPersistentId'; + await sut.getDatasetFiles(testWrongPersistentId).catch((e) => (error = e)); + + assert.match( + error.message, + `There was an error when reading the resource. Reason was: [404] Dataset with Persistent ID ${testWrongPersistentId} not found.`, + ); + }); + }); }); describe('getFileGuestbookResponsesCount', () => { test('should return count filtering by file id', async () => { - const currentTestFiles = await sut.getFilesByDatasetId(TestConstants.TEST_CREATED_DATASET_ID); + const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); const testFile = currentTestFiles[0]; const actual = await sut.getFileGuestbookResponsesCount(testFile.id); assert.match(actual, 0); }); - // TODO: Error test + test('should return error when file does not exist', async () => { + let error: ReadError = undefined; + + const nonExistentFiledId = 200; + await sut.getFileGuestbookResponsesCount(nonExistentFiledId).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 8ff53502..d6680908 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -27,160 +27,162 @@ describe('FilesRepository', () => { sandbox.restore(); }); - describe('getFilesByDatasetId', () => { - const testDatasetId = 1; + describe('getDatasetFiles', () => { + describe('by numeric id', () => { + const testDatasetId = 1; - test('should return files on successful response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); + test('should return files on successful response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`; - const expectedFiles = [testFile]; + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`; + const expectedFiles = [testFile]; - // API Key auth - let actual = await sut.getFilesByDatasetId(testDatasetId); + // API Key auth + let actual = await sut.getDatasetFiles(testDatasetId); - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, expectedFiles); + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedFiles); - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - actual = await sut.getFilesByDatasetId(testDatasetId); + actual = await sut.getDatasetFiles(testDatasetId); - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, expectedFiles); - }); + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedFiles); + }); - test('should return files when providing id, optional params, and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - - const testVersionId = ':draft'; - const testLimit = 10; - const testOffset = 20; - const testFileOrderCriteria = FileOrderCriteria.NEWEST; - - const actual = await sut.getFilesByDatasetId( - testDatasetId, - testVersionId, - testLimit, - testOffset, - testFileOrderCriteria, - ); - - const expectedRequestConfig = { - params: { - limit: testLimit, - offset: testOffset, - orderCriteria: testFileOrderCriteria.toString(), - }, - headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers, - }; - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/${testVersionId}/files`, - expectedRequestConfig, - ); - assert.match(actual, [testFile]); - }); + test('should return files when providing id, optional params, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - test('should return error result on error response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + const testVersionId = ':draft'; + const testLimit = 10; + const testOffset = 20; + const testFileOrderCriteria = FileOrderCriteria.NEWEST; - let error: ReadError = undefined; - await sut.getFilesByDatasetId(testDatasetId).catch((e) => (error = e)); + const actual = await sut.getDatasetFiles( + testDatasetId, + testVersionId, + testLimit, + testOffset, + testFileOrderCriteria, + ); - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); - }); - }); + const expectedRequestConfig = { + params: { + limit: testLimit, + offset: testOffset, + orderCriteria: testFileOrderCriteria.toString(), + }, + headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers, + }; - describe('getFilesByDatasetPersistentId', () => { - test('should return files on successful response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; - const expectedFiles = [testFile]; - - // API Key auth - let actual = await sut.getFilesByDatasetPersistentId(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, expectedFiles); - - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - - actual = await sut.getFilesByDatasetPersistentId(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, expectedFiles); - }); + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/${testVersionId}/files`, + expectedRequestConfig, + ); + assert.match(actual, [testFile]); + }); - test('should return files when providing persistent id, optional params, and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - - const testVersionId = ':draft'; - const testLimit = 10; - const testOffset = 20; - const testFileOrderCriteria = FileOrderCriteria.NEWEST; - - const actual = await sut.getFilesByDatasetPersistentId( - TestConstants.TEST_DUMMY_PERSISTENT_ID, - testVersionId, - testLimit, - testOffset, - testFileOrderCriteria, - ); - - const expectedRequestConfig = { - params: { - limit: testLimit, - offset: testOffset, - orderCriteria: testFileOrderCriteria.toString(), - }, - headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers, - }; - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/${testVersionId}/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, - expectedRequestConfig, - ); - assert.match(actual, [testFile]); + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getDatasetFiles(testDatasetId).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetId}/versions/:latest/files`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); }); - test('should return error result on error response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + describe('by persistent id', () => { + test('should return files on successful response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; + const expectedFiles = [testFile]; + + // API Key auth + let actual = await sut.getDatasetFiles(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedFiles); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getDatasetFiles(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedFiles); + }); + + test('should return files when providing persistent id, optional params, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFilesSuccessfulResponse); - let error: ReadError = undefined; - await sut.getFilesByDatasetPersistentId(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + const testVersionId = ':draft'; + const testLimit = 10; + const testOffset = 20; + const testFileOrderCriteria = FileOrderCriteria.NEWEST; - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); + const actual = await sut.getDatasetFiles( + TestConstants.TEST_DUMMY_PERSISTENT_ID, + testVersionId, + testLimit, + testOffset, + testFileOrderCriteria, + ); + + const expectedRequestConfig = { + params: { + limit: testLimit, + offset: testOffset, + orderCriteria: testFileOrderCriteria.toString(), + }, + headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers, + }; + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/${testVersionId}/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + expectedRequestConfig, + ); + assert.match(actual, [testFile]); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getDatasetFiles(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest/files?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); }); }); diff --git a/test/unit/files/GetFilesByDatasetId.test.ts b/test/unit/files/GetDatasetFiles.test.ts similarity index 59% rename from test/unit/files/GetFilesByDatasetId.test.ts rename to test/unit/files/GetDatasetFiles.test.ts index fb79c0b9..94b449f1 100644 --- a/test/unit/files/GetFilesByDatasetId.test.ts +++ b/test/unit/files/GetDatasetFiles.test.ts @@ -1,4 +1,4 @@ -import { GetFilesByDatasetId } from '../../../src/files/domain/useCases/GetFilesByDatasetId'; +import { GetDatasetFiles } from '../../../src/files/domain/useCases/GetDatasetFiles'; import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; import { assert, createSandbox, SinonSandbox } from 'sinon'; import { ReadError } from '../../../src/core/domain/repositories/ReadError'; @@ -13,26 +13,26 @@ describe('execute', () => { }); test('should return files on repository success', async () => { - const testFiles : File[] = [createFileModel()]; + const testFiles: File[] = [createFileModel()]; const filesRepositoryStub = {}; - const getFilesByDatasetIdStub = sandbox.stub().returns(testFiles); - filesRepositoryStub.getFilesByDatasetId = getFilesByDatasetIdStub; - const sut = new GetFilesByDatasetId(filesRepositoryStub); + const getDatasetFilesStub = sandbox.stub().returns(testFiles); + filesRepositoryStub.getDatasetFiles = getDatasetFilesStub; + const sut = new GetDatasetFiles(filesRepositoryStub); const actual = await sut.execute(1); assert.match(actual, testFiles); - assert.calledWithExactly(getFilesByDatasetIdStub, 1, undefined, undefined, undefined, undefined); + assert.calledWithExactly(getDatasetFilesStub, 1, undefined, undefined, undefined, undefined); }); test('should return error result on repository error', async () => { const filesRepositoryStub = {}; const testReadError = new ReadError(); - filesRepositoryStub.getFilesByDatasetId = sandbox.stub().throwsException(testReadError); - const sut = new GetFilesByDatasetId(filesRepositoryStub); + filesRepositoryStub.getDatasetFiles = sandbox.stub().throwsException(testReadError); + const sut = new GetDatasetFiles(filesRepositoryStub); let actualError: ReadError = undefined; - await sut.execute(1).catch((e) => (actualError = e)); + await sut.execute(1).catch((e: ReadError) => (actualError = e)); assert.match(actualError, testReadError); }); diff --git a/test/unit/files/GetFilesByDatasetPersistentId.test.ts b/test/unit/files/GetFilesByDatasetPersistentId.test.ts deleted file mode 100644 index 3a710c74..00000000 --- a/test/unit/files/GetFilesByDatasetPersistentId.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { GetFilesByDatasetPersistentId } from '../../../src/files/domain/useCases/GetFilesByDatasetPersistentId'; -import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; -import { assert, createSandbox, SinonSandbox } from 'sinon'; -import { ReadError } from '../../../src/core/domain/repositories/ReadError'; -import { File } from '../../../src/files/domain/models/File'; -import { createFileModel } from '../../testHelpers/files/filesHelper'; - -describe('execute', () => { - const sandbox: SinonSandbox = createSandbox(); - - afterEach(() => { - sandbox.restore(); - }); - - test('should return files on repository success', async () => { - const testFiles: File[] = [createFileModel()]; - const filesRepositoryStub = {}; - const getFilesByDatasetPersistentIdStub = sandbox.stub().returns(testFiles); - filesRepositoryStub.getFilesByDatasetPersistentId = getFilesByDatasetPersistentIdStub; - const sut = new GetFilesByDatasetPersistentId(filesRepositoryStub); - - const actual = await sut.execute('test'); - - assert.match(actual, testFiles); - assert.calledWithExactly(getFilesByDatasetPersistentIdStub, 'test', undefined, undefined, undefined, undefined); - }); - - test('should return error result on repository error', async () => { - const filesRepositoryStub = {}; - const testReadError = new ReadError(); - filesRepositoryStub.getFilesByDatasetPersistentId = sandbox.stub().throwsException(testReadError); - const sut = new GetFilesByDatasetPersistentId(filesRepositoryStub); - - let actualError: ReadError = undefined; - await sut.execute('test').catch((e) => (actualError = e)); - - assert.match(actualError, testReadError); - }); -}); From bd59020ac23927a290f0a0707d225485f7193993 Mon Sep 17 00:00:00 2001 From: GPortas Date: Thu, 20 Jul 2023 19:07:32 +0100 Subject: [PATCH 14/25] Refactor: single GetDataset use case managing numeric and PID ids --- .../repositories/IDatasetsRepository.ts | 3 +- .../{GetDatasetById.ts => GetDataset.ts} | 6 +- .../useCases/GetDatasetByPersistentId.ts | 15 - src/datasets/index.ts | 9 +- .../infra/repositories/DatasetsRepository.ts | 29 +- .../datasets/DatasetsRepository.test.ts | 75 ++--- .../integration/files/FilesRepository.test.ts | 6 +- test/unit/datasets/DatasetsRepository.test.ts | 296 +++++++++--------- ...DatasetById.test.ts => GetDataset.test.ts} | 10 +- .../datasets/GetDatasetByPersistentId.test.ts | 38 --- 10 files changed, 211 insertions(+), 276 deletions(-) rename src/datasets/domain/useCases/{GetDatasetById.ts => GetDataset.ts} (61%) delete mode 100644 src/datasets/domain/useCases/GetDatasetByPersistentId.ts rename test/unit/datasets/{GetDatasetById.test.ts => GetDataset.test.ts} (76%) delete mode 100644 test/unit/datasets/GetDatasetByPersistentId.test.ts diff --git a/src/datasets/domain/repositories/IDatasetsRepository.ts b/src/datasets/domain/repositories/IDatasetsRepository.ts index 59aae717..dae4c042 100644 --- a/src/datasets/domain/repositories/IDatasetsRepository.ts +++ b/src/datasets/domain/repositories/IDatasetsRepository.ts @@ -2,8 +2,7 @@ import { Dataset } from '../models/Dataset'; export interface IDatasetsRepository { getDatasetSummaryFieldNames(): Promise; - getDatasetById(datasetId: number, datasetVersionId?: string): Promise; - getDatasetByPersistentId(datasetPersistentId: string, datasetVersionId?: string): Promise; + getDataset(datasetId: number | string, datasetVersionId?: string): Promise; getPrivateUrlDataset(token: string): Promise; getDatasetCitation(datasetId: number, datasetVersionId?: string): Promise; getPrivateUrlDatasetCitation(token: string): Promise; diff --git a/src/datasets/domain/useCases/GetDatasetById.ts b/src/datasets/domain/useCases/GetDataset.ts similarity index 61% rename from src/datasets/domain/useCases/GetDatasetById.ts rename to src/datasets/domain/useCases/GetDataset.ts index 15e14748..959c0929 100644 --- a/src/datasets/domain/useCases/GetDatasetById.ts +++ b/src/datasets/domain/useCases/GetDataset.ts @@ -2,14 +2,14 @@ import { UseCase } from '../../../core/domain/useCases/UseCase'; import { IDatasetsRepository } from '../repositories/IDatasetsRepository'; import { Dataset } from '../models/Dataset'; -export class GetDatasetById implements UseCase { +export class GetDataset implements UseCase { private datasetsRepository: IDatasetsRepository; constructor(datasetsRepository: IDatasetsRepository) { this.datasetsRepository = datasetsRepository; } - async execute(datasetId: number, datasetVersionId?: string): Promise { - return await this.datasetsRepository.getDatasetById(datasetId, datasetVersionId); + async execute(datasetId: number | string, datasetVersionId?: string): Promise { + return await this.datasetsRepository.getDataset(datasetId, datasetVersionId); } } diff --git a/src/datasets/domain/useCases/GetDatasetByPersistentId.ts b/src/datasets/domain/useCases/GetDatasetByPersistentId.ts deleted file mode 100644 index 66a39a3b..00000000 --- a/src/datasets/domain/useCases/GetDatasetByPersistentId.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { UseCase } from '../../../core/domain/useCases/UseCase'; -import { IDatasetsRepository } from '../repositories/IDatasetsRepository'; -import { Dataset } from '../models/Dataset'; - -export class GetDatasetByPersistentId implements UseCase { - private datasetsRepository: IDatasetsRepository; - - constructor(datasetsRepository: IDatasetsRepository) { - this.datasetsRepository = datasetsRepository; - } - - async execute(datasetPersistentId: string, datasetVersionId?: string): Promise { - return await this.datasetsRepository.getDatasetByPersistentId(datasetPersistentId, datasetVersionId); - } -} diff --git a/src/datasets/index.ts b/src/datasets/index.ts index 90394f78..dfd59277 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -1,7 +1,6 @@ import { DatasetsRepository } from './infra/repositories/DatasetsRepository'; import { GetDatasetSummaryFieldNames } from './domain/useCases/GetDatasetSummaryFieldNames'; -import { GetDatasetById } from './domain/useCases/GetDatasetById'; -import { GetDatasetByPersistentId } from './domain/useCases/GetDatasetByPersistentId'; +import { GetDataset } from './domain/useCases/GetDataset'; import { GetPrivateUrlDataset } from './domain/useCases/GetPrivateUrlDataset'; import { GetDatasetCitation } from './domain/useCases/GetDatasetCitation'; import { GetPrivateUrlDatasetCitation } from './domain/useCases/GetPrivateUrlDatasetCitation'; @@ -9,16 +8,14 @@ import { GetPrivateUrlDatasetCitation } from './domain/useCases/GetPrivateUrlDat const datasetsRepository = new DatasetsRepository(); const getDatasetSummaryFieldNames = new GetDatasetSummaryFieldNames(datasetsRepository); -const getDatasetById = new GetDatasetById(datasetsRepository); -const getDatasetByPersistentId = new GetDatasetByPersistentId(datasetsRepository); +const getDataset = new GetDataset(datasetsRepository); const getPrivateUrlDataset = new GetPrivateUrlDataset(datasetsRepository); const getDatasetCitation = new GetDatasetCitation(datasetsRepository); const getPrivateUrlDatasetCitation = new GetPrivateUrlDatasetCitation(datasetsRepository); export { getDatasetSummaryFieldNames, - getDatasetById, - getDatasetByPersistentId, + getDataset, getPrivateUrlDataset, getDatasetCitation, getPrivateUrlDatasetCitation, diff --git a/src/datasets/infra/repositories/DatasetsRepository.ts b/src/datasets/infra/repositories/DatasetsRepository.ts index cbec5c19..f1d1dc52 100644 --- a/src/datasets/infra/repositories/DatasetsRepository.ts +++ b/src/datasets/infra/repositories/DatasetsRepository.ts @@ -20,20 +20,21 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }); } - public async getDatasetById(datasetId: number, datasetVersionId?: string): Promise { + public async getDataset(datasetId: number | string, datasetVersionId?: string): Promise { if (datasetVersionId === undefined) { datasetVersionId = this.DATASET_VERSION_LATEST; } - return this.getDatasetVersion(`/datasets/${datasetId}/versions/${datasetVersionId}`); - } - - public async getDatasetByPersistentId(datasetPersistentId: string, datasetVersionId?: string): Promise { - if (datasetVersionId === undefined) { - datasetVersionId = this.DATASET_VERSION_LATEST; + let endpoint; + if (typeof datasetId === 'number') { + endpoint = `/datasets/${datasetId}/versions/${datasetVersionId}`; + } else { + endpoint = `/datasets/:persistentId/versions/${datasetVersionId}?persistentId=${datasetId}`; } - return this.getDatasetVersion( - `/datasets/:persistentId/versions/${datasetVersionId}?persistentId=${datasetPersistentId}`, - ); + return this.doGet(endpoint, true) + .then((response) => transformVersionResponseToDataset(response)) + .catch((error) => { + throw error; + }); } public async getDatasetCitation(datasetId: number, datasetVersionId?: string): Promise { @@ -47,14 +48,6 @@ export class DatasetsRepository extends ApiRepository implements IDatasetsReposi }); } - private async getDatasetVersion(endpoint: string): Promise { - return this.doGet(endpoint, true) - .then((response) => transformVersionResponseToDataset(response)) - .catch((error) => { - throw error; - }); - } - public async getPrivateUrlDatasetCitation(token: string): Promise { return this.doGet(`/datasets/privateUrlDatasetVersion/${token}/citation`) .then((response) => response.data.data.message) diff --git a/test/integration/datasets/DatasetsRepository.test.ts b/test/integration/datasets/DatasetsRepository.test.ts index 7a2b8b26..9d81bab6 100644 --- a/test/integration/datasets/DatasetsRepository.test.ts +++ b/test/integration/datasets/DatasetsRepository.test.ts @@ -26,51 +26,52 @@ describe('DatasetsRepository', () => { }); }); - describe('getDatasetById', () => { - test('should return dataset when it exists filtering by id', async () => { - const actual = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); - expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); - }); - - test('should return dataset when it exists filtering by id and version id', async () => { - const actual = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID, ':draft'); - expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); - }); + describe('getDataset', () => { + describe('by numeric id', () => { + test('should return dataset when it exists filtering by id', async () => { + const actual = await sut.getDataset(TestConstants.TEST_CREATED_DATASET_ID); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); + }); - test('should return error when dataset does not exist', async () => { - let error: ReadError = undefined; - await sut.getDatasetById(nonExistentTestDatasetId).catch((e) => (error = e)); + test('should return dataset when it exists filtering by id and version id', async () => { + const actual = await sut.getDataset(TestConstants.TEST_CREATED_DATASET_ID, ':draft'); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); + }); - assert.match( - error.message, - `There was an error when reading the resource. Reason was: [404] Dataset with ID ${nonExistentTestDatasetId} not found.`, - ); - }); - }); + test('should return error when dataset does not exist', async () => { + let error: ReadError = undefined; + await sut.getDataset(nonExistentTestDatasetId).catch((e) => (error = e)); - describe('getDatasetByPersistentId', () => { - test('should return dataset when it exists filtering by persistent id', async () => { - const createdDataset = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); - const actual = await sut.getDatasetByPersistentId(createdDataset.persistentId); - expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); + assert.match( + error.message, + `There was an error when reading the resource. Reason was: [404] Dataset with ID ${nonExistentTestDatasetId} not found.`, + ); + }); }); + describe('by persistent id', () => { + test('should return dataset when it exists filtering by persistent id', async () => { + const createdDataset = await sut.getDataset(TestConstants.TEST_CREATED_DATASET_ID); + const actual = await sut.getDataset(createdDataset.persistentId); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); + }); - test('should return dataset when it exists filtering by persistent id and version id', async () => { - const createdDataset = await sut.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); - const actual = await sut.getDatasetByPersistentId(createdDataset.persistentId, ':draft'); - expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); - }); + test('should return dataset when it exists filtering by persistent id and version id', async () => { + const createdDataset = await sut.getDataset(TestConstants.TEST_CREATED_DATASET_ID); + const actual = await sut.getDataset(createdDataset.persistentId, ':draft'); + expect(actual.id).toBe(TestConstants.TEST_CREATED_DATASET_ID); + }); - test('should return error when dataset does not exist', async () => { - let error: ReadError = undefined; + test('should return error when dataset does not exist', async () => { + let error: ReadError = undefined; - const testWrongPersistentId = 'wrongPersistentId'; - await sut.getDatasetByPersistentId(testWrongPersistentId).catch((e) => (error = e)); + const testWrongPersistentId = 'wrongPersistentId'; + await sut.getDataset(testWrongPersistentId).catch((e) => (error = e)); - assert.match( - error.message, - `There was an error when reading the resource. Reason was: [404] Dataset with Persistent ID ${testWrongPersistentId} not found.`, - ); + assert.match( + error.message, + `There was an error when reading the resource. Reason was: [404] Dataset with Persistent ID ${testWrongPersistentId} not found.`, + ); + }); }); }); diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 551b6959..eb4ed547 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -92,7 +92,7 @@ describe('FilesRepository', () => { const datasetRepository = new DatasetsRepository(); test('should return all files filtering by persistent id', async () => { - const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + const testDataset = await datasetRepository.getDataset(TestConstants.TEST_CREATED_DATASET_ID); const actual = await sut.getDatasetFiles(testDataset.persistentId); assert.match(actual.length, 3); assert.match(actual[0].name, testFile1Name); @@ -101,14 +101,14 @@ describe('FilesRepository', () => { }); test('should return correct files filtering by persistent id and paginating', async () => { - const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + const testDataset = await datasetRepository.getDataset(TestConstants.TEST_CREATED_DATASET_ID); const actual = await sut.getDatasetFiles(testDataset.persistentId, undefined, 2, 2, undefined); assert.match(actual.length, 1); assert.match(actual[0].name, testFile3Name); }); test('should return correct files filtering by persistent id and applying order criteria', async () => { - const testDataset = await datasetRepository.getDatasetById(TestConstants.TEST_CREATED_DATASET_ID); + const testDataset = await datasetRepository.getDataset(TestConstants.TEST_CREATED_DATASET_ID); let actual = await sut.getDatasetFiles( testDataset.persistentId, undefined, diff --git a/test/unit/datasets/DatasetsRepository.test.ts b/test/unit/datasets/DatasetsRepository.test.ts index 9a1f194b..ca7004cf 100644 --- a/test/unit/datasets/DatasetsRepository.test.ts +++ b/test/unit/datasets/DatasetsRepository.test.ts @@ -76,156 +76,154 @@ describe('DatasetsRepository', () => { }); }); - describe('getDatasetById', () => { - test('should return Dataset when providing id, no version, and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/:latest`; - - // API Key auth - let actual = await sut.getDatasetById(testDatasetModel.id); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, testDatasetModel); - - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - actual = await sut.getDatasetById(testDatasetModel.id); - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, testDatasetModel); - }); - - test('should return Dataset when providing id, version, and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); - - const actual = await sut.getDatasetById(testDatasetModel.id, String(testDatasetModel.versionId)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/${testDatasetModel.versionId}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, testDatasetModel); - }); - - test('should return Dataset when providing id, version, and response with license is successful', async () => { - const testDatasetLicense = createDatasetLicenseModel(); - const testDatasetVersionWithLicenseSuccessfulResponse = { - data: { - status: 'OK', - data: createDatasetVersionPayload(testDatasetLicense), - }, - }; - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionWithLicenseSuccessfulResponse); - - const actual = await sut.getDatasetById(testDatasetModel.id, String(testDatasetModel.versionId)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/${testDatasetModel.versionId}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, createDatasetModel(testDatasetLicense)); - }); - - test('should return Dataset when providing id, version, and response with license without icon URI is successful', async () => { - const testDatasetLicenseWithoutIconUri = createDatasetLicenseModel(false); - const testDatasetVersionWithLicenseSuccessfulResponse = { - data: { - status: 'OK', - data: createDatasetVersionPayload(testDatasetLicenseWithoutIconUri), - }, - }; - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionWithLicenseSuccessfulResponse); - - const actual = await sut.getDatasetById(testDatasetModel.id, String(testDatasetModel.versionId)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/${testDatasetModel.versionId}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, createDatasetModel(testDatasetLicenseWithoutIconUri)); + describe('getDataset', () => { + describe('by numeric id', () => { + test('should return Dataset when providing id, no version, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/:latest`; + + // API Key auth + let actual = await sut.getDataset(testDatasetModel.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testDatasetModel); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + actual = await sut.getDataset(testDatasetModel.id); + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, testDatasetModel); + }); + + test('should return Dataset when providing id, version, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); + + const actual = await sut.getDataset(testDatasetModel.id, String(testDatasetModel.versionId)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/${testDatasetModel.versionId}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testDatasetModel); + }); + + test('should return Dataset when providing id, version, and response with license is successful', async () => { + const testDatasetLicense = createDatasetLicenseModel(); + const testDatasetVersionWithLicenseSuccessfulResponse = { + data: { + status: 'OK', + data: createDatasetVersionPayload(testDatasetLicense), + }, + }; + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionWithLicenseSuccessfulResponse); + + const actual = await sut.getDataset(testDatasetModel.id, String(testDatasetModel.versionId)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/${testDatasetModel.versionId}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, createDatasetModel(testDatasetLicense)); + }); + + test('should return Dataset when providing id, version, and response with license without icon URI is successful', async () => { + const testDatasetLicenseWithoutIconUri = createDatasetLicenseModel(false); + const testDatasetVersionWithLicenseSuccessfulResponse = { + data: { + status: 'OK', + data: createDatasetVersionPayload(testDatasetLicenseWithoutIconUri), + }, + }; + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionWithLicenseSuccessfulResponse); + + const actual = await sut.getDataset(testDatasetModel.id, String(testDatasetModel.versionId)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/${testDatasetModel.versionId}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, createDatasetModel(testDatasetLicenseWithoutIconUri)); + }); + + 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.getDataset(testDatasetModel.id).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/:latest`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); }); - - 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.getDatasetById(testDatasetModel.id).catch((e) => (error = e)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/${testDatasetModel.id}/versions/:latest`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); - }); - }); - - describe('getDatasetByPersistentId', () => { - test('should return Dataset when providing persistent id, no version, and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest?persistentId=${testDatasetModel.persistentId}`; - - // API Key auth - let actual = await sut.getDatasetByPersistentId(testDatasetModel.persistentId); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, testDatasetModel); - - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - - actual = await sut.getDatasetByPersistentId(testDatasetModel.persistentId); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, testDatasetModel); - }); - - test('should return Dataset when providing persistent id, version, and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); - - const actual = await sut.getDatasetByPersistentId( - testDatasetModel.persistentId, - String(testDatasetModel.versionId), - ); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/${testDatasetModel.versionId}?persistentId=${testDatasetModel.persistentId}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, testDatasetModel); - }); - - 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.getDatasetByPersistentId(testDatasetModel.persistentId).catch((e) => (error = e)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest?persistentId=${testDatasetModel.persistentId}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); + describe('by persistent id', () => { + test('should return Dataset when providing persistent id, no version, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest?persistentId=${testDatasetModel.persistentId}`; + + // API Key auth + let actual = await sut.getDataset(testDatasetModel.persistentId); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testDatasetModel); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getDataset(testDatasetModel.persistentId); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, testDatasetModel); + }); + + test('should return Dataset when providing persistent id, version, and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testDatasetVersionSuccessfulResponse); + + const actual = await sut.getDataset(testDatasetModel.persistentId, String(testDatasetModel.versionId)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/${testDatasetModel.versionId}?persistentId=${testDatasetModel.persistentId}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testDatasetModel); + }); + + 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.getDataset(testDatasetModel.persistentId).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/datasets/:persistentId/versions/:latest?persistentId=${testDatasetModel.persistentId}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); }); }); diff --git a/test/unit/datasets/GetDatasetById.test.ts b/test/unit/datasets/GetDataset.test.ts similarity index 76% rename from test/unit/datasets/GetDatasetById.test.ts rename to test/unit/datasets/GetDataset.test.ts index 07199b37..9e11aa45 100644 --- a/test/unit/datasets/GetDatasetById.test.ts +++ b/test/unit/datasets/GetDataset.test.ts @@ -1,4 +1,4 @@ -import { GetDatasetById } from '../../../src/datasets/domain/useCases/GetDatasetById'; +import { GetDataset } from '../../../src/datasets/domain/useCases/GetDataset'; import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'; import { assert, createSandbox, SinonSandbox } from 'sinon'; import { createDatasetModel } from '../../testHelpers/datasets/datasetHelper'; @@ -15,8 +15,8 @@ describe('execute', () => { const testDataset = createDatasetModel(); const datasetsRepositoryStub = {}; const getDatasetStub = sandbox.stub().returns(testDataset); - datasetsRepositoryStub.getDatasetById = getDatasetStub; - const sut = new GetDatasetById(datasetsRepositoryStub); + datasetsRepositoryStub.getDataset = getDatasetStub; + const sut = new GetDataset(datasetsRepositoryStub); const actual = await sut.execute(1); @@ -27,8 +27,8 @@ describe('execute', () => { test('should return error result on repository error', async () => { const datasetsRepositoryStub = {}; const testReadError = new ReadError(); - datasetsRepositoryStub.getDatasetById = sandbox.stub().throwsException(testReadError); - const sut = new GetDatasetById(datasetsRepositoryStub); + datasetsRepositoryStub.getDataset = sandbox.stub().throwsException(testReadError); + const sut = new GetDataset(datasetsRepositoryStub); let actualError: ReadError = undefined; await sut.execute(1).catch((e) => (actualError = e)); diff --git a/test/unit/datasets/GetDatasetByPersistentId.test.ts b/test/unit/datasets/GetDatasetByPersistentId.test.ts deleted file mode 100644 index b3ba5060..00000000 --- a/test/unit/datasets/GetDatasetByPersistentId.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { GetDatasetByPersistentId } from '../../../src/datasets/domain/useCases/GetDatasetByPersistentId'; -import { IDatasetsRepository } from '../../../src/datasets/domain/repositories/IDatasetsRepository'; -import { assert, createSandbox, SinonSandbox } from 'sinon'; -import { createDatasetModel } from '../../testHelpers/datasets/datasetHelper'; -import { ReadError } from '../../../src/core/domain/repositories/ReadError'; - -describe('execute', () => { - const sandbox: SinonSandbox = createSandbox(); - - afterEach(() => { - sandbox.restore(); - }); - - test('should return dataset on repository success', async () => { - const testDataset = createDatasetModel(); - const datasetsRepositoryStub = {}; - const getDatasetStub = sandbox.stub().returns(testDataset); - datasetsRepositoryStub.getDatasetByPersistentId = getDatasetStub; - const sut = new GetDatasetByPersistentId(datasetsRepositoryStub); - - const actual = await sut.execute('1'); - - assert.match(actual, testDataset); - assert.calledWithExactly(getDatasetStub, '1', undefined); - }); - - test('should return error result on repository error', async () => { - const datasetsRepositoryStub = {}; - const testReadError = new ReadError(); - datasetsRepositoryStub.getDatasetByPersistentId = sandbox.stub().throwsException(testReadError); - const sut = new GetDatasetByPersistentId(datasetsRepositoryStub); - - let actualError: ReadError = undefined; - await sut.execute('1').catch((e) => (actualError = e)); - - assert.match(actualError, testReadError); - }); -}); From 44dab6a6125a0004eef8cd0f00d2b42a1c34d052 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 21 Jul 2023 12:07:33 +0100 Subject: [PATCH 15/25] Added: canFileBeDownloaded use case --- .../domain/repositories/IFilesRepository.ts | 2 + .../domain/useCases/CanFileBeDownloaded.ts | 14 +++ src/files/index.ts | 4 +- .../infra/repositories/FilesRepository.ts | 41 ++++---- src/index.ts | 1 + .../integration/files/FilesRepository.test.ts | 23 ++++- test/unit/files/CanFileBeDownloaded.test.ts | 51 ++++++++++ test/unit/files/FilesRepository.test.ts | 96 +++++++++++++++++++ 8 files changed, 212 insertions(+), 20 deletions(-) create mode 100644 src/files/domain/useCases/CanFileBeDownloaded.ts create mode 100644 test/unit/files/CanFileBeDownloaded.test.ts diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index df1de02f..c2592e7b 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -11,4 +11,6 @@ export interface IFilesRepository { ): Promise; getFileGuestbookResponsesCount(fileId: number | string): Promise; + + canFileBeDownloaded(fileId: number | string): Promise; } diff --git a/src/files/domain/useCases/CanFileBeDownloaded.ts b/src/files/domain/useCases/CanFileBeDownloaded.ts new file mode 100644 index 00000000..f8404ca3 --- /dev/null +++ b/src/files/domain/useCases/CanFileBeDownloaded.ts @@ -0,0 +1,14 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IFilesRepository } from '../repositories/IFilesRepository'; + +export class CanFileBeDownloaded implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute(fileId: number | string): Promise { + return await this.filesRepository.canFileBeDownloaded(fileId); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index c16f0ca1..21793106 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1,13 +1,15 @@ import { FilesRepository } from './infra/repositories/FilesRepository'; import { GetDatasetFiles } from './domain/useCases/GetDatasetFiles'; import { GetFileGuestbookResponsesCount } from './domain/useCases/GetFileGuestbookResponsesCount'; +import { CanFileBeDownloaded } from './domain/useCases/CanFileBeDownloaded'; const filesRepository = new FilesRepository(); const getDatasetFiles = new GetDatasetFiles(filesRepository); const getFileGuestbookResponsesCount = new GetFileGuestbookResponsesCount(filesRepository); +const canFileBeDownloaded = new CanFileBeDownloaded(filesRepository); -export { getDatasetFiles, getFileGuestbookResponsesCount }; +export { getDatasetFiles, getFileGuestbookResponsesCount, canFileBeDownloaded }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 0b6c0321..ebdc8548 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -27,7 +27,21 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { } else { endpoint = `/datasets/:persistentId/versions/${datasetVersionId}/files?persistentId=${datasetId}`; } - return this.getFiles(endpoint, limit, offset, orderCriteria); + const queryParams: GetFilesQueryParams = {}; + if (limit !== undefined) { + queryParams.limit = limit; + } + if (offset !== undefined) { + queryParams.offset = offset; + } + if (orderCriteria !== undefined) { + queryParams.orderCriteria = orderCriteria.toString(); + } + return this.doGet(endpoint, true, queryParams) + .then((response) => transformFilesResponseToFiles(response)) + .catch((error) => { + throw error; + }); } public async getFileGuestbookResponsesCount(fileId: number | string): Promise { @@ -44,24 +58,15 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { }); } - private async getFiles( - endpoint: string, - limit?: number, - offset?: number, - orderCriteria?: FileOrderCriteria, - ): Promise { - const queryParams: GetFilesQueryParams = {}; - if (limit !== undefined) { - queryParams.limit = limit; - } - if (offset !== undefined) { - queryParams.offset = offset; - } - if (orderCriteria !== undefined) { - queryParams.orderCriteria = orderCriteria.toString(); + public async canFileBeDownloaded(fileId: string | number): Promise { + let endpoint; + if (typeof fileId === 'number') { + endpoint = `/files/${fileId}/canBeDownloaded`; + } else { + endpoint = `/files/:persistentId/canBeDownloaded?persistentId=${fileId}`; } - return this.doGet(endpoint, true, queryParams) - .then((response) => transformFilesResponseToFiles(response)) + return this.doGet(endpoint, true) + .then((response) => response.data.data as boolean) .catch((error) => { throw error; }); diff --git a/src/index.ts b/src/index.ts index 549bb7d2..d5479039 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,3 +4,4 @@ export * from './users'; export * from './auth'; export * from './datasets'; export * from './metadataBlocks'; +export * from './files'; diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index eb4ed547..be338bbd 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -15,6 +15,8 @@ describe('FilesRepository', () => { const testFile2Name = 'test-file-2.txt'; const testFile3Name = 'test-file-3.txt'; + const nonExistentFiledId = 200; + beforeAll(async () => { ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.API_KEY, process.env.TEST_API_KEY); await createDatasetViaApi() @@ -147,7 +149,6 @@ describe('FilesRepository', () => { test('should return error when file does not exist', async () => { let error: ReadError = undefined; - const nonExistentFiledId = 200; await sut.getFileGuestbookResponsesCount(nonExistentFiledId).catch((e) => (error = e)); assert.match( @@ -156,4 +157,24 @@ describe('FilesRepository', () => { ); }); }); + + describe('canFileBeDownloaded', () => { + test('should return result filtering by file id', async () => { + const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); + const testFile = currentTestFiles[0]; + const actual = await sut.canFileBeDownloaded(testFile.id); + assert.match(actual, true); + }); + + test('should return error when file does not exist', async () => { + let error: ReadError = undefined; + + await sut.canFileBeDownloaded(nonExistentFiledId).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/CanFileBeDownloaded.test.ts b/test/unit/files/CanFileBeDownloaded.test.ts new file mode 100644 index 00000000..cbbe5b32 --- /dev/null +++ b/test/unit/files/CanFileBeDownloaded.test.ts @@ -0,0 +1,51 @@ +import { CanFileBeDownloaded } from '../../../src/files/domain/useCases/CanFileBeDownloaded'; +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { TestConstants } from '../../testHelpers/TestConstants'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + const testFileId = 1; + const expectedResult = true; + + afterEach(() => { + sandbox.restore(); + }); + + test('should return result on repository success filtering by id', async () => { + const filesRepositoryStub = {}; + const canFileBeDownloadedStub = sandbox.stub().returns(expectedResult); + filesRepositoryStub.canFileBeDownloaded = canFileBeDownloadedStub; + const sut = new CanFileBeDownloaded(filesRepositoryStub); + + const actual = await sut.execute(testFileId); + + assert.match(actual, expectedResult); + assert.calledWithExactly(canFileBeDownloadedStub, testFileId); + }); + + test('should return result on repository success filtering by persistent id', async () => { + const filesRepositoryStub = {}; + const canFileBeDownloadedStub = sandbox.stub().returns(expectedResult); + filesRepositoryStub.canFileBeDownloaded = canFileBeDownloadedStub; + const sut = new CanFileBeDownloaded(filesRepositoryStub); + + const actual = await sut.execute(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.match(actual, expectedResult); + assert.calledWithExactly(canFileBeDownloadedStub, TestConstants.TEST_DUMMY_PERSISTENT_ID); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.canFileBeDownloaded = sandbox.stub().throwsException(testReadError); + const sut = new CanFileBeDownloaded(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute(testFileId).catch((e) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +}); diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index d6680908..f7dae626 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -283,4 +283,100 @@ describe('FilesRepository', () => { }); }); }); + + describe('canFileBeDownloaded', () => { + const expectedResult = true; + const testCanFileBeDownloadedResponse = { + data: { + status: 'OK', + data: expectedResult, + }, + }; + + describe('by numeric id', () => { + test('should return result when providing id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testCanFileBeDownloadedResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/canBeDownloaded`; + + // API Key auth + let actual = await sut.canFileBeDownloaded(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedResult); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.canFileBeDownloaded(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedResult); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.canFileBeDownloaded(testFile.id).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/${testFile.id}/canBeDownloaded`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + + describe('by persistent id', () => { + test('should return result when providing persistent id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testCanFileBeDownloadedResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/canBeDownloaded?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; + + // API Key auth + let actual = await sut.canFileBeDownloaded(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedResult); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.canFileBeDownloaded(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedResult); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.canFileBeDownloaded(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/:persistentId/canBeDownloaded?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + }); }); From b7ff9d1234403e4f9b781e9ea78b4ca42bca50a2 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 21 Jul 2023 12:43:15 +0100 Subject: [PATCH 16/25] Added: getFileThumbnailClass use case --- src/files/domain/models/FileThumbnailClass.ts | 13 +++ .../domain/repositories/IFilesRepository.ts | 3 + .../domain/useCases/GetFileThumbnailClass.ts | 15 +++ src/files/index.ts | 5 +- .../infra/repositories/FilesRepository.ts | 15 +++ .../integration/files/FilesRepository.test.ts | 21 ++++ test/unit/files/FilesRepository.test.ts | 99 +++++++++++++++++++ test/unit/files/GetFileThumbnailClass.test.ts | 52 ++++++++++ 8 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/files/domain/models/FileThumbnailClass.ts create mode 100644 src/files/domain/useCases/GetFileThumbnailClass.ts create mode 100644 test/unit/files/GetFileThumbnailClass.test.ts diff --git a/src/files/domain/models/FileThumbnailClass.ts b/src/files/domain/models/FileThumbnailClass.ts new file mode 100644 index 00000000..02ea8f95 --- /dev/null +++ b/src/files/domain/models/FileThumbnailClass.ts @@ -0,0 +1,13 @@ +export enum FileThumbnailClass { + AUDIO = 'audio', + CODE = 'code', + DOCUMENT = 'document', + ASTRO = 'astro', + IMAGE = 'image', + NETWORK = 'network', + GEODATA = 'geodata', + TABULAR = 'tabular', + VIDEO = 'video', + PACKAGE = 'package', + OTHER = 'other', +} diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index c2592e7b..5a9404e0 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -1,5 +1,6 @@ import { FileOrderCriteria } from '../models/FileOrderCriteria'; import { File } from '../models/File'; +import { FileThumbnailClass } from '../models/FileThumbnailClass'; export interface IFilesRepository { getDatasetFiles( @@ -13,4 +14,6 @@ export interface IFilesRepository { getFileGuestbookResponsesCount(fileId: number | string): Promise; canFileBeDownloaded(fileId: number | string): Promise; + + getFileThumbnailClass(fileId: number | string): Promise; } diff --git a/src/files/domain/useCases/GetFileThumbnailClass.ts b/src/files/domain/useCases/GetFileThumbnailClass.ts new file mode 100644 index 00000000..c4591026 --- /dev/null +++ b/src/files/domain/useCases/GetFileThumbnailClass.ts @@ -0,0 +1,15 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IFilesRepository } from '../repositories/IFilesRepository'; +import { FileThumbnailClass } from '../models/FileThumbnailClass'; + +export class GetFileThumbnailClass implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute(fileId: number | string): Promise { + return await this.filesRepository.getFileThumbnailClass(fileId); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index 21793106..d6d66372 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -2,14 +2,17 @@ import { FilesRepository } from './infra/repositories/FilesRepository'; import { GetDatasetFiles } from './domain/useCases/GetDatasetFiles'; import { GetFileGuestbookResponsesCount } from './domain/useCases/GetFileGuestbookResponsesCount'; import { CanFileBeDownloaded } from './domain/useCases/CanFileBeDownloaded'; +import { GetFileThumbnailClass } from './domain/useCases/GetFileThumbnailClass'; const filesRepository = new FilesRepository(); const getDatasetFiles = new GetDatasetFiles(filesRepository); const getFileGuestbookResponsesCount = new GetFileGuestbookResponsesCount(filesRepository); const canFileBeDownloaded = new CanFileBeDownloaded(filesRepository); +const getFileThumbnailClass = new GetFileThumbnailClass(filesRepository); -export { getDatasetFiles, getFileGuestbookResponsesCount, canFileBeDownloaded }; +export { getDatasetFiles, getFileGuestbookResponsesCount, canFileBeDownloaded, getFileThumbnailClass }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; +export { FileThumbnailClass } from './domain/models/FileThumbnailClass'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index ebdc8548..459a99ad 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -3,6 +3,7 @@ import { IFilesRepository } from '../../domain/repositories/IFilesRepository'; import { FileOrderCriteria } from '../../domain/models/FileOrderCriteria'; import { File } from '../../domain/models/File'; import { transformFilesResponseToFiles } from './transformers/fileTransformers'; +import { FileThumbnailClass } from '../../domain/models/FileThumbnailClass'; export interface GetFilesQueryParams { limit?: number; @@ -71,4 +72,18 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { throw error; }); } + + public async getFileThumbnailClass(fileId: string | number): Promise { + let endpoint; + if (typeof fileId === 'number') { + endpoint = `/files/${fileId}/thumbnailClass`; + } else { + endpoint = `/files/:persistentId/thumbnailClass?persistentId=${fileId}`; + } + return this.doGet(endpoint, true) + .then((response) => response.data.data.message as FileThumbnailClass) + .catch((error) => { + throw error; + }); + } } diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index be338bbd..65b5f906 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -7,6 +7,7 @@ import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'; import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbnailClass'; describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository(); @@ -177,4 +178,24 @@ describe('FilesRepository', () => { ); }); }); + + describe('getFileThumbnailClass', () => { + test('should return thumbnail class filtering by file id', async () => { + const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); + const testFile = currentTestFiles[0]; + const actual = await sut.getFileThumbnailClass(testFile.id); + assert.match(actual, FileThumbnailClass.DOCUMENT); + }); + + test('should return error when file does not exist', async () => { + let error: ReadError = undefined; + + await sut.getFileThumbnailClass(nonExistentFiledId).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 f7dae626..702db348 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -7,6 +7,7 @@ import { ApiConfig, DataverseApiAuthMechanism } from '../../../src/core/infra/re import { TestConstants } from '../../testHelpers/TestConstants'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; import { createFilePayload, createFileModel } from '../../testHelpers/files/filesHelper'; +import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbnailClass'; describe('FilesRepository', () => { const sandbox: SinonSandbox = createSandbox(); @@ -379,4 +380,102 @@ describe('FilesRepository', () => { }); }); }); + + describe('getFileThumbnailClass', () => { + const expectedThumbnailClass = FileThumbnailClass.IMAGE; + const testGetFileThumbnailClassResponse = { + data: { + status: 'OK', + data: { + message: expectedThumbnailClass.toString(), + }, + }, + }; + + describe('by numeric id', () => { + test('should return thumbnail class when providing id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileThumbnailClassResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/thumbnailClass`; + + // API Key auth + let actual = await sut.getFileThumbnailClass(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedThumbnailClass); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileThumbnailClass(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedThumbnailClass); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileThumbnailClass(testFile.id).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/${testFile.id}/thumbnailClass`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + + describe('by persistent id', () => { + test('should return thumbnail class when providing persistent id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileThumbnailClassResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/thumbnailClass?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; + + // API Key auth + let actual = await sut.getFileThumbnailClass(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedThumbnailClass); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileThumbnailClass(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedThumbnailClass); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileThumbnailClass(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/:persistentId/thumbnailClass?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + }); }); diff --git a/test/unit/files/GetFileThumbnailClass.test.ts b/test/unit/files/GetFileThumbnailClass.test.ts new file mode 100644 index 00000000..1e07869a --- /dev/null +++ b/test/unit/files/GetFileThumbnailClass.test.ts @@ -0,0 +1,52 @@ +import { GetFileThumbnailClass } from '../../../src/files/domain/useCases/GetFileThumbnailClass'; +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { TestConstants } from '../../testHelpers/TestConstants'; +import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbnailClass'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + const testFileId = 1; + const testThumbnailClass = FileThumbnailClass.IMAGE; + + afterEach(() => { + sandbox.restore(); + }); + + test('should return thumbnail class on repository success filtering by id', async () => { + const filesRepositoryStub = {}; + const getFileThumbnailClassStub = sandbox.stub().returns(testThumbnailClass); + filesRepositoryStub.getFileThumbnailClass = getFileThumbnailClassStub; + const sut = new GetFileThumbnailClass(filesRepositoryStub); + + const actual = await sut.execute(testFileId); + + assert.match(actual, testThumbnailClass); + assert.calledWithExactly(getFileThumbnailClassStub, testFileId); + }); + + test('should return thumbnail class on repository success filtering by persistent id', async () => { + const filesRepositoryStub = {}; + const getFileThumbnailClassStub = sandbox.stub().returns(testThumbnailClass); + filesRepositoryStub.getFileThumbnailClass = getFileThumbnailClassStub; + const sut = new GetFileThumbnailClass(filesRepositoryStub); + + const actual = await sut.execute(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.match(actual, testThumbnailClass); + assert.calledWithExactly(getFileThumbnailClassStub, TestConstants.TEST_DUMMY_PERSISTENT_ID); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.getFileThumbnailClass = sandbox.stub().throwsException(testReadError); + const sut = new GetFileThumbnailClass(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute(testFileId).catch((e) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +}); From 93e30d2474716edf67dade3caada166268263d18 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 21 Jul 2023 13:36:26 +0100 Subject: [PATCH 17/25] Added: getFileDataTables use case (pending repository logic) --- src/files/domain/models/FileDataTable.ts | 1 + .../domain/repositories/IFilesRepository.ts | 3 ++ .../domain/useCases/GetFileDataTables.ts | 15 +++++++ src/files/index.ts | 11 ++++- .../infra/repositories/FilesRepository.ts | 5 +++ .../testHelpers/files/fileDataTablesHelper.ts | 5 +++ test/unit/files/GetFileDataTables.test.ts | 40 +++++++++++++++++++ 7 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/files/domain/models/FileDataTable.ts create mode 100644 src/files/domain/useCases/GetFileDataTables.ts create mode 100644 test/testHelpers/files/fileDataTablesHelper.ts create mode 100644 test/unit/files/GetFileDataTables.test.ts diff --git a/src/files/domain/models/FileDataTable.ts b/src/files/domain/models/FileDataTable.ts new file mode 100644 index 00000000..9cc2549e --- /dev/null +++ b/src/files/domain/models/FileDataTable.ts @@ -0,0 +1 @@ +export interface FileDataTable {} diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index 5a9404e0..54b18e37 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -1,6 +1,7 @@ import { FileOrderCriteria } from '../models/FileOrderCriteria'; import { File } from '../models/File'; import { FileThumbnailClass } from '../models/FileThumbnailClass'; +import { FileDataTable } from '../models/FileDataTable'; export interface IFilesRepository { getDatasetFiles( @@ -16,4 +17,6 @@ export interface IFilesRepository { canFileBeDownloaded(fileId: number | string): Promise; getFileThumbnailClass(fileId: number | string): Promise; + + getFileDataTables(fileId: number | string): Promise; } diff --git a/src/files/domain/useCases/GetFileDataTables.ts b/src/files/domain/useCases/GetFileDataTables.ts new file mode 100644 index 00000000..906114c9 --- /dev/null +++ b/src/files/domain/useCases/GetFileDataTables.ts @@ -0,0 +1,15 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IFilesRepository } from '../repositories/IFilesRepository'; +import { FileDataTable } from '../models/FileDataTable'; + +export class GetFileDataTables implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute(datasetId: number | string): Promise { + return await this.filesRepository.getFileDataTables(datasetId); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index d6d66372..96903e0e 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -3,6 +3,7 @@ import { GetDatasetFiles } from './domain/useCases/GetDatasetFiles'; import { GetFileGuestbookResponsesCount } from './domain/useCases/GetFileGuestbookResponsesCount'; import { CanFileBeDownloaded } from './domain/useCases/CanFileBeDownloaded'; import { GetFileThumbnailClass } from './domain/useCases/GetFileThumbnailClass'; +import { GetFileDataTables } from './domain/useCases/GetFileDataTables'; const filesRepository = new FilesRepository(); @@ -10,9 +11,17 @@ const getDatasetFiles = new GetDatasetFiles(filesRepository); const getFileGuestbookResponsesCount = new GetFileGuestbookResponsesCount(filesRepository); const canFileBeDownloaded = new CanFileBeDownloaded(filesRepository); const getFileThumbnailClass = new GetFileThumbnailClass(filesRepository); +const getFileDataTables = new GetFileDataTables(filesRepository); -export { getDatasetFiles, getFileGuestbookResponsesCount, canFileBeDownloaded, getFileThumbnailClass }; +export { + getDatasetFiles, + getFileGuestbookResponsesCount, + canFileBeDownloaded, + getFileThumbnailClass, + getFileDataTables, +}; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; export { FileThumbnailClass } from './domain/models/FileThumbnailClass'; +export { FileDataTable } from './domain/models/FileDataTable'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 459a99ad..29c229f6 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -4,6 +4,7 @@ import { FileOrderCriteria } from '../../domain/models/FileOrderCriteria'; import { File } from '../../domain/models/File'; import { transformFilesResponseToFiles } from './transformers/fileTransformers'; import { FileThumbnailClass } from '../../domain/models/FileThumbnailClass'; +import { FileDataTable } from '../../domain/models/FileDataTable'; export interface GetFilesQueryParams { limit?: number; @@ -86,4 +87,8 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { throw error; }); } + + public async getFileDataTables(fileId: string | number): Promise { + throw new Error(`Method not implemented. Param ${fileId}`); + } } diff --git a/test/testHelpers/files/fileDataTablesHelper.ts b/test/testHelpers/files/fileDataTablesHelper.ts new file mode 100644 index 00000000..a7fade0c --- /dev/null +++ b/test/testHelpers/files/fileDataTablesHelper.ts @@ -0,0 +1,5 @@ +import { FileDataTable } from '../../../src/files/domain/models/FileDataTable'; + +export const createFileDataTableModel = (): FileDataTable => { + return {}; +}; diff --git a/test/unit/files/GetFileDataTables.test.ts b/test/unit/files/GetFileDataTables.test.ts new file mode 100644 index 00000000..34104e2a --- /dev/null +++ b/test/unit/files/GetFileDataTables.test.ts @@ -0,0 +1,40 @@ +import { GetFileDataTables } from '../../../src/files/domain/useCases/GetFileDataTables'; +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { FileDataTable } from '../../../src/files/domain/models/FileDataTable'; +import { createFileDataTableModel } from '../../testHelpers/files/fileDataTablesHelper'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + const testFileId = 1; + + afterEach(() => { + sandbox.restore(); + }); + + test('should return file data tables on repository success', async () => { + const testDataTables: FileDataTable[] = [createFileDataTableModel()]; + const filesRepositoryStub = {}; + const getFileDataTablesStub = sandbox.stub().returns(testDataTables); + filesRepositoryStub.getFileDataTables = getFileDataTablesStub; + const sut = new GetFileDataTables(filesRepositoryStub); + + const actual = await sut.execute(testFileId); + + assert.match(actual, testDataTables); + assert.calledWithExactly(getFileDataTablesStub, testFileId); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.getFileDataTables = sandbox.stub().throwsException(testReadError); + const sut = new GetFileDataTables(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute(testFileId).catch((e: ReadError) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +}); From b008283d2177a1852e8177770a2d069c63228f65 Mon Sep 17 00:00:00 2001 From: GPortas Date: Fri, 21 Jul 2023 14:08:27 +0100 Subject: [PATCH 18/25] Stash: file data table API access logic and model properties WIP --- src/files/domain/models/FileDataTable.ts | 54 ++++++++++++++++++- src/files/index.ts | 11 +++- .../infra/repositories/FilesRepository.ts | 12 ++++- .../transformers/fileDataTableTransformers.ts | 17 ++++++ 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/files/infra/repositories/transformers/fileDataTableTransformers.ts diff --git a/src/files/domain/models/FileDataTable.ts b/src/files/domain/models/FileDataTable.ts index 9cc2549e..9cb24a7b 100644 --- a/src/files/domain/models/FileDataTable.ts +++ b/src/files/domain/models/FileDataTable.ts @@ -1 +1,53 @@ -export interface FileDataTable {} +export interface FileDataTable { + varQuantity?: number; + caseQuantity?: number; + recordsPerCase?: number; + UNF: string; + dataVariables: FileDataVariable[]; +} + +export interface FileDataVariable { + id: number; + name: string; + label?: string; + weighted: boolean; + variableIntervalType?: FileDataVariableIntervalType; + variableFormatType?: FileDataVariableFormatType; + formatCategory?: string; + format?: string; + isOrderedCategorical: boolean; + fileOrder: number; + UNF: string; + fileStartPosition?: number; + fileEndPosition?: number; + recordSegmentNumber?: number; + numberOfDecimalPoints?: number; + variableMetadata?: FileDataVariableMetadata[]; + invalidRanges?: FileDataVariableInvalidRanges[]; + summaryStatistics?: FileDataVariableSummaryStatistics; + variableCategories?: FileDataVariableCategory[]; +} + +// TODO +export interface FileDataVariableMetadata {} + +// TODO +export interface FileDataVariableInvalidRanges {} + +// TODO +export interface FileDataVariableSummaryStatistics {} + +// TODO +export interface FileDataVariableCategory {} + +export enum FileDataVariableIntervalType { + DISCRETE = 'discrete', + CONTIN = 'contin', + NOMINAL = 'nominal', + DICHOTOMOUS = 'dichotomous', +} + +export enum FileDataVariableFormatType { + NUMERIC = 'NUMERIC', + CHARACTER = 'CHARACTER', +} diff --git a/src/files/index.ts b/src/files/index.ts index 96903e0e..393e77e7 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -24,4 +24,13 @@ export { export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; export { FileThumbnailClass } from './domain/models/FileThumbnailClass'; -export { FileDataTable } from './domain/models/FileDataTable'; +export { + FileDataTable, + FileDataVariable, + FileDataVariableMetadata, + FileDataVariableInvalidRanges, + FileDataVariableSummaryStatistics, + FileDataVariableCategory, + FileDataVariableIntervalType, + FileDataVariableFormatType, +} from './domain/models/FileDataTable'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 29c229f6..b97b895e 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -89,6 +89,16 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { } public async getFileDataTables(fileId: string | number): Promise { - throw new Error(`Method not implemented. Param ${fileId}`); + let endpoint; + if (typeof fileId === 'number') { + endpoint = `/files/${fileId}/dataTables`; + } else { + endpoint = `/files/:persistentId/dataTables?persistentId=${fileId}`; + } + return this.doGet(endpoint, true) + .then((response) => transformDataTablesResponseToDataTables(response)) + .catch((error) => { + throw error; + }); } } diff --git a/src/files/infra/repositories/transformers/fileDataTableTransformers.ts b/src/files/infra/repositories/transformers/fileDataTableTransformers.ts new file mode 100644 index 00000000..87213b1a --- /dev/null +++ b/src/files/infra/repositories/transformers/fileDataTableTransformers.ts @@ -0,0 +1,17 @@ +import { AxiosResponse } from 'axios'; +import { FileDataTable } from '../../../domain/models/FileDataTable'; + +export const transformDataTablesResponseToDataTables = (response: AxiosResponse): FileDataTable[] => { + const files: FileDataTable[] = []; + const fileDataTablesPayload = response.data.data; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fileDataTablesPayload.forEach(function (fileDataTablePayload: any) { + files.push(transformFileDataTablePayloadToFileDataTable(fileDataTablePayload)); + }); + return files; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformFileDataTablePayloadToFileDataTable = (filePayload: any): FileDataTable => { + return {}; +}; From f087fc0dbce9b7c4403c53deac064e739400c3f8 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 24 Jul 2023 10:44:32 +0100 Subject: [PATCH 19/25] Added: file data table models with API access and model transform --- src/files/domain/models/FileDataTable.ts | 47 +++++- .../infra/repositories/FilesRepository.ts | 1 + .../transformers/fileDataTableTransformers.ts | 155 +++++++++++++++++- .../testHelpers/files/fileDataTablesHelper.ts | 82 ++++++++- test/unit/files/FilesRepository.test.ts | 97 +++++++++++ 5 files changed, 367 insertions(+), 15 deletions(-) diff --git a/src/files/domain/models/FileDataTable.ts b/src/files/domain/models/FileDataTable.ts index 9cb24a7b..2c110370 100644 --- a/src/files/domain/models/FileDataTable.ts +++ b/src/files/domain/models/FileDataTable.ts @@ -24,21 +24,50 @@ export interface FileDataVariable { numberOfDecimalPoints?: number; variableMetadata?: FileDataVariableMetadata[]; invalidRanges?: FileDataVariableInvalidRanges[]; - summaryStatistics?: FileDataVariableSummaryStatistics; + summaryStatistics?: object; variableCategories?: FileDataVariableCategory[]; } -// TODO -export interface FileDataVariableMetadata {} +export interface FileDataVariableMetadata { + id: number; + metadataId: number; + label?: string; + isWeightVar: boolean; + isWeighted: boolean; + weightVariableId?: number; + literalQuestion?: string; + interviewInstruction?: string; + postQuestion?: string; + universe?: string; + notes?: string; + categoryMetadatas: FileDataVariableCategoryMetadata[]; +} -// TODO -export interface FileDataVariableInvalidRanges {} +export interface FileDataVariableCategoryMetadata { + wFreq?: number; + categoryValue?: string; +} -// TODO -export interface FileDataVariableSummaryStatistics {} +export interface FileDataVariableInvalidRanges { + beginValue?: string; + hasBeginValueType: boolean; + isBeginValueTypePoint: boolean; + isBeginValueTypeMin: boolean; + isBeginValueTypeMinExcl: boolean; + isBeginValueTypeMax: boolean; + isBeginValueTypeMaxExcl: boolean; + endValue?: string; + hasEndValueType: boolean; + endValueTypeMax: boolean; + endValueTypeMaxExcl: boolean; +} -// TODO -export interface FileDataVariableCategory {} +export interface FileDataVariableCategory { + label?: string; + value?: string; + isMissing: boolean; + frequency?: number; +} export enum FileDataVariableIntervalType { DISCRETE = 'discrete', diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index b97b895e..9f874102 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -5,6 +5,7 @@ import { File } from '../../domain/models/File'; import { transformFilesResponseToFiles } from './transformers/fileTransformers'; import { FileThumbnailClass } from '../../domain/models/FileThumbnailClass'; import { FileDataTable } from '../../domain/models/FileDataTable'; +import { transformDataTablesResponseToDataTables } from './transformers/fileDataTableTransformers'; export interface GetFilesQueryParams { limit?: number; diff --git a/src/files/infra/repositories/transformers/fileDataTableTransformers.ts b/src/files/infra/repositories/transformers/fileDataTableTransformers.ts index 87213b1a..55ed3019 100644 --- a/src/files/infra/repositories/transformers/fileDataTableTransformers.ts +++ b/src/files/infra/repositories/transformers/fileDataTableTransformers.ts @@ -1,17 +1,164 @@ import { AxiosResponse } from 'axios'; -import { FileDataTable } from '../../../domain/models/FileDataTable'; +import { + FileDataTable, + FileDataVariable, + FileDataVariableIntervalType, + FileDataVariableFormatType, + FileDataVariableMetadata, + FileDataVariableInvalidRanges, + FileDataVariableCategory, + FileDataVariableCategoryMetadata, +} from '../../../domain/models/FileDataTable'; export const transformDataTablesResponseToDataTables = (response: AxiosResponse): FileDataTable[] => { const files: FileDataTable[] = []; const fileDataTablesPayload = response.data.data; // eslint-disable-next-line @typescript-eslint/no-explicit-any fileDataTablesPayload.forEach(function (fileDataTablePayload: any) { - files.push(transformFileDataTablePayloadToFileDataTable(fileDataTablePayload)); + files.push(transformPayloadToFileDataTable(fileDataTablePayload)); }); return files; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -const transformFileDataTablePayloadToFileDataTable = (filePayload: any): FileDataTable => { - return {}; +const transformPayloadToFileDataTable = (fileDataTablePayload: any): FileDataTable => { + return { + ...(fileDataTablePayload.varQuantity && { varQuantity: fileDataTablePayload.varQuantity }), + ...(fileDataTablePayload.caseQuantity && { caseQuantity: fileDataTablePayload.caseQuantity }), + ...(fileDataTablePayload.recordsPerCase && { recordsPerCase: fileDataTablePayload.recordsPerCase }), + UNF: fileDataTablePayload.UNF, + dataVariables: transformPayloadToDataVariables(fileDataTablePayload.dataVariables), + }; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformPayloadToDataVariables = (fileDataVariablesPayload: any): FileDataVariable[] => { + const fileDataVariables: FileDataVariable[] = []; + fileDataVariablesPayload.forEach(function (fileDataVariablePayload: any) { + fileDataVariables.push({ + id: fileDataVariablePayload.id, + name: fileDataVariablePayload.name, + ...(fileDataVariablePayload.label && { label: fileDataVariablePayload.label }), + weighted: fileDataVariablePayload.weighted, + ...(fileDataVariablePayload.variableIntervalType && { + variableIntervalType: fileDataVariablePayload.variableIntervalType as FileDataVariableIntervalType, + }), + ...(fileDataVariablePayload.variableFormatType && { + variableFormatType: fileDataVariablePayload.variableFormatType as FileDataVariableFormatType, + }), + ...(fileDataVariablePayload.formatCategory && { formatCategory: fileDataVariablePayload.formatCategory }), + ...(fileDataVariablePayload.format && { format: fileDataVariablePayload.format }), + isOrderedCategorical: fileDataVariablePayload.isOrderedCategorical, + fileOrder: fileDataVariablePayload.fileOrder, + UNF: fileDataVariablePayload.UNF, + ...(fileDataVariablePayload.fileStartPosition && { + fileStartPosition: fileDataVariablePayload.fileStartPosition, + }), + ...(fileDataVariablePayload.fileEndPosition && { fileEndPosition: fileDataVariablePayload.fileEndPosition }), + ...(fileDataVariablePayload.recordSegmentNumber && { + recordSegmentNumber: fileDataVariablePayload.recordSegmentNumber, + }), + ...(fileDataVariablePayload.numberOfDecimalPoints && { + numberOfDecimalPoints: fileDataVariablePayload.numberOfDecimalPoints, + }), + ...(fileDataVariablePayload.variableMetadata && { + variableMetadata: transformPayloadToFileVariableMetadata(fileDataVariablePayload.variableMetadata), + }), + ...(fileDataVariablePayload.invalidRanges && { + invalidRanges: transformPayloadToDataVariableInvalidRanges(fileDataVariablePayload.invalidRanges), + }), + ...(fileDataVariablePayload.summaryStatistics && { + summaryStatistics: fileDataVariablePayload.summaryStatistics, + }), + ...(fileDataVariablePayload.variableCategories && { + variableCategories: transformPayloadToFileDataVariableCategories(fileDataVariablePayload.variableCategories), + }), + }); + }); + return fileDataVariables; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformPayloadToFileVariableMetadata = (fileVariableMetadataPayload: any): FileDataVariableMetadata[] => { + const fileDataVariableMetadata: FileDataVariableMetadata[] = []; + fileVariableMetadataPayload.forEach(function (fileVariableMetadataElementPayload: any) { + fileDataVariableMetadata.push({ + id: fileVariableMetadataElementPayload.id, + metadataId: fileVariableMetadataElementPayload.metadataId, + ...(fileVariableMetadataElementPayload.label && { label: fileVariableMetadataElementPayload.label }), + isWeightVar: fileVariableMetadataElementPayload.isWeightVar, + isWeighted: fileVariableMetadataElementPayload.isWeighted, + ...(fileVariableMetadataElementPayload.weightVariableId && { + weightVariableId: fileVariableMetadataElementPayload.weightVariableId, + }), + ...(fileVariableMetadataElementPayload.literalQuestion && { + literalQuestion: fileVariableMetadataElementPayload.literalQuestion, + }), + ...(fileVariableMetadataElementPayload.interviewInstruction && { + interviewInstruction: fileVariableMetadataElementPayload.interviewInstruction, + }), + ...(fileVariableMetadataElementPayload.postQuestion && { + postQuestion: fileVariableMetadataElementPayload.postQuestion, + }), + ...(fileVariableMetadataElementPayload.universe && { universe: fileVariableMetadataElementPayload.universe }), + ...(fileVariableMetadataElementPayload.notes && { notes: fileVariableMetadataElementPayload.notes }), + categoryMetadatas: transformPayloadToFileDataVariableCategoryMetadatas( + fileVariableMetadataElementPayload.categoryMetadatas, + ), + }); + }); + return fileDataVariableMetadata; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformPayloadToFileDataVariableCategoryMetadatas = ( + categoryMetadatasPayload: any, +): FileDataVariableCategoryMetadata[] => { + const fileDataVariableCategoryMetadatas: FileDataVariableCategoryMetadata[] = []; + categoryMetadatasPayload.forEach(function (categoryMetadataPayload: any) { + fileDataVariableCategoryMetadatas.push({ + ...(categoryMetadataPayload.wFreq && { wFreq: categoryMetadataPayload.wFreq }), + ...(categoryMetadataPayload.categoryValue && { categoryValue: categoryMetadataPayload.categoryValue }), + }); + }); + return fileDataVariableCategoryMetadatas; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformPayloadToDataVariableInvalidRanges = ( + dataVariableInvalidRangesPayload: any, +): FileDataVariableInvalidRanges[] => { + const dataVariableInvalidRanges: FileDataVariableInvalidRanges[] = []; + dataVariableInvalidRangesPayload.forEach(function (dataVariableInvalidRangePayload: any) { + dataVariableInvalidRanges.push({ + ...(dataVariableInvalidRangePayload.beginValue && { beginValue: dataVariableInvalidRangePayload.beginValue }), + hasBeginValueType: dataVariableInvalidRangePayload.hasBeginValueType, + isBeginValueTypePoint: dataVariableInvalidRangePayload.isBeginValueTypePoint, + isBeginValueTypeMin: dataVariableInvalidRangePayload.isBeginValueTypeMin, + isBeginValueTypeMinExcl: dataVariableInvalidRangePayload.isBeginValueTypeMinExcl, + isBeginValueTypeMax: dataVariableInvalidRangePayload.isBeginValueTypeMax, + isBeginValueTypeMaxExcl: dataVariableInvalidRangePayload.isBeginValueTypeMaxExcl, + ...(dataVariableInvalidRangePayload.endValue && { endValue: dataVariableInvalidRangePayload.endValue }), + hasEndValueType: dataVariableInvalidRangePayload.hasEndValueType, + endValueTypeMax: dataVariableInvalidRangePayload.endValueTypeMax, + endValueTypeMaxExcl: dataVariableInvalidRangePayload.endValueTypeMaxExcl, + }); + }); + return dataVariableInvalidRanges; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const transformPayloadToFileDataVariableCategories = ( + fileDataVariableCategoriesPayload: any, +): FileDataVariableCategory[] => { + const fileDataVariableCategories: FileDataVariableCategory[] = []; + fileDataVariableCategoriesPayload.forEach(function (fileDataVariableCategoryPayload: any) { + fileDataVariableCategories.push({ + ...(fileDataVariableCategoryPayload.label && { label: fileDataVariableCategoryPayload.label }), + ...(fileDataVariableCategoryPayload.value && { value: fileDataVariableCategoryPayload.value }), + isMissing: fileDataVariableCategoryPayload.isMissing, + ...(fileDataVariableCategoryPayload.frequency && { frequency: fileDataVariableCategoryPayload.frequency }), + }); + }); + return fileDataVariableCategories; }; diff --git a/test/testHelpers/files/fileDataTablesHelper.ts b/test/testHelpers/files/fileDataTablesHelper.ts index a7fade0c..94c490bb 100644 --- a/test/testHelpers/files/fileDataTablesHelper.ts +++ b/test/testHelpers/files/fileDataTablesHelper.ts @@ -1,5 +1,83 @@ -import { FileDataTable } from '../../../src/files/domain/models/FileDataTable'; +import { + FileDataTable, + FileDataVariableIntervalType, + FileDataVariableFormatType, +} from '../../../src/files/domain/models/FileDataTable'; export const createFileDataTableModel = (): FileDataTable => { - return {}; + return { + varQuantity: 21, + caseQuantity: 122, + UNF: 'UNF:6:awd/lz8bNdoXn8f1sjAAAQ==', + dataVariables: [ + { + id: 3, + name: 'test1', + label: 'test1', + weighted: false, + variableIntervalType: FileDataVariableIntervalType.DISCRETE, + variableFormatType: FileDataVariableFormatType.CHARACTER, + isOrderedCategorical: false, + fileOrder: 0, + UNF: 'UNF:6:j194WNS7SAe+mFrz/3oCwQ==', + variableMetadata: [], + summaryStatistics: { + medn: '28.27591', + mode: '.', + min: '27.6741', + }, + }, + { + id: 15, + name: 'test2', + label: 'test2', + weighted: false, + variableIntervalType: FileDataVariableIntervalType.DISCRETE, + variableFormatType: FileDataVariableFormatType.CHARACTER, + isOrderedCategorical: false, + fileOrder: 1, + UNF: 'UNF:6:KPoFCWSEsLpy11Lh11CXWQ==', + variableMetadata: [], + }, + ], + }; +}; + +export const createFileDataTablePayload = (): any => { + return { + varQuantity: 21, + caseQuantity: 122, + UNF: 'UNF:6:awd/lz8bNdoXn8f1sjAAAQ==', + dataVariables: [ + { + id: 3, + name: 'test1', + label: 'test1', + weighted: false, + variableIntervalType: 'discrete', + variableFormatType: 'CHARACTER', + isOrderedCategorical: false, + fileOrder: 0, + UNF: 'UNF:6:j194WNS7SAe+mFrz/3oCwQ==', + variableMetadata: [], + summaryStatistics: { + medn: '28.27591', + mode: '.', + min: '27.6741', + }, + }, + { + id: 15, + name: 'test2', + label: 'test2', + weighted: false, + variableIntervalType: 'discrete', + variableFormatType: 'CHARACTER', + isOrderedCategorical: false, + fileOrder: 1, + UNF: 'UNF:6:KPoFCWSEsLpy11Lh11CXWQ==', + variableMetadata: [], + }, + ], + }; }; diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index 702db348..12a2ba41 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -8,6 +8,7 @@ import { TestConstants } from '../../testHelpers/TestConstants'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; import { createFilePayload, createFileModel } from '../../testHelpers/files/filesHelper'; import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbnailClass'; +import { createFileDataTablePayload, createFileDataTableModel } from '../../testHelpers/files/fileDataTablesHelper'; describe('FilesRepository', () => { const sandbox: SinonSandbox = createSandbox(); @@ -478,4 +479,100 @@ describe('FilesRepository', () => { }); }); }); + + describe('getFileDataTables', () => { + const expectedDataTables = [createFileDataTableModel()]; + const testGetFileDataTablesResponse = { + data: { + status: 'OK', + data: [createFileDataTablePayload()], + }, + }; + + describe('by numeric id', () => { + test('should return data tables when providing id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileDataTablesResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/dataTables`; + + // API Key auth + let actual = await sut.getFileDataTables(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedDataTables); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileDataTables(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedDataTables); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileDataTables(testFile.id).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/${testFile.id}/dataTables`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + + describe('by persistent id', () => { + test('should return data tables when providing persistent id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileDataTablesResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/dataTables?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; + + // API Key auth + let actual = await sut.getFileDataTables(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, expectedDataTables); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileDataTables(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, expectedDataTables); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileDataTables(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/files/:persistentId/dataTables?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + }); }); From 28a03cd8af5882f4ff98110da23d765347d88652 Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 24 Jul 2023 10:46:52 +0100 Subject: [PATCH 20/25] Fixed: files module exports --- src/files/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files/index.ts b/src/files/index.ts index 393e77e7..466a9151 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -29,7 +29,7 @@ export { FileDataVariable, FileDataVariableMetadata, FileDataVariableInvalidRanges, - FileDataVariableSummaryStatistics, + FileDataVariableCategoryMetadata, FileDataVariableCategory, FileDataVariableIntervalType, FileDataVariableFormatType, From 03746b2750923c039c34a30950a487554771586b Mon Sep 17 00:00:00 2001 From: GPortas Date: Mon, 24 Jul 2023 11:24:44 +0100 Subject: [PATCH 21/25] Added: getFileDataTables integration test --- .../integration/files/FilesRepository.test.ts | 104 +++++++++++++----- test/testHelpers/files/test-file-4.tab | 16 +++ 2 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 test/testHelpers/files/test-file-4.tab diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 65b5f906..9f060918 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -12,9 +12,10 @@ import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbna describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository(); - const testFile1Name = 'test-file-1.txt'; - const testFile2Name = 'test-file-2.txt'; - const testFile3Name = 'test-file-3.txt'; + const testTextFile1Name = 'test-file-1.txt'; + const testTextFile2Name = 'test-file-2.txt'; + const testTextFile3Name = 'test-file-3.txt'; + const testTabFile4Name = 'test-file-4.tab'; const nonExistentFiledId = 200; @@ -26,25 +27,32 @@ describe('FilesRepository', () => { fail('Test beforeAll(): Error while creating test Dataset'); }); // Uploading test file 1 - await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testFile1Name) + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testTextFile1Name) .then() .catch((e) => { console.log(e); - fail(`Tests beforeAll(): Error while uploading file ${testFile1Name}`); + fail(`Tests beforeAll(): Error while uploading file ${testTextFile1Name}`); }); // Uploading test file 2 - await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testFile2Name) + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testTextFile2Name) .then() .catch((e) => { console.log(e); - fail(`Tests beforeAll(): Error while uploading file ${testFile2Name}`); + fail(`Tests beforeAll(): Error while uploading file ${testTextFile2Name}`); }); // Uploading test file 3 - await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testFile3Name) + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testTextFile3Name) .then() .catch((e) => { console.log(e); - fail(`Tests beforeAll(): Error while uploading file ${testFile3Name}`); + fail(`Tests beforeAll(): Error while uploading file ${testTextFile3Name}`); + }); + // Uploading test file 4 + await uploadFileViaApi(TestConstants.TEST_CREATED_DATASET_ID, testTabFile4Name) + .then() + .catch((e) => { + console.log(e); + fail(`Tests beforeAll(): Error while uploading file ${testTabFile4Name}`); }); }); @@ -52,16 +60,17 @@ describe('FilesRepository', () => { describe('by numeric id', () => { test('should return all files filtering by dataset id', async () => { const actual = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile1Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile3Name); + assert.match(actual.length, 4); + assert.match(actual[0].name, testTextFile1Name); + assert.match(actual[1].name, testTextFile2Name); + assert.match(actual[2].name, testTextFile3Name); + assert.match(actual[3].name, testTabFile4Name); }); test('should return correct files filtering by dataset id and paginating', async () => { - const actual = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID, undefined, 2, 2, undefined); + const actual = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID, undefined, 3, 3, undefined); assert.match(actual.length, 1); - assert.match(actual[0].name, testFile3Name); + assert.match(actual[0].name, testTabFile4Name); }); test('should return correct files filtering by dataset id and applying order criteria', async () => { @@ -72,10 +81,11 @@ describe('FilesRepository', () => { undefined, FileOrderCriteria.NEWEST, ); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile3Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile1Name); + assert.match(actual.length, 4); + assert.match(actual[0].name, testTabFile4Name); + assert.match(actual[1].name, testTextFile3Name); + assert.match(actual[2].name, testTextFile2Name); + assert.match(actual[3].name, testTextFile1Name); }); test('should return error when dataset does not exist', async () => { @@ -97,17 +107,18 @@ describe('FilesRepository', () => { test('should return all files filtering by persistent id', async () => { const testDataset = await datasetRepository.getDataset(TestConstants.TEST_CREATED_DATASET_ID); const actual = await sut.getDatasetFiles(testDataset.persistentId); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile1Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile3Name); + assert.match(actual.length, 4); + assert.match(actual[0].name, testTextFile1Name); + assert.match(actual[1].name, testTextFile2Name); + assert.match(actual[2].name, testTextFile3Name); + assert.match(actual[3].name, testTabFile4Name); }); test('should return correct files filtering by persistent id and paginating', async () => { const testDataset = await datasetRepository.getDataset(TestConstants.TEST_CREATED_DATASET_ID); - const actual = await sut.getDatasetFiles(testDataset.persistentId, undefined, 2, 2, undefined); + const actual = await sut.getDatasetFiles(testDataset.persistentId, undefined, 3, 3, undefined); assert.match(actual.length, 1); - assert.match(actual[0].name, testFile3Name); + assert.match(actual[0].name, testTabFile4Name); }); test('should return correct files filtering by persistent id and applying order criteria', async () => { @@ -119,10 +130,11 @@ describe('FilesRepository', () => { undefined, FileOrderCriteria.NEWEST, ); - assert.match(actual.length, 3); - assert.match(actual[0].name, testFile3Name); - assert.match(actual[1].name, testFile2Name); - assert.match(actual[2].name, testFile1Name); + assert.match(actual.length, 4); + assert.match(actual[0].name, testTabFile4Name); + assert.match(actual[1].name, testTextFile3Name); + assert.match(actual[2].name, testTextFile2Name); + assert.match(actual[3].name, testTextFile1Name); }); test('should return error when dataset does not exist', async () => { @@ -198,4 +210,38 @@ describe('FilesRepository', () => { ); }); }); + + describe('getFileDataTables', () => { + test('should return data tables filtering by tabular file id', async () => { + const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); + const testFile = currentTestFiles[3]; + const actual = await sut.getFileDataTables(testFile.id); + assert.match(actual[0].varQuantity, 1); + }); + + test('should return error when file is not tabular', async () => { + const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); + const testFile = currentTestFiles[0]; + + let error: ReadError = undefined; + + await sut.getFileDataTables(testFile.id).catch((e) => (error = e)); + + assert.match( + error.message, + 'There was an error when reading the resource. Reason was: [400] This operation is only available for tabular files.', + ); + }); + + test('should return error when file does not exist', async () => { + let error: ReadError = undefined; + + await sut.getFileDataTables(nonExistentFiledId).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/testHelpers/files/test-file-4.tab b/test/testHelpers/files/test-file-4.tab new file mode 100644 index 00000000..628a309a --- /dev/null +++ b/test/testHelpers/files/test-file-4.tab @@ -0,0 +1,16 @@ +test1 test2 test3 test4 +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" +"test1 test2 test3 test4" From 378cc1e3e9716223728c71a3504b07da175c9bd1 Mon Sep 17 00:00:00 2001 From: GPortas Date: Tue, 25 Jul 2023 17:34:42 +0100 Subject: [PATCH 22/25] Changed: more realistic test tab file content --- .../integration/files/FilesRepository.test.ts | 2 +- test/testHelpers/files/test-file-4.tab | 27 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 9f060918..1f33a3c6 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -216,7 +216,7 @@ describe('FilesRepository', () => { const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); const testFile = currentTestFiles[3]; const actual = await sut.getFileDataTables(testFile.id); - assert.match(actual[0].varQuantity, 1); + assert.match(actual[0].varQuantity, 3); }); test('should return error when file is not tabular', async () => { diff --git a/test/testHelpers/files/test-file-4.tab b/test/testHelpers/files/test-file-4.tab index 628a309a..d750d42d 100644 --- a/test/testHelpers/files/test-file-4.tab +++ b/test/testHelpers/files/test-file-4.tab @@ -1,16 +1,11 @@ -test1 test2 test3 test4 -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" -"test1 test2 test3 test4" +position name age +1 "Belle" 36 +2 "Lola" 37 +3 "Jayden" 45 +4 "Margaret" 37 +5 "Russell" 40 +6 "Bertie" 60 +7 "Maud" 34 +8 "Mabel" 31 +9 "Trevor" 51 +10 "Duane" 26 From 1d5fffda8630e50a76d70af7b9293c2a7557bec2 Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 9 Aug 2023 09:54:50 +0100 Subject: [PATCH 23/25] Changed: GetFileDownloadCount renaming --- .../domain/repositories/IFilesRepository.ts | 2 +- ...ponsesCount.ts => GetFileDownloadCount.ts} | 4 +-- src/files/index.ts | 12 ++------ .../infra/repositories/FilesRepository.ts | 6 ++-- .../integration/files/FilesRepository.test.ts | 6 ++-- test/unit/files/FilesRepository.test.ts | 28 +++++++++---------- ...t.test.ts => GetFileDownloadCount.test.ts} | 14 +++++----- 7 files changed, 33 insertions(+), 39 deletions(-) rename src/files/domain/useCases/{GetFileGuestbookResponsesCount.ts => GetFileDownloadCount.ts} (69%) rename test/unit/files/{GetFileGuestbookResponsesCount.test.ts => GetFileDownloadCount.test.ts} (72%) diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index 54b18e37..f384b2d5 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -12,7 +12,7 @@ export interface IFilesRepository { orderCriteria?: FileOrderCriteria, ): Promise; - getFileGuestbookResponsesCount(fileId: number | string): Promise; + getFileDownloadCount(fileId: number | string): Promise; canFileBeDownloaded(fileId: number | string): Promise; diff --git a/src/files/domain/useCases/GetFileGuestbookResponsesCount.ts b/src/files/domain/useCases/GetFileDownloadCount.ts similarity index 69% rename from src/files/domain/useCases/GetFileGuestbookResponsesCount.ts rename to src/files/domain/useCases/GetFileDownloadCount.ts index 9bdb7748..4d2b41eb 100644 --- a/src/files/domain/useCases/GetFileGuestbookResponsesCount.ts +++ b/src/files/domain/useCases/GetFileDownloadCount.ts @@ -1,7 +1,7 @@ import { UseCase } from '../../../core/domain/useCases/UseCase'; import { IFilesRepository } from '../repositories/IFilesRepository'; -export class GetFileGuestbookResponsesCount implements UseCase { +export class GetFileDownloadCount implements UseCase { private filesRepository: IFilesRepository; constructor(filesRepository: IFilesRepository) { @@ -9,6 +9,6 @@ export class GetFileGuestbookResponsesCount implements UseCase { } async execute(fileId: number | string): Promise { - return await this.filesRepository.getFileGuestbookResponsesCount(fileId); + return await this.filesRepository.getFileDownloadCount(fileId); } } diff --git a/src/files/index.ts b/src/files/index.ts index 466a9151..81706865 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1,6 +1,6 @@ import { FilesRepository } from './infra/repositories/FilesRepository'; import { GetDatasetFiles } from './domain/useCases/GetDatasetFiles'; -import { GetFileGuestbookResponsesCount } from './domain/useCases/GetFileGuestbookResponsesCount'; +import { GetFileDownloadCount } from './domain/useCases/GetFileDownloadCount'; import { CanFileBeDownloaded } from './domain/useCases/CanFileBeDownloaded'; import { GetFileThumbnailClass } from './domain/useCases/GetFileThumbnailClass'; import { GetFileDataTables } from './domain/useCases/GetFileDataTables'; @@ -8,18 +8,12 @@ import { GetFileDataTables } from './domain/useCases/GetFileDataTables'; const filesRepository = new FilesRepository(); const getDatasetFiles = new GetDatasetFiles(filesRepository); -const getFileGuestbookResponsesCount = new GetFileGuestbookResponsesCount(filesRepository); +const getFileDownloadCount = new GetFileDownloadCount(filesRepository); const canFileBeDownloaded = new CanFileBeDownloaded(filesRepository); const getFileThumbnailClass = new GetFileThumbnailClass(filesRepository); const getFileDataTables = new GetFileDataTables(filesRepository); -export { - getDatasetFiles, - getFileGuestbookResponsesCount, - canFileBeDownloaded, - getFileThumbnailClass, - getFileDataTables, -}; +export { getDatasetFiles, getFileDownloadCount, canFileBeDownloaded, getFileThumbnailClass, getFileDataTables }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 9f874102..dcf3a1c6 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -47,12 +47,12 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { }); } - public async getFileGuestbookResponsesCount(fileId: number | string): Promise { + public async getFileDownloadCount(fileId: number | string): Promise { let endpoint; if (typeof fileId === 'number') { - endpoint = `/files/${fileId}/guestbookResponses/count`; + endpoint = `/files/${fileId}/downloadCount`; } else { - endpoint = `/files/:persistentId/guestbookResponses/count?persistentId=${fileId}`; + endpoint = `/files/:persistentId/downloadCount?persistentId=${fileId}`; } return this.doGet(endpoint, true) .then((response) => response.data.data.message as number) diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 1f33a3c6..a7821f13 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -151,18 +151,18 @@ describe('FilesRepository', () => { }); }); - describe('getFileGuestbookResponsesCount', () => { + describe('getFileDownloadCount', () => { test('should return count filtering by file id', async () => { const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); const testFile = currentTestFiles[0]; - const actual = await sut.getFileGuestbookResponsesCount(testFile.id); + const actual = await sut.getFileDownloadCount(testFile.id); assert.match(actual, 0); }); test('should return error when file does not exist', async () => { let error: ReadError = undefined; - await sut.getFileGuestbookResponsesCount(nonExistentFiledId).catch((e) => (error = e)); + await sut.getFileDownloadCount(nonExistentFiledId).catch((e) => (error = e)); assert.match( error.message, diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index 12a2ba41..392621b9 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -188,9 +188,9 @@ describe('FilesRepository', () => { }); }); - describe('getFileGuestbookResponsesCount', () => { + describe('getFileDownloadCount', () => { const testCount = 1; - const testFileGuestbookResponseCountResponse = { + const testFileDownloadCountResponse = { data: { status: 'OK', data: { @@ -201,11 +201,11 @@ describe('FilesRepository', () => { describe('by numeric id', () => { test('should return count when providing id and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileGuestbookResponseCountResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/guestbookResponses/count`; + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileDownloadCountResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/downloadCount`; // API Key auth - let actual = await sut.getFileGuestbookResponsesCount(testFile.id); + let actual = await sut.getFileDownloadCount(testFile.id); assert.calledWithExactly( axiosGetStub, @@ -217,7 +217,7 @@ describe('FilesRepository', () => { // Session cookie auth ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - actual = await sut.getFileGuestbookResponsesCount(testFile.id); + actual = await sut.getFileDownloadCount(testFile.id); assert.calledWithExactly( axiosGetStub, @@ -231,11 +231,11 @@ describe('FilesRepository', () => { const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); let error: ReadError = undefined; - await sut.getFileGuestbookResponsesCount(testFile.id).catch((e) => (error = e)); + await sut.getFileDownloadCount(testFile.id).catch((e) => (error = e)); assert.calledWithExactly( axiosGetStub, - `${TestConstants.TEST_API_URL}/files/${testFile.id}/guestbookResponses/count`, + `${TestConstants.TEST_API_URL}/files/${testFile.id}/downloadCount`, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, ); expect(error).to.be.instanceOf(Error); @@ -244,11 +244,11 @@ describe('FilesRepository', () => { describe('by persistent id', () => { test('should return count when providing persistent id and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileGuestbookResponseCountResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/guestbookResponses/count?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileDownloadCountResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/downloadCount?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; // API Key auth - let actual = await sut.getFileGuestbookResponsesCount(TestConstants.TEST_DUMMY_PERSISTENT_ID); + let actual = await sut.getFileDownloadCount(TestConstants.TEST_DUMMY_PERSISTENT_ID); assert.calledWithExactly( axiosGetStub, @@ -260,7 +260,7 @@ describe('FilesRepository', () => { // Session cookie auth ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - actual = await sut.getFileGuestbookResponsesCount(TestConstants.TEST_DUMMY_PERSISTENT_ID); + actual = await sut.getFileDownloadCount(TestConstants.TEST_DUMMY_PERSISTENT_ID); assert.calledWithExactly( axiosGetStub, @@ -274,11 +274,11 @@ describe('FilesRepository', () => { const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); let error: ReadError = undefined; - await sut.getFileGuestbookResponsesCount(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + await sut.getFileDownloadCount(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); assert.calledWithExactly( axiosGetStub, - `${TestConstants.TEST_API_URL}/files/:persistentId/guestbookResponses/count?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + `${TestConstants.TEST_API_URL}/files/:persistentId/downloadCount?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, ); expect(error).to.be.instanceOf(Error); diff --git a/test/unit/files/GetFileGuestbookResponsesCount.test.ts b/test/unit/files/GetFileDownloadCount.test.ts similarity index 72% rename from test/unit/files/GetFileGuestbookResponsesCount.test.ts rename to test/unit/files/GetFileDownloadCount.test.ts index d1f4a575..694959b3 100644 --- a/test/unit/files/GetFileGuestbookResponsesCount.test.ts +++ b/test/unit/files/GetFileDownloadCount.test.ts @@ -1,4 +1,4 @@ -import { GetFileGuestbookResponsesCount } from '../../../src/files/domain/useCases/GetFileGuestbookResponsesCount'; +import { GetFileDownloadCount } from '../../../src/files/domain/useCases/GetFileDownloadCount'; import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; import { assert, createSandbox, SinonSandbox } from 'sinon'; import { ReadError } from '../../../src/core/domain/repositories/ReadError'; @@ -16,8 +16,8 @@ describe('execute', () => { test('should return count on repository success filtering by id', async () => { const filesRepositoryStub = {}; const getFileGuestbookResponsesCountStub = sandbox.stub().returns(testCount); - filesRepositoryStub.getFileGuestbookResponsesCount = getFileGuestbookResponsesCountStub; - const sut = new GetFileGuestbookResponsesCount(filesRepositoryStub); + filesRepositoryStub.getFileDownloadCount = getFileGuestbookResponsesCountStub; + const sut = new GetFileDownloadCount(filesRepositoryStub); const actual = await sut.execute(testFileId); @@ -28,8 +28,8 @@ describe('execute', () => { test('should return count on repository success filtering by persistent id', async () => { const filesRepositoryStub = {}; const getFileGuestbookResponsesCountStub = sandbox.stub().returns(testCount); - filesRepositoryStub.getFileGuestbookResponsesCount = getFileGuestbookResponsesCountStub; - const sut = new GetFileGuestbookResponsesCount(filesRepositoryStub); + filesRepositoryStub.getFileDownloadCount = getFileGuestbookResponsesCountStub; + const sut = new GetFileDownloadCount(filesRepositoryStub); const actual = await sut.execute(TestConstants.TEST_DUMMY_PERSISTENT_ID); @@ -40,8 +40,8 @@ describe('execute', () => { test('should return error result on repository error', async () => { const filesRepositoryStub = {}; const testReadError = new ReadError(); - filesRepositoryStub.getFileGuestbookResponsesCount = sandbox.stub().throwsException(testReadError); - const sut = new GetFileGuestbookResponsesCount(filesRepositoryStub); + filesRepositoryStub.getFileDownloadCount = sandbox.stub().throwsException(testReadError); + const sut = new GetFileDownloadCount(filesRepositoryStub); let actualError: ReadError = undefined; await sut.execute(testFileId).catch((e) => (actualError = e)); From 76104a1b4c2ec06f0e22728a27ea1ae31b4e911b Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 9 Aug 2023 10:08:00 +0100 Subject: [PATCH 24/25] Removed: GetFileThumbnailClass use case --- src/files/domain/models/FileThumbnailClass.ts | 13 -- .../domain/repositories/IFilesRepository.ts | 5 - .../domain/useCases/CanFileBeDownloaded.ts | 14 -- .../domain/useCases/GetFileThumbnailClass.ts | 15 -- src/files/index.ts | 7 +- .../infra/repositories/FilesRepository.ts | 29 --- .../integration/files/FilesRepository.test.ts | 41 ---- test/unit/files/CanFileBeDownloaded.test.ts | 51 ----- test/unit/files/FilesRepository.test.ts | 195 ------------------ test/unit/files/GetFileThumbnailClass.test.ts | 52 ----- 10 files changed, 1 insertion(+), 421 deletions(-) delete mode 100644 src/files/domain/models/FileThumbnailClass.ts delete mode 100644 src/files/domain/useCases/CanFileBeDownloaded.ts delete mode 100644 src/files/domain/useCases/GetFileThumbnailClass.ts delete mode 100644 test/unit/files/CanFileBeDownloaded.test.ts delete mode 100644 test/unit/files/GetFileThumbnailClass.test.ts diff --git a/src/files/domain/models/FileThumbnailClass.ts b/src/files/domain/models/FileThumbnailClass.ts deleted file mode 100644 index 02ea8f95..00000000 --- a/src/files/domain/models/FileThumbnailClass.ts +++ /dev/null @@ -1,13 +0,0 @@ -export enum FileThumbnailClass { - AUDIO = 'audio', - CODE = 'code', - DOCUMENT = 'document', - ASTRO = 'astro', - IMAGE = 'image', - NETWORK = 'network', - GEODATA = 'geodata', - TABULAR = 'tabular', - VIDEO = 'video', - PACKAGE = 'package', - OTHER = 'other', -} diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index f384b2d5..be435053 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -1,6 +1,5 @@ import { FileOrderCriteria } from '../models/FileOrderCriteria'; import { File } from '../models/File'; -import { FileThumbnailClass } from '../models/FileThumbnailClass'; import { FileDataTable } from '../models/FileDataTable'; export interface IFilesRepository { @@ -14,9 +13,5 @@ export interface IFilesRepository { getFileDownloadCount(fileId: number | string): Promise; - canFileBeDownloaded(fileId: number | string): Promise; - - getFileThumbnailClass(fileId: number | string): Promise; - getFileDataTables(fileId: number | string): Promise; } diff --git a/src/files/domain/useCases/CanFileBeDownloaded.ts b/src/files/domain/useCases/CanFileBeDownloaded.ts deleted file mode 100644 index f8404ca3..00000000 --- a/src/files/domain/useCases/CanFileBeDownloaded.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { UseCase } from '../../../core/domain/useCases/UseCase'; -import { IFilesRepository } from '../repositories/IFilesRepository'; - -export class CanFileBeDownloaded implements UseCase { - private filesRepository: IFilesRepository; - - constructor(filesRepository: IFilesRepository) { - this.filesRepository = filesRepository; - } - - async execute(fileId: number | string): Promise { - return await this.filesRepository.canFileBeDownloaded(fileId); - } -} diff --git a/src/files/domain/useCases/GetFileThumbnailClass.ts b/src/files/domain/useCases/GetFileThumbnailClass.ts deleted file mode 100644 index c4591026..00000000 --- a/src/files/domain/useCases/GetFileThumbnailClass.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { UseCase } from '../../../core/domain/useCases/UseCase'; -import { IFilesRepository } from '../repositories/IFilesRepository'; -import { FileThumbnailClass } from '../models/FileThumbnailClass'; - -export class GetFileThumbnailClass implements UseCase { - private filesRepository: IFilesRepository; - - constructor(filesRepository: IFilesRepository) { - this.filesRepository = filesRepository; - } - - async execute(fileId: number | string): Promise { - return await this.filesRepository.getFileThumbnailClass(fileId); - } -} diff --git a/src/files/index.ts b/src/files/index.ts index 81706865..27ee256e 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1,23 +1,18 @@ import { FilesRepository } from './infra/repositories/FilesRepository'; import { GetDatasetFiles } from './domain/useCases/GetDatasetFiles'; import { GetFileDownloadCount } from './domain/useCases/GetFileDownloadCount'; -import { CanFileBeDownloaded } from './domain/useCases/CanFileBeDownloaded'; -import { GetFileThumbnailClass } from './domain/useCases/GetFileThumbnailClass'; import { GetFileDataTables } from './domain/useCases/GetFileDataTables'; const filesRepository = new FilesRepository(); const getDatasetFiles = new GetDatasetFiles(filesRepository); const getFileDownloadCount = new GetFileDownloadCount(filesRepository); -const canFileBeDownloaded = new CanFileBeDownloaded(filesRepository); -const getFileThumbnailClass = new GetFileThumbnailClass(filesRepository); const getFileDataTables = new GetFileDataTables(filesRepository); -export { getDatasetFiles, getFileDownloadCount, canFileBeDownloaded, getFileThumbnailClass, getFileDataTables }; +export { getDatasetFiles, getFileDownloadCount, getFileDataTables }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; -export { FileThumbnailClass } from './domain/models/FileThumbnailClass'; export { FileDataTable, FileDataVariable, diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index dcf3a1c6..2f7e38b5 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -3,7 +3,6 @@ import { IFilesRepository } from '../../domain/repositories/IFilesRepository'; import { FileOrderCriteria } from '../../domain/models/FileOrderCriteria'; import { File } from '../../domain/models/File'; import { transformFilesResponseToFiles } from './transformers/fileTransformers'; -import { FileThumbnailClass } from '../../domain/models/FileThumbnailClass'; import { FileDataTable } from '../../domain/models/FileDataTable'; import { transformDataTablesResponseToDataTables } from './transformers/fileDataTableTransformers'; @@ -61,34 +60,6 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { }); } - public async canFileBeDownloaded(fileId: string | number): Promise { - let endpoint; - if (typeof fileId === 'number') { - endpoint = `/files/${fileId}/canBeDownloaded`; - } else { - endpoint = `/files/:persistentId/canBeDownloaded?persistentId=${fileId}`; - } - return this.doGet(endpoint, true) - .then((response) => response.data.data as boolean) - .catch((error) => { - throw error; - }); - } - - public async getFileThumbnailClass(fileId: string | number): Promise { - let endpoint; - if (typeof fileId === 'number') { - endpoint = `/files/${fileId}/thumbnailClass`; - } else { - endpoint = `/files/:persistentId/thumbnailClass?persistentId=${fileId}`; - } - return this.doGet(endpoint, true) - .then((response) => response.data.data.message as FileThumbnailClass) - .catch((error) => { - throw error; - }); - } - public async getFileDataTables(fileId: string | number): Promise { let endpoint; if (typeof fileId === 'number') { diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index a7821f13..0574bb75 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -7,7 +7,6 @@ import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'; import { ReadError } from '../../../src/core/domain/repositories/ReadError'; -import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbnailClass'; describe('FilesRepository', () => { const sut: FilesRepository = new FilesRepository(); @@ -171,46 +170,6 @@ describe('FilesRepository', () => { }); }); - describe('canFileBeDownloaded', () => { - test('should return result filtering by file id', async () => { - const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); - const testFile = currentTestFiles[0]; - const actual = await sut.canFileBeDownloaded(testFile.id); - assert.match(actual, true); - }); - - test('should return error when file does not exist', async () => { - let error: ReadError = undefined; - - await sut.canFileBeDownloaded(nonExistentFiledId).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.`, - ); - }); - }); - - describe('getFileThumbnailClass', () => { - test('should return thumbnail class filtering by file id', async () => { - const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); - const testFile = currentTestFiles[0]; - const actual = await sut.getFileThumbnailClass(testFile.id); - assert.match(actual, FileThumbnailClass.DOCUMENT); - }); - - test('should return error when file does not exist', async () => { - let error: ReadError = undefined; - - await sut.getFileThumbnailClass(nonExistentFiledId).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.`, - ); - }); - }); - describe('getFileDataTables', () => { test('should return data tables filtering by tabular file id', async () => { const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); diff --git a/test/unit/files/CanFileBeDownloaded.test.ts b/test/unit/files/CanFileBeDownloaded.test.ts deleted file mode 100644 index cbbe5b32..00000000 --- a/test/unit/files/CanFileBeDownloaded.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { CanFileBeDownloaded } from '../../../src/files/domain/useCases/CanFileBeDownloaded'; -import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; -import { assert, createSandbox, SinonSandbox } from 'sinon'; -import { ReadError } from '../../../src/core/domain/repositories/ReadError'; -import { TestConstants } from '../../testHelpers/TestConstants'; - -describe('execute', () => { - const sandbox: SinonSandbox = createSandbox(); - const testFileId = 1; - const expectedResult = true; - - afterEach(() => { - sandbox.restore(); - }); - - test('should return result on repository success filtering by id', async () => { - const filesRepositoryStub = {}; - const canFileBeDownloadedStub = sandbox.stub().returns(expectedResult); - filesRepositoryStub.canFileBeDownloaded = canFileBeDownloadedStub; - const sut = new CanFileBeDownloaded(filesRepositoryStub); - - const actual = await sut.execute(testFileId); - - assert.match(actual, expectedResult); - assert.calledWithExactly(canFileBeDownloadedStub, testFileId); - }); - - test('should return result on repository success filtering by persistent id', async () => { - const filesRepositoryStub = {}; - const canFileBeDownloadedStub = sandbox.stub().returns(expectedResult); - filesRepositoryStub.canFileBeDownloaded = canFileBeDownloadedStub; - const sut = new CanFileBeDownloaded(filesRepositoryStub); - - const actual = await sut.execute(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.match(actual, expectedResult); - assert.calledWithExactly(canFileBeDownloadedStub, TestConstants.TEST_DUMMY_PERSISTENT_ID); - }); - - test('should return error result on repository error', async () => { - const filesRepositoryStub = {}; - const testReadError = new ReadError(); - filesRepositoryStub.canFileBeDownloaded = sandbox.stub().throwsException(testReadError); - const sut = new CanFileBeDownloaded(filesRepositoryStub); - - let actualError: ReadError = undefined; - await sut.execute(testFileId).catch((e) => (actualError = e)); - - assert.match(actualError, testReadError); - }); -}); diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index 392621b9..df1686d4 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -7,7 +7,6 @@ import { ApiConfig, DataverseApiAuthMechanism } from '../../../src/core/infra/re import { TestConstants } from '../../testHelpers/TestConstants'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; import { createFilePayload, createFileModel } from '../../testHelpers/files/filesHelper'; -import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbnailClass'; import { createFileDataTablePayload, createFileDataTableModel } from '../../testHelpers/files/fileDataTablesHelper'; describe('FilesRepository', () => { @@ -286,200 +285,6 @@ describe('FilesRepository', () => { }); }); - describe('canFileBeDownloaded', () => { - const expectedResult = true; - const testCanFileBeDownloadedResponse = { - data: { - status: 'OK', - data: expectedResult, - }, - }; - - describe('by numeric id', () => { - test('should return result when providing id and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testCanFileBeDownloadedResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/canBeDownloaded`; - - // API Key auth - let actual = await sut.canFileBeDownloaded(testFile.id); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, expectedResult); - - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - - actual = await sut.canFileBeDownloaded(testFile.id); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, expectedResult); - }); - - test('should return error result on error response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); - - let error: ReadError = undefined; - await sut.canFileBeDownloaded(testFile.id).catch((e) => (error = e)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/files/${testFile.id}/canBeDownloaded`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); - }); - }); - - describe('by persistent id', () => { - test('should return result when providing persistent id and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testCanFileBeDownloadedResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/canBeDownloaded?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; - - // API Key auth - let actual = await sut.canFileBeDownloaded(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, expectedResult); - - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - - actual = await sut.canFileBeDownloaded(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, expectedResult); - }); - - test('should return error result on error response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); - - let error: ReadError = undefined; - await sut.canFileBeDownloaded(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/files/:persistentId/canBeDownloaded?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); - }); - }); - }); - - describe('getFileThumbnailClass', () => { - const expectedThumbnailClass = FileThumbnailClass.IMAGE; - const testGetFileThumbnailClassResponse = { - data: { - status: 'OK', - data: { - message: expectedThumbnailClass.toString(), - }, - }, - }; - - describe('by numeric id', () => { - test('should return thumbnail class when providing id and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileThumbnailClassResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/thumbnailClass`; - - // API Key auth - let actual = await sut.getFileThumbnailClass(testFile.id); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, expectedThumbnailClass); - - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - - actual = await sut.getFileThumbnailClass(testFile.id); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, expectedThumbnailClass); - }); - - test('should return error result on error response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); - - let error: ReadError = undefined; - await sut.getFileThumbnailClass(testFile.id).catch((e) => (error = e)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/files/${testFile.id}/thumbnailClass`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); - }); - }); - - describe('by persistent id', () => { - test('should return thumbnail class when providing persistent id and response is successful', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileThumbnailClassResponse); - const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/thumbnailClass?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; - - // API Key auth - let actual = await sut.getFileThumbnailClass(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - assert.match(actual, expectedThumbnailClass); - - // Session cookie auth - ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); - - actual = await sut.getFileThumbnailClass(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.calledWithExactly( - axiosGetStub, - expectedApiEndpoint, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, - ); - assert.match(actual, expectedThumbnailClass); - }); - - test('should return error result on error response', async () => { - const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); - - let error: ReadError = undefined; - await sut.getFileThumbnailClass(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); - - assert.calledWithExactly( - axiosGetStub, - `${TestConstants.TEST_API_URL}/files/:persistentId/thumbnailClass?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, - TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, - ); - expect(error).to.be.instanceOf(Error); - }); - }); - }); - describe('getFileDataTables', () => { const expectedDataTables = [createFileDataTableModel()]; const testGetFileDataTablesResponse = { diff --git a/test/unit/files/GetFileThumbnailClass.test.ts b/test/unit/files/GetFileThumbnailClass.test.ts deleted file mode 100644 index 1e07869a..00000000 --- a/test/unit/files/GetFileThumbnailClass.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { GetFileThumbnailClass } from '../../../src/files/domain/useCases/GetFileThumbnailClass'; -import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; -import { assert, createSandbox, SinonSandbox } from 'sinon'; -import { ReadError } from '../../../src/core/domain/repositories/ReadError'; -import { TestConstants } from '../../testHelpers/TestConstants'; -import { FileThumbnailClass } from '../../../src/files/domain/models/FileThumbnailClass'; - -describe('execute', () => { - const sandbox: SinonSandbox = createSandbox(); - const testFileId = 1; - const testThumbnailClass = FileThumbnailClass.IMAGE; - - afterEach(() => { - sandbox.restore(); - }); - - test('should return thumbnail class on repository success filtering by id', async () => { - const filesRepositoryStub = {}; - const getFileThumbnailClassStub = sandbox.stub().returns(testThumbnailClass); - filesRepositoryStub.getFileThumbnailClass = getFileThumbnailClassStub; - const sut = new GetFileThumbnailClass(filesRepositoryStub); - - const actual = await sut.execute(testFileId); - - assert.match(actual, testThumbnailClass); - assert.calledWithExactly(getFileThumbnailClassStub, testFileId); - }); - - test('should return thumbnail class on repository success filtering by persistent id', async () => { - const filesRepositoryStub = {}; - const getFileThumbnailClassStub = sandbox.stub().returns(testThumbnailClass); - filesRepositoryStub.getFileThumbnailClass = getFileThumbnailClassStub; - const sut = new GetFileThumbnailClass(filesRepositoryStub); - - const actual = await sut.execute(TestConstants.TEST_DUMMY_PERSISTENT_ID); - - assert.match(actual, testThumbnailClass); - assert.calledWithExactly(getFileThumbnailClassStub, TestConstants.TEST_DUMMY_PERSISTENT_ID); - }); - - test('should return error result on repository error', async () => { - const filesRepositoryStub = {}; - const testReadError = new ReadError(); - filesRepositoryStub.getFileThumbnailClass = sandbox.stub().throwsException(testReadError); - const sut = new GetFileThumbnailClass(filesRepositoryStub); - - let actualError: ReadError = undefined; - await sut.execute(testFileId).catch((e) => (actualError = e)); - - assert.match(actualError, testReadError); - }); -}); From 3e0c21eed729ba3e546955a622352158d697bb7f Mon Sep 17 00:00:00 2001 From: GPortas Date: Wed, 9 Aug 2023 10:48:51 +0100 Subject: [PATCH 25/25] Added: GetFileUserPermissions use case --- .../domain/models/FileUserPermissions.ts | 4 + .../domain/repositories/IFilesRepository.ts | 3 + .../domain/useCases/GetFileDataTables.ts | 4 +- .../domain/useCases/GetFileUserPermissions.ts | 15 +++ src/files/index.ts | 4 +- .../infra/repositories/FilesRepository.ts | 16 ++++ .../fileUserPermissionsTransformers.ts | 12 +++ .../integration/files/FilesRepository.test.ts | 21 ++++ .../files/fileUserPermissionsHelper.ts | 8 ++ test/unit/files/FilesRepository.test.ts | 96 +++++++++++++++++++ .../unit/files/GetFileUserPermissions.test.ts | 39 ++++++++ 11 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 src/files/domain/models/FileUserPermissions.ts create mode 100644 src/files/domain/useCases/GetFileUserPermissions.ts create mode 100644 src/files/infra/repositories/transformers/fileUserPermissionsTransformers.ts create mode 100644 test/testHelpers/files/fileUserPermissionsHelper.ts create mode 100644 test/unit/files/GetFileUserPermissions.test.ts diff --git a/src/files/domain/models/FileUserPermissions.ts b/src/files/domain/models/FileUserPermissions.ts new file mode 100644 index 00000000..25fc6716 --- /dev/null +++ b/src/files/domain/models/FileUserPermissions.ts @@ -0,0 +1,4 @@ +export interface FileUserPermissions { + canDownloadFile: boolean; + canEditOwnerDataset: boolean; +} diff --git a/src/files/domain/repositories/IFilesRepository.ts b/src/files/domain/repositories/IFilesRepository.ts index be435053..9cc94591 100644 --- a/src/files/domain/repositories/IFilesRepository.ts +++ b/src/files/domain/repositories/IFilesRepository.ts @@ -1,6 +1,7 @@ import { FileOrderCriteria } from '../models/FileOrderCriteria'; import { File } from '../models/File'; import { FileDataTable } from '../models/FileDataTable'; +import { FileUserPermissions } from '../models/FileUserPermissions'; export interface IFilesRepository { getDatasetFiles( @@ -13,5 +14,7 @@ export interface IFilesRepository { getFileDownloadCount(fileId: number | string): Promise; + getFileUserPermissions(fileId: number | string): Promise; + getFileDataTables(fileId: number | string): Promise; } diff --git a/src/files/domain/useCases/GetFileDataTables.ts b/src/files/domain/useCases/GetFileDataTables.ts index 906114c9..d8a65891 100644 --- a/src/files/domain/useCases/GetFileDataTables.ts +++ b/src/files/domain/useCases/GetFileDataTables.ts @@ -9,7 +9,7 @@ export class GetFileDataTables implements UseCase { this.filesRepository = filesRepository; } - async execute(datasetId: number | string): Promise { - return await this.filesRepository.getFileDataTables(datasetId); + async execute(fileId: number | string): Promise { + return await this.filesRepository.getFileDataTables(fileId); } } diff --git a/src/files/domain/useCases/GetFileUserPermissions.ts b/src/files/domain/useCases/GetFileUserPermissions.ts new file mode 100644 index 00000000..454984ef --- /dev/null +++ b/src/files/domain/useCases/GetFileUserPermissions.ts @@ -0,0 +1,15 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase'; +import { IFilesRepository } from '../repositories/IFilesRepository'; +import { FileUserPermissions } from '../models/FileUserPermissions'; + +export class GetFileUserPermissions implements UseCase { + private filesRepository: IFilesRepository; + + constructor(filesRepository: IFilesRepository) { + this.filesRepository = filesRepository; + } + + async execute(fileId: number | string): Promise { + return await this.filesRepository.getFileUserPermissions(fileId); + } +} diff --git a/src/files/index.ts b/src/files/index.ts index 27ee256e..8dad404b 100644 --- a/src/files/index.ts +++ b/src/files/index.ts @@ -1,15 +1,17 @@ import { FilesRepository } from './infra/repositories/FilesRepository'; import { GetDatasetFiles } from './domain/useCases/GetDatasetFiles'; import { GetFileDownloadCount } from './domain/useCases/GetFileDownloadCount'; +import { GetFileUserPermissions } from './domain/useCases/GetFileUserPermissions'; import { GetFileDataTables } from './domain/useCases/GetFileDataTables'; const filesRepository = new FilesRepository(); const getDatasetFiles = new GetDatasetFiles(filesRepository); const getFileDownloadCount = new GetFileDownloadCount(filesRepository); +const getFileUserPermissions = new GetFileUserPermissions(filesRepository); const getFileDataTables = new GetFileDataTables(filesRepository); -export { getDatasetFiles, getFileDownloadCount, getFileDataTables }; +export { getDatasetFiles, getFileDownloadCount, getFileUserPermissions, getFileDataTables }; export { File, FileEmbargo, FileChecksum } from './domain/models/File'; export { FileOrderCriteria } from './domain/models/FileOrderCriteria'; diff --git a/src/files/infra/repositories/FilesRepository.ts b/src/files/infra/repositories/FilesRepository.ts index 2f7e38b5..ec1231bd 100644 --- a/src/files/infra/repositories/FilesRepository.ts +++ b/src/files/infra/repositories/FilesRepository.ts @@ -5,6 +5,8 @@ import { File } from '../../domain/models/File'; import { transformFilesResponseToFiles } from './transformers/fileTransformers'; import { FileDataTable } from '../../domain/models/FileDataTable'; import { transformDataTablesResponseToDataTables } from './transformers/fileDataTableTransformers'; +import { FileUserPermissions } from '../../domain/models/FileUserPermissions'; +import { transformFileUserPermissionsResponseToFileUserPermissions } from './transformers/fileUserPermissionsTransformers'; export interface GetFilesQueryParams { limit?: number; @@ -60,6 +62,20 @@ export class FilesRepository extends ApiRepository implements IFilesRepository { }); } + public async getFileUserPermissions(fileId: number | string): Promise { + let endpoint; + if (typeof fileId === 'number') { + endpoint = `/access/datafile/${fileId}/userPermissions`; + } else { + endpoint = `/access/datafile/:persistentId/userPermissions?persistentId=${fileId}`; + } + return this.doGet(endpoint, true) + .then((response) => transformFileUserPermissionsResponseToFileUserPermissions(response)) + .catch((error) => { + throw error; + }); + } + public async getFileDataTables(fileId: string | number): Promise { let endpoint; if (typeof fileId === 'number') { diff --git a/src/files/infra/repositories/transformers/fileUserPermissionsTransformers.ts b/src/files/infra/repositories/transformers/fileUserPermissionsTransformers.ts new file mode 100644 index 00000000..4954c44c --- /dev/null +++ b/src/files/infra/repositories/transformers/fileUserPermissionsTransformers.ts @@ -0,0 +1,12 @@ +import { AxiosResponse } from 'axios'; +import { FileUserPermissions } from '../../../domain/models/FileUserPermissions'; + +export const transformFileUserPermissionsResponseToFileUserPermissions = ( + response: AxiosResponse, +): FileUserPermissions => { + const fileUserPermissionsPayload = response.data.data; + return { + canDownloadFile: fileUserPermissionsPayload.canDownloadFile, + canEditOwnerDataset: fileUserPermissionsPayload.canEditOwnerDataset, + }; +}; diff --git a/test/integration/files/FilesRepository.test.ts b/test/integration/files/FilesRepository.test.ts index 0574bb75..263f5aa4 100644 --- a/test/integration/files/FilesRepository.test.ts +++ b/test/integration/files/FilesRepository.test.ts @@ -170,6 +170,27 @@ describe('FilesRepository', () => { }); }); + describe('getFileUserPermissions', () => { + test('should return user permissions filtering by file id', async () => { + const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); + const testFile = currentTestFiles[0]; + const actual = await sut.getFileUserPermissions(testFile.id); + assert.match(actual.canDownloadFile, true); + assert.match(actual.canEditOwnerDataset, true); + }); + + test('should return error when file does not exist', async () => { + let error: ReadError = undefined; + + await sut.getFileUserPermissions(nonExistentFiledId).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.`, + ); + }); + }); + describe('getFileDataTables', () => { test('should return data tables filtering by tabular file id', async () => { const currentTestFiles = await sut.getDatasetFiles(TestConstants.TEST_CREATED_DATASET_ID); diff --git a/test/testHelpers/files/fileUserPermissionsHelper.ts b/test/testHelpers/files/fileUserPermissionsHelper.ts new file mode 100644 index 00000000..f2af5cc2 --- /dev/null +++ b/test/testHelpers/files/fileUserPermissionsHelper.ts @@ -0,0 +1,8 @@ +import { FileUserPermissions } from '../../../src/files/domain/models/FileUserPermissions'; + +export const createFileUserPermissionsModel = (): FileUserPermissions => { + return { + canDownloadFile: true, + canEditOwnerDataset: true, + }; +}; diff --git a/test/unit/files/FilesRepository.test.ts b/test/unit/files/FilesRepository.test.ts index df1686d4..d6cecfa8 100644 --- a/test/unit/files/FilesRepository.test.ts +++ b/test/unit/files/FilesRepository.test.ts @@ -8,6 +8,7 @@ import { TestConstants } from '../../testHelpers/TestConstants'; import { FileOrderCriteria } from '../../../src/files/domain/models/FileOrderCriteria'; import { createFilePayload, createFileModel } from '../../testHelpers/files/filesHelper'; import { createFileDataTablePayload, createFileDataTableModel } from '../../testHelpers/files/fileDataTablesHelper'; +import { createFileUserPermissionsModel } from '../../testHelpers/files/fileUserPermissionsHelper'; describe('FilesRepository', () => { const sandbox: SinonSandbox = createSandbox(); @@ -285,6 +286,101 @@ describe('FilesRepository', () => { }); }); + describe('getFileUserPermissions', () => { + const testFileUserPermissions = createFileUserPermissionsModel(); + const testFileUserPermissionsResponse = { + data: { + status: 'OK', + data: testFileUserPermissions, + }, + }; + + describe('by numeric id', () => { + test('should return file user permissions when providing id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileUserPermissionsResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/access/datafile/${testFile.id}/userPermissions`; + + // API Key auth + let actual = await sut.getFileUserPermissions(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testFileUserPermissions); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileUserPermissions(testFile.id); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, testFileUserPermissions); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileUserPermissions(testFile.id).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/access/datafile/${testFile.id}/userPermissions`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + + describe('by persistent id', () => { + test('should return file user permissions when providing persistent id and response is successful', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').resolves(testFileUserPermissionsResponse); + const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/access/datafile/:persistentId/userPermissions?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`; + // API Key auth + let actual = await sut.getFileUserPermissions(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + assert.match(actual, testFileUserPermissions); + + // Session cookie auth + ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE); + + actual = await sut.getFileUserPermissions(TestConstants.TEST_DUMMY_PERSISTENT_ID); + + assert.calledWithExactly( + axiosGetStub, + expectedApiEndpoint, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE, + ); + assert.match(actual, testFileUserPermissions); + }); + + test('should return error result on error response', async () => { + const axiosGetStub = sandbox.stub(axios, 'get').rejects(TestConstants.TEST_ERROR_RESPONSE); + + let error: ReadError = undefined; + await sut.getFileUserPermissions(TestConstants.TEST_DUMMY_PERSISTENT_ID).catch((e) => (error = e)); + + assert.calledWithExactly( + axiosGetStub, + `${TestConstants.TEST_API_URL}/access/datafile/:persistentId/userPermissions?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`, + TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY, + ); + expect(error).to.be.instanceOf(Error); + }); + }); + }); + describe('getFileDataTables', () => { const expectedDataTables = [createFileDataTableModel()]; const testGetFileDataTablesResponse = { diff --git a/test/unit/files/GetFileUserPermissions.test.ts b/test/unit/files/GetFileUserPermissions.test.ts new file mode 100644 index 00000000..d113e730 --- /dev/null +++ b/test/unit/files/GetFileUserPermissions.test.ts @@ -0,0 +1,39 @@ +import { GetFileUserPermissions } from '../../../src/files/domain/useCases/GetFileUserPermissions'; +import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'; +import { assert, createSandbox, SinonSandbox } from 'sinon'; +import { ReadError } from '../../../src/core/domain/repositories/ReadError'; +import { createFileUserPermissionsModel } from '../../testHelpers/files/fileUserPermissionsHelper'; + +describe('execute', () => { + const sandbox: SinonSandbox = createSandbox(); + const testFileId = 1; + + afterEach(() => { + sandbox.restore(); + }); + + test('should return file user permissions on repository success', async () => { + const testFileUserPermissions = createFileUserPermissionsModel(); + const filesRepositoryStub = {}; + const getFileUserPermissionsStub = sandbox.stub().returns(testFileUserPermissions); + filesRepositoryStub.getFileUserPermissions = getFileUserPermissionsStub; + const sut = new GetFileUserPermissions(filesRepositoryStub); + + const actual = await sut.execute(testFileId); + + assert.match(actual, testFileUserPermissions); + assert.calledWithExactly(getFileUserPermissionsStub, testFileId); + }); + + test('should return error result on repository error', async () => { + const filesRepositoryStub = {}; + const testReadError = new ReadError(); + filesRepositoryStub.getFileUserPermissions = sandbox.stub().throwsException(testReadError); + const sut = new GetFileUserPermissions(filesRepositoryStub); + + let actualError: ReadError = undefined; + await sut.execute(testFileId).catch((e: ReadError) => (actualError = e)); + + assert.match(actualError, testReadError); + }); +});