Skip to content

Commit e689f25

Browse files
authored
Merge pull request #266 from IQSS/feat/259-restrict-unrestrict-file-use-case
Restrict File use case
2 parents 37f11fd + 8e979e1 commit e689f25

File tree

10 files changed

+331
-9
lines changed

10 files changed

+331
-9
lines changed

docs/useCases.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ The different use cases currently available in the package are classified below,
5151
- [Files write use cases](#files-write-use-cases)
5252
- [File Uploading Use Cases](#file-uploading-use-cases)
5353
- [Delete a File](#delete-a-file)
54+
- [Restrict or Unrestrict a File](#restrict-or-unrestrict-a-file)
5455
- [Metadata Blocks](#metadata-blocks)
5556
- [Metadata Blocks read use cases](#metadata-blocks-read-use-cases)
5657
- [Get All Facetable Metadata Fields](#get-all-facetable-metadata-fields)
@@ -1264,6 +1265,28 @@ Note that the behavior of deleting files depends on if the dataset has ever been
12641265
- If the dataset has published, the file is deleted from the draft (and future published versions).
12651266
- If the dataset has published, the deleted file can still be downloaded because it was part of a published version.
12661267

1268+
#### Restrict or Unrestrict a File
1269+
1270+
Restrict or unrestrict an existing file.
1271+
1272+
##### Example call:
1273+
1274+
```typescript
1275+
import { restrictFile } from '@iqss/dataverse-client-javascript'
1276+
1277+
/* ... */
1278+
1279+
const fileId = 12345
1280+
1281+
restrictFile.execute(fileId, true)
1282+
1283+
/* ... */
1284+
```
1285+
1286+
_See [use case](../src/files/domain/useCases/RestrictFile.ts) implementation_.
1287+
1288+
The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.
1289+
12671290
## Metadata Blocks
12681291

12691292
### Metadata Blocks read use cases

src/core/infra/repositories/ApiRepository.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export abstract class ApiRepository {
2020

2121
public async doPost(
2222
apiEndpoint: string,
23-
data: string | object,
23+
data: string | object | boolean,
2424
queryParams: object = {},
2525
contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON
2626
): Promise<AxiosResponse> {
@@ -29,7 +29,7 @@ export abstract class ApiRepository {
2929

3030
public async doPut(
3131
apiEndpoint: string,
32-
data: string | object,
32+
data: string | object | boolean,
3333
queryParams: object = {},
3434
contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON
3535
): Promise<AxiosResponse> {
@@ -70,12 +70,18 @@ export abstract class ApiRepository {
7070
private async doRequest(
7171
method: 'post' | 'put',
7272
apiEndpoint: string,
73-
data: string | object,
73+
data: string | object | boolean,
7474
queryParams: object = {},
7575
contentType: string = ApiConstants.CONTENT_TYPE_APPLICATION_JSON
7676
): Promise<AxiosResponse> {
77-
const requestData =
78-
contentType == ApiConstants.CONTENT_TYPE_APPLICATION_JSON ? JSON.stringify(data) : data
77+
let requestData = data
78+
79+
if (contentType === ApiConstants.CONTENT_TYPE_APPLICATION_JSON) {
80+
if (typeof data === 'object' || typeof data === 'boolean') {
81+
requestData = JSON.stringify(data)
82+
}
83+
}
84+
7985
const requestUrl = buildRequestUrl(apiEndpoint)
8086
const requestConfig = buildRequestConfig(true, queryParams, contentType)
8187

src/files/domain/repositories/IFilesRepository.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,6 @@ export interface IFilesRepository {
6161
): Promise<undefined>
6262

6363
deleteFile(fileId: number | string): Promise<undefined>
64+
65+
restrictFile(fileId: number | string, restrict: boolean): Promise<undefined>
6466
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { IFilesRepository } from '../repositories/IFilesRepository'
2+
import { UseCase } from '../../../core/domain/useCases/UseCase'
3+
4+
export class RestrictFile implements UseCase<void> {
5+
constructor(private readonly filesRepository: IFilesRepository) {}
6+
7+
/**
8+
* Restrict or unrestrict an existing file.
9+
* More detailed information about the file restriction behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#restrict-files
10+
*
11+
* @param {number | string} [fileId] - The File identifier, which can be a string (for persistent identifiers), or a number (for numeric identifiers).
12+
* @param {boolean} [restrict] - A boolean value that indicates whether the file should be restricted or unrestricted.
13+
* @returns {Promise<void>} -This method does not return anything upon successful completion.
14+
*/
15+
async execute(fileId: number | string, restrict: boolean): Promise<void> {
16+
return await this.filesRepository.restrictFile(fileId, restrict)
17+
}
18+
}

src/files/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { UploadFile } from './domain/useCases/UploadFile'
1212
import { DirectUploadClient } from './infra/clients/DirectUploadClient'
1313
import { AddUploadedFilesToDataset } from './domain/useCases/AddUploadedFilesToDataset'
1414
import { DeleteFile } from './domain/useCases/DeleteFile'
15+
import { RestrictFile } from './domain/useCases/RestrictFile'
1516

1617
const filesRepository = new FilesRepository()
1718
const directUploadClient = new DirectUploadClient(filesRepository)
@@ -28,6 +29,7 @@ const getFileCitation = new GetFileCitation(filesRepository)
2829
const uploadFile = new UploadFile(directUploadClient)
2930
const addUploadedFilesToDataset = new AddUploadedFilesToDataset(filesRepository)
3031
const deleteFile = new DeleteFile(filesRepository)
32+
const restrictFile = new RestrictFile(filesRepository)
3133

3234
export {
3335
getDatasetFiles,
@@ -41,7 +43,8 @@ export {
4143
getFileCitation,
4244
uploadFile,
4345
addUploadedFilesToDataset,
44-
deleteFile
46+
deleteFile,
47+
restrictFile
4548
}
4649

4750
export { FileModel as File, FileEmbargo, FileChecksum } from './domain/models/FileModel'

src/files/infra/repositories/FilesRepository.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,4 +301,12 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
301301
throw error
302302
})
303303
}
304+
305+
public async restrictFile(fileId: number | string, restrict: boolean): Promise<undefined> {
306+
return this.doPut(this.buildApiEndpoint(this.filesResourceName, 'restrict', fileId), restrict)
307+
.then(() => undefined)
308+
.catch((error) => {
309+
throw error
310+
})
311+
}
304312
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import {
2+
ApiConfig,
3+
createDataset,
4+
CreatedDatasetIdentifiers,
5+
restrictFile,
6+
getDatasetFiles,
7+
WriteError
8+
} from '../../../src'
9+
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
10+
import {
11+
createCollectionViaApi,
12+
deleteCollectionViaApi
13+
} from '../../testHelpers/collections/collectionHelper'
14+
import { deleteUnpublishedDatasetViaApi } from '../../testHelpers/datasets/datasetHelper'
15+
import { uploadFileViaApi } from '../../testHelpers/files/filesHelper'
16+
import { TestConstants } from '../../testHelpers/TestConstants'
17+
18+
describe('execute', () => {
19+
const testCollectionAlias = 'restrictFileFunctionalTest'
20+
let testDatasetIds: CreatedDatasetIdentifiers
21+
const testTextFile1Name = 'test-file-1.txt'
22+
23+
beforeAll(async () => {
24+
ApiConfig.init(
25+
TestConstants.TEST_API_URL,
26+
DataverseApiAuthMechanism.API_KEY,
27+
process.env.TEST_API_KEY
28+
)
29+
await createCollectionViaApi(testCollectionAlias)
30+
31+
try {
32+
testDatasetIds = await createDataset.execute(
33+
TestConstants.TEST_NEW_DATASET_DTO,
34+
testCollectionAlias
35+
)
36+
} catch (error) {
37+
throw new Error('Tests beforeAll(): Error while creating test dataset')
38+
}
39+
await uploadFileViaApi(testDatasetIds.numericId, testTextFile1Name).catch(() => {
40+
throw new Error(`Tests beforeAll(): Error while uploading file ${testTextFile1Name}`)
41+
})
42+
})
43+
44+
afterAll(async () => {
45+
try {
46+
await deleteUnpublishedDatasetViaApi(testDatasetIds.numericId)
47+
} catch (error) {
48+
throw new Error('Tests afterAll(): Error while deleting test dataset')
49+
}
50+
try {
51+
await deleteCollectionViaApi(testCollectionAlias)
52+
} catch (error) {
53+
throw new Error('Tests afterAll(): Error while deleting test collection')
54+
}
55+
})
56+
57+
test('should successfully restrict a file', async () => {
58+
try {
59+
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
60+
61+
await restrictFile.execute(datasetFiles.files[0].id, true)
62+
} catch (error) {
63+
throw new Error('File should be deleted')
64+
} finally {
65+
const datasetFilesAfterRestriction = await getDatasetFiles.execute(testDatasetIds.numericId)
66+
67+
expect(datasetFilesAfterRestriction.files[0].restricted).toEqual(true)
68+
69+
// Unrestrict the file for the next test
70+
await restrictFile.execute(datasetFilesAfterRestriction.files[0].id, false)
71+
}
72+
})
73+
74+
test('should succesfully unrestrict a file', async () => {
75+
try {
76+
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
77+
78+
await restrictFile.execute(datasetFiles.files[0].id, true)
79+
80+
await restrictFile.execute(datasetFiles.files[0].id, false)
81+
} catch (error) {
82+
throw new Error('File should be deleted')
83+
} finally {
84+
const datasetFilesAfterRestriction = await getDatasetFiles.execute(testDatasetIds.numericId)
85+
86+
expect(datasetFilesAfterRestriction.files[0].restricted).toEqual(false)
87+
}
88+
})
89+
90+
test('should throw an error when the file id does not exist', async () => {
91+
expect.assertions(2)
92+
let writeError: WriteError | undefined = undefined
93+
const nonExistentFileId = 5
94+
95+
try {
96+
await restrictFile.execute(nonExistentFileId, true)
97+
throw new Error('Use case should throw an error')
98+
} catch (error) {
99+
writeError = error as WriteError
100+
} finally {
101+
expect(writeError).toBeInstanceOf(WriteError)
102+
103+
expect(writeError?.message).toEqual(
104+
`There was an error when writing the resource. Reason was: [400] Could not find datafile with id ${nonExistentFileId}`
105+
)
106+
}
107+
})
108+
})

test/integration/files/FilesRepository.test.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,4 +725,133 @@ describe('FilesRepository', () => {
725725
await expect(sut.deleteFile(nonExistentFiledId)).rejects.toThrow(expectedError)
726726
})
727727
})
728+
729+
describe('restrictFile', () => {
730+
let restrictFileDatasetIds: CreatedDatasetIdentifiers
731+
const testTextFile1Name = 'test-file-1.txt'
732+
733+
const setFileToRestricted = async (fileId: number) => {
734+
await sut.restrictFile(fileId, true)
735+
}
736+
737+
const setFileToUnrestricted = async (fileId: number) => {
738+
await sut.restrictFile(fileId, false)
739+
}
740+
741+
beforeEach(async () => {
742+
try {
743+
restrictFileDatasetIds = await createDataset.execute(TestConstants.TEST_NEW_DATASET_DTO)
744+
} catch (error) {
745+
throw new Error('Tests beforeEach(): Error while creating test dataset')
746+
}
747+
await uploadFileViaApi(restrictFileDatasetIds.numericId, testTextFile1Name).catch(() => {
748+
throw new Error(`Tests beforeEach(): Error while uploading file ${testTextFile1Name}`)
749+
})
750+
})
751+
752+
afterEach(async () => {
753+
await deleteUnpublishedDatasetViaApi(restrictFileDatasetIds.numericId)
754+
})
755+
756+
test('should successfully restrict a file', async () => {
757+
const datasetFiles = await sut.getDatasetFiles(
758+
restrictFileDatasetIds.numericId,
759+
DatasetNotNumberedVersion.LATEST,
760+
false,
761+
FileOrderCriteria.NAME_AZ
762+
)
763+
764+
expect(datasetFiles.files[0].restricted).toEqual(false)
765+
766+
await setFileToRestricted(datasetFiles.files[0].id)
767+
768+
const datasetFilesAfterRestrict = await sut.getDatasetFiles(
769+
restrictFileDatasetIds.numericId,
770+
DatasetNotNumberedVersion.LATEST,
771+
false,
772+
FileOrderCriteria.NAME_AZ
773+
)
774+
775+
expect(datasetFilesAfterRestrict.files[0].restricted).toEqual(true)
776+
777+
// Unrestrict the file Just in case to avoid conflicts with other tests
778+
await setFileToUnrestricted(datasetFiles.files[0].id)
779+
})
780+
781+
test('should successfully unrestrict a file', async () => {
782+
const datasetFiles = await sut.getDatasetFiles(
783+
restrictFileDatasetIds.numericId,
784+
DatasetNotNumberedVersion.LATEST,
785+
false,
786+
FileOrderCriteria.NAME_AZ
787+
)
788+
789+
expect(datasetFiles.files[0].restricted).toEqual(false)
790+
791+
await setFileToRestricted(datasetFiles.files[0].id)
792+
793+
const datasetFilesAfterRestrict = await sut.getDatasetFiles(
794+
restrictFileDatasetIds.numericId,
795+
DatasetNotNumberedVersion.LATEST,
796+
false,
797+
FileOrderCriteria.NAME_AZ
798+
)
799+
800+
expect(datasetFilesAfterRestrict.files[0].restricted).toEqual(true)
801+
802+
await setFileToUnrestricted(datasetFiles.files[0].id)
803+
804+
const datasetFilesAfterUnrestrict = await sut.getDatasetFiles(
805+
restrictFileDatasetIds.numericId,
806+
DatasetNotNumberedVersion.LATEST,
807+
false,
808+
FileOrderCriteria.NAME_AZ
809+
)
810+
811+
expect(datasetFilesAfterUnrestrict.files[0].restricted).toEqual(false)
812+
})
813+
814+
test('should return error when file was already restricted', async () => {
815+
const datasetFiles = await sut.getDatasetFiles(
816+
restrictFileDatasetIds.numericId,
817+
DatasetNotNumberedVersion.LATEST,
818+
false,
819+
FileOrderCriteria.NAME_AZ
820+
)
821+
822+
await setFileToRestricted(datasetFiles.files[0].id)
823+
824+
const expectedError = new WriteError(
825+
`[400] Problem trying to update restriction status on ${testTextFile1Name}: File ${testTextFile1Name} is already restricted`
826+
)
827+
828+
await expect(setFileToRestricted(datasetFiles.files[0].id)).rejects.toThrow(expectedError)
829+
830+
// Unrestrict the file Just in case to avoid conflicts with other tests
831+
await setFileToUnrestricted(datasetFiles.files[0].id)
832+
})
833+
834+
test('should return error when files was already unrestricted', async () => {
835+
const datasetFiles = await sut.getDatasetFiles(
836+
restrictFileDatasetIds.numericId,
837+
DatasetNotNumberedVersion.LATEST,
838+
false,
839+
FileOrderCriteria.NAME_AZ
840+
)
841+
842+
const expectedError = new WriteError(
843+
`[400] Problem trying to update restriction status on ${testTextFile1Name}: File ${testTextFile1Name} is already unrestricted`
844+
)
845+
846+
await expect(setFileToUnrestricted(datasetFiles.files[0].id)).rejects.toThrow(expectedError)
847+
})
848+
849+
test('should return error when file does not exist', async () => {
850+
const expectedError = new WriteError(
851+
`[400] Could not find datafile with id ${nonExistentFiledId}`
852+
)
853+
854+
await expect(setFileToRestricted(nonExistentFiledId)).rejects.toThrow(expectedError)
855+
})
856+
})
728857
})

0 commit comments

Comments
 (0)