Skip to content

Commit 1b949b5

Browse files
authored
Merge pull request #279 from IQSS/edit-metadata-allow-empty-values
File Restrict Use Case Extension: enable access request and edit terms of access
2 parents 2142915 + 6e36a70 commit 1b949b5

File tree

8 files changed

+171
-32
lines changed

8 files changed

+171
-32
lines changed

docs/useCases.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,7 +1410,7 @@ The use case returns a number, which is the identifier of the new file.
14101410

14111411
#### Restrict or Unrestrict a File
14121412

1413-
Restrict or unrestrict an existing file.
1413+
Restrict or unrestrict an existing file, given a [RestrictFileDTO](../src/users/domain/dtos/RestrictFileDTO.ts)
14141414

14151415
##### Example call:
14161416

@@ -1420,15 +1420,23 @@ import { restrictFile } from '@iqss/dataverse-client-javascript'
14201420
/* ... */
14211421

14221422
const fileId = 12345
1423+
const restrictFileDTO = {
1424+
restrict: true,
1425+
enableAccessRequest: false,
1426+
termsOfAccess: 'terms of access'
1427+
}
14231428

1424-
restrictFile.execute(fileId, true)
1429+
restrictFile.execute(fileId, restrictFileDTO)
14251430

14261431
/* ... */
14271432
```
14281433

14291434
_See [use case](../src/files/domain/useCases/RestrictFile.ts) implementation_.
14301435

14311436
The `fileId` parameter can be a string, for persistent identifiers, or a number, for numeric identifiers.
1437+
If restrict is false then enableAccessRequest and termsOfAccess are ignored
1438+
If restrict is true and enableAccessRequest is false then termsOfAccess is required.
1439+
The enableAccessRequest and termsOfAccess are applied to the Draft version of the Dataset and affect all of the restricted files in said Draft version.
14321440

14331441
## Metadata Blocks
14341442

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface RestrictFileDTO {
2+
restrict: boolean
3+
enableAccessRequest?: boolean
4+
termsOfAccess?: string
5+
}

src/files/domain/repositories/IFilesRepository.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Dataset } from '../../../datasets'
99
import { FileUploadDestination } from '../models/FileUploadDestination'
1010
import { UploadedFileDTO } from '../dtos/UploadedFileDTO'
1111
import { UpdateFileMetadataDTO } from '../dtos/UpdateFileMetadataDTO'
12+
import { RestrictFileDTO } from '../dtos/RestrictFileDTO'
1213

1314
export interface IFilesRepository {
1415
getDatasetFiles(
@@ -65,7 +66,8 @@ export interface IFilesRepository {
6566

6667
replaceFile(fileId: number | string, uploadedFileDTO: UploadedFileDTO): Promise<number>
6768

68-
restrictFile(fileId: number | string, restrict: boolean): Promise<undefined>
69+
restrictFile(fileId: number | string, restrictFileDTO: RestrictFileDTO): Promise<void>
70+
6971
updateFileMetadata(
7072
fileId: number | string,
7173
updateFileMetadataDTO: UpdateFileMetadataDTO
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IFilesRepository } from '../repositories/IFilesRepository'
22
import { UseCase } from '../../../core/domain/useCases/UseCase'
3+
import { RestrictFileDTO } from '../dtos/RestrictFileDTO'
34

45
export class RestrictFile implements UseCase<void> {
56
constructor(private readonly filesRepository: IFilesRepository) {}
@@ -9,10 +10,11 @@ export class RestrictFile implements UseCase<void> {
910
* More detailed information about the file restriction behavior can be found in https://guides.dataverse.org/en/latest/api/native-api.html#restrict-files
1011
*
1112
* @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+
* @param {RestrictFileDTO} [restrictFileDTO] - The DTO containing the file restriction information.
1314
* @returns {Promise<void>} -This method does not return anything upon successful completion.
1415
*/
15-
async execute(fileId: number | string, restrict: boolean): Promise<void> {
16-
return await this.filesRepository.restrictFile(fileId, restrict)
16+
17+
async execute(fileId: number | string, restrictFileDTO: RestrictFileDTO): Promise<void> {
18+
return await this.filesRepository.restrictFile(fileId, restrictFileDTO)
1719
}
1820
}

src/files/infra/repositories/FilesRepository.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { transformUploadDestinationsResponseToUploadDestination } from './transf
2121
import { UploadedFileDTO } from '../../domain/dtos/UploadedFileDTO'
2222
import { UpdateFileMetadataDTO } from '../../domain/dtos/UpdateFileMetadataDTO'
2323
import { ApiConstants } from '../../../core/infra/repositories/ApiConstants'
24+
import { RestrictFileDTO } from '../../domain/dtos/RestrictFileDTO'
2425

2526
export interface GetFilesQueryParams {
2627
includeDeaccessioned: boolean
@@ -346,9 +347,17 @@ export class FilesRepository extends ApiRepository implements IFilesRepository {
346347
})
347348
}
348349

349-
public async restrictFile(fileId: number | string, restrict: boolean): Promise<undefined> {
350-
return this.doPut(this.buildApiEndpoint(this.filesResourceName, 'restrict', fileId), restrict)
351-
.then(() => undefined)
350+
public async restrictFile(
351+
fileId: number | string,
352+
restrictFileDTO: RestrictFileDTO
353+
): Promise<void> {
354+
return this.doPut(
355+
this.buildApiEndpoint(this.filesResourceName, 'restrict', fileId),
356+
restrictFileDTO
357+
)
358+
.then((response) => {
359+
return response.data.data.message
360+
})
352361
.catch((error) => {
353362
throw error
354363
})

test/functional/files/RestrictFile.test.ts

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
CreatedDatasetIdentifiers,
55
restrictFile,
66
getDatasetFiles,
7-
WriteError
7+
WriteError,
8+
getDataset
89
} from '../../../src'
910
import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig'
1011
import {
@@ -58,32 +59,62 @@ describe('execute', () => {
5859
try {
5960
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
6061

61-
await restrictFile.execute(datasetFiles.files[0].id, true)
62+
await restrictFile.execute(datasetFiles.files[0].id, { restrict: true })
6263
} catch (error) {
63-
throw new Error('File should be deleted')
64+
throw new Error('File should be restricted')
6465
} finally {
6566
const datasetFilesAfterRestriction = await getDatasetFiles.execute(testDatasetIds.numericId)
66-
6767
expect(datasetFilesAfterRestriction.files[0].restricted).toEqual(true)
68-
6968
// Unrestrict the file for the next test
70-
await restrictFile.execute(datasetFilesAfterRestriction.files[0].id, false)
69+
await restrictFile.execute(datasetFilesAfterRestriction.files[0].id, { restrict: false })
7170
}
7271
})
7372

74-
test('should succesfully unrestrict a file', async () => {
73+
test('should successfully restrict a file with terms of use', async () => {
7574
try {
7675
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
7776

78-
await restrictFile.execute(datasetFiles.files[0].id, true)
77+
await restrictFile.execute(datasetFiles.files[0].id, {
78+
restrict: true,
79+
enableAccessRequest: false,
80+
termsOfAccess: 'This file is restricted for testing purposes'
81+
})
82+
} catch (error) {
83+
throw new Error('File should be restricted')
84+
} finally {
85+
const datasetFilesAfterRestriction = await getDatasetFiles.execute(testDatasetIds.numericId)
86+
87+
expect(datasetFilesAfterRestriction.files[0].restricted).toEqual(true)
88+
89+
const dataset = await getDataset.execute(testDatasetIds.numericId)
90+
91+
expect(dataset.termsOfUse.termsOfAccess.termsOfAccessForRestrictedFiles).toEqual(
92+
'This file is restricted for testing purposes'
93+
)
94+
95+
await restrictFile.execute(datasetFilesAfterRestriction.files[0].id, { restrict: false })
96+
}
97+
})
7998

80-
await restrictFile.execute(datasetFiles.files[0].id, false)
99+
test('should succesfully unrestrict a file', async () => {
100+
try {
101+
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
102+
await restrictFile.execute(datasetFiles.files[0].id, { restrict: true })
81103
} catch (error) {
82-
throw new Error('File should be deleted')
104+
throw new Error('File should be restricted')
83105
} finally {
84106
const datasetFilesAfterRestriction = await getDatasetFiles.execute(testDatasetIds.numericId)
107+
expect(datasetFilesAfterRestriction.files[0].restricted).toEqual(true)
108+
}
85109

86-
expect(datasetFilesAfterRestriction.files[0].restricted).toEqual(false)
110+
try {
111+
const datasetFilesAfterRestriction = await getDatasetFiles.execute(testDatasetIds.numericId)
112+
await restrictFile.execute(datasetFilesAfterRestriction.files[0].id, { restrict: false })
113+
} catch (error) {
114+
throw new Error('File should be unrestricted')
115+
} finally {
116+
const datasetFilesAfterUnrestriction = await getDatasetFiles.execute(testDatasetIds.numericId)
117+
expect(datasetFilesAfterUnrestriction.files[0].restricted).toEqual(false)
87118
}
88119
})
89120

@@ -93,7 +124,7 @@ describe('execute', () => {
93124
const nonExistentFileId = 5
94125

95126
try {
96-
await restrictFile.execute(nonExistentFileId, true)
127+
await restrictFile.execute(nonExistentFileId, { restrict: true })
97128
throw new Error('Use case should throw an error')
98129
} catch (error) {
99130
writeError = error as WriteError
@@ -105,4 +136,23 @@ describe('execute', () => {
105136
)
106137
}
107138
})
139+
140+
test('should throw an error when the terms of use is empty while enableAccess is false', async () => {
141+
let caughtError: unknown
142+
try {
143+
const datasetFiles = await getDatasetFiles.execute(testDatasetIds.numericId)
144+
await restrictFile.execute(datasetFiles.files[0].id, {
145+
restrict: true,
146+
enableAccessRequest: false
147+
})
148+
} catch (error) {
149+
caughtError = error
150+
}
151+
152+
expect(caughtError).toBeInstanceOf(WriteError)
153+
expect((caughtError as WriteError).message).toEqual(
154+
new WriteError().message +
155+
' Reason was: [409] Terms of Use and Access are invalid. You must enable request access or add terms of access in datasets with restricted files.'
156+
)
157+
})
108158
})

test/integration/files/FilesRepository.test.ts

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,12 @@ import {
4141
deleteCollectionViaApi,
4242
setStorageDriverViaApi
4343
} from '../../testHelpers/collections/collectionHelper'
44+
import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO'
45+
import { DatasetsRepository } from '../../../src/datasets/infra/repositories/DatasetsRepository'
4446

4547
describe('FilesRepository', () => {
4648
const sut: FilesRepository = new FilesRepository()
49+
const sutDataset: DatasetsRepository = new DatasetsRepository()
4750

4851
let testDatasetIds: CreatedDatasetIdentifiers
4952

@@ -766,13 +769,20 @@ describe('FilesRepository', () => {
766769
describe('restrictFile', () => {
767770
let restrictFileDatasetIds: CreatedDatasetIdentifiers
768771
const testTextFile1Name = 'test-file-1.txt'
772+
const restrictFileDTO: RestrictFileDTO = {
773+
restrict: true,
774+
enableAccessRequest: true,
775+
termsOfAccess: 'This file is restricted for testing purposes'
776+
}
777+
778+
const unrestrictFileDTO: RestrictFileDTO = { restrict: false }
769779

770780
const setFileToRestricted = async (fileId: number) => {
771-
await sut.restrictFile(fileId, true)
781+
await sut.restrictFile(fileId, restrictFileDTO)
772782
}
773783

774784
const setFileToUnrestricted = async (fileId: number) => {
775-
await sut.restrictFile(fileId, false)
785+
await sut.restrictFile(fileId, unrestrictFileDTO)
776786
}
777787

778788
beforeEach(async () => {
@@ -786,11 +796,15 @@ describe('FilesRepository', () => {
786796
})
787797
})
788798

789-
afterEach(async () => {
790-
await deleteUnpublishedDatasetViaApi(restrictFileDatasetIds.numericId)
791-
})
799+
test('should successfully restrict a file enabling access request', async () => {
800+
await publishDatasetViaApi(restrictFileDatasetIds.numericId).catch(() => {
801+
throw new Error('Error while publishing test Dataset')
802+
})
803+
804+
await waitForNoLocks(restrictFileDatasetIds.numericId, 10).catch(() => {
805+
throw new Error('Error while waiting for no locks')
806+
})
792807

793-
test('should successfully restrict a file', async () => {
794808
const datasetFiles = await sut.getDatasetFiles(
795809
restrictFileDatasetIds.numericId,
796810
DatasetNotNumberedVersion.LATEST,
@@ -809,10 +823,22 @@ describe('FilesRepository', () => {
809823
FileOrderCriteria.NAME_AZ
810824
)
811825

812-
expect(datasetFilesAfterRestrict.files[0].restricted).toEqual(true)
826+
expect(datasetFilesAfterRestrict.files[0].restricted).toEqual(restrictFileDTO.restrict)
813827

814-
// Unrestrict the file Just in case to avoid conflicts with other tests
815-
await setFileToUnrestricted(datasetFiles.files[0].id)
828+
const dataset = await sutDataset.getDataset(
829+
restrictFileDatasetIds.numericId,
830+
DatasetNotNumberedVersion.LATEST,
831+
false,
832+
false
833+
)
834+
expect(datasetFilesAfterRestrict.files[0].fileAccessRequest).toEqual(
835+
restrictFileDTO.enableAccessRequest
836+
)
837+
expect(dataset.termsOfUse.termsOfAccess.termsOfAccessForRestrictedFiles).toEqual(
838+
restrictFileDTO.termsOfAccess
839+
)
840+
841+
await deletePublishedDatasetViaApi(restrictFileDatasetIds.persistentId)
816842
})
817843

818844
test('should successfully unrestrict a file', async () => {
@@ -846,6 +872,8 @@ describe('FilesRepository', () => {
846872
)
847873

848874
expect(datasetFilesAfterUnrestrict.files[0].restricted).toEqual(false)
875+
876+
await deleteUnpublishedDatasetViaApi(restrictFileDatasetIds.numericId)
849877
})
850878

851879
test('should return error when file was already restricted', async () => {
@@ -866,6 +894,8 @@ describe('FilesRepository', () => {
866894

867895
// Unrestrict the file Just in case to avoid conflicts with other tests
868896
await setFileToUnrestricted(datasetFiles.files[0].id)
897+
898+
await deleteUnpublishedDatasetViaApi(restrictFileDatasetIds.numericId)
869899
})
870900

871901
test('should return error when files was already unrestricted', async () => {
@@ -881,6 +911,8 @@ describe('FilesRepository', () => {
881911
)
882912

883913
await expect(setFileToUnrestricted(datasetFiles.files[0].id)).rejects.toThrow(expectedError)
914+
915+
await deleteUnpublishedDatasetViaApi(restrictFileDatasetIds.numericId)
884916
})
885917

886918
test('should return error when file does not exist', async () => {
@@ -889,6 +921,30 @@ describe('FilesRepository', () => {
889921
)
890922

891923
await expect(setFileToRestricted(nonExistentFiledId)).rejects.toThrow(expectedError)
924+
925+
await deleteUnpublishedDatasetViaApi(restrictFileDatasetIds.numericId)
926+
})
927+
928+
test('should return error when the terms of use is empty while enableAccess is false', async () => {
929+
const datasetFiles = await sut.getDatasetFiles(
930+
restrictFileDatasetIds.numericId,
931+
DatasetNotNumberedVersion.LATEST,
932+
false,
933+
FileOrderCriteria.NAME_AZ
934+
)
935+
936+
const errorExpected = new WriteError(
937+
`[409] Terms of Use and Access are invalid. You must enable request access or add terms of access in datasets with restricted files.`
938+
)
939+
940+
await expect(
941+
sut.restrictFile(datasetFiles.files[0].id, {
942+
restrict: true,
943+
enableAccessRequest: false
944+
})
945+
).rejects.toThrow(errorExpected)
946+
947+
await deleteUnpublishedDatasetViaApi(restrictFileDatasetIds.numericId)
892948
})
893949
})
894950
})

test/unit/files/RestrictFile.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import { WriteError } from '../../../src'
2+
import { RestrictFileDTO } from '../../../src/files/domain/dtos/RestrictFileDTO'
23
import { IFilesRepository } from '../../../src/files/domain/repositories/IFilesRepository'
34
import { RestrictFile } from '../../../src/files/domain/useCases/RestrictFile'
45

56
describe('execute', () => {
7+
const restrictFileDTO: RestrictFileDTO = {
8+
restrict: true,
9+
enableAccessRequest: true,
10+
termsOfAccess: 'This file is restricted for testing purposes'
11+
}
12+
613
test('should return undefined when repository call is successful', async () => {
714
const filesRepositoryStub: IFilesRepository = {} as IFilesRepository
815
filesRepositoryStub.restrictFile = jest.fn().mockResolvedValue(undefined)
916

1017
const sut = new RestrictFile(filesRepositoryStub)
1118

12-
const actual = await sut.execute(1, true)
19+
const actual = await sut.execute(1, restrictFileDTO)
1320

1421
expect(actual).toEqual(undefined)
1522
})
@@ -20,6 +27,6 @@ describe('execute', () => {
2027

2128
const sut = new RestrictFile(filesRepositoryStub)
2229

23-
await expect(sut.execute(1, true)).rejects.toThrow(WriteError)
30+
await expect(sut.execute(1, restrictFileDTO)).rejects.toThrow(WriteError)
2431
})
2532
})

0 commit comments

Comments
 (0)