Skip to content

Commit

Permalink
Merge pull request #129 from IQSS/get_file_return_dataset_version
Browse files Browse the repository at this point in the history
Adds returnDatasetVersion optional param to getFile use case
  • Loading branch information
MellyGray authored Feb 28, 2024
2 parents 4633015 + cd6db63 commit 6a0cf8a
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 93 deletions.
30 changes: 30 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The different use cases currently available in the package are classified below,
- [Files](#Files)
- [Files read use cases](#files-read-use-cases)
- [Get a File](#get-a-file)
- [Get a File and its Dataset](#get-a-file-and-its-dataset)
- [Get File Citation Text](#get-file-citation-text)
- [Get File Counts in a Dataset](#get-file-counts-in-a-dataset)
- [Get File Data Tables](#get-file-data-tables)
Expand Down Expand Up @@ -336,6 +337,35 @@ The `fileId` parameter can be a string, for persistent identifiers, or a number,

The optional `datasetVersionId` parameter can correspond to a numeric version identifier, as in the previous example, or a [DatasetNotNumberedVersion](../src/datasets/domain/models/DatasetNotNumberedVersion.ts) enum value. If not set, the default value is `DatasetNotNumberedVersion.LATEST`.

#### Get a File and its Dataset

Returns a tuple of [File](../src/files/domain/models/File.ts) and [Dataset](../src/datasets/domain/models/Dataset.ts) objects (`[File, Dataset]`), given the search parameters to identify the file.

The returned dataset object corresponds to the dataset version associated with the requested file.

##### Example call:

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

/* ... */

const fileId = 2;
const datasetVersionId = '1.0';

getFileAndDataset.execute(fileId, datasetVersionId).then((fileAndDataset: [File, Dataset]) => {
/* ... */
});

/* ... */
```

_See [use case](../src/files/domain/useCases/GetFileAndDataset.ts)_ definition.

The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.

The optional `datasetVersionId` parameter can correspond to a numeric version identifier, as in the previous example, or a [DatasetNotNumberedVersion](../src/datasets/domain/models/DatasetNotNumberedVersion.ts) enum value. If not set, the default value is `DatasetNotNumberedVersion.LATEST`.

#### Get File Citation Text

Returns the File citation text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const transformVersionResponseToDataset = (response: AxiosResponse): Data
return transformVersionPayloadToDataset(versionPayload);
};

const transformVersionPayloadToDataset = (versionPayload: DatasetPayload): Dataset => {
export const transformVersionPayloadToDataset = (versionPayload: DatasetPayload): Dataset => {
const datasetModel: Dataset = {
id: versionPayload.datasetId,
persistentId: versionPayload.datasetPersistentId,
Expand All @@ -39,7 +39,7 @@ const transformVersionPayloadToDataset = (versionPayload: DatasetPayload): Datas
releaseTime: new Date(versionPayload.releaseTime),
},
metadataBlocks: transformPayloadToDatasetMetadataBlocks(versionPayload.metadataBlocks),
isPartOf: transformPayloadToOwnerNode(versionPayload.isPartOf),
...(versionPayload.isPartOf && { isPartOf: transformPayloadToOwnerNode(versionPayload.isPartOf) }),
};
if ('license' in versionPayload) {
datasetModel.license = transformPayloadToDatasetLicense(versionPayload.license);
Expand Down
7 changes: 6 additions & 1 deletion src/files/domain/repositories/IFilesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FileSearchCriteria, FileOrderCriteria } from '../models/FileCriteria';
import { FileCounts } from '../models/FileCounts';
import { FileDownloadSizeMode } from '../models/FileDownloadSizeMode';
import { File } from '../models/File';
import { Dataset } from '../../../datasets';

export interface IFilesRepository {
getDatasetFiles(
Expand Down Expand Up @@ -38,7 +39,11 @@ export interface IFilesRepository {

getFileDataTables(fileId: number | string): Promise<FileDataTable[]>;

getFile(fileId: number | string, datasetVersionId: string): Promise<File>;
getFile(
fileId: number | string,
datasetVersionId: string,
returnDatasetVersion: boolean,
): Promise<File | [File, Dataset]>;

getFileCitation(fileId: number | string, datasetVersionId: string, includeDeaccessioned: boolean): Promise<string>;
}
5 changes: 3 additions & 2 deletions src/files/domain/useCases/GetFile.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IFilesRepository } from '../repositories/IFilesRepository';
import { File } from '../models/File';
import { DatasetNotNumberedVersion } from '../../../datasets';
import { UseCase } from '../../../core/domain/useCases/UseCase';

export class GetFile {
export class GetFile implements UseCase<File> {
constructor(private readonly filesRepository: IFilesRepository) {}

/**
Expand All @@ -16,6 +17,6 @@ export class GetFile {
fileId: number | string,
datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST,
): Promise<File> {
return await this.filesRepository.getFile(fileId, datasetVersionId);
return (await this.filesRepository.getFile(fileId, datasetVersionId, false)) as File;
}
}
22 changes: 22 additions & 0 deletions src/files/domain/useCases/GetFileAndDataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IFilesRepository } from '../repositories/IFilesRepository';
import { File } from '../models/File';
import { DatasetNotNumberedVersion, Dataset } from '../../../datasets';
import { UseCase } from '../../../core/domain/useCases/UseCase';

export class GetFileAndDataset implements UseCase<[File, Dataset]> {
constructor(private readonly filesRepository: IFilesRepository) {}

/**
* Returns a tuple including the File instance and the associated Dataset, given the search parameters to identify the File.
*
* @param {number | string} [fileId] - The File identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
* @param {string | DatasetNotNumberedVersion} [datasetVersionId=DatasetNotNumberedVersion.LATEST] - The dataset version identifier, which can be a version-specific numeric string (for example, 1.0) or a DatasetNotNumberedVersion enum value. If this parameter is not set, the default value is: DatasetNotNumberedVersion.LATEST
* @returns {Promise<[File, Dataset]>}
*/
async execute(
fileId: number | string,
datasetVersionId: string | DatasetNotNumberedVersion = DatasetNotNumberedVersion.LATEST,
): Promise<[File, Dataset]> {
return (await this.filesRepository.getFile(fileId, datasetVersionId, true)) as [File, Dataset];
}
}
3 changes: 3 additions & 0 deletions src/files/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GetFileDataTables } from './domain/useCases/GetFileDataTables';
import { GetDatasetFilesTotalDownloadSize } from './domain/useCases/GetDatasetFilesTotalDownloadSize';
import { GetFile } from './domain/useCases/GetFile';
import { GetFileCitation } from './domain/useCases/GetFileCitation';
import { GetFileAndDataset } from './domain/useCases/GetFileAndDataset';

const filesRepository = new FilesRepository();

Expand All @@ -17,6 +18,7 @@ const getFileUserPermissions = new GetFileUserPermissions(filesRepository);
const getFileDataTables = new GetFileDataTables(filesRepository);
const getDatasetFilesTotalDownloadSize = new GetDatasetFilesTotalDownloadSize(filesRepository);
const getFile = new GetFile(filesRepository);
const getFileAndDataset = new GetFileAndDataset(filesRepository);
const getFileCitation = new GetFileCitation(filesRepository);

export {
Expand All @@ -27,6 +29,7 @@ export {
getDatasetFileCounts,
getDatasetFilesTotalDownloadSize,
getFile,
getFileAndDataset,
getFileCitation,
};

Expand Down
10 changes: 8 additions & 2 deletions src/files/infra/repositories/FilesRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { FileSearchCriteria, FileOrderCriteria } from '../../domain/models/FileC
import { FileCounts } from '../../domain/models/FileCounts';
import { transformFileCountsResponseToFileCounts } from './transformers/fileCountsTransformers';
import { FileDownloadSizeMode } from '../../domain/models/FileDownloadSizeMode';
import { Dataset } from '../../../datasets';

export interface GetFilesQueryParams {
includeDeaccessioned: boolean;
Expand Down Expand Up @@ -144,11 +145,16 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
});
}

public async getFile(fileId: number | string, datasetVersionId: string): Promise<File> {
public async getFile(
fileId: number | string,
datasetVersionId: string,
returnDatasetVersion: boolean,
): Promise<File | [File, Dataset]> {
return this.doGet(this.buildApiEndpoint(this.filesResourceName, `versions/${datasetVersionId}`, fileId), true, {
returnDatasetVersion: returnDatasetVersion,
returnOwners: true,
})
.then((response) => transformFileResponseToFile(response))
.then((response) => transformFileResponseToFile(response, returnDatasetVersion))
.catch((error) => {
throw error;
});
Expand Down
10 changes: 9 additions & 1 deletion src/files/infra/repositories/transformers/fileTransformers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { File, FileEmbargo, FileChecksum } from '../../../domain/models/File';
import { AxiosResponse } from 'axios';
import { FilesSubset } from '../../../domain/models/FilesSubset';
import { Dataset } from '../../../../datasets';
import { transformVersionPayloadToDataset } from '../../../../datasets/infra/repositories/transformers/datasetTransformers';
import { ChecksumPayload, EmbargoPayload, FilePayload } from './FilePayload';
import { transformPayloadToOwnerNode } from '../../../../core/infra/repositories/transformers/dvObjectOwnerNodeTransformer';

Expand All @@ -17,8 +19,14 @@ export const transformFilesResponseToFilesSubset = (response: AxiosResponse): Fi
};
};

export const transformFileResponseToFile = (response: AxiosResponse): File => {
export const transformFileResponseToFile = (
response: AxiosResponse,
returnDatasetVersion: boolean,
): File | [File, Dataset] => {
const filePayload = response.data.data;
if (returnDatasetVersion) {
return [transformFilePayloadToFile(filePayload), transformVersionPayloadToDataset(filePayload.datasetVersion)];
}
return transformFilePayloadToFile(filePayload);
};

Expand Down
24 changes: 17 additions & 7 deletions test/integration/files/FilesRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { registerFileViaApi, uploadFileViaApi } from '../../testHelpers/files/fi
import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository';
import { ReadError } from '../../../src/core/domain/repositories/ReadError';
import { FileSearchCriteria, FileAccessStatus, FileOrderCriteria } from '../../../src/files/domain/models/FileCriteria';
import { DatasetNotNumberedVersion } from '../../../src/datasets';
import { DatasetNotNumberedVersion, Dataset } from '../../../src/datasets';
import { File } from '../../../src/files/domain/models/File';
import { FileCounts } from '../../../src/files/domain/models/FileCounts';
import { FileDownloadSizeMode } from '../../../src';
import { fail } from 'assert';
Expand Down Expand Up @@ -454,21 +455,28 @@ describe('FilesRepository', () => {
describe('getFile', () => {
describe('by numeric id', () => {
test('should return file when providing a valid id', async () => {
const actual = await sut.getFile(testFileId, DatasetNotNumberedVersion.LATEST);
const actual: File = (await sut.getFile(testFileId, DatasetNotNumberedVersion.LATEST, false)) as File;

assert.match(actual.name, testTextFile1Name);
});

test('should return file draft when providing a valid id and version is draft', async () => {
const actual = await sut.getFile(testFileId, DatasetNotNumberedVersion.DRAFT);
const actual: File = (await sut.getFile(testFileId, DatasetNotNumberedVersion.DRAFT, false)) as File;

assert.match(actual.name, testTextFile1Name);
});

test('should return file and dataset when providing id, version, and returnDatasetVersion is true', async () => {
const actual = (await sut.getFile(testFileId, DatasetNotNumberedVersion.DRAFT, true)) as [File, Dataset];

assert.match(actual[0].name, testTextFile1Name);
assert.match(actual[1].id, TestConstants.TEST_CREATED_DATASET_1_ID);
});

test('should return error when file does not exist', async () => {
let error: ReadError = undefined;

await sut.getFile(nonExistentFiledId, DatasetNotNumberedVersion.LATEST).catch((e) => (error = e));
await sut.getFile(nonExistentFiledId, DatasetNotNumberedVersion.LATEST, false).catch((e) => (error = e));

assert.match(
error.message,
Expand All @@ -478,13 +486,13 @@ describe('FilesRepository', () => {
});
describe('by persistent id', () => {
test('should return file when providing a valid persistent id', async () => {
const actual = await sut.getFile(testFilePersistentId, DatasetNotNumberedVersion.LATEST);
const actual = (await sut.getFile(testFilePersistentId, DatasetNotNumberedVersion.LATEST, false)) as File;

assert.match(actual.name, testTextFile1Name);
});

test('should return file draft when providing a valid persistent id and version is draft', async () => {
const actual = await sut.getFile(testFilePersistentId, DatasetNotNumberedVersion.DRAFT);
const actual = (await sut.getFile(testFilePersistentId, DatasetNotNumberedVersion.DRAFT, false)) as File;

assert.match(actual.name, testTextFile1Name);
});
Expand All @@ -493,7 +501,9 @@ describe('FilesRepository', () => {
let error: ReadError = undefined;

const nonExistentFiledPersistentId = 'nonExistentFiledPersistentId';
await sut.getFile(nonExistentFiledPersistentId, DatasetNotNumberedVersion.LATEST).catch((e) => (error = e));
await sut
.getFile(nonExistentFiledPersistentId, DatasetNotNumberedVersion.LATEST, false)
.catch((e) => (error = e));

assert.match(
error.message,
Expand Down
62 changes: 32 additions & 30 deletions test/unit/files/FilesRepository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -777,73 +777,75 @@ describe('FilesRepository', () => {
});
});
describe('getFile', () => {
const expectedConfigApiKey = {
...TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY,
params: { returnOwners: true },
const expectedRequestParams = {
returnDatasetVersion: false,
returnOwners: true
};
const expectedConfigSessionCookie = {
...TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_SESSION_COOKIE,
params: { returnOwners: true },

const expectedRequestConfigApiKey = {
params: expectedRequestParams,
headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers,
};

const expectedRequestConfigSessionCookie = {
params: expectedRequestParams,
headers: TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY.headers,
};

const testGetFileResponse = {
data: {
status: 'OK',
data: createFilePayload(),
},
};

describe('by numeric id', () => {
const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/${testFile.id}/versions/${DatasetNotNumberedVersion.LATEST}`;
const testGetFileResponse = {
data: {
status: 'OK',
data: createFilePayload(),
},
};
test('should return file when providing id and response is successful', async () => {
const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileResponse);

// API Key auth
let actual = await sut.getFile(testFile.id, DatasetNotNumberedVersion.LATEST);
let actual = await sut.getFile(testFile.id, DatasetNotNumberedVersion.LATEST, false);

assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedConfigApiKey);
assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedRequestConfigApiKey);
assert.match(actual, createFileModel());

// Session cookie auth
ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE);

actual = await sut.getFile(testFile.id, DatasetNotNumberedVersion.LATEST);
actual = await sut.getFile(testFile.id, DatasetNotNumberedVersion.LATEST, false);

assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedConfigSessionCookie);
assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedRequestConfigSessionCookie);
assert.match(actual, createFileModel());
});

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.getFile(testFile.id, DatasetNotNumberedVersion.LATEST).catch((e) => (error = e));
await sut.getFile(testFile.id, DatasetNotNumberedVersion.LATEST, false).catch((e) => (error = e));

assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedConfigApiKey);
assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedRequestConfigApiKey);
expect(error).to.be.instanceOf(Error);
});
});
describe('by persistent id', () => {
const expectedApiEndpoint = `${TestConstants.TEST_API_URL}/files/:persistentId/versions/${DatasetNotNumberedVersion.LATEST}?persistentId=${TestConstants.TEST_DUMMY_PERSISTENT_ID}`;
const testGetFileResponse = {
data: {
status: 'OK',
data: createFilePayload(),
},
};
test('should return file when providing persistent id and response is successful', async () => {
const axiosGetStub = sandbox.stub(axios, 'get').resolves(testGetFileResponse);

// API Key auth
let actual = await sut.getFile(TestConstants.TEST_DUMMY_PERSISTENT_ID, DatasetNotNumberedVersion.LATEST);
let actual = await sut.getFile(TestConstants.TEST_DUMMY_PERSISTENT_ID, DatasetNotNumberedVersion.LATEST, false);

assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedConfigApiKey);
assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedRequestConfigApiKey);
assert.match(actual, createFileModel());

// Session cookie auth
ApiConfig.init(TestConstants.TEST_API_URL, DataverseApiAuthMechanism.SESSION_COOKIE);

actual = await sut.getFile(TestConstants.TEST_DUMMY_PERSISTENT_ID, DatasetNotNumberedVersion.LATEST);
actual = await sut.getFile(TestConstants.TEST_DUMMY_PERSISTENT_ID, DatasetNotNumberedVersion.LATEST, false);

assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedConfigSessionCookie);
assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedRequestConfigSessionCookie);
assert.match(actual, createFileModel());
});

Expand All @@ -852,10 +854,10 @@ describe('FilesRepository', () => {

let error: ReadError = undefined;
await sut
.getFile(TestConstants.TEST_DUMMY_PERSISTENT_ID, DatasetNotNumberedVersion.LATEST)
.getFile(TestConstants.TEST_DUMMY_PERSISTENT_ID, DatasetNotNumberedVersion.LATEST, false)
.catch((e) => (error = e));

assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedConfigApiKey);
assert.calledWithExactly(axiosGetStub, expectedApiEndpoint, expectedRequestConfigApiKey);
expect(error).to.be.instanceOf(Error);
});
});
Expand Down
Loading

0 comments on commit 6a0cf8a

Please sign in to comment.