Skip to content

Commit

Permalink
Merge pull request #923 from US-Trustee-Program/CAMS-283-sync-timer-f…
Browse files Browse the repository at this point in the history
…unction

CAMS-283 - Add office staff sync timer
  • Loading branch information
btposey authored Sep 20, 2024
2 parents 5310d5c + fd7cc68 commit 8642575
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 51 deletions.
4 changes: 4 additions & 0 deletions backend/functions/lib/controllers/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ import { CamsHttpResponseInit } from '../adapters/utils/http-response';
export interface CamsController {
handleRequest(context: ApplicationContext): Promise<CamsHttpResponseInit<object | undefined>>;
}

export interface CamsTimerController {
handleTimer(context: ApplicationContext): Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import { OfficesController } from './offices.controller';
import { OFFICES } from '../../../../../common/src/cams/test-utilities/offices.mock';
import { CamsError } from '../../common-errors/cams-error';
import { mockCamsHttpRequest } from '../../testing/mock-data/cams-http-request-helper';
import { UnknownError } from '../../common-errors/unknown-error';

let getOffices = jest.fn();
let getOfficeAttorneys = jest.fn();
const syncOfficeStaff = jest.fn();

jest.mock('../../use-cases/offices/offices', () => {
return {
OfficesUseCase: jest.fn().mockImplementation(() => {
return {
getOffices,
getOfficeAttorneys,
syncOfficeStaff,
};
}),
};
Expand All @@ -30,6 +33,20 @@ describe('offices controller tests', () => {
jest.restoreAllMocks();
});

test('should return successful when handleTimer is called', async () => {
const controller = new OfficesController();
await expect(controller.handleTimer(applicationContext)).resolves.toBeFalsy();
expect(syncOfficeStaff).toHaveBeenCalled();
});

test('should throw error when handleTimer throws', async () => {
const error = new UnknownError('TEST_MODULE');
syncOfficeStaff.mockRejectedValue(error);
const controller = new OfficesController();
await expect(controller.handleTimer(applicationContext)).rejects.toThrow(error);
expect(syncOfficeStaff).toHaveBeenCalled();
});

test('should return successful response', async () => {
getOffices = jest.fn().mockResolvedValue(OFFICES);

Expand Down
12 changes: 10 additions & 2 deletions backend/functions/lib/controllers/offices/offices.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@ import { ApplicationContext } from '../../adapters/types/basic';
import { OfficeDetails } from '../../../../../common/src/cams/courts';
import { CamsHttpResponseInit, httpSuccess } from '../../adapters/utils/http-response';
import { getCamsError } from '../../common-errors/error-utilities';
import { CamsController } from '../controller';
import { CamsController, CamsTimerController } from '../controller';
import { CamsUserReference } from '../../../../../common/src/cams/users';
import { BadRequestError } from '../../common-errors/bad-request';

const MODULE_NAME = 'OFFICES-CONTROLLER';

export class OfficesController implements CamsController {
export class OfficesController implements CamsController, CamsTimerController {
private readonly useCase: OfficesUseCase;

constructor() {
this.useCase = new OfficesUseCase();
}

public async handleTimer(context: ApplicationContext): Promise<void> {
try {
await this.useCase.syncOfficeStaff(context);
} catch (originalError) {
throw getCamsError(originalError, MODULE_NAME);
}
}

public async handleRequest(
context: ApplicationContext,
): Promise<CamsHttpResponseInit<OfficeDetails[] | CamsUserReference[]>> {
Expand Down
31 changes: 21 additions & 10 deletions backend/functions/lib/controllers/orders/orders.controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ describe('orders controller tests', () => {
jest.clearAllMocks();
});

test('should return successful when handleTimer is called', async () => {
const syncOrdersSpy = jest
.spyOn(OrdersUseCase.prototype, 'syncOrders')
.mockResolvedValue(syncResponse);

const controller = new OrdersController(applicationContext);
await expect(controller.handleTimer(applicationContext)).resolves.toBeFalsy();
expect(syncOrdersSpy).toHaveBeenCalled();
});

test('should throw error when handleTimer throws', async () => {
const error = new UnknownError('TEST_MODULE');
const syncOrdersSpy = jest
.spyOn(OrdersUseCase.prototype, 'syncOrders')
.mockRejectedValue(error);

const controller = new OrdersController(applicationContext);
await expect(controller.handleTimer(applicationContext)).rejects.toThrow(error);
expect(syncOrdersSpy).toHaveBeenCalled();
});

test('should get orders', async () => {
const mockRead = jest
.spyOn(MockHumbleQuery.prototype, 'fetchAll')
Expand Down Expand Up @@ -95,16 +116,6 @@ describe('orders controller tests', () => {
expect(updateOrderSpy).toHaveBeenCalledWith(applicationContext, id, orderTransfer);
});

test('should sync orders', async () => {
const syncOrdersSpy = jest
.spyOn(OrdersUseCase.prototype, 'syncOrders')
.mockResolvedValue(syncResponse);

const controller = new OrdersController(applicationContext);
await controller.syncOrders(applicationContext);
expect(syncOrdersSpy).toHaveBeenCalledWith(applicationContext, undefined);
});

test('should get suggested cases', async () => {
const suggestedCases = [CASE_SUMMARIES[0]];

Expand Down
17 changes: 11 additions & 6 deletions backend/functions/lib/controllers/orders/orders.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { BadRequestError } from '../../common-errors/bad-request';
import { CamsHttpResponseInit, httpSuccess } from '../../adapters/utils/http-response';
import { getCamsError } from '../../common-errors/error-utilities';
import HttpStatusCodes from '../../../../../common/src/api/http-status-codes';
import { CamsController } from '../controller';
import { CamsController, CamsTimerController } from '../controller';
import { NotFoundError } from '../../common-errors/not-found-error';

const MODULE_NAME = 'ORDERS-CONTROLLER';
Expand All @@ -33,7 +33,7 @@ export type UpdateOrderResponse = CamsHttpResponseInit;
export type SyncOrdersResponse = CamsHttpResponseInit<SyncOrdersStatus>;
export type ManageConsolidationResponse = CamsHttpResponseInit<ConsolidationOrder[]>;

export class OrdersController implements CamsController {
export class OrdersController implements CamsController, CamsTimerController {
private readonly useCase: OrdersUseCase;

constructor(context: ApplicationContext) {
Expand All @@ -46,13 +46,18 @@ export class OrdersController implements CamsController {
getConsolidationOrdersRepository(context),
);
}

public async handleTimer(context: ApplicationContext): Promise<void> {
try {
await this.useCase.syncOrders(context);
} catch (originalError) {
throw getCamsError(originalError, MODULE_NAME);
}
}

public async handleRequest(
context: ApplicationContext,
): Promise<CamsHttpResponseInit<CaseSummary[] | Order[] | SyncOrdersStatus | undefined>> {
if (!context.request) {
return this.syncOrders(context);
}

const simplePath = new URL(context.request.url).pathname.split('/')[2];

switch (simplePath) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { LoggerImpl } from '../lib/adapters/services/logger.service';
import { CamsError } from '../lib/common-errors/cams-error';
import timerTrigger from './office-staff-sync.function';
import { Timer } from '@azure/functions';
import { createMockAzureFunctionContext } from '../azure/testing-helpers';
import { OfficesController } from '../lib/controllers/offices/offices.controller';

describe('Office Staff Sync Function tests', () => {
const context = createMockAzureFunctionContext();
const timer: Timer = {
isPastDue: false,
schedule: {
adjustForDST: false,
},
scheduleStatus: {
last: '',
next: '',
lastUpdated: '',
},
};

test('Should call offices controller method handleTimer', async () => {
const handleTimer = jest
.spyOn(OfficesController.prototype, 'handleTimer')
.mockImplementation(() => Promise.resolve());
await timerTrigger(timer, context);
expect(handleTimer).toHaveBeenCalled();
});

test('Should log a camsError if handleTimer throws a CamsError', async () => {
const handleTimer = jest
.spyOn(OfficesController.prototype, 'handleTimer')
.mockRejectedValue(new CamsError('TEST_MODULE', { message: 'error' }));
const camsError = jest.spyOn(LoggerImpl.prototype, 'camsError').mockImplementation(() => {});
await timerTrigger(timer, context);
expect(handleTimer).toHaveBeenCalled();
expect(camsError).toHaveBeenCalled();
});
});
31 changes: 31 additions & 0 deletions backend/functions/office-staff-sync/office-staff-sync.function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as dotenv from 'dotenv';
import { app, InvocationContext, Timer } from '@azure/functions';
import ContextCreator from '../azure/application-context-creator';
import { initializeApplicationInsights } from '../azure/app-insights';
import { toAzureError } from '../azure/functions';
import { OfficesController } from '../lib/controllers/offices/offices.controller';

dotenv.config();

initializeApplicationInsights();

const MODULE_NAME = 'OFFICE-STAFF-SYNC-FUNCTION';

export default async function timerTrigger(
_myTimer: Timer,
invocationContext: InvocationContext,
): Promise<void> {
const logger = ContextCreator.getLogger(invocationContext);
try {
const appContext = await ContextCreator.getApplicationContext({ invocationContext, logger });
const controller = new OfficesController();
await controller.handleTimer(appContext);
} catch (error) {
toAzureError(logger, MODULE_NAME, error);
}
}

app.timer('office-staff-sync', {
schedule: '0 0 * * * *',
handler: timerTrigger,
});
43 changes: 11 additions & 32 deletions backend/functions/orders-sync/orders-sync.function.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
import { LoggerImpl } from '../lib/adapters/services/logger.service';
import { CamsError } from '../lib/common-errors/cams-error';
import { OrdersController } from '../lib/controllers/orders/orders.controller';
import { SyncOrdersStatus } from '../lib/use-cases/orders/orders';
import timerTrigger from './orders-sync.function';
import { Timer } from '@azure/functions';
import { buildTestResponseSuccess, createMockAzureFunctionContext } from '../azure/testing-helpers';

const syncResponse: SyncOrdersStatus = {
options: {
txIdOverride: '10',
},
initialSyncState: {
documentType: 'ORDERS_SYNC_STATE',
txId: '464',
id: '28e35739-58cd-400b-9d4b-26969773618b',
},
finalSyncState: {
documentType: 'ORDERS_SYNC_STATE',
txId: '464',
id: '28e35739-58cd-400b-9d4b-26969773618b',
},
length: 13,
startingTxId: '10',
maxTxId: '464',
};
import { createMockAzureFunctionContext } from '../azure/testing-helpers';
import { OrdersController } from '../lib/controllers/orders/orders.controller';

describe('Orders Sync Function tests', () => {
const context = createMockAzureFunctionContext();
Expand All @@ -39,22 +19,21 @@ describe('Orders Sync Function tests', () => {
},
};

test('Should call orders controller method syncOrders', async () => {
const { camsHttpResponse } = buildTestResponseSuccess<SyncOrdersStatus>({ data: syncResponse });
const syncOrders = jest
.spyOn(OrdersController.prototype, 'syncOrders')
.mockResolvedValue(camsHttpResponse);
test('Should call orders controller method handleTimer', async () => {
const handleTimer = jest
.spyOn(OrdersController.prototype, 'handleTimer')
.mockImplementation(() => Promise.resolve());
await timerTrigger(timer, context);
expect(syncOrders).toHaveBeenCalled();
expect(handleTimer).toHaveBeenCalled();
});

test('Should log a camsError if syncOrders throws a CamsError', async () => {
const syncOrders = jest
.spyOn(OrdersController.prototype, 'syncOrders')
test('Should log a camsError if handleTimer throws a CamsError', async () => {
const handleTimer = jest
.spyOn(OrdersController.prototype, 'handleTimer')
.mockRejectedValue(new CamsError('TEST_MODULE', { message: 'error' }));
const camsError = jest.spyOn(LoggerImpl.prototype, 'camsError').mockImplementation(() => {});
await timerTrigger(timer, context);
expect(syncOrders).toHaveBeenCalled();
expect(handleTimer).toHaveBeenCalled();
expect(camsError).toHaveBeenCalled();
});
});
2 changes: 1 addition & 1 deletion backend/functions/orders-sync/orders-sync.function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default async function timerTrigger(
try {
const appContext = await ContextCreator.getApplicationContext({ invocationContext, logger });
const ordersController = new OrdersController(appContext);
await ordersController.handleRequest(appContext);
await ordersController.handleTimer(appContext);
} catch (error) {
toAzureError(logger, MODULE_NAME, error);
}
Expand Down

0 comments on commit 8642575

Please sign in to comment.