Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement use case for sending information to contacts #232

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
38 changes: 38 additions & 0 deletions docs/useCases.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,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

Expand Down Expand Up @@ -1392,3 +1394,39 @@ 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 fromEmail, subject, body.

##### Example call:

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

/* ... */

const contactDTO: ContactDTO = {
targedId: 1
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

submitContactInfo.execute(contactDTO)

/* ... */
```
ChengShi-1 marked this conversation as resolved.
Show resolved Hide resolved

_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.
6 changes: 6 additions & 0 deletions src/contactInfo/domain/dtos/ContactDTO.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface ContactDTO {
targetId?: number
subject: string
body: string
fromEmail: string
}
5 changes: 5 additions & 0 deletions src/contactInfo/domain/models/Contact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Contact {
fromEmail: string
body: string
subject: string
}
6 changes: 6 additions & 0 deletions src/contactInfo/domain/repositories/IContactRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Contact } from '../models/Contact'
import { ContactDTO } from '../dtos/ContactDTO'

export interface IContactRepository {
submitContactInfo(contactDTO: ContactDTO): Promise<Contact[]>
}
23 changes: 23 additions & 0 deletions src/contactInfo/domain/useCases/SubmitContactInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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<Contact[]> {
private contactRepository: IContactRepository

constructor(contactRepository: IContactRepository) {
this.contactRepository = contactRepository
}

/**
* Submits contact information and returns a Contact model containing the submitted data.
*
* @param {ContactDTO} contactDTO - The contact information to be submitted.
* @returns {Promise<Contact[]>} A promise resolving to a ContactDTO.
*/

async execute(contactDTO: ContactDTO): Promise<Contact[]> {
return await this.contactRepository.submitContactInfo(contactDTO)
}
}
9 changes: 9 additions & 0 deletions src/contactInfo/index.ts
Original file line number Diff line number Diff line change
@@ -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'
23 changes: 23 additions & 0 deletions src/contactInfo/infra/repositories/ContactRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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<Contact[]> {
return this.doPost(`/sendfeedback`, contactDTO)
.then((response) => {
const responseData = response.data
const contact: Contact[] = responseData.data.map((item: Contact) => ({
fromEmail: item.fromEmail,
subject: item.subject,
body: item.body
}))

return contact
})
.catch((error) => {
throw error
})
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './datasets'
export * from './collections'
export * from './metadataBlocks'
export * from './files'
export * from './contactInfo'
65 changes: 65 additions & 0 deletions test/functional/contact/SubmitContactInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ApiConfig, submitContactInfo, ContactDTO, WriteError, Contact } 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 Contact result successfully', async () => {
const contactDTO: ContactDTO = {
targetId: 1,
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

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('[email protected]')
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: '[email protected]'
}

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('[email protected]')
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 () => {
const contactDTO: ContactDTO = {
targetId: 0,
subject: '',
body: '',
fromEmail: '[email protected]'
}
const expectedError = new WriteError(`[400] Feedback target object not found.`)
await expect(submitContactInfo.execute(contactDTO)).rejects.toThrow(expectedError)
})
})
60 changes: 60 additions & 0 deletions test/integration/contact/ContactRepository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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: 1,
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: '[email protected]'
}

const sut: ContactRepository = new ContactRepository()

test('should return Contact 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).toEqual(expect.any(String))
expect(contactInfo[0].body).toEqual(expect.any(String))
})

test('should return a Contact when targetId is not provided', async () => {
const contactDTOWithoutTargetId: Partial<ContactDTO> = {
subject: 'General Inquiry',
body: 'I have a general question.',
fromEmail: '[email protected]'
}

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 () => {
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)
})
})
103 changes: 103 additions & 0 deletions test/unit/contact/SubmitContactInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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 = '[email protected]'

const contactDTO: ContactDTO = {
targetId: 6,
subject: 'Data Question',
body: 'Please help me understand your data. Thank you!',
fromEmail: fromEmail
}

const collectionAlias = 'collection-1'
const baseUrl = TestConstants.TEST_API_URL + '/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,
subject: 'Root contact: ' + contactDTO.subject,
body: bodyMessage
}
]

const contactRepositoryStub = <IContactRepository>{}

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 a Contact when targetId is not provided', async () => {
const fromEmail = '[email protected]'

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 = <IContactRepository>{}

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: '',
body: '',
fromEmail: ''
}
const contactRepositoryStub = <IContactRepository>{}
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)
})
})