From 35ce9171bdbde0116170be6195751c877f3c9581 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 17 Dec 2024 14:13:07 -0500 Subject: [PATCH 1/8] feat: add use case to submit feedback to contacts --- docs/useCases.md | 22 +++++ src/contactInfo/domain/dtos/ContactDTO.ts | 6 ++ src/contactInfo/domain/models/Contact.ts | 6 ++ .../domain/repositories/IContactRepository.ts | 6 ++ .../domain/useCases/SubmitContactInfo.ts | 25 ++++++ src/contactInfo/index.ts | 9 +++ .../infra/repositories/ContactRepository.ts | 24 ++++++ src/index.ts | 1 + .../contact/SubmitContactInfo.test.ts | 80 +++++++++++++++++++ .../contact/ContactRepository.test.ts | 73 +++++++++++++++++ test/unit/contact/SubmitContactInfo.test.ts | 74 +++++++++++++++++ 11 files changed, 326 insertions(+) create mode 100644 src/contactInfo/domain/dtos/ContactDTO.ts create mode 100644 src/contactInfo/domain/models/Contact.ts create mode 100644 src/contactInfo/domain/repositories/IContactRepository.ts create mode 100644 src/contactInfo/domain/useCases/SubmitContactInfo.ts create mode 100644 src/contactInfo/index.ts create mode 100644 src/contactInfo/infra/repositories/ContactRepository.ts create mode 100644 test/functional/contact/SubmitContactInfo.test.ts create mode 100644 test/integration/contact/ContactRepository.test.ts create mode 100644 test/unit/contact/SubmitContactInfo.test.ts diff --git a/docs/useCases.md b/docs/useCases.md index 8a3bd8fc..fcf40b24 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -63,6 +63,8 @@ The different use cases currently available in the package are classified below, - [Get Dataverse Backend Version](#get-dataverse-backend-version) - [Get Maximum Embargo Duration In Months](#get-maximum-embargo-duration-in-months) - [Get ZIP Download Limit](#get-zip-download-limit) +- [Contact](#Contact) + - [Send Feedback to Object Contacts](#send-feedback-to-object-contacts) ## Collections @@ -1363,3 +1365,23 @@ getZipDownloadLimit.execute().then((downloadLimit: number) => { ``` _See [use case](../src/info/domain/useCases/GetZipDownloadLimit.ts) implementation_. + +##Contact + +#### Send Feedback to Object Contacts + +Returns a [Contact](../src/contactInfo/domain/models/Contact.ts) object, which contains contact return information, showing toEmail, fromEmail, subject, body. + +##### Example call: + +```typescript +import { submitContactInfo } from '@iqss/dataverse-client-javascript' + +/* ... */ + +submitContactInfo.execute((contact: ContactDTO)).then( => { +/* ... */ +}) + +/* ... */ +``` diff --git a/src/contactInfo/domain/dtos/ContactDTO.ts b/src/contactInfo/domain/dtos/ContactDTO.ts new file mode 100644 index 00000000..5b759e3d --- /dev/null +++ b/src/contactInfo/domain/dtos/ContactDTO.ts @@ -0,0 +1,6 @@ +export interface ContactDTO { + targetId: number + subject: string + body: string + fromEmail: string +} diff --git a/src/contactInfo/domain/models/Contact.ts b/src/contactInfo/domain/models/Contact.ts new file mode 100644 index 00000000..06435840 --- /dev/null +++ b/src/contactInfo/domain/models/Contact.ts @@ -0,0 +1,6 @@ +export interface Contact { + fromEmail: string + toEmail: string + body: string + subject: string +} diff --git a/src/contactInfo/domain/repositories/IContactRepository.ts b/src/contactInfo/domain/repositories/IContactRepository.ts new file mode 100644 index 00000000..c56d2bbc --- /dev/null +++ b/src/contactInfo/domain/repositories/IContactRepository.ts @@ -0,0 +1,6 @@ +import { Contact } from '../models/Contact' +import { ContactDTO } from '../dtos/ContactDTO' + +export interface IContactRepository { + submitContactInfo(contactDTO: ContactDTO): Promise +} diff --git a/src/contactInfo/domain/useCases/SubmitContactInfo.ts b/src/contactInfo/domain/useCases/SubmitContactInfo.ts new file mode 100644 index 00000000..70c2e453 --- /dev/null +++ b/src/contactInfo/domain/useCases/SubmitContactInfo.ts @@ -0,0 +1,25 @@ +import { UseCase } from '../../../core/domain/useCases/UseCase' +import { ContactDTO } from '../dtos/ContactDTO' +import { Contact } from '../models/Contact' +import { IContactRepository } from '../repositories/IContactRepository' + +export class SubmitContactInfo implements UseCase { + private contactRepository: IContactRepository + + constructor(contactRepository: IContactRepository) { + this.contactRepository = contactRepository + } + /** + * Submits contact information and returns a DTO containing the submitted data. + * + * @param {ContactDTO} contactDTO - The contact information to be submitted. + * @returns {Promise} A promise resolving to a ContactDTO. + */ + async execute(contactDTO: ContactDTO): Promise { + try { + return await this.contactRepository.submitContactInfo(contactDTO) + } catch (error) { + throw new Error(`${error.message}`) + } + } +} diff --git a/src/contactInfo/index.ts b/src/contactInfo/index.ts new file mode 100644 index 00000000..a34eb082 --- /dev/null +++ b/src/contactInfo/index.ts @@ -0,0 +1,9 @@ +import { SubmitContactInfo } from './domain/useCases/SubmitContactInfo' +import { ContactRepository } from './infra/repositories/ContactRepository' + +const contactRepository = new ContactRepository() +const submitContactInfo = new SubmitContactInfo(contactRepository) + +export { submitContactInfo } +export { Contact } from './domain/models/Contact' +export { ContactDTO } from './domain/dtos/ContactDTO' diff --git a/src/contactInfo/infra/repositories/ContactRepository.ts b/src/contactInfo/infra/repositories/ContactRepository.ts new file mode 100644 index 00000000..12d87510 --- /dev/null +++ b/src/contactInfo/infra/repositories/ContactRepository.ts @@ -0,0 +1,24 @@ +import { ApiRepository } from '../../../core/infra/repositories/ApiRepository' +import { Contact } from '../../domain/models/Contact' +import { IContactRepository } from '../../domain/repositories/IContactRepository' +import { ContactDTO } from '../../domain/dtos/ContactDTO' + +export class ContactRepository extends ApiRepository implements IContactRepository { + public async submitContactInfo(contactDTO: ContactDTO): Promise { + return this.doPost(`/admin/feedback`, contactDTO) + .then((response) => { + const responseData = response.data + const contact: Contact[] = responseData.data.map((item: Contact) => ({ + fromEmail: item.fromEmail, + toEmail: item.toEmail, + subject: item.subject, + body: item.body + })) + + return contact + }) + .catch((error) => { + throw error + }) + } +} diff --git a/src/index.ts b/src/index.ts index 63562fda..2abdf089 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,3 +6,4 @@ export * from './datasets' export * from './collections' export * from './metadataBlocks' export * from './files' +export * from './contactInfo' diff --git a/test/functional/contact/SubmitContactInfo.test.ts b/test/functional/contact/SubmitContactInfo.test.ts new file mode 100644 index 00000000..4b2a08eb --- /dev/null +++ b/test/functional/contact/SubmitContactInfo.test.ts @@ -0,0 +1,80 @@ +import { ApiConfig, submitContactInfo, ContactDTO, WriteError } from '../../../src' +import { TestConstants } from '../../testHelpers/TestConstants' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' + +describe('submitContactInfo', () => { + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + test('should return success result on repository success', async () => { + const subject = 'Data Question' + const fromEmail = '1314@gmail.com' + const collectionAlias = 'collection-1' + const collectionEmail = 'pi@example.edu,student@example.edu' + const baseUrl = 'http://localhost:8080/dataverse/' + + const contactDTO: ContactDTO = { + targetId: 6, + subject: subject, + body: 'Please help me understand your data. Thank you!', + fromEmail: fromEmail + } + + const bodyMessage = + 'You have just been sent the following message from ' + + fromEmail + + ' via the Root hosted dataverse named "' + + collectionAlias + + '":\n' + + '\n' + + '---\n' + + '\n' + + 'Please help me understand your data. Thank you!\n' + + '\n' + + '---\n' + + '\n' + + 'Root Support\n' + + 'null\n' + + '\n' + + 'Go to dataverse ' + + baseUrl + + collectionAlias + + '\n' + + '\n' + + 'You received this email because you have been listed as a contact for the dataverse. If you believe this was an error, please contact Root Support at null. To respond directly to the individual who sent the message, simply reply to this email.' + + const expectedResponse = [ + { + fromEmail: fromEmail, + toEmail: collectionEmail, + subject: 'Root contact: ' + subject, + body: bodyMessage + } + ] + + let contactInfo + try { + contactInfo = await submitContactInfo.execute(contactDTO) + } catch (error) { + throw new Error('Contact info should be submitted') + } finally { + expect(contactInfo).toEqual(expectedResponse) + } + }) + + test('should return error if the target id is unexisted', async () => { + const contactDTO: ContactDTO = { + targetId: 0, // non-existent target id + subject: '', + body: '', + fromEmail: '1314@gmail.com' + } + const expectedError = new WriteError(`[400] Feedback target object not found`) + await expect(submitContactInfo.execute(contactDTO)).rejects.toThrow(expectedError) + }) +}) diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts new file mode 100644 index 00000000..e47fbfed --- /dev/null +++ b/test/integration/contact/ContactRepository.test.ts @@ -0,0 +1,73 @@ +import { ContactRepository } from '../../../src/contactInfo/infra/repositories/ContactRepository' +import { ApiConfig, Contact, ContactDTO, WriteError } from '../../../src' +import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' +import { TestConstants } from '../../testHelpers/TestConstants' + +describe('submitContactInfo', () => { + beforeAll(async () => { + ApiConfig.init( + TestConstants.TEST_API_URL, + DataverseApiAuthMechanism.API_KEY, + process.env.TEST_API_KEY + ) + }) + + const testContactDTO: ContactDTO = { + targetId: 6, + subject: 'Data Question', + body: 'Please help me understand your data. Thank you!', + fromEmail: '1314@gmail.com' + } + + const sut: ContactRepository = new ContactRepository() + + test('should return ContactDTO when contact info is successfully submitted', async () => { + const collectionAlias = 'collection-1' + const collectionEmail = 'pi@example.edu,student@example.edu' + const baseUrl = 'http://localhost:8080/dataverse/' + const bodyMessage = + 'You have just been sent the following message from ' + + testContactDTO.fromEmail + + ' via the Root hosted dataverse named "' + + collectionAlias + + '":\n' + + '\n' + + '---\n' + + '\n' + + 'Please help me understand your data. Thank you!\n' + + '\n' + + '---\n' + + '\n' + + 'Root Support\n' + + 'null\n' + + '\n' + + 'Go to dataverse ' + + baseUrl + + collectionAlias + + '\n' + + '\n' + + 'You received this email because you have been listed as a contact for the dataverse. If you believe this was an error, please contact Root Support at null. To respond directly to the individual who sent the message, simply reply to this email.' + + const expectedResponse: Contact[] = [ + { + fromEmail: testContactDTO.fromEmail, + toEmail: collectionEmail, + subject: 'Root contact: ' + testContactDTO.subject, + body: bodyMessage + } + ] + const actual = await sut.submitContactInfo(testContactDTO) + expect(actual).toEqual(expectedResponse) + }) + + test('should return error if the target id is unexisted', async () => { + const invalidContactDTO: ContactDTO = { + targetId: 0, + subject: '', + body: '', + fromEmail: '' + } + const expectedError = new WriteError(`[400] Feedback target object not found`) + await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError) + }) +}) diff --git a/test/unit/contact/SubmitContactInfo.test.ts b/test/unit/contact/SubmitContactInfo.test.ts new file mode 100644 index 00000000..224a9e7b --- /dev/null +++ b/test/unit/contact/SubmitContactInfo.test.ts @@ -0,0 +1,74 @@ +import { WriteError, Contact, ContactDTO } from '../../../src' +import { SubmitContactInfo } from '../../../src/contactInfo/domain/useCases/SubmitContactInfo' +import { IContactRepository } from '../../../src/contactInfo/domain/repositories/IContactRepository' + +describe('execute', () => { + test('should return a ContactDTO when repository call is successful', async () => { + const fromEmail = '1314@gmail.com' + + const contactDTO: ContactDTO = { + targetId: 6, + subject: 'Data Question', + body: 'Please help me understand your data. Thank you!', + fromEmail: fromEmail + } + + const collectionAlias = 'collection-1' + const collectionEmail = 'pi@example.edu,student@example.edu' + const baseUrl = 'http://localhost:8080/dataverse/' + const bodyMessage = + 'You have just been sent the following message from ' + + fromEmail + + ' via the Root hosted dataverse named "' + + collectionAlias + + '":\n' + + '\n' + + '---\n' + + '\n' + + 'Please help me understand your data. Thank you!\n' + + '\n' + + '---\n' + + '\n' + + 'Root Support\n' + + 'null\n' + + '\n' + + 'Go to dataverse ' + + baseUrl + + collectionAlias + + '\n' + + '\n' + + 'You received this email because you have been listed as a contact for the dataverse. If you believe this was an error, please contact Root Support at null. To respond directly to the individual who sent the message, simply reply to this email.' + + const expectedResponse: Contact[] = [ + { + fromEmail: contactDTO.fromEmail, + toEmail: collectionEmail, + subject: 'Root contact: ' + contactDTO.subject, + body: bodyMessage + } + ] + + const contactRepositoryStub = {} + + contactRepositoryStub.submitContactInfo = jest.fn().mockResolvedValue(expectedResponse) + const sut = new SubmitContactInfo(contactRepositoryStub) + const actual = await sut.execute(contactDTO) + expect(actual).toEqual(expectedResponse) + expect(contactRepositoryStub.submitContactInfo).toHaveBeenCalledWith(contactDTO) + }) + + test('should return error result on error response', async () => { + const contactDTO: ContactDTO = { + targetId: 0, + subject: '', + body: '', + fromEmail: '' + } + const contactRepositoryStub = {} + const error = new WriteError(`[400] Feedback target object not found`) + contactRepositoryStub.submitContactInfo = jest.fn().mockRejectedValue(error) + const sut = new SubmitContactInfo(contactRepositoryStub) + await expect(sut.execute(contactDTO)).rejects.toThrow(error) + expect(contactRepositoryStub.submitContactInfo).toHaveBeenCalledWith(contactDTO) + }) +}) From 50590eb2688db6e7d0516a38f0aaacf596f6e9c4 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 17 Dec 2024 16:35:17 -0500 Subject: [PATCH 2/8] fix: fix test cases --- docs/useCases.md | 2 +- .../contact/SubmitContactInfo.test.ts | 44 +++---------------- .../contact/ContactRepository.test.ts | 43 +++--------------- 3 files changed, 15 insertions(+), 74 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index fcf40b24..b415f270 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1366,7 +1366,7 @@ getZipDownloadLimit.execute().then((downloadLimit: number) => { _See [use case](../src/info/domain/useCases/GetZipDownloadLimit.ts) implementation_. -##Contact +## Contact #### Send Feedback to Object Contacts diff --git a/test/functional/contact/SubmitContactInfo.test.ts b/test/functional/contact/SubmitContactInfo.test.ts index 4b2a08eb..2c505589 100644 --- a/test/functional/contact/SubmitContactInfo.test.ts +++ b/test/functional/contact/SubmitContactInfo.test.ts @@ -14,9 +14,6 @@ describe('submitContactInfo', () => { test('should return success result on repository success', async () => { const subject = 'Data Question' const fromEmail = '1314@gmail.com' - const collectionAlias = 'collection-1' - const collectionEmail = 'pi@example.edu,student@example.edu' - const baseUrl = 'http://localhost:8080/dataverse/' const contactDTO: ContactDTO = { targetId: 6, @@ -25,51 +22,24 @@ describe('submitContactInfo', () => { fromEmail: fromEmail } - const bodyMessage = - 'You have just been sent the following message from ' + - fromEmail + - ' via the Root hosted dataverse named "' + - collectionAlias + - '":\n' + - '\n' + - '---\n' + - '\n' + - 'Please help me understand your data. Thank you!\n' + - '\n' + - '---\n' + - '\n' + - 'Root Support\n' + - 'null\n' + - '\n' + - 'Go to dataverse ' + - baseUrl + - collectionAlias + - '\n' + - '\n' + - 'You received this email because you have been listed as a contact for the dataverse. If you believe this was an error, please contact Root Support at null. To respond directly to the individual who sent the message, simply reply to this email.' - - const expectedResponse = [ - { - fromEmail: fromEmail, - toEmail: collectionEmail, - subject: 'Root contact: ' + subject, - body: bodyMessage - } - ] - let contactInfo try { contactInfo = await submitContactInfo.execute(contactDTO) + console.log('contactInfo:', contactInfo) } catch (error) { throw new Error('Contact info should be submitted') } finally { - expect(contactInfo).toEqual(expectedResponse) + expect(contactInfo).toBeDefined() + expect(contactInfo[0].fromEmail).toEqual(fromEmail) + expect(contactInfo[0].subject).toBeDefined() + expect(contactInfo[0].body).toBeDefined() + expect(contactInfo[0].toEmail).toBeDefined() } }) test('should return error if the target id is unexisted', async () => { const contactDTO: ContactDTO = { - targetId: 0, // non-existent target id + targetId: 0, subject: '', body: '', fromEmail: '1314@gmail.com' diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts index e47fbfed..32a692ae 100644 --- a/test/integration/contact/ContactRepository.test.ts +++ b/test/integration/contact/ContactRepository.test.ts @@ -1,5 +1,5 @@ import { ContactRepository } from '../../../src/contactInfo/infra/repositories/ContactRepository' -import { ApiConfig, Contact, ContactDTO, WriteError } from '../../../src' +import { ApiConfig, ContactDTO, WriteError } from '../../../src' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' import { TestConstants } from '../../testHelpers/TestConstants' @@ -22,42 +22,13 @@ describe('submitContactInfo', () => { const sut: ContactRepository = new ContactRepository() test('should return ContactDTO when contact info is successfully submitted', async () => { - const collectionAlias = 'collection-1' - const collectionEmail = 'pi@example.edu,student@example.edu' - const baseUrl = 'http://localhost:8080/dataverse/' - const bodyMessage = - 'You have just been sent the following message from ' + - testContactDTO.fromEmail + - ' via the Root hosted dataverse named "' + - collectionAlias + - '":\n' + - '\n' + - '---\n' + - '\n' + - 'Please help me understand your data. Thank you!\n' + - '\n' + - '---\n' + - '\n' + - 'Root Support\n' + - 'null\n' + - '\n' + - 'Go to dataverse ' + - baseUrl + - collectionAlias + - '\n' + - '\n' + - 'You received this email because you have been listed as a contact for the dataverse. If you believe this was an error, please contact Root Support at null. To respond directly to the individual who sent the message, simply reply to this email.' + const contactInfo = await sut.submitContactInfo(testContactDTO) - const expectedResponse: Contact[] = [ - { - fromEmail: testContactDTO.fromEmail, - toEmail: collectionEmail, - subject: 'Root contact: ' + testContactDTO.subject, - body: bodyMessage - } - ] - const actual = await sut.submitContactInfo(testContactDTO) - expect(actual).toEqual(expectedResponse) + expect(contactInfo).toBeDefined() + expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail) + expect(contactInfo[0].subject).toBeDefined() + expect(contactInfo[0].body).toBeDefined() + expect(contactInfo[0].toEmail).toBeDefined() }) test('should return error if the target id is unexisted', async () => { From 8728186a2d7e138cfbceaddd1a9ac7389a136afe Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 17 Dec 2024 22:22:13 -0500 Subject: [PATCH 3/8] fix: fix test problem --- .../contact/SubmitContactInfo.test.ts | 8 ++-- .../contact/ContactRepository.test.ts | 40 ++++++++++--------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/test/functional/contact/SubmitContactInfo.test.ts b/test/functional/contact/SubmitContactInfo.test.ts index 2c505589..973c8d80 100644 --- a/test/functional/contact/SubmitContactInfo.test.ts +++ b/test/functional/contact/SubmitContactInfo.test.ts @@ -16,7 +16,7 @@ describe('submitContactInfo', () => { const fromEmail = '1314@gmail.com' const contactDTO: ContactDTO = { - targetId: 6, + targetId: 1, subject: subject, body: 'Please help me understand your data. Thank you!', fromEmail: fromEmail @@ -31,9 +31,9 @@ describe('submitContactInfo', () => { } finally { expect(contactInfo).toBeDefined() expect(contactInfo[0].fromEmail).toEqual(fromEmail) - expect(contactInfo[0].subject).toBeDefined() - expect(contactInfo[0].body).toBeDefined() - expect(contactInfo[0].toEmail).toBeDefined() + expect(contactInfo[0].subject).toEqual(expect.any(String)) + expect(contactInfo[0].body).toEqual(expect.any(String)) + expect(contactInfo[0].toEmail).toEqual(expect.any(String)) } }) diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts index 32a692ae..2a1d1cd4 100644 --- a/test/integration/contact/ContactRepository.test.ts +++ b/test/integration/contact/ContactRepository.test.ts @@ -13,7 +13,7 @@ describe('submitContactInfo', () => { }) const testContactDTO: ContactDTO = { - targetId: 6, + targetId: 1, subject: 'Data Question', body: 'Please help me understand your data. Thank you!', fromEmail: '1314@gmail.com' @@ -22,23 +22,27 @@ describe('submitContactInfo', () => { const sut: ContactRepository = new ContactRepository() test('should return ContactDTO when contact info is successfully submitted', async () => { - const contactInfo = await sut.submitContactInfo(testContactDTO) - - expect(contactInfo).toBeDefined() - expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail) - expect(contactInfo[0].subject).toBeDefined() - expect(contactInfo[0].body).toBeDefined() - expect(contactInfo[0].toEmail).toBeDefined() - }) - - test('should return error if the target id is unexisted', async () => { - const invalidContactDTO: ContactDTO = { - targetId: 0, - subject: '', - body: '', - fromEmail: '' + try { + const contactInfo = await sut.submitContactInfo(testContactDTO) + expect(contactInfo).toBeDefined() + expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail) + expect(contactInfo[0].subject).toEqual(expect.any(String)) + expect(contactInfo[0].body).toEqual(expect.any(String)) + expect(contactInfo[0].toEmail).toEqual(expect.any(String)) + } catch (error) { + console.error('Error during submission:', error.message, error.response?.data) + throw error } - const expectedError = new WriteError(`[400] Feedback target object not found`) - await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError) + + test('should return error if the target id is unexisted', async () => { + const invalidContactDTO: ContactDTO = { + targetId: 0, + subject: '', + body: '', + fromEmail: '' + } + const expectedError = new WriteError(`[400] Feedback target object not found`) + await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError) + }) }) }) From 8f606e3a78ab51c739ef02c1f882d2d1ef6c5453 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 17 Dec 2024 22:28:45 -0500 Subject: [PATCH 4/8] fix: fix test problem --- .../contact/SubmitContactInfo.test.ts | 1 - .../contact/ContactRepository.test.ts | 20 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/functional/contact/SubmitContactInfo.test.ts b/test/functional/contact/SubmitContactInfo.test.ts index 973c8d80..b4cf79a4 100644 --- a/test/functional/contact/SubmitContactInfo.test.ts +++ b/test/functional/contact/SubmitContactInfo.test.ts @@ -25,7 +25,6 @@ describe('submitContactInfo', () => { let contactInfo try { contactInfo = await submitContactInfo.execute(contactDTO) - console.log('contactInfo:', contactInfo) } catch (error) { throw new Error('Contact info should be submitted') } finally { diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts index 2a1d1cd4..24f86c22 100644 --- a/test/integration/contact/ContactRepository.test.ts +++ b/test/integration/contact/ContactRepository.test.ts @@ -33,16 +33,16 @@ describe('submitContactInfo', () => { console.error('Error during submission:', error.message, error.response?.data) throw error } + }) - test('should return error if the target id is unexisted', async () => { - const invalidContactDTO: ContactDTO = { - targetId: 0, - subject: '', - body: '', - fromEmail: '' - } - const expectedError = new WriteError(`[400] Feedback target object not found`) - await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError) - }) + test('should return error if the target id is unexisted', async () => { + const invalidContactDTO: ContactDTO = { + targetId: 0, + subject: '', + body: '', + fromEmail: '' + } + const expectedError = new WriteError(`[400] Feedback target object not found`) + await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError) }) }) From 25e7f52af620e9beae57af0aa3ef56aa8bc4a1bc Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 17 Dec 2024 23:04:42 -0500 Subject: [PATCH 5/8] chore: solve some chore problems, make the code prettier --- .../domain/useCases/SubmitContactInfo.ts | 2 ++ .../contact/SubmitContactInfo.test.ts | 9 +++------ .../contact/ContactRepository.test.ts | 18 +++++++----------- test/unit/contact/SubmitContactInfo.test.ts | 2 +- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/contactInfo/domain/useCases/SubmitContactInfo.ts b/src/contactInfo/domain/useCases/SubmitContactInfo.ts index 70c2e453..d8dfaf6c 100644 --- a/src/contactInfo/domain/useCases/SubmitContactInfo.ts +++ b/src/contactInfo/domain/useCases/SubmitContactInfo.ts @@ -9,12 +9,14 @@ export class SubmitContactInfo implements UseCase { constructor(contactRepository: IContactRepository) { this.contactRepository = contactRepository } + /** * Submits contact information and returns a DTO containing the submitted data. * * @param {ContactDTO} contactDTO - The contact information to be submitted. * @returns {Promise} A promise resolving to a ContactDTO. */ + async execute(contactDTO: ContactDTO): Promise { try { return await this.contactRepository.submitContactInfo(contactDTO) diff --git a/test/functional/contact/SubmitContactInfo.test.ts b/test/functional/contact/SubmitContactInfo.test.ts index b4cf79a4..1fdbeb47 100644 --- a/test/functional/contact/SubmitContactInfo.test.ts +++ b/test/functional/contact/SubmitContactInfo.test.ts @@ -12,14 +12,11 @@ describe('submitContactInfo', () => { }) test('should return success result on repository success', async () => { - const subject = 'Data Question' - const fromEmail = '1314@gmail.com' - const contactDTO: ContactDTO = { targetId: 1, - subject: subject, + subject: 'Data Question', body: 'Please help me understand your data. Thank you!', - fromEmail: fromEmail + fromEmail: '1314@gmail.com' } let contactInfo @@ -29,7 +26,7 @@ describe('submitContactInfo', () => { throw new Error('Contact info should be submitted') } finally { expect(contactInfo).toBeDefined() - expect(contactInfo[0].fromEmail).toEqual(fromEmail) + expect(contactInfo[0].fromEmail).toEqual('1314@gmail.com') expect(contactInfo[0].subject).toEqual(expect.any(String)) expect(contactInfo[0].body).toEqual(expect.any(String)) expect(contactInfo[0].toEmail).toEqual(expect.any(String)) diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts index 24f86c22..29a699e3 100644 --- a/test/integration/contact/ContactRepository.test.ts +++ b/test/integration/contact/ContactRepository.test.ts @@ -22,17 +22,13 @@ describe('submitContactInfo', () => { const sut: ContactRepository = new ContactRepository() test('should return ContactDTO when contact info is successfully submitted', async () => { - try { - const contactInfo = await sut.submitContactInfo(testContactDTO) - expect(contactInfo).toBeDefined() - expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail) - expect(contactInfo[0].subject).toEqual(expect.any(String)) - expect(contactInfo[0].body).toEqual(expect.any(String)) - expect(contactInfo[0].toEmail).toEqual(expect.any(String)) - } catch (error) { - console.error('Error during submission:', error.message, error.response?.data) - throw error - } + const contactInfo = await sut.submitContactInfo(testContactDTO) + + expect(contactInfo).toBeDefined() + expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail) + expect(contactInfo[0].subject).toEqual(expect.any(String)) + expect(contactInfo[0].body).toEqual(expect.any(String)) + expect(contactInfo[0].toEmail).toEqual(expect.any(String)) }) test('should return error if the target id is unexisted', async () => { diff --git a/test/unit/contact/SubmitContactInfo.test.ts b/test/unit/contact/SubmitContactInfo.test.ts index 224a9e7b..aec53404 100644 --- a/test/unit/contact/SubmitContactInfo.test.ts +++ b/test/unit/contact/SubmitContactInfo.test.ts @@ -2,7 +2,7 @@ import { WriteError, Contact, ContactDTO } from '../../../src' import { SubmitContactInfo } from '../../../src/contactInfo/domain/useCases/SubmitContactInfo' import { IContactRepository } from '../../../src/contactInfo/domain/repositories/IContactRepository' -describe('execute', () => { +describe('execute submit information to contacts', () => { test('should return a ContactDTO when repository call is successful', async () => { const fromEmail = '1314@gmail.com' From e4fdc92d0cf80eda1cce541673ea6fc01be39b5c Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 6 Jan 2025 11:15:23 -0500 Subject: [PATCH 6/8] fix: typos and confusion between contact and contactDTO --- src/contactInfo/domain/useCases/SubmitContactInfo.ts | 10 +++------- test/integration/contact/ContactRepository.test.ts | 2 +- test/unit/contact/SubmitContactInfo.test.ts | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/contactInfo/domain/useCases/SubmitContactInfo.ts b/src/contactInfo/domain/useCases/SubmitContactInfo.ts index d8dfaf6c..85a49c1c 100644 --- a/src/contactInfo/domain/useCases/SubmitContactInfo.ts +++ b/src/contactInfo/domain/useCases/SubmitContactInfo.ts @@ -11,17 +11,13 @@ export class SubmitContactInfo implements UseCase { } /** - * Submits contact information and returns a DTO containing the submitted data. + * Submits contact information and returns a Contact model containing the submitted data. * * @param {ContactDTO} contactDTO - The contact information to be submitted. - * @returns {Promise} A promise resolving to a ContactDTO. + * @returns {Promise} A promise resolving to a ContactDTO. */ async execute(contactDTO: ContactDTO): Promise { - try { - return await this.contactRepository.submitContactInfo(contactDTO) - } catch (error) { - throw new Error(`${error.message}`) - } + return await this.contactRepository.submitContactInfo(contactDTO) } } diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts index 29a699e3..10a702c1 100644 --- a/test/integration/contact/ContactRepository.test.ts +++ b/test/integration/contact/ContactRepository.test.ts @@ -21,7 +21,7 @@ describe('submitContactInfo', () => { const sut: ContactRepository = new ContactRepository() - test('should return ContactDTO when contact info is successfully submitted', async () => { + test('should return Contact when contact info is successfully submitted', async () => { const contactInfo = await sut.submitContactInfo(testContactDTO) expect(contactInfo).toBeDefined() diff --git a/test/unit/contact/SubmitContactInfo.test.ts b/test/unit/contact/SubmitContactInfo.test.ts index aec53404..a744dab7 100644 --- a/test/unit/contact/SubmitContactInfo.test.ts +++ b/test/unit/contact/SubmitContactInfo.test.ts @@ -3,7 +3,7 @@ import { SubmitContactInfo } from '../../../src/contactInfo/domain/useCases/Subm import { IContactRepository } from '../../../src/contactInfo/domain/repositories/IContactRepository' describe('execute submit information to contacts', () => { - test('should return a ContactDTO when repository call is successful', async () => { + test('should return a Contact when repository call is successful', async () => { const fromEmail = '1314@gmail.com' const contactDTO: ContactDTO = { From c6206798f5d4bd373706c208178cdeb336bcbfdc Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 22 Jan 2025 17:19:15 -0500 Subject: [PATCH 7/8] feat: update sendfeedback api and add testcase --- docs/useCases.md | 24 ++++++++++-- src/contactInfo/domain/dtos/ContactDTO.ts | 2 +- src/contactInfo/domain/models/Contact.ts | 1 - .../infra/repositories/ContactRepository.ts | 3 +- .../contact/SubmitContactInfo.test.ts | 33 ++++++++++++---- .../contact/ContactRepository.test.ts | 22 +++++++++-- test/unit/contact/SubmitContactInfo.test.ts | 39 ++++++++++++++++--- 7 files changed, 101 insertions(+), 23 deletions(-) diff --git a/docs/useCases.md b/docs/useCases.md index b415f270..7a0d438b 100644 --- a/docs/useCases.md +++ b/docs/useCases.md @@ -1370,7 +1370,7 @@ _See [use case](../src/info/domain/useCases/GetZipDownloadLimit.ts) implementati #### Send Feedback to Object Contacts -Returns a [Contact](../src/contactInfo/domain/models/Contact.ts) object, which contains contact return information, showing toEmail, fromEmail, subject, body. +Returns a [Contact](../src/contactInfo/domain/models/Contact.ts) object, which contains contact return information, showing fromEmail, subject, body. ##### Example call: @@ -1379,9 +1379,25 @@ import { submitContactInfo } from '@iqss/dataverse-client-javascript' /* ... */ -submitContactInfo.execute((contact: ContactDTO)).then( => { -/* ... */ -}) +const contactDTO: ContactDTO = { + targedId: 1 + subject: 'Data Question', + body: 'Please help me understand your data. Thank you!', + fromEmail: 'test@gmail.com' +} + +submitContactInfo.execute(contactDTO) /* ... */ ``` + +_See [use case](../src/info/domain/useCases/submitContactInfo.ts) implementation_. + +The above example would submit feedback to all contacts of a object where the object targetId = 1. + +In ContactDTO, it takes the following information: + +- **targetId**: the numeric identifier of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional) +- **subject**: the email subject line +- **body**: the email body to send +- **fromEmail**: the email to list in the reply-to field. diff --git a/src/contactInfo/domain/dtos/ContactDTO.ts b/src/contactInfo/domain/dtos/ContactDTO.ts index 5b759e3d..190bd2f5 100644 --- a/src/contactInfo/domain/dtos/ContactDTO.ts +++ b/src/contactInfo/domain/dtos/ContactDTO.ts @@ -1,5 +1,5 @@ export interface ContactDTO { - targetId: number + targetId?: number subject: string body: string fromEmail: string diff --git a/src/contactInfo/domain/models/Contact.ts b/src/contactInfo/domain/models/Contact.ts index 06435840..e4f54897 100644 --- a/src/contactInfo/domain/models/Contact.ts +++ b/src/contactInfo/domain/models/Contact.ts @@ -1,6 +1,5 @@ export interface Contact { fromEmail: string - toEmail: string body: string subject: string } diff --git a/src/contactInfo/infra/repositories/ContactRepository.ts b/src/contactInfo/infra/repositories/ContactRepository.ts index 12d87510..ba330872 100644 --- a/src/contactInfo/infra/repositories/ContactRepository.ts +++ b/src/contactInfo/infra/repositories/ContactRepository.ts @@ -5,12 +5,11 @@ import { ContactDTO } from '../../domain/dtos/ContactDTO' export class ContactRepository extends ApiRepository implements IContactRepository { public async submitContactInfo(contactDTO: ContactDTO): Promise { - return this.doPost(`/admin/feedback`, contactDTO) + return this.doPost(`/sendfeedback`, contactDTO) .then((response) => { const responseData = response.data const contact: Contact[] = responseData.data.map((item: Contact) => ({ fromEmail: item.fromEmail, - toEmail: item.toEmail, subject: item.subject, body: item.body })) diff --git a/test/functional/contact/SubmitContactInfo.test.ts b/test/functional/contact/SubmitContactInfo.test.ts index 1fdbeb47..ffa1bcd0 100644 --- a/test/functional/contact/SubmitContactInfo.test.ts +++ b/test/functional/contact/SubmitContactInfo.test.ts @@ -1,4 +1,4 @@ -import { ApiConfig, submitContactInfo, ContactDTO, WriteError } from '../../../src' +import { ApiConfig, submitContactInfo, ContactDTO, WriteError, Contact } from '../../../src' import { TestConstants } from '../../testHelpers/TestConstants' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' @@ -11,25 +11,44 @@ describe('submitContactInfo', () => { ) }) - test('should return success result on repository success', async () => { + test('should return Contact result successfully', async () => { const contactDTO: ContactDTO = { targetId: 1, subject: 'Data Question', body: 'Please help me understand your data. Thank you!', - fromEmail: '1314@gmail.com' + fromEmail: 'example@gmail.com' } - let contactInfo + let contactInfo: Contact[] = [] try { contactInfo = await submitContactInfo.execute(contactDTO) } catch (error) { throw new Error('Contact info should be submitted') } finally { expect(contactInfo).toBeDefined() - expect(contactInfo[0].fromEmail).toEqual('1314@gmail.com') + expect(contactInfo[0].fromEmail).toEqual('example@gmail.com') + expect(contactInfo[0].subject).toEqual(expect.any(String)) + expect(contactInfo[0].body).toEqual(expect.any(String)) + } + }) + + test('should return a Contact when targetId is not provided', async () => { + const contactDTO: ContactDTO = { + subject: 'General Inquiry', + body: 'I have a general question.', + fromEmail: 'example@gmail.com' + } + + let contactInfo: Contact[] = [] + try { + contactInfo = await submitContactInfo.execute(contactDTO) + } catch (error) { + throw new Error('Contact info should be submitted even if target id is missing') + } finally { + expect(contactInfo).toBeDefined() + expect(contactInfo[0].fromEmail).toEqual('example@gmail.com') expect(contactInfo[0].subject).toEqual(expect.any(String)) expect(contactInfo[0].body).toEqual(expect.any(String)) - expect(contactInfo[0].toEmail).toEqual(expect.any(String)) } }) @@ -38,7 +57,7 @@ describe('submitContactInfo', () => { targetId: 0, subject: '', body: '', - fromEmail: '1314@gmail.com' + fromEmail: 'example@gmail.com' } const expectedError = new WriteError(`[400] Feedback target object not found`) await expect(submitContactInfo.execute(contactDTO)).rejects.toThrow(expectedError) diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts index 10a702c1..4cac5cf1 100644 --- a/test/integration/contact/ContactRepository.test.ts +++ b/test/integration/contact/ContactRepository.test.ts @@ -1,5 +1,5 @@ import { ContactRepository } from '../../../src/contactInfo/infra/repositories/ContactRepository' -import { ApiConfig, ContactDTO, WriteError } from '../../../src' +import { ApiConfig, Contact, ContactDTO, WriteError } from '../../../src' import { DataverseApiAuthMechanism } from '../../../src/core/infra/repositories/ApiConfig' import { TestConstants } from '../../testHelpers/TestConstants' @@ -16,7 +16,7 @@ describe('submitContactInfo', () => { targetId: 1, subject: 'Data Question', body: 'Please help me understand your data. Thank you!', - fromEmail: '1314@gmail.com' + fromEmail: 'example@gmail.com' } const sut: ContactRepository = new ContactRepository() @@ -28,7 +28,23 @@ describe('submitContactInfo', () => { expect(contactInfo[0].fromEmail).toEqual(testContactDTO.fromEmail) expect(contactInfo[0].subject).toEqual(expect.any(String)) expect(contactInfo[0].body).toEqual(expect.any(String)) - expect(contactInfo[0].toEmail).toEqual(expect.any(String)) + }) + + test('should return a Contact when targetId is not provided', async () => { + const contactDTOWithoutTargetId: Partial = { + subject: 'General Inquiry', + body: 'I have a general question.', + fromEmail: 'example@gmail.com' + } + + const contactInfo: Contact[] = await sut.submitContactInfo( + contactDTOWithoutTargetId as ContactDTO + ) + + expect(contactInfo).toBeDefined() + expect(contactInfo[0].fromEmail).toEqual(contactDTOWithoutTargetId.fromEmail) + expect(contactInfo[0].subject).toEqual(expect.any(String)) + expect(contactInfo[0].body).toEqual(expect.any(String)) }) test('should return error if the target id is unexisted', async () => { diff --git a/test/unit/contact/SubmitContactInfo.test.ts b/test/unit/contact/SubmitContactInfo.test.ts index a744dab7..0589df1a 100644 --- a/test/unit/contact/SubmitContactInfo.test.ts +++ b/test/unit/contact/SubmitContactInfo.test.ts @@ -1,10 +1,11 @@ import { WriteError, Contact, ContactDTO } from '../../../src' import { SubmitContactInfo } from '../../../src/contactInfo/domain/useCases/SubmitContactInfo' import { IContactRepository } from '../../../src/contactInfo/domain/repositories/IContactRepository' +import { TestConstants } from '../../testHelpers/TestConstants' describe('execute submit information to contacts', () => { test('should return a Contact when repository call is successful', async () => { - const fromEmail = '1314@gmail.com' + const fromEmail = 'example@gmail.com' const contactDTO: ContactDTO = { targetId: 6, @@ -14,8 +15,7 @@ describe('execute submit information to contacts', () => { } const collectionAlias = 'collection-1' - const collectionEmail = 'pi@example.edu,student@example.edu' - const baseUrl = 'http://localhost:8080/dataverse/' + const baseUrl = TestConstants.TEST_API_URL + '/dataverse/' const bodyMessage = 'You have just been sent the following message from ' + fromEmail + @@ -42,7 +42,6 @@ describe('execute submit information to contacts', () => { const expectedResponse: Contact[] = [ { fromEmail: contactDTO.fromEmail, - toEmail: collectionEmail, subject: 'Root contact: ' + contactDTO.subject, body: bodyMessage } @@ -57,7 +56,37 @@ describe('execute submit information to contacts', () => { expect(contactRepositoryStub.submitContactInfo).toHaveBeenCalledWith(contactDTO) }) - test('should return error result on error response', async () => { + test('should return a Contact when targetId is not provided', async () => { + const fromEmail = 'test@gmail.com' + + const contactDTO: ContactDTO = { + subject: 'Data Question', + body: 'Please help me understand your data. Thank you!', + fromEmail: fromEmail + } + + const bodyMessage = + 'Root Support,\n\nThe following message was sent from ' + + fromEmail + + '.\n\n---\n\nPlease help me understand your data. Thank you!\n\n---\n\nMessage sent from Support contact form.' + const expectedResponse: Contact[] = [ + { + fromEmail: contactDTO.fromEmail, + subject: 'Root Support Request: ' + contactDTO.subject, + body: bodyMessage + } + ] + + const contactRepositoryStub = {} + + contactRepositoryStub.submitContactInfo = jest.fn().mockResolvedValue(expectedResponse) + const sut = new SubmitContactInfo(contactRepositoryStub) + const actual = await sut.execute(contactDTO) + expect(actual).toEqual(expectedResponse) + expect(contactRepositoryStub.submitContactInfo).toHaveBeenCalledWith(contactDTO) + }) + + test('should return error result once there is a invalid targetId', async () => { const contactDTO: ContactDTO = { targetId: 0, subject: '', From 9e70fe6f82bb096a03ddd737ac7bc1564e196cf9 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 31 Jan 2025 17:15:23 -0500 Subject: [PATCH 8/8] fix: error message --- test/functional/contact/SubmitContactInfo.test.ts | 2 +- test/integration/contact/ContactRepository.test.ts | 2 +- test/unit/contact/SubmitContactInfo.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/contact/SubmitContactInfo.test.ts b/test/functional/contact/SubmitContactInfo.test.ts index ffa1bcd0..c0530318 100644 --- a/test/functional/contact/SubmitContactInfo.test.ts +++ b/test/functional/contact/SubmitContactInfo.test.ts @@ -59,7 +59,7 @@ describe('submitContactInfo', () => { body: '', fromEmail: 'example@gmail.com' } - const expectedError = new WriteError(`[400] Feedback target object not found`) + const expectedError = new WriteError(`[400] Feedback target object not found.`) await expect(submitContactInfo.execute(contactDTO)).rejects.toThrow(expectedError) }) }) diff --git a/test/integration/contact/ContactRepository.test.ts b/test/integration/contact/ContactRepository.test.ts index 4cac5cf1..0b3170db 100644 --- a/test/integration/contact/ContactRepository.test.ts +++ b/test/integration/contact/ContactRepository.test.ts @@ -54,7 +54,7 @@ describe('submitContactInfo', () => { body: '', fromEmail: '' } - const expectedError = new WriteError(`[400] Feedback target object not found`) + const expectedError = new WriteError(`[400] Feedback target object not found.`) await expect(sut.submitContactInfo(invalidContactDTO)).rejects.toThrow(expectedError) }) }) diff --git a/test/unit/contact/SubmitContactInfo.test.ts b/test/unit/contact/SubmitContactInfo.test.ts index 0589df1a..63b22580 100644 --- a/test/unit/contact/SubmitContactInfo.test.ts +++ b/test/unit/contact/SubmitContactInfo.test.ts @@ -94,7 +94,7 @@ describe('execute submit information to contacts', () => { fromEmail: '' } const contactRepositoryStub = {} - const error = new WriteError(`[400] Feedback target object not found`) + const error = new WriteError(`[400] Feedback target object not found.`) contactRepositoryStub.submitContactInfo = jest.fn().mockRejectedValue(error) const sut = new SubmitContactInfo(contactRepositoryStub) await expect(sut.execute(contactDTO)).rejects.toThrow(error)