From 304d2c1ba4f2db2b0fef35035de574115cb5f300 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 24 Mar 2026 15:59:48 -0400 Subject: [PATCH 1/3] add format as an optional param to submitGuestbook for download use cases --- .../domain/repositories/IAccessRepository.ts | 12 ++- .../SubmitGuestbookForDatafileDownload.ts | 12 ++- .../SubmitGuestbookForDatafilesDownload.ts | 6 +- .../SubmitGuestbookForDatasetDownload.ts | 6 +- ...ubmitGuestbookForDatasetVersionDownload.ts | 6 +- .../infra/repositories/AccessRepository.ts | 28 ++++-- .../access/AccessRepository.test.ts | 11 +++ .../access/SubmitGuestbookDownloads.test.ts | 87 ++++++++++++++++++- 8 files changed, 145 insertions(+), 23 deletions(-) diff --git a/src/access/domain/repositories/IAccessRepository.ts b/src/access/domain/repositories/IAccessRepository.ts index 484842ed..868734e7 100644 --- a/src/access/domain/repositories/IAccessRepository.ts +++ b/src/access/domain/repositories/IAccessRepository.ts @@ -3,22 +3,26 @@ import { GuestbookResponseDTO } from '../dtos/GuestbookResponseDTO' export interface IAccessRepository { submitGuestbookForDatafileDownload( fileId: number | string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise submitGuestbookForDatafilesDownload( fileIds: string | Array, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise submitGuestbookForDatasetDownload( datasetId: number | string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise submitGuestbookForDatasetVersionDownload( datasetId: number | string, versionId: string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise } diff --git a/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts index 357da78f..e5592f33 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts @@ -12,7 +12,15 @@ export class SubmitGuestbookForDatafileDownload implements UseCase { * @param {GuestbookResponseDTO} guestbookResponse - Guestbook response payload. * @returns {Promise} - Signed URL for the download. */ - async execute(fileId: number | string, guestbookResponse: GuestbookResponseDTO): Promise { - return await this.accessRepository.submitGuestbookForDatafileDownload(fileId, guestbookResponse) + async execute( + fileId: number | string, + guestbookResponse: GuestbookResponseDTO, + format?: string + ): Promise { + return await this.accessRepository.submitGuestbookForDatafileDownload( + fileId, + guestbookResponse, + format + ) } } diff --git a/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts index d682da8d..da7e77d9 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts @@ -14,11 +14,13 @@ export class SubmitGuestbookForDatafilesDownload implements UseCase { */ async execute( fileIds: string | Array, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise { return await this.accessRepository.submitGuestbookForDatafilesDownload( fileIds, - guestbookResponse + guestbookResponse, + format ) } } diff --git a/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts index 1ac3113e..13f83494 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts @@ -14,11 +14,13 @@ export class SubmitGuestbookForDatasetDownload implements UseCase { */ async execute( datasetId: number | string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise { return await this.accessRepository.submitGuestbookForDatasetDownload( datasetId, - guestbookResponse + guestbookResponse, + format ) } } diff --git a/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts index 3d811f14..46de63ae 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts @@ -16,12 +16,14 @@ export class SubmitGuestbookForDatasetVersionDownload implements UseCase async execute( datasetId: number | string, versionId: string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise { return await this.accessRepository.submitGuestbookForDatasetVersionDownload( datasetId, versionId, - guestbookResponse + guestbookResponse, + format ) } } diff --git a/src/access/infra/repositories/AccessRepository.ts b/src/access/infra/repositories/AccessRepository.ts index 1d511504..fbb288fd 100644 --- a/src/access/infra/repositories/AccessRepository.ts +++ b/src/access/infra/repositories/AccessRepository.ts @@ -7,10 +7,13 @@ export class AccessRepository extends ApiRepository implements IAccessRepository public async submitGuestbookForDatafileDownload( fileId: number | string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise { const endpoint = this.buildApiEndpoint(`${this.accessResourceName}/datafile`, undefined, fileId) - return this.doPost(endpoint, guestbookResponse, { signed: true }) + const queryParams = format ? { signed: true, format } : { signed: true } + + return this.doPost(endpoint, guestbookResponse, queryParams) .then((response) => { const signedUrl = response.data.data.signedUrl return signedUrl @@ -22,15 +25,18 @@ export class AccessRepository extends ApiRepository implements IAccessRepository public async submitGuestbookForDatafilesDownload( fileIds: Array, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise { + const queryParams = format ? { signed: true, format } : { signed: true } + return this.doPost( this.buildApiEndpoint( this.accessResourceName, `datafiles/${Array.isArray(fileIds) ? fileIds.join(',') : fileIds}` ), guestbookResponse, - { signed: true } + queryParams ) .then((response) => { const signedUrl = response.data.data.signedUrl @@ -43,14 +49,17 @@ export class AccessRepository extends ApiRepository implements IAccessRepository public async submitGuestbookForDatasetDownload( datasetId: number | string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise { const endpoint = this.buildApiEndpoint( `${this.accessResourceName}/dataset`, undefined, datasetId ) - return this.doPost(endpoint, guestbookResponse, { signed: true }) + const queryParams = format ? { signed: true, format } : { signed: true } + + return this.doPost(endpoint, guestbookResponse, queryParams) .then((response) => { const signedUrl = response.data.data.signedUrl return signedUrl @@ -63,14 +72,17 @@ export class AccessRepository extends ApiRepository implements IAccessRepository public async submitGuestbookForDatasetVersionDownload( datasetId: number | string, versionId: string, - guestbookResponse: GuestbookResponseDTO + guestbookResponse: GuestbookResponseDTO, + format?: string ): Promise { const endpoint = this.buildApiEndpoint( `${this.accessResourceName}/dataset`, `versions/${versionId}`, datasetId ) - return this.doPost(endpoint, guestbookResponse, { signed: true }) + const queryParams = format ? { signed: true, format } : { signed: true } + + return this.doPost(endpoint, guestbookResponse, queryParams) .then((response) => { const signedUrl = response.data.data.signedUrl return signedUrl diff --git a/test/integration/access/AccessRepository.test.ts b/test/integration/access/AccessRepository.test.ts index 53f5bdb9..23e10698 100644 --- a/test/integration/access/AccessRepository.test.ts +++ b/test/integration/access/AccessRepository.test.ts @@ -97,6 +97,17 @@ describe('AccessRepository', () => { expect(() => new URL(actual)).not.toThrow() }) + test('should preserve format=tab in signed url for dataset download', async () => { + const actual = await sut.submitGuestbookForDatasetDownload( + testDatasetIds.numericId, + guestbookResponse, + 'original' + ) + const signedUrl = new URL(actual) + + expect(signedUrl.searchParams.get('format')).toEqual('original') + }) + test('should return error when dataset does not exist', async () => { const nonExistentId = 999999999 await expect( diff --git a/test/unit/access/SubmitGuestbookDownloads.test.ts b/test/unit/access/SubmitGuestbookDownloads.test.ts index 850bb17a..c6f49015 100644 --- a/test/unit/access/SubmitGuestbookDownloads.test.ts +++ b/test/unit/access/SubmitGuestbookDownloads.test.ts @@ -22,10 +22,31 @@ describe('access download use cases', () => { const actual = await sut.execute(1, guestbookResponse) - expect(repository.submitGuestbookForDatafileDownload).toHaveBeenCalledWith(1, guestbookResponse) + expect(repository.submitGuestbookForDatafileDownload).toHaveBeenCalledWith( + 1, + guestbookResponse, + undefined + ) expect(actual).toEqual('https://signed.datafile') }) + test('should submit datafile download with format and return signed url', async () => { + const repository: IAccessRepository = {} as IAccessRepository + repository.submitGuestbookForDatafileDownload = jest + .fn() + .mockResolvedValue('https://signed.datafile?format=original') + const sut = new SubmitGuestbookForDatafileDownload(repository) + + const actual = await sut.execute(1, guestbookResponse, 'original') + + expect(repository.submitGuestbookForDatafileDownload).toHaveBeenCalledWith( + 1, + guestbookResponse, + 'original' + ) + expect(actual).toEqual('https://signed.datafile?format=original') + }) + test('should submit datafiles download and return signed url', async () => { const repository: IAccessRepository = {} as IAccessRepository repository.submitGuestbookForDatafilesDownload = jest @@ -37,11 +58,29 @@ describe('access download use cases', () => { expect(repository.submitGuestbookForDatafilesDownload).toHaveBeenCalledWith( [1, 2], - guestbookResponse + guestbookResponse, + undefined ) expect(actual).toEqual('https://signed.datafiles') }) + test('should submit datafiles download with format and return signed url', async () => { + const repository: IAccessRepository = {} as IAccessRepository + repository.submitGuestbookForDatafilesDownload = jest + .fn() + .mockResolvedValue('https://signed.datafiles?format=original') + const sut = new SubmitGuestbookForDatafilesDownload(repository) + + const actual = await sut.execute([1, 2], guestbookResponse, 'original') + + expect(repository.submitGuestbookForDatafilesDownload).toHaveBeenCalledWith( + [1, 2], + guestbookResponse, + 'original' + ) + expect(actual).toEqual('https://signed.datafiles?format=original') + }) + test('should submit dataset download and return signed url', async () => { const repository: IAccessRepository = {} as IAccessRepository repository.submitGuestbookForDatasetDownload = jest @@ -53,11 +92,29 @@ describe('access download use cases', () => { expect(repository.submitGuestbookForDatasetDownload).toHaveBeenCalledWith( 'doi:10.5072/FK2/TEST', - guestbookResponse + guestbookResponse, + undefined ) expect(actual).toEqual('https://signed.dataset') }) + test('should submit dataset download with format and return signed url', async () => { + const repository: IAccessRepository = {} as IAccessRepository + repository.submitGuestbookForDatasetDownload = jest + .fn() + .mockResolvedValue('https://signed.dataset?format=original') + const sut = new SubmitGuestbookForDatasetDownload(repository) + + const actual = await sut.execute('doi:10.5072/FK2/TEST', guestbookResponse, 'original') + + expect(repository.submitGuestbookForDatasetDownload).toHaveBeenCalledWith( + 'doi:10.5072/FK2/TEST', + guestbookResponse, + 'original' + ) + expect(actual).toEqual('https://signed.dataset?format=original') + }) + test('should throw WriteError when dataset version download fails', async () => { const repository: IAccessRepository = {} as IAccessRepository repository.submitGuestbookForDatasetVersionDownload = jest @@ -66,5 +123,29 @@ describe('access download use cases', () => { const sut = new SubmitGuestbookForDatasetVersionDownload(repository) await expect(sut.execute(10, '2.0', guestbookResponse)).rejects.toThrow(WriteError) + expect(repository.submitGuestbookForDatasetVersionDownload).toHaveBeenCalledWith( + 10, + '2.0', + guestbookResponse, + undefined + ) + }) + + test('should submit dataset version download with format and return signed url', async () => { + const repository: IAccessRepository = {} as IAccessRepository + repository.submitGuestbookForDatasetVersionDownload = jest + .fn() + .mockResolvedValue('https://signed.dataset.version?format=original') + const sut = new SubmitGuestbookForDatasetVersionDownload(repository) + + const actual = await sut.execute(10, '2.0', guestbookResponse, 'original') + + expect(repository.submitGuestbookForDatasetVersionDownload).toHaveBeenCalledWith( + 10, + '2.0', + guestbookResponse, + 'original' + ) + expect(actual).toEqual('https://signed.dataset.version?format=original') }) }) From 19b28df0303cf6404b1609128ecccdbfeb7c8dbc Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 24 Mar 2026 16:22:34 -0400 Subject: [PATCH 2/3] fix test description, update use case documentation --- docs/useCases.md | 101 +++++++++++------- .../access/AccessRepository.test.ts | 2 +- 2 files changed, 62 insertions(+), 41 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index 93aa6b34..0c33b290 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -3018,14 +3018,18 @@ Submits guestbook answers for a datafile and returns a signed URL. import { submitGuestbookForDatafileDownload } from '@iqss/dataverse-client-javascript' submitGuestbookForDatafileDownload - .execute(10, { - guestbookResponse: { - answers: [ - { id: 123, value: 'Good' }, - { id: 124, value: ['Multi', 'Line'] } - ] - } - }) + .execute( + 10, + { + guestbookResponse: { + answers: [ + { id: 123, value: 'Good' }, + { id: 124, value: ['Multi', 'Line'] } + ] + } + }, + 'original' + ) .then((signedUrl: string) => { /* ... */ }) @@ -3043,15 +3047,19 @@ Submits guestbook answers for multiple files and returns a signed URL. import { submitGuestbookForDatafilesDownload } from '@iqss/dataverse-client-javascript' submitGuestbookForDatafilesDownload - .execute([10, 11], { - guestbookResponse: { - answers: [ - { id: 123, value: 'Good' }, - { id: 124, value: ['Multi', 'Line'] }, - { id: 125, value: 'Yellow' } - ] - } - }) + .execute( + [10, 11], + { + guestbookResponse: { + answers: [ + { id: 123, value: 'Good' }, + { id: 124, value: ['Multi', 'Line'] }, + { id: 125, value: 'Yellow' } + ] + } + }, + 'original' + ) .then((signedUrl: string) => { /* ... */ }) @@ -3069,15 +3077,19 @@ Submits guestbook answers for dataset download and returns a signed URL. import { submitGuestbookForDatasetDownload } from '@iqss/dataverse-client-javascript' submitGuestbookForDatasetDownload - .execute('doi:10.5072/FK2/XXXXXX', { - guestbookResponse: { - answers: [ - { id: 123, value: 'Good' }, - { id: 124, value: ['Multi', 'Line'] }, - { id: 125, value: 'Yellow' } - ] - } - }) + .execute( + 'doi:10.5072/FK2/XXXXXX', + { + guestbookResponse: { + answers: [ + { id: 123, value: 'Good' }, + { id: 124, value: ['Multi', 'Line'] }, + { id: 125, value: 'Yellow' } + ] + } + }, + 'original' + ) .then((signedUrl: string) => { /* ... */ }) @@ -3095,19 +3107,24 @@ Submits guestbook answers for a specific dataset version and returns a signed UR import { submitGuestbookForDatasetVersionDownload } from '@iqss/dataverse-client-javascript' submitGuestbookForDatasetVersionDownload - .execute(10, ':latest', { - guestbookResponse: { - name: 'Jane Doe', - email: 'jane@example.org', - institution: 'Example University', - position: 'Researcher', - answers: [ - { id: 123, value: 'Good' }, - { id: 124, value: ['Multi', 'Line'] }, - { id: 125, value: 'Yellow' } - ] - } - }) + .execute( + 10, + ':latest', + { + guestbookResponse: { + name: 'Jane Doe', + email: 'jane@example.org', + institution: 'Example University', + position: 'Researcher', + answers: [ + { id: 123, value: 'Good' }, + { id: 124, value: ['Multi', 'Line'] }, + { id: 125, value: 'Yellow' } + ] + } + }, + 'original' + ) .then((signedUrl: string) => { /* ... */ }) @@ -3119,4 +3136,8 @@ The `datasetId` parameter can be a string, for persistent identifiers, or a numb The `versionId` parameter accepts a numbered version such as `'1.0'` or a non-numbered version such as `':latest'`. -The third parameter must match [GuestbookResponseDTO](../src/access/domain/dtos/GuestbookResponseDTO.ts). The resolved value is a signed download URL as a string. +The `guestbookResponse` parameter must match [GuestbookResponseDTO](../src/access/domain/dtos/GuestbookResponseDTO.ts). + +The optional `format` parameter is sent as a query parameter on the download endpoint. For example, pass `'original'` to request the original dataset or file format. + +The resolved value is a signed download URL as a string. diff --git a/test/integration/access/AccessRepository.test.ts b/test/integration/access/AccessRepository.test.ts index 23e10698..639b23e9 100644 --- a/test/integration/access/AccessRepository.test.ts +++ b/test/integration/access/AccessRepository.test.ts @@ -97,7 +97,7 @@ describe('AccessRepository', () => { expect(() => new URL(actual)).not.toThrow() }) - test('should preserve format=tab in signed url for dataset download', async () => { + test('should preserve format=original in signed url for dataset download', async () => { const actual = await sut.submitGuestbookForDatasetDownload( testDatasetIds.numericId, guestbookResponse, From 0c8eb793716af182a0b644c6adf44b75d5a908d5 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Tue, 24 Mar 2026 16:27:52 -0400 Subject: [PATCH 3/3] added JSDoc @param for fomat in use cases --- src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts | 1 + .../domain/useCases/SubmitGuestbookForDatafilesDownload.ts | 1 + src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts | 1 + .../domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts index e5592f33..c6234e1d 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatafileDownload.ts @@ -10,6 +10,7 @@ export class SubmitGuestbookForDatafileDownload implements UseCase { * * @param {number | string} fileId - Datafile identifier (numeric id or persistent id). * @param {GuestbookResponseDTO} guestbookResponse - Guestbook response payload. + * @param {string} [format] - Optional download format passed as a query parameter. * @returns {Promise} - Signed URL for the download. */ async execute( diff --git a/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts index da7e77d9..f8788e4c 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatafilesDownload.ts @@ -10,6 +10,7 @@ export class SubmitGuestbookForDatafilesDownload implements UseCase { * * @param {string | Array} fileIds - Comma-separated string or array of file ids. * @param {GuestbookResponseDTO} guestbookResponse - Guestbook response payload. + * @param {string} [format] - Optional download format passed as a query parameter. * @returns {Promise} - Signed URL for the download. */ async execute( diff --git a/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts index 13f83494..b70c31c5 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatasetDownload.ts @@ -10,6 +10,7 @@ export class SubmitGuestbookForDatasetDownload implements UseCase { * * @param {number | string} datasetId - Dataset identifier (numeric id or persistent id). * @param {GuestbookResponseDTO} guestbookResponse - Guestbook response payload. + * @param {string} [format] - Optional download format passed as a query parameter. * @returns {Promise} - Signed URL for the download. */ async execute( diff --git a/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts b/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts index 46de63ae..fee73048 100644 --- a/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts +++ b/src/access/domain/useCases/SubmitGuestbookForDatasetVersionDownload.ts @@ -11,6 +11,7 @@ export class SubmitGuestbookForDatasetVersionDownload implements UseCase * @param {number | string} datasetId - Dataset identifier (numeric id or persistent id). * @param {string} versionId - Dataset version identifier (for example, ':latest' or '1.0'). * @param {GuestbookResponseDTO} guestbookResponse - Guestbook response payload. + * @param {string} [format] - Optional download format passed as a query parameter. * @returns {Promise} - Signed URL for the download. */ async execute(