diff --git a/hrm-domain/hrm-core/contact/canPerformContactAction.ts b/hrm-domain/hrm-core/contact/canPerformContactAction.ts index 8cfd962c0..e5de9d1bc 100644 --- a/hrm-domain/hrm-core/contact/canPerformContactAction.ts +++ b/hrm-domain/hrm-core/contact/canPerformContactAction.ts @@ -50,7 +50,7 @@ const canPerformActionOnContact = ( // This is a dirty hack that relies on the catch block in the try/catch below to return a 404 throw new Error('contact not found'); } - if (contactObj.finalizedAt) { + if (contactObj.finalizedAt || action !== 'editContact') { if (can(user, action, contactObj)) { await authorizeIfAdditionalValidationPasses( req, @@ -107,10 +107,7 @@ const canPerformActionOnContact = ( } } } catch (err) { - if ( - err instanceof Error && - err.message.toLowerCase().includes('contact not found') - ) { + if (err instanceof Error && err.message.toLowerCase().includes('not found')) { throw createError(404); } else { console.error('Failed to authorize contact editing', err); diff --git a/hrm-domain/hrm-core/contact/contactRoutesV0.ts b/hrm-domain/hrm-core/contact/contactRoutesV0.ts index c87ebd5cc..0a2d5e655 100644 --- a/hrm-domain/hrm-core/contact/contactRoutesV0.ts +++ b/hrm-domain/hrm-core/contact/contactRoutesV0.ts @@ -71,7 +71,6 @@ contactsRouter.get('/byTaskSid/:taskSid', publicEndpoint, async (req, res) => { contactsRouter.put( '/:contactId/connectToCase', - publicEndpoint, canChangeContactConnection, async (req, res) => { const { accountSid, user } = req; @@ -99,7 +98,6 @@ contactsRouter.put( contactsRouter.delete( '/:contactId/connectToCase', - publicEndpoint, canDisconnectContact, async (req, res) => { const { accountSid, user } = req; diff --git a/hrm-domain/hrm-service/service-tests/contact/connectToCase.test.ts b/hrm-domain/hrm-service/service-tests/contact/connectToCase.test.ts new file mode 100644 index 000000000..9095192a2 --- /dev/null +++ b/hrm-domain/hrm-service/service-tests/contact/connectToCase.test.ts @@ -0,0 +1,407 @@ +/** + * Copyright (C) 2021-2023 Technology Matters + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +import { db } from '@tech-matters/hrm-core/connection-pool'; +import { + accountSid, + ALWAYS_CAN, + case1, + case2, + contact1, + contact2, + workerSid, +} from '../mocks'; +import '../case/caseValidation'; +import * as caseApi from '@tech-matters/hrm-core/case/caseService'; +import * as caseDb from '@tech-matters/hrm-core/case/caseDataAccess'; +import * as contactApi from '@tech-matters/hrm-core/contact/contactService'; +import * as contactDb from '@tech-matters/hrm-core/contact/contactDataAccess'; +import { mockingProxy, mockSuccessfulTwilioAuthentication } from '@tech-matters/testing'; +import { getRequest, getServer, headers, useOpenRules } from '../server'; +import { twilioUser } from '@tech-matters/twilio-worker-auth'; +import { deleteContactById, fullClearDown } from './dbCleanup'; +import each from 'jest-each'; + +const server = getServer(); +const request = getRequest(server); + +beforeAll(async () => { + await mockingProxy.start(); + await mockSuccessfulTwilioAuthentication(workerSid); + await fullClearDown(); +}); + +afterAll(async () => { + await mockingProxy.stop(); + server.close(); +}); + +beforeEach(() => { + useOpenRules(); + jest.clearAllMocks(); +}); + +afterEach(async () => { + await fullClearDown(); +}); + +const route = `/v0/accounts/${accountSid}/contacts`; + +type PermissionTestCase = { + allowAddContactToCase: boolean; + allowRemoveContactFromCase: boolean; + allowUpdateCaseContacts: boolean; + expectActionIsPermitted: boolean; +}; + +const setRulesForPermissionTest = ({} // allowAddContactToCase, +// allowUpdateCaseContacts, +// allowRemoveContactFromCase, +: PermissionTestCase) => { + /* + const permittedConditions: TKConditionsSets<'case'> = [['everyone']]; + const forbiddenConditions: TKConditionsSets<'case'> = [['isSupervisor']]; + setRules({ + [actionsMaps.case.UPDATE_CASE_CONTACTS]: allowUpdateCaseContacts + ? permittedConditions + : forbiddenConditions, + [actionsMaps.contact.REMOVE_CONTACT_FROM_CASE]: allowRemoveContactFromCase + ? permittedConditions + : forbiddenConditions, + [actionsMaps.contact.ADD_CONTACT_TO_CASE]: allowAddContactToCase + ? permittedConditions + : forbiddenConditions, + }); + */ +}; + +describe('/contacts/:contactId/connectToCase route', () => { + let createdContact; + let createdCase; + let anotherCreatedCase; + let existingContactId; + let nonExistingContactId; + let existingCaseId; + let anotherExistingCaseId; + let nonExistingCaseId; + + const byGreaterId = (a, b) => b.id - a.id; + + beforeEach(async () => { + createdContact = await contactApi.createContact( + accountSid, + workerSid, + contact1, + { + user: twilioUser(workerSid, []), + can: () => true, + }, + ); + createdCase = await caseApi.createCase(case1, accountSid, workerSid); + anotherCreatedCase = await caseApi.createCase(case2, accountSid, workerSid); + const contactToBeDeleted = await contactApi.createContact( + accountSid, + workerSid, + contact2, + { user: twilioUser(workerSid, []), can: () => true }, + ); + const caseToBeDeleted = await caseApi.createCase(case1, accountSid, workerSid); + + existingContactId = createdContact.id; + existingCaseId = createdCase.id; + anotherExistingCaseId = anotherCreatedCase.id; + nonExistingContactId = contactToBeDeleted.id; + nonExistingCaseId = caseToBeDeleted.id; + + await deleteContactById(contactToBeDeleted.id, contactToBeDeleted.accountSid); + await caseDb.deleteById(caseToBeDeleted.id, accountSid); + }); + + afterEach(async () => { + await deleteContactById(createdContact.id, createdContact.accountSid); + await caseDb.deleteById(createdCase.id, accountSid); + await caseDb.deleteById(anotherCreatedCase.id, accountSid); + }); + + describe('PUT', () => { + const subRoute = contactId => `${route}/${contactId}/connectToCase`; + + test('should return 401', async () => { + const response = await request.put(subRoute(existingContactId)).send({}); + + expect(response.status).toBe(401); + expect(response.body.error).toBe('Authorization failed'); + }); + + test('should return 200', async () => { + const response = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: existingCaseId }); + + expect(response.status).toBe(200); + expect(response.body.caseId).toBe(existingCaseId); + + // Test the association + expect(response.body.csamReports).toHaveLength(0); + }); + + // const selectCreatedCaseAudits = () => + // `SELECT * FROM "Audits" WHERE "tableName" = 'Cases' AND ("oldRecord"->>'id' = '${createdCase.id}' OR "newRecord"->>'id' = '${createdCase.id}')`; + const countCasesAudits = async () => + parseInt( + ( + await db.task(t => + t.any(`SELECT COUNT(*) FROM "Audits" WHERE "tableName" = 'Cases'`), + ) + )[0].count, + 10, + ); + + const selectCasesAudits = () => + db.task(t => t.any(`SELECT * FROM "Audits" WHERE "tableName" = 'Cases'`)); + + const countContactsAudits = async () => + parseInt( + ( + await db.task(t => + t.any(`SELECT COUNT(*) FROM "Audits" WHERE "tableName" = 'Contacts'`), + ) + )[0].count, + 10, + ); + const selectContactsAudits = () => + db.task(t => t.any(`SELECT * FROM "Audits" WHERE "tableName" = 'Contacts'`)); + + test('should create a CaseAudit', async () => { + const casesAuditPreviousCount = await countCasesAudits(); + const contactsAuditPreviousCount = await countContactsAudits(); + + const response = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: existingCaseId }); + + expect(response.status).toBe(200); + + const casesAudits = await selectCasesAudits(); + const contactsAudits = await selectContactsAudits(); + + // Connecting contacts to cases updates contacts, but also touches the updatedat / updatedby fields on the case + expect(casesAudits).toHaveLength(casesAuditPreviousCount + 1); + expect(contactsAudits).toHaveLength(contactsAuditPreviousCount + 1); + + const lastContactAudit = contactsAudits.sort(byGreaterId)[0]; + const { oldRecord, newRecord } = lastContactAudit; + + expect(oldRecord.caseId).toBe(null); + expect(newRecord.caseId).toBe(existingCaseId); + }); + + test('Idempotence on connect contact to case - generates audit', async () => { + const response1 = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: existingCaseId }); + + expect(response1.status).toBe(200); + + const casesAuditPreviousCount = await countCasesAudits(); + const contactsAuditPreviousCount = await countContactsAudits(); + + // repeat above operation (should do nothing but emit an audit) + const response2 = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: existingCaseId }); + + expect(response2.status).toBe(200); + expect(response2.body.caseId).toBe(existingCaseId); + + const casesAuditAfterCount = await countCasesAudits(); + const contactsAuditAfterCount = await countContactsAudits(); + + expect(casesAuditAfterCount).toBe(casesAuditPreviousCount + 1); + expect(contactsAuditAfterCount).toBe(contactsAuditPreviousCount + 1); + }); + + test('Should create audit for a Contact if caseId changes', async () => { + const response1 = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: existingCaseId }); + + expect(response1.status).toBe(200); + + const casesAuditPreviousCount = await countCasesAudits(); + const contactsAuditPreviousCount = await countContactsAudits(); + + // repeat above operation (should do nothing but emit an audit) + const response2 = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: anotherExistingCaseId }); + + expect(response2.status).toBe(200); + + const casesAuditAfterCount = await countCasesAudits(); + const contactsAuditAfterCount = await countContactsAudits(); + + expect(casesAuditAfterCount).toBe(casesAuditPreviousCount + 1); + expect(contactsAuditAfterCount).toBe(contactsAuditPreviousCount + 1); + }); + + describe('use non-existent contactId', () => { + test('should return 404', async () => { + const response = await request + .put(subRoute(nonExistingContactId)) + .set(headers) + .send({ caseId: existingCaseId }); + + expect(response.status).toBe(404); + }); + }); + describe('use non-existent caseId', () => { + test('should return 404', async () => { + const response = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: nonExistingCaseId }); + + expect(response.status).toBe(404); + }); + }); + describe.skip('permissions', () => { + const testCases: PermissionTestCase[] = [ + { + allowAddContactToCase: false, + allowRemoveContactFromCase: true, + allowUpdateCaseContacts: true, + expectActionIsPermitted: false, + }, + { + allowAddContactToCase: true, + allowRemoveContactFromCase: false, + allowUpdateCaseContacts: true, + expectActionIsPermitted: true, + }, + { + allowAddContactToCase: true, + allowRemoveContactFromCase: true, + allowUpdateCaseContacts: false, + expectActionIsPermitted: false, + }, + ]; + each(testCases).test( + 'User has permissions UPDATE_CASE_CONTACTS: $allowUpdateCaseContacts, REMOVE_CONTACT_FROM_CASE: $allowRemoveContactFromCase and ADD_CONTACT_TO_CASE: $allowAddContactToCase, action permitted: $expectActionIsPermitted', + async (testCase: PermissionTestCase) => { + setRulesForPermissionTest(testCase); + const response = await request + .put(subRoute(existingContactId)) + .set(headers) + .send({ caseId: existingCaseId }); + + if (testCase.expectActionIsPermitted) { + expect(response.status).toBe(200); + expect(response.body.caseId).toBe(existingCaseId); + + const contact = await contactDb.getById(accountSid, response.body.id); + + expect(contact.caseId).toBe(existingCaseId); + } else { + expect(response.status).toBe(401); + } + }, + ); + }); + }); + describe('DELETE', () => { + const subRoute = contactId => `${route}/${contactId}/connectToCase`; + + beforeEach(async () => { + await contactApi.connectContactToCase( + accountSid, + workerSid, + existingContactId, + existingCaseId, + ALWAYS_CAN, + ); + }); + + test('should return 401', async () => { + const response = await request.delete(subRoute(existingContactId)); + + expect(response.status).toBe(401); + expect(response.body.error).toBe('Authorization failed'); + }); + + test('contact exists and user has permission - should return 200', async () => { + const response = await request.delete(subRoute(existingContactId)).set(headers); + + const contact = await contactDb.getById(accountSid, response.body.id); + + expect(response.status).toBe(200); + expect(response.body.caseId).toBe(null); + expect(contact.caseId).toBe(null); + }); + + test('non existent contact Id - should return 404', async () => { + const response = await request.delete(subRoute(nonExistingContactId)).set(headers); + + expect(response.status).toBe(404); + }); + + describe.skip('permissions', () => { + const testCases: PermissionTestCase[] = [ + { + allowAddContactToCase: false, + allowRemoveContactFromCase: true, + allowUpdateCaseContacts: true, + expectActionIsPermitted: true, + }, + { + allowAddContactToCase: true, + allowRemoveContactFromCase: false, + allowUpdateCaseContacts: true, + expectActionIsPermitted: false, + }, + { + allowAddContactToCase: true, + allowRemoveContactFromCase: true, + allowUpdateCaseContacts: false, + expectActionIsPermitted: false, + }, + ]; + each(testCases).test( + 'User has permissions UPDATE_CASE_CONTACTS: $allowUpdateCaseContacts, REMOVE_CONTACT_FROM_CASE: $allowRemoveContactFromCase and ADD_CONTACT_TO_CASE: $allowAddContactToCase, action permitted: $expectActionIsPermitted', + async (testCase: PermissionTestCase) => { + setRulesForPermissionTest(testCase); + const response = await request.delete(subRoute(existingContactId)).set(headers); + if (testCase.expectActionIsPermitted) { + expect(response.status).toBe(200); + expect(response.body.caseId).toBe(null); + + const contact = await contactDb.getById(accountSid, response.body.id); + + expect(contact.caseId).toBe(null); + } else { + expect(response.status).toBe(401); + } + }, + ); + }); + }); +}); diff --git a/hrm-domain/hrm-service/service-tests/contact/contactById.test.ts b/hrm-domain/hrm-service/service-tests/contact/contactById.test.ts index 553a991a9..817cb2585 100644 --- a/hrm-domain/hrm-service/service-tests/contact/contactById.test.ts +++ b/hrm-domain/hrm-service/service-tests/contact/contactById.test.ts @@ -27,7 +27,7 @@ import { cleanupContactsJobs, cleanupCsamReports, cleanupReferrals, -} from './db-cleanup'; +} from './dbCleanup'; useOpenRules(); const server = getServer(); diff --git a/hrm-domain/hrm-service/service-tests/contact/contactByTaskId.test.ts b/hrm-domain/hrm-service/service-tests/contact/contactByTaskId.test.ts index 180415e3a..7ac528112 100644 --- a/hrm-domain/hrm-service/service-tests/contact/contactByTaskId.test.ts +++ b/hrm-domain/hrm-service/service-tests/contact/contactByTaskId.test.ts @@ -27,7 +27,7 @@ import { cleanupContactsJobs, cleanupCsamReports, cleanupReferrals, -} from './db-cleanup'; +} from './dbCleanup'; useOpenRules(); const server = getServer(); diff --git a/hrm-domain/hrm-service/service-tests/contact/contactConversationMedia.test.ts b/hrm-domain/hrm-service/service-tests/contact/contactConversationMedia.test.ts index 1ffb29502..33430646e 100644 --- a/hrm-domain/hrm-service/service-tests/contact/contactConversationMedia.test.ts +++ b/hrm-domain/hrm-service/service-tests/contact/contactConversationMedia.test.ts @@ -32,7 +32,7 @@ import { cleanupContactsJobs, cleanupCsamReports, cleanupReferrals, -} from './db-cleanup'; +} from './dbCleanup'; import each from 'jest-each'; import { chatChannels } from '@tech-matters/hrm-core/contact/channelTypes'; import { ContactJobType } from '@tech-matters/types/dist/ContactJob'; diff --git a/hrm-domain/hrm-service/service-tests/contact/contactPatch.test.ts b/hrm-domain/hrm-service/service-tests/contact/contactPatch.test.ts index ba7149bf2..268ee11c7 100644 --- a/hrm-domain/hrm-service/service-tests/contact/contactPatch.test.ts +++ b/hrm-domain/hrm-service/service-tests/contact/contactPatch.test.ts @@ -38,7 +38,7 @@ import { cleanupReferrals, deleteContactById, deleteJobsByContactId, -} from './db-cleanup'; +} from './dbCleanup'; import { NewContactRecord } from '@tech-matters/hrm-core/contact/sql/contactInsertSql'; import { finalizeContact } from './finalizeContact'; diff --git a/hrm-domain/hrm-service/service-tests/contact/db-cleanup.ts b/hrm-domain/hrm-service/service-tests/contact/dbCleanup.ts similarity index 84% rename from hrm-domain/hrm-service/service-tests/contact/db-cleanup.ts rename to hrm-domain/hrm-service/service-tests/contact/dbCleanup.ts index 96f7b4a36..1c1a5e431 100644 --- a/hrm-domain/hrm-service/service-tests/contact/db-cleanup.ts +++ b/hrm-domain/hrm-service/service-tests/contact/dbCleanup.ts @@ -69,3 +69,25 @@ export const deleteJobsByContactId = (contactId: number, accountSid: string) => WHERE "contactId" = ${contactId} AND "accountSid" = '${accountSid}'; `), ); + +export const fullClearDown = async () => + db.task(async t => { + await t.none(` + DELETE FROM "ContactJobs" + `); + await t.none(` + DELETE FROM "CSAMReports" + `); + await t.none(` + DELETE FROM "Referrals" + `); + await t.none(` + DELETE FROM "Contacts" + `); + await t.none(` + DELETE FROM "CaseSections" + `); + await t.none(` + DELETE FROM "Cases" + `); + }); diff --git a/hrm-domain/hrm-service/service-tests/contacts.test.ts b/hrm-domain/hrm-service/service-tests/contacts.test.ts index 50054e1a9..e2b8442ec 100644 --- a/hrm-domain/hrm-service/service-tests/contacts.test.ts +++ b/hrm-domain/hrm-service/service-tests/contacts.test.ts @@ -27,8 +27,6 @@ import { another2, broken1, broken2, - case1, - case2, contact1, contact2, conversationMedia, @@ -39,8 +37,6 @@ import { workerSid, } from './mocks'; import './case/caseValidation'; -import * as caseApi from '@tech-matters/hrm-core/case/caseService'; -import * as caseDb from '@tech-matters/hrm-core/case/caseDataAccess'; import * as contactApi from '@tech-matters/hrm-core/contact/contactService'; import * as contactDb from '@tech-matters/hrm-core/contact/contactDataAccess'; import { mockingProxy, mockSuccessfulTwilioAuthentication } from '@tech-matters/testing'; @@ -60,7 +56,7 @@ import { cleanupReferrals, deleteContactById, deleteJobsByContactId, -} from './contact/db-cleanup'; +} from './contact/dbCleanup'; import { addConversationMediaToContact } from '@tech-matters/hrm-core/contact/contactService'; import { NewContactRecord } from '@tech-matters/hrm-core/contact/sql/contactInsertSql'; import supertest from 'supertest'; @@ -1046,230 +1042,4 @@ describe('/contacts route', () => { }); }); }); - - describe('/contacts/:contactId/connectToCase route', () => { - let createdContact; - let createdCase; - let anotherCreatedCase; - let existingContactId; - let nonExistingContactId; - let existingCaseId; - let anotherExistingCaseId; - let nonExistingCaseId; - - const byGreaterId = (a, b) => b.id - a.id; - - beforeEach(async () => { - createdContact = await contactApi.createContact( - accountSid, - workerSid, - contact1, - { - user: twilioUser(workerSid, []), - can: () => true, - }, - ); - createdCase = await caseApi.createCase(case1, accountSid, workerSid); - anotherCreatedCase = await caseApi.createCase(case2, accountSid, workerSid); - const contactToBeDeleted = await contactApi.createContact( - accountSid, - workerSid, - contact2, - { user: twilioUser(workerSid, []), can: () => true }, - ); - const caseToBeDeleted = await caseApi.createCase(case1, accountSid, workerSid); - - existingContactId = createdContact.id; - existingCaseId = createdCase.id; - anotherExistingCaseId = anotherCreatedCase.id; - nonExistingContactId = contactToBeDeleted.id; - nonExistingCaseId = caseToBeDeleted.id; - - await deleteContactById(contactToBeDeleted.id, contactToBeDeleted.accountSid); - await caseDb.deleteById(caseToBeDeleted.id, accountSid); - }); - - afterEach(async () => { - await deleteContactById(createdContact.id, createdContact.accountSid); - await caseDb.deleteById(createdCase.id, accountSid); - await caseDb.deleteById(anotherCreatedCase.id, accountSid); - }); - - describe('PUT', () => { - const subRoute = contactId => `${route}/${contactId}/connectToCase`; - - test('should return 401', async () => { - const response = await request.put(subRoute(existingContactId)).send({}); - - expect(response.status).toBe(401); - expect(response.body.error).toBe('Authorization failed'); - }); - - test('should return 200', async () => { - const response = await request - .put(subRoute(existingContactId)) - .set(headers) - .send({ caseId: existingCaseId }); - - expect(response.status).toBe(200); - expect(response.body.caseId).toBe(existingCaseId); - - // Test the association - expect(response.body.csamReports).toHaveLength(0); - }); - - // const selectCreatedCaseAudits = () => - // `SELECT * FROM "Audits" WHERE "tableName" = 'Cases' AND ("oldRecord"->>'id' = '${createdCase.id}' OR "newRecord"->>'id' = '${createdCase.id}')`; - const countCasesAudits = async () => - parseInt( - ( - await db.task(t => - t.any(`SELECT COUNT(*) FROM "Audits" WHERE "tableName" = 'Cases'`), - ) - )[0].count, - 10, - ); - - const selectCasesAudits = () => - db.task(t => t.any(`SELECT * FROM "Audits" WHERE "tableName" = 'Cases'`)); - - const countContactsAudits = async () => - parseInt( - ( - await db.task(t => - t.any(`SELECT COUNT(*) FROM "Audits" WHERE "tableName" = 'Contacts'`), - ) - )[0].count, - 10, - ); - const selectContactsAudits = () => - db.task(t => t.any(`SELECT * FROM "Audits" WHERE "tableName" = 'Contacts'`)); - - test('should create a CaseAudit', async () => { - const casesAuditPreviousCount = await countCasesAudits(); - const contactsAuditPreviousCount = await countContactsAudits(); - - const response = await request - .put(subRoute(existingContactId)) - .set(headers) - .send({ caseId: existingCaseId }); - - expect(response.status).toBe(200); - - const casesAudits = await selectCasesAudits(); - const contactsAudits = await selectContactsAudits(); - - // Connecting contacts to cases updates contacts, but also touches the updatedat / updatedby fields on the case - expect(casesAudits).toHaveLength(casesAuditPreviousCount + 1); - expect(contactsAudits).toHaveLength(contactsAuditPreviousCount + 1); - - const lastContactAudit = contactsAudits.sort(byGreaterId)[0]; - const { oldRecord, newRecord } = lastContactAudit; - - expect(oldRecord.caseId).toBe(null); - expect(newRecord.caseId).toBe(existingCaseId); - }); - - test('Idempotence on connect contact to case - generates audit', async () => { - const response1 = await request - .put(subRoute(existingContactId)) - .set(headers) - .send({ caseId: existingCaseId }); - - expect(response1.status).toBe(200); - - const casesAuditPreviousCount = await countCasesAudits(); - const contactsAuditPreviousCount = await countContactsAudits(); - - // repeat above operation (should do nothing but emit an audit) - const response2 = await request - .put(subRoute(existingContactId)) - .set(headers) - .send({ caseId: existingCaseId }); - - expect(response2.status).toBe(200); - expect(response2.body.caseId).toBe(existingCaseId); - - const casesAuditAfterCount = await countCasesAudits(); - const contactsAuditAfterCount = await countContactsAudits(); - - expect(casesAuditAfterCount).toBe(casesAuditPreviousCount + 1); - expect(contactsAuditAfterCount).toBe(contactsAuditPreviousCount + 1); - }); - - test('Should create audit for a Contact if caseId changes', async () => { - const response1 = await request - .put(subRoute(existingContactId)) - .set(headers) - .send({ caseId: existingCaseId }); - - expect(response1.status).toBe(200); - - const casesAuditPreviousCount = await countCasesAudits(); - const contactsAuditPreviousCount = await countContactsAudits(); - - // repeat above operation (should do nothing but emit an audit) - const response2 = await request - .put(subRoute(existingContactId)) - .set(headers) - .send({ caseId: anotherExistingCaseId }); - - expect(response2.status).toBe(200); - - const casesAuditAfterCount = await countCasesAudits(); - const contactsAuditAfterCount = await countContactsAudits(); - - expect(casesAuditAfterCount).toBe(casesAuditPreviousCount + 1); - expect(contactsAuditAfterCount).toBe(contactsAuditPreviousCount + 1); - }); - - describe('use non-existent contactId', () => { - test('should return 404', async () => { - const response = await request - .put(subRoute(nonExistingContactId)) - .set(headers) - .send({ caseId: existingCaseId }); - - expect(response.status).toBe(404); - }); - }); - describe('use non-existent caseId', () => { - test('should return 404', async () => { - const response = await request - .put(subRoute(existingContactId)) - .set(headers) - .send({ caseId: nonExistingCaseId }); - - expect(response.status).toBe(404); - }); - }); - }); - describe('DELETE', () => { - const subRoute = contactId => `${route}/${contactId}/connectToCase`; - test('should return 401', async () => { - const response = await request.delete(subRoute(existingContactId)); - - expect(response.status).toBe(401); - expect(response.body.error).toBe('Authorization failed'); - }); - - test('should return 200', async () => { - const response = await request.delete(subRoute(existingContactId)).set(headers); - - const contact = await contactDb.getById(accountSid, response.body.id); - - expect(response.status).toBe(200); - expect(response.body.caseId).toBe(null); - expect(contact.caseId).toBe(null); - }); - - test('should return 404', async () => { - const response = await request - .delete(subRoute(nonExistingContactId)) - .set(headers); - - expect(response.status).toBe(404); - }); - }); - }); });