diff --git a/backend/functions/azure/functions.test.ts b/backend/functions/azure/functions.test.ts new file mode 100644 index 000000000..ddfa3cc44 --- /dev/null +++ b/backend/functions/azure/functions.test.ts @@ -0,0 +1,47 @@ +import { azureToCamsHttpRequest, toAzureSuccess } from './functions'; +import { HttpRequest, HttpResponseInit } from '@azure/functions'; +import { CamsHttpRequest } from '../lib/adapters/types/http'; +import { CamsHttpResponseInit } from '../lib/adapters/utils/http-response'; + +describe('functions test', () => { + test('should return properly formatted CamsHttpRequest from malformed headers and query', async () => { + const request = { + method: 'GET', + url: '/test', + query: 'bar', + params: { arg1: 'hello' }, + } as unknown as HttpRequest; + + const expected: CamsHttpRequest = { + method: 'GET', + url: '/test', + headers: {}, + query: {}, + params: { arg1: 'hello' }, + body: undefined, + }; + + const response = await azureToCamsHttpRequest(request); + expect(response).toEqual(expected); + }); + + test('should return empty Azure response init', () => { + const responseInit = toAzureSuccess(); + expect(responseInit).toEqual({}); + }); + + test('should return Azure response init', () => { + const camsResponseInit: CamsHttpResponseInit<{ prop1: boolean }> = { + headers: { foo: 'bar' }, + statusCode: 200, + body: { data: { prop1: true } }, + }; + const expected: HttpResponseInit = { + headers: { foo: 'bar' }, + status: 200, + jsonBody: { data: { prop1: true } }, + }; + const responseInit = toAzureSuccess(camsResponseInit); + expect(responseInit).toEqual(expected); + }); +}); diff --git a/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.test.ts b/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.test.ts index 29a5c4ecc..7eb144332 100644 --- a/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.test.ts +++ b/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.test.ts @@ -57,13 +57,12 @@ describe('offices repo', () => { ...session.user, ttl, }); - - const insertOneSpy = jest - .spyOn(MongoCollectionAdapter.prototype, 'insertOne') + const replaceOneSpy = jest + .spyOn(MongoCollectionAdapter.prototype, 'replaceOne') .mockResolvedValue('inserted-id'); await repo.putOfficeStaff(officeCode, session.user); - expect(insertOneSpy).toHaveBeenCalledWith({ ...staff, updatedOn: expect.anything() }); + expect(replaceOneSpy).toHaveBeenCalledWith(expect.anything(), staff, true); }); describe('error handling', () => { diff --git a/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.ts b/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.ts index d6abb23b6..a32640137 100644 --- a/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.ts +++ b/backend/functions/lib/adapters/gateways/mongo/offices.mongo.repository.ts @@ -34,8 +34,11 @@ export class OfficesMongoRepository extends BaseMongoRepository implements Offic ...user, ttl, }); + const query = QueryBuilder.build( + and(equals('id', staff.id), equals('officeCode', officeCode)), + ); try { - await this.getAdapter().insertOne(staff); + await this.getAdapter().replaceOne(query, staff, true); } catch (originalError) { throw getCamsError(originalError, MODULE_NAME); } diff --git a/backend/functions/lib/adapters/gateways/mongo/orders.mongo.repository.ts b/backend/functions/lib/adapters/gateways/mongo/orders.mongo.repository.ts index be2d8de0a..c532ce9fe 100644 --- a/backend/functions/lib/adapters/gateways/mongo/orders.mongo.repository.ts +++ b/backend/functions/lib/adapters/gateways/mongo/orders.mongo.repository.ts @@ -5,7 +5,6 @@ import { TransferOrderAction, } from '../../../../../../common/src/cams/orders'; import { ApplicationContext } from '../../types/basic'; -import { NotFoundError } from '../../../common-errors/not-found-error'; import { OrdersRepository } from '../../../use-cases/gateways.types'; import QueryBuilder, { ConditionOrConjunction } from '../../../query/query-builder'; import { getCamsError } from '../../../common-errors/error-utilities'; @@ -51,9 +50,6 @@ export class OrdersMongoRepository extends BaseMongoRepository implements Orders const adapter = this.getAdapter(); const { docketSuggestedCaseNumber: _docketSuggestedCaseNumber, ...existingOrder } = (await adapter.findOne(query)) as TransferOrder; - if (!existingOrder) { - throw new NotFoundError(MODULE_NAME, { message: `Order not found with id ${data.id}` }); - } const { id: _id, orderType: _orderType, caseId: _caseId, ...mutableProperties } = data; const updatedOrder = { ...existingOrder, diff --git a/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.test.ts b/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.test.ts index 642ab771c..9165818f8 100644 --- a/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.test.ts +++ b/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.test.ts @@ -1,9 +1,9 @@ import { CamsError } from '../../../../common-errors/cams-error'; import { NotFoundError } from '../../../../common-errors/not-found-error'; import { UnknownError } from '../../../../common-errors/unknown-error'; -import { CollectionHumble } from '../../../../humble-objects/mongo-humble'; +import { CollectionHumble, DocumentClient } from '../../../../humble-objects/mongo-humble'; import QueryBuilder from '../../../../query/query-builder'; -import { MongoCollectionAdapter } from './mongo-adapter'; +import { MongoCollectionAdapter, removeIds } from './mongo-adapter'; const { and, orderBy } = QueryBuilder; @@ -29,7 +29,11 @@ const spies = { countDocuments, }; -type TestType = object; +type TestType = { + _id?: unknown; + id?: string; + foo?: string; +}; describe('Mongo adapter', () => { const testQuery = QueryBuilder.build(and()); @@ -40,6 +44,23 @@ describe('Mongo adapter', () => { jest.resetAllMocks(); }); + test('should return an instance of the adapter from newAdapter', () => { + const mockClient: DocumentClient = { + database: () => { + return { + collection: <_t>() => {}, + }; + }, + } as unknown as DocumentClient; + const adapter = MongoCollectionAdapter.newAdapter( + 'module', + 'collection', + 'database', + mockClient, + ); + expect(adapter).toBeInstanceOf(MongoCollectionAdapter); + }); + test('should return items from a getAll', async () => { find.mockResolvedValue([{}, {}, {}]); const item = await adapter.getAll(); @@ -47,6 +68,13 @@ describe('Mongo adapter', () => { expect(find).toHaveBeenCalled(); }); + // TODO: maybe remove this? + test('should remove Ids from an item', async () => { + const expectedItem = { arbitraryValue: 'arbitrary-value' }; + const item = { _id: 'some_id', id: 'someId', ...expectedItem }; + expect(removeIds(item)).toEqual(expectedItem); + }); + test('should return a sorted list of items from a getAll', async () => { function* generator() { yield Promise.resolve({}); @@ -101,24 +129,77 @@ describe('Mongo adapter', () => { ); }); - test('should return a single Id from a replaceOne', async () => { - const id = '123456'; - replaceOne.mockResolvedValue({ acknowdledged: true, upsertedId: id }); - const result = await adapter.replaceOne(testQuery, {}); - expect(result).toEqual(id); + test('should return a single Id from replaceOne', async () => { + const testObject: TestType = { id: '12345', foo: 'bar' }; + const _id = 'mongoGeneratedId'; + replaceOne.mockResolvedValue({ + acknowledged: true, + matchedCount: 1, + modifiedCount: 1, + upsertedId: _id, + }); + const result = await adapter.replaceOne(testQuery, testObject); + expect(result).not.toEqual(_id); + expect(result).toEqual(testObject.id); + }); + + test('should throw an error calling replaceOne for a nonexistant record and upsert=false', async () => { + const testObject: TestType = { id: '12345', foo: 'bar' }; + replaceOne.mockResolvedValue({ + acknowledged: false, + matchedCount: 0, + modifiedCount: 0, + upsertedId: null, + }); + await expect(adapter.replaceOne(testQuery, testObject)).rejects.toThrow( + 'No matching item found.', + ); + }); + + test('should return a single Id from replaceOne when upsert = true', async () => { + const testObject: TestType = { id: '12345', foo: 'bar' }; + const _id = 'mongoGeneratedId'; + + replaceOne.mockResolvedValue({ + acknowledged: true, + matchedCount: 0, + modifiedCount: 1, + upsertedId: _id, + }); + const result = await adapter.replaceOne(testQuery, testObject, true); + expect(result).toEqual(testObject.id); + expect(result).not.toEqual(_id); + }); + + test('should throw an error if replaceOne does not match.', async () => { + const testObject: TestType = { id: '12345', foo: 'bar' }; + replaceOne.mockResolvedValue({ + acknowledged: false, + matchedCount: 0, + modifiedCount: 0, + upsertedId: null, + }); + await expect(adapter.replaceOne(testQuery, testObject, true)).rejects.toThrow( + 'Failed to insert document into database.', + ); }); test('should return a single Id from insertOne', async () => { const id = '123456'; - insertOne.mockResolvedValue({ acknowdledged: true, insertedId: id }); + insertOne.mockResolvedValue({ acknowledged: true, insertedId: id }); const result = await adapter.insertOne({}); expect(result.split('-').length).toEqual(5); }); + test('should throw an error if insertOne does not insert.', async () => { + insertOne.mockResolvedValue({ acknowledged: false }); + await expect(adapter.insertOne({})).rejects.toThrow('Failed to insert document into database.'); + }); + test('should return a list of Ids from insertMany', async () => { const ids = ['0', '1', '2', '3', '4']; insertMany.mockResolvedValue({ - acknowdledged: true, + acknowledged: true, insertedIds: ids, insertedCount: ids.length, }); @@ -127,7 +208,7 @@ describe('Mongo adapter', () => { }); test('should return a count of 1 for 1 item deleted', async () => { - deleteOne.mockResolvedValue({ acknowdledged: true, deletedCount: 1 }); + deleteOne.mockResolvedValue({ acknowledged: true, deletedCount: 1 }); const result = await adapter.deleteOne(testQuery); expect(result).toEqual(1); }); @@ -140,7 +221,7 @@ describe('Mongo adapter', () => { }); test('should return a count of 5 for 5 items deleted', async () => { - deleteMany.mockResolvedValue({ acknowdledged: true, deletedCount: 5 }); + deleteMany.mockResolvedValue({ acknowledged: true, deletedCount: 5 }); const result = await adapter.deleteMany(testQuery); expect(result).toEqual(5); }); @@ -158,8 +239,15 @@ describe('Mongo adapter', () => { expect(result).toEqual(5); }); + test('should return a count of 6 when countAllDocuments is called and there are 6 documents', async () => { + countDocuments.mockResolvedValue(6); + const result = await adapter.countAllDocuments(); + expect(result).toEqual(6); + }); + test('should throw CamsError when some but not all items are inserted', async () => { insertMany.mockResolvedValue({ + acknowledged: true, insertedIds: { one: 'one', two: 'two', @@ -176,22 +264,6 @@ describe('Mongo adapter', () => { expect(async () => await adapter.insertMany([{}, {}, {}, {}])).rejects.toThrow(error); }); - test('should handle acknowledged == false', async () => { - const response = { acknowledged: false }; - const error = new UnknownError(MODULE_NAME, { - message: 'Operation returned Not Acknowledged.', - }); - Object.values(spies).forEach((spy) => { - spy.mockResolvedValue(response); - }); - - await expect(adapter.replaceOne(testQuery, {})).rejects.toThrow(error); - await expect(adapter.insertOne({})).rejects.toThrow(error); - await expect(adapter.insertMany([{}])).rejects.toThrow(error); - await expect(adapter.deleteOne(testQuery)).rejects.toThrow(error); - await expect(adapter.deleteMany(testQuery)).rejects.toThrow(error); - }); - test('should handle errors', async () => { const originalError = new Error('Test Exception'); const expectedError = new UnknownError(MODULE_NAME, { originalError }); @@ -206,6 +278,8 @@ describe('Mongo adapter', () => { await expect(adapter.deleteMany(testQuery)).rejects.toThrow(expectedError); await expect(adapter.find(testQuery)).rejects.toThrow(expectedError); await expect(adapter.countDocuments(testQuery)).rejects.toThrow(expectedError); + await expect(adapter.countAllDocuments()).rejects.toThrow(expectedError); await expect(adapter.findOne(testQuery)).rejects.toThrow(expectedError); + await expect(adapter.getAll()).rejects.toThrow(expectedError); }); }); diff --git a/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.ts b/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.ts index 3d4285085..9f069ac09 100644 --- a/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.ts +++ b/backend/functions/lib/adapters/gateways/mongo/utils/mongo-adapter.ts @@ -10,21 +10,11 @@ import { randomUUID } from 'crypto'; export class MongoCollectionAdapter implements DocumentCollectionAdapter { private collectionHumble: CollectionHumble; - private readonly notAcknowledged: UnknownError; private readonly moduleName: string; - private testAcknowledged(result: { acknowledged?: boolean }) { - if (result.acknowledged === false) { - throw this.notAcknowledged; - } - } - constructor(moduleName: string, collection: CollectionHumble) { this.collectionHumble = collection; this.moduleName = moduleName; - this.notAcknowledged = new UnknownError(this.moduleName, { - message: 'Operation returned Not Acknowledged.', - }); } public async find(query: ConditionOrConjunction, sort?: Sort): Promise { @@ -79,9 +69,16 @@ export class MongoCollectionAdapter implements DocumentCollectionAdapter { const mongoItem = createOrGetId(item); try { const result = await this.collectionHumble.replaceOne(mongoQuery, mongoItem, upsert); - this.testAcknowledged(result); - - return result.upsertedId?.toString(); + if (!result.acknowledged) { + if (upsert) { + throw new UnknownError(this.moduleName, { + message: 'Failed to insert document into database.', + }); + } else { + throw new NotFoundError(this.moduleName, { message: 'No matching item found.' }); + } + } + return mongoItem.id; } catch (originalError) { throw getCamsError(originalError, this.moduleName); } @@ -90,11 +87,15 @@ export class MongoCollectionAdapter implements DocumentCollectionAdapter { public async insertOne(item: T) { try { const cleanItem = removeIds(item); - const identifiableItem = createOrGetId(cleanItem); - const result = await this.collectionHumble.insertOne(identifiableItem); - this.testAcknowledged(result); + const mongoItem = createOrGetId(cleanItem); + const result = await this.collectionHumble.insertOne(mongoItem); + if (!result.acknowledged) { + throw new UnknownError(this.moduleName, { + message: 'Failed to insert document into database.', + }); + } - return identifiableItem.id; + return mongoItem.id; } catch (originalError) { throw getCamsError(originalError, this.moduleName); } @@ -107,7 +108,6 @@ export class MongoCollectionAdapter implements DocumentCollectionAdapter { return createOrGetId(cleanItem); }); const result = await this.collectionHumble.insertMany(mongoItems); - this.testAcknowledged(result); const insertedIds = mongoItems.map((item) => item.id); if (insertedIds.length !== result.insertedCount) { throw new CamsError(this.moduleName, { @@ -125,7 +125,6 @@ export class MongoCollectionAdapter implements DocumentCollectionAdapter { const mongoQuery = toMongoQuery(query); try { const result = await this.collectionHumble.deleteOne(mongoQuery); - this.testAcknowledged(result); if (result.deletedCount !== 1) { throw new NotFoundError(this.moduleName, { message: 'No items deleted' }); } @@ -140,7 +139,6 @@ export class MongoCollectionAdapter implements DocumentCollectionAdapter { const mongoQuery = toMongoQuery(query); try { const result = await this.collectionHumble.deleteMany(mongoQuery); - this.testAcknowledged(result); if (result.deletedCount < 1) { throw new NotFoundError(this.moduleName, { message: 'No items deleted' }); } @@ -190,10 +188,11 @@ function createOrGetId(item: CamsItem): CamsItem { return mongoItem; } -function removeIds(item: CamsItem): CamsItem { +// TODO: sus. +export function removeIds(item: CamsItem): CamsItem { const cleanItem = { ...item }; - delete cleanItem?._id; - delete cleanItem?.id; + delete cleanItem._id; + delete cleanItem.id; return cleanItem; } diff --git a/backend/functions/lib/controllers/cases/cases.controller.test.ts b/backend/functions/lib/controllers/cases/cases.controller.test.ts index 1d5461859..edef2a3ee 100644 --- a/backend/functions/lib/controllers/cases/cases.controller.test.ts +++ b/backend/functions/lib/controllers/cases/cases.controller.test.ts @@ -10,6 +10,7 @@ import { } from '../../testing/mock-data/cams-http-request-helper'; import { createMockApplicationContext } from '../../testing/testing-utilities'; import { ResourceActions } from '../../../../../common/src/cams/actions'; +import { CamsError } from '../../common-errors/cams-error'; describe('cases controller test', () => { const caseId1 = '081-11-06541'; @@ -31,7 +32,7 @@ describe('cases controller test', () => { }); describe('handleRequest test', () => { - test('should call getCaseDetails', () => { + test('should call getCaseDetails', async () => { const spy = jest .spyOn(CasesController.prototype, 'getCaseDetails') .mockResolvedValue({ data: MockData.getCaseDetail() }); @@ -43,9 +44,37 @@ describe('cases controller test', () => { params: { caseId: caseId1 }, }; - controller.handleRequest(context); + await controller.handleRequest(context); expect(spy).toHaveBeenCalledWith({ caseId: caseId1 }); }); + + test('should search for cases', async () => { + const searchSpy = jest + .spyOn(CasesController.prototype, 'searchCases') + .mockResolvedValue({ data: MockData.buildArray(MockData.getCaseBasics, 4) }); + context.request = { + method: 'GET', + url: 'http://localhost:3000', + headers: {}, + query: {}, + params: {}, + }; + await controller.handleRequest(context); + expect(searchSpy).toHaveBeenCalledWith(context.request); + }); + + test('should throw CamsError', async () => { + const error = new CamsError('test', { message: 'some CAMS error' }); + jest.spyOn(CasesController.prototype, 'searchCases').mockRejectedValue(error); + context.request = { + method: 'GET', + url: 'http://localhost:3000', + headers: {}, + query: {}, + params: {}, + }; + await expect(controller.handleRequest(context)).rejects.toThrow(error); + }); }); describe('getCaseDetails', () => { diff --git a/backend/functions/lib/controllers/offices/offices.controller.test.ts b/backend/functions/lib/controllers/offices/offices.controller.test.ts index 61d0a34ec..25f75b77a 100644 --- a/backend/functions/lib/controllers/offices/offices.controller.test.ts +++ b/backend/functions/lib/controllers/offices/offices.controller.test.ts @@ -115,6 +115,23 @@ describe('offices controller tests', () => { expect(getOfficeAttorneys).toHaveBeenCalledWith(applicationContext, officeCode); }); + test('should call getOffices', async () => { + getOffices = jest.fn().mockResolvedValue([]); + + const camsHttpRequest = mockCamsHttpRequest(); + applicationContext.request = camsHttpRequest; + + const controller = new OfficesController(); + const attorneys = await controller.handleRequest(applicationContext); + expect(attorneys).toEqual( + expect.objectContaining({ + body: { meta: expect.objectContaining({ self: expect.any(String) }), data: [] }, + }), + ); + expect(getOfficeAttorneys).not.toHaveBeenCalled(); + expect(getOffices).toHaveBeenCalledWith(applicationContext); + }); + test('should throw error for unsupported subResource', async () => { getOffices = jest .fn() diff --git a/backend/functions/lib/controllers/orders/orders.controller.test.ts b/backend/functions/lib/controllers/orders/orders.controller.test.ts index 9981b28a5..59a06eadc 100644 --- a/backend/functions/lib/controllers/orders/orders.controller.test.ts +++ b/backend/functions/lib/controllers/orders/orders.controller.test.ts @@ -19,6 +19,8 @@ import { CamsHttpResponseInit, commonHeaders } from '../../adapters/utils/http-r import HttpStatusCodes from '../../../../../common/src/api/http-status-codes'; import { mockCamsHttpRequest } from '../../testing/mock-data/cams-http-request-helper'; import { ResponseBody } from '../../../../../common/src/api/response'; +import { NotFoundError } from '../../common-errors/not-found-error'; +import { BadRequestError } from '../../common-errors/bad-request'; const syncResponse: SyncOrdersStatus = { options: { @@ -110,6 +112,19 @@ describe('orders controller tests', () => { expect(updateOrderSpy).toHaveBeenCalledWith(applicationContext, id, orderTransfer); }); + test('should throw BadRequestError when updating an order and the id does not match data.id', async () => { + applicationContext.request = mockCamsHttpRequest(); + const expectedError = new BadRequestError('ORDERS-CONTROLLER', { + message: 'Cannot update order. ID of order does not match ID of request.', + }); + const failTransfer = { ...orderTransfer }; + failTransfer.id = crypto.randomUUID().toString(); + const controller = new OrdersController(applicationContext); + expect( + async () => await controller.updateOrder(applicationContext, id, failTransfer), + ).rejects.toThrow(expectedError); + }); + test('should get suggested cases', async () => { const suggestedCases = [CASE_SUMMARIES[0]]; @@ -162,28 +177,22 @@ describe('orders controller tests', () => { applicationContext.request = mockCamsHttpRequest({ params: { caseId: 'mockId' } }); await expect(controller.getSuggestedCases(applicationContext)).rejects.toThrow(unknownError); }); + test('should throw UnknownError if any other error is encountered', async () => { + const originalError = new Error('Test'); + const unknownError = new UnknownError('TEST', { originalError }); + jest.spyOn(OrdersUseCase.prototype, 'getOrders').mockRejectedValue(originalError); + jest.spyOn(OrdersUseCase.prototype, 'updateTransferOrder').mockRejectedValue(originalError); + jest.spyOn(OrdersUseCase.prototype, 'syncOrders').mockRejectedValue(originalError); + jest.spyOn(OrdersUseCase.prototype, 'getSuggestedCases').mockRejectedValue(originalError); - test('should call reject consolidation', async () => { - const mockConsolidationOrder = MockData.getConsolidationOrder(); - const mockConsolidationOrderActionRejection: ConsolidationOrderActionRejection = { - ...mockConsolidationOrder, - rejectedCases: [mockConsolidationOrder.childCases[0].caseId], - leadCase: undefined, - }; - const request = mockCamsHttpRequest({ body: mockConsolidationOrderActionRejection }); - applicationContext.request = request; - const expectedBody: ResponseBody = { data: [mockConsolidationOrder] }; - jest - .spyOn(OrdersUseCase.prototype, 'rejectConsolidation') - .mockResolvedValue([mockConsolidationOrder]); const controller = new OrdersController(applicationContext); - - const actualResult = await controller.rejectConsolidation(applicationContext); - expect(actualResult).toEqual({ - headers: commonHeaders, - statusCode: HttpStatusCodes.OK, - body: expectedBody, - }); + await expect(controller.getOrders(applicationContext)).rejects.toThrow(unknownError); + await expect(controller.updateOrder(applicationContext, id, orderTransfer)).rejects.toThrow( + unknownError, + ); + await expect(controller.syncOrders(applicationContext)).rejects.toThrow(unknownError); + applicationContext.request = mockCamsHttpRequest({ params: { caseId: 'mockId' } }); + await expect(controller.getSuggestedCases(applicationContext)).rejects.toThrow(unknownError); }); test('should call reject consolidation and handle error', async () => { @@ -252,8 +261,13 @@ describe('orders controller tests', () => { await expect(controller.approveConsolidation(applicationContext)).rejects.toThrow(CamsError); // setup missing lead case - mockConsolidationOrderActionApproval.approvedCases = ['11-11111']; - mockConsolidationOrderActionApproval.leadCase = undefined; + const mockConsolidationOrderActionApproval3: ConsolidationOrderActionApproval = { + ...mockConsolidationOrder, + approvedCases: [mockConsolidationOrder.childCases[0].caseId], + leadCase: undefined, + }; + const request3 = mockCamsHttpRequest({ body: mockConsolidationOrderActionApproval3 }); + applicationContext.request = request3; await expect(controller.approveConsolidation(applicationContext)).rejects.toThrow(CamsError); }); @@ -266,6 +280,20 @@ describe('orders controller exception tests', () => { applicationContext = await createMockApplicationContext(); }); + test('should throw unknown error when path is invalid', async () => { + const expectedUrl = 'http://mockhost/api/failure'; + const expectedError = new NotFoundError('ORDERS-CONTROLLER', { + message: 'Could not map requested path to action ' + expectedUrl, + }); + const request = mockCamsHttpRequest({ url: expectedUrl }); + applicationContext.request = request; + const controller = new OrdersController(applicationContext); + + expect(async () => await controller.handleRequest(applicationContext)).rejects.toThrow( + expectedError, + ); + }); + test('should wrap unexpected errors with CamsError', async () => { const error = new Error('GenericError'); const camsError = new UnknownError('TEST-MODULE', { originalError: error }); diff --git a/backend/functions/lib/use-cases/offices/offices.test.ts b/backend/functions/lib/use-cases/offices/offices.test.ts index 6e20e9159..bd731ee74 100644 --- a/backend/functions/lib/use-cases/offices/offices.test.ts +++ b/backend/functions/lib/use-cases/offices/offices.test.ts @@ -4,13 +4,14 @@ import { createMockApplicationContext } from '../../testing/testing-utilities'; import * as factory from '../../factory'; import OktaUserGroupGateway from '../../adapters/gateways/okta/okta-user-group-gateway'; import { UserGroupGatewayConfig } from '../../adapters/types/authorization'; -import { CamsUserGroup, CamsUserReference } from '../../../../../common/src/cams/users'; +import { CamsUserGroup, Staff } from '../../../../../common/src/cams/users'; import MockData from '../../../../../common/src/cams/test-utilities/mock-data'; import { USTP_OFFICES_ARRAY } from '../../../../../common/src/cams/offices'; import { TRIAL_ATTORNEYS } from '../../../../../common/src/cams/test-utilities/attorneys.mock'; import AttorneysList from '../attorneys'; import { MockMongoRepository } from '../../testing/mock-gateways/mock-mongo.repository'; import { MockOfficesRepository } from '../../testing/mock-gateways/mock.offices.repository'; +import { CamsRole } from '../../../../../common/src/cams/roles'; describe('offices use case tests', () => { let applicationContext: ApplicationContext; @@ -86,9 +87,12 @@ describe('offices use case tests', () => { const seattleGroup: CamsUserGroup = { id: 'three', name: 'USTP CAMS Region 18 Office Seattle' }; const seattleOfficeCode = 'USTP_CAMS_Region_18_Office_Seattle'; const trialAttorneyGroup: CamsUserGroup = { id: 'four', name: 'USTP CAMS Trial Attorney' }; - const users: CamsUserReference[] = MockData.buildArray(MockData.getAttorneyUser, 4); + const dataVerifierGroup: CamsUserGroup = { id: 'five', name: 'USTP CAMS Data Verifier' }; + const users: Staff[] = MockData.buildArray(MockData.getAttorneyUser, 4); const seattleUsers = [users[0], users[1], users[3]]; const attorneyUsers = [users[1], users[2], users[3]]; + const dataVerifierUsers = [users[3]]; + users[3].roles.push(CamsRole.DataVerifier); jest .spyOn(OktaUserGroupGateway, 'getUserGroups') .mockResolvedValue([ @@ -96,6 +100,7 @@ describe('offices use case tests', () => { { id: 'two', name: 'group-b' }, seattleGroup, trialAttorneyGroup, + dataVerifierGroup, ]); jest .spyOn(OktaUserGroupGateway, 'getUserGroupUsers') @@ -109,6 +114,8 @@ describe('offices use case tests', () => { return Promise.resolve(seattleUsers); } else if (group.name === 'USTP CAMS Trial Attorney') { return Promise.resolve(attorneyUsers); + } else if (group.name === 'USTP CAMS Data Verifier') { + return Promise.resolve(dataVerifierUsers); } else if (group.name === 'group-a' || group.name === 'group-b') { throw new Error('Tried to retrieve users for invalid group.'); } diff --git a/backend/functions/lib/use-cases/offices/offices.ts b/backend/functions/lib/use-cases/offices/offices.ts index 7aafbe4b8..06ca6059d 100644 --- a/backend/functions/lib/use-cases/offices/offices.ts +++ b/backend/functions/lib/use-cases/offices/offices.ts @@ -80,6 +80,7 @@ export class OfficesUseCase { if (!userMap.has(user.id)) { userMap.set(user.id, user); } + // TODO: the following line is partially covered and I cannot see how we would reach the negative case const userWithRoles = userMap.has(user.id) ? userMap.get(user.id) : user; office.staff.push(userWithRoles); await repository.putOfficeStaff(office.officeCode, userWithRoles); diff --git a/user-interface/src/lib/models/api2.test.ts b/user-interface/src/lib/models/api2.test.ts index 5038a9b23..9265145df 100644 --- a/user-interface/src/lib/models/api2.test.ts +++ b/user-interface/src/lib/models/api2.test.ts @@ -94,8 +94,10 @@ describe('_Api2 functions', async () => { await callApiFunction(api2.Api2.getCaseDocket, 'some-id', api); await callApiFunction(api2.Api2.getCaseHistory, 'some-id', api); await callApiFunction(api2.Api2.getCaseSummary, 'some-id', api); + await callApiFunction(api2.Api2.getCourts, null, api); await callApiFunction(api2.Api2.getMe, null, api); await callApiFunction(api2.Api2.getOffices, null, api); + await callApiFunction(api2.Api2.getOfficeAttorneys, null, api); await callApiFunction(api2.Api2.getOrders, null, api); await callApiFunction(api2.Api2.getOrderSuggestions, 'some-id', api); await callApiFunction(api2.Api2.putConsolidationOrderApproval, 'some-id', api);