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

CAMS-461 - Migrate consolidations from ACMS #1043

Closed
wants to merge 44 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3445e39
WIP - POC for durable functions
btposey Nov 14, 2024
e16d894
Finish durable function proof of concept
jamesobrooks Nov 15, 2024
174440e
Initial Refactor of POC
fmaddenflx Nov 15, 2024
c6fca99
Started work on use cases
fmaddenflx Nov 16, 2024
36a789b
Move transform and load to use case
jamesobrooks Nov 16, 2024
1dd6c69
WIP - Move to using the controller
jamesobrooks Nov 18, 2024
0b6acb2
Refactor factory to return singleton references
btposey Nov 18, 2024
6fae00d
WIP: Fleshing out implementation of Acms Consolidations
fmaddenflx Nov 19, 2024
3a1970c
WIP - Implement ACMS Gateway
btposey Nov 19, 2024
12d828c
Apply consistent naming to consolidation migration
btposey Nov 20, 2024
68fa8d2
WIP - Migrate consolidations - Add leadCaseId query
btposey Nov 20, 2024
2e4a659
WIP - migrate consolidations - transform and load logic
jamesobrooks Nov 20, 2024
d32fcd9
WIP - Associate cases from ACMS consolidations
jamesobrooks Nov 20, 2024
a78bae3
Write ACMS consolidation histories to CAMS
btposey Nov 21, 2024
6358f1c
Fix input parameter to migrateConsolidation
btposey Nov 21, 2024
ca3b2d4
Add a todo for using upsert
jamesobrooks Nov 21, 2024
e6b5bca
Stub processing report for migrateConsolidation
btposey Nov 21, 2024
36b6b30
corrected ACMS naming and access for USTP environment
amorrow-flexion Nov 22, 2024
2124acd
Implement rudimentary fan-in for migration process
btposey Nov 22, 2024
71c02bc
Added error logging to the migration process
fmaddenflx Nov 22, 2024
9626d7c
Remove test.only
btposey Nov 22, 2024
d135997
WIP - Begin testing orchestrator
jamesobrooks Nov 22, 2024
18cde3c
Add history to migrated ACMS consolidations
btposey Nov 22, 2024
cf25769
Remove console.log
btposey Nov 22, 2024
291953a
Add logic to not migrate previously migrated cases
btposey Nov 22, 2024
cb8f798
Ignore orchestrators and client
jamesobrooks Nov 25, 2024
909fa2c
Add additional tests for acms orders use case
btposey Nov 25, 2024
262f10c
WIP: Test coverage.
fmaddenflx Nov 25, 2024
7e45209
Test coverage on abstract-mssql-client
fmaddenflx Nov 27, 2024
85011aa
updated TaskHubName for durable functions within slots
amorrow-flexion Nov 27, 2024
46d1954
increased coverage on acms.gateway
amorrow-flexion Nov 27, 2024
32d234f
corrected var for MyTaskHub env var
amorrow-flexion Nov 27, 2024
8ea79af
properly renamed acms.gateway admin consol test
amorrow-flexion Nov 27, 2024
730b20e
Properly translate chapters to query
btposey Nov 26, 2024
7290f0f
renamed Acms types, added note for var keyword in db tests
amorrow-flexion Nov 27, 2024
c44007e
fixed named of Acms imports in acms.gateway
amorrow-flexion Nov 27, 2024
8cd12a0
Wrap errors encounted in case details retrieval
jamesobrooks Nov 27, 2024
b74d858
Generate new package-lock
jamesobrooks Dec 2, 2024
218414b
Do not close connection until we should
jamesobrooks Dec 2, 2024
9ccefb7
Fix broken test from repo singleton refactor
btposey Dec 3, 2024
d34f2ba
Converted case mongo repos to new instance management model.
fmaddenflx Dec 3, 2024
f92132c
Refactor repos to use consistent singleton pattern
btposey Dec 3, 2024
8396223
updated CaseHistory use case to allow for release
amorrow-flexion Dec 3, 2024
de52a5f
updated runtimestate repo to use deferclose
amorrow-flexion Dec 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/functions/case-history/case-history.function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default async function handler(
logger,
request,
);
const caseHistoryController = new CaseHistoryController(applicationContext);
const caseHistoryController = new CaseHistoryController();
const responseBody = await caseHistoryController.handleRequest(applicationContext);
return toAzureSuccess(responseBody);
} catch (error) {
Expand Down
5 changes: 5 additions & 0 deletions backend/functions/host.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"extensions": {
"durableTask": {
"hubName": "%MyTaskHub%"
}
}
}
3 changes: 3 additions & 0 deletions backend/functions/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ module.exports = {
'lib/testing/testing-utilities.ts',
'jest.*config.js',
'lib/adapters/gateways/okta/HumbleVerifier.ts',
'migration/client/',
'migration/orchestration/',
'migration/loadConsolidations.ts',
],
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)', '!**/?(*.)+(integration).(spec|test).[jt]s?(x)'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { QueryResults, IDbConfig } from '../types/database';
import { ConnectionError, MSSQLError, RequestError } from 'mssql';
import { createMockApplicationContext } from '../../testing/testing-utilities';
import { AbstractMssqlClient } from './abstract-mssql-client';
import { ApplicationContext } from '../types/basic';

// Setting default Jest mocks for mssql
//NOTE: using const here causes these tests to error out with 'Cannot access {var} before initialization
// eslint-disable-next-line no-var
var connectionError = new ConnectionError('');
// eslint-disable-next-line no-var
var requestError = new RequestError('');
// eslint-disable-next-line no-var
var mssqlError = new MSSQLError('');
btposey marked this conversation as resolved.
Show resolved Hide resolved

type AggregateError = Error & {
errors?: Error[];
};

const mockClose = jest.fn();
const mockConnectionPoolClose = jest.fn();
const mockQuery = jest.fn();
const mockRequest = jest.fn().mockImplementation(() => ({
input: jest.fn(),
query: mockQuery,
}));
const mockConnect = jest.fn().mockImplementation(
(): Promise<unknown> =>
Promise.resolve({
request: mockRequest,
close: mockClose,
}),
);
jest.mock('mssql', () => {
return {
ConnectionError: jest.fn().mockReturnValue(connectionError),
RequestError: jest.fn().mockReturnValue(requestError),
MSSQLError: jest.fn().mockReturnValue(mssqlError),
ConnectionPool: jest.fn().mockImplementation(() => {
return {
close: mockConnectionPoolClose,
connect: mockConnect,
};
}),
};
});

class MssqlClient extends AbstractMssqlClient {
constructor(context: ApplicationContext) {
const config = { server: 'foo' } as IDbConfig;
super(context, config, 'Exception Tests');
}
}

describe('Tests database client exceptions', () => {
let client: MssqlClient;
let context: ApplicationContext;

beforeEach(async () => {
jest.resetModules();
context = await createMockApplicationContext();
client = new MssqlClient(context);
});

test('should handle miscelaneous mssql error exceptions', async () => {
const expectedErrorMessage = 'Test request error exception';
const requestError = new RequestError(expectedErrorMessage);
requestError.name = 'RequestError';
requestError.code = '';
requestError.message = expectedErrorMessage;
mockRequest.mockImplementation(() => {
throw requestError;
});

// method under test
const queryResult: QueryResults = await client.executeQuery(context, 'SELECT * FROM bar', []);

expect(mockRequest).toThrow(expectedErrorMessage);
expect(queryResult.success).toBeFalsy();
});

test('should handle known mssql MSSQLError exceptions', async () => {
const expectedErrorMessage = 'Test MSSQLError exception';
const requestError = new RequestError(expectedErrorMessage);
requestError.name = 'RequestError';
requestError.code = '';
requestError.message = expectedErrorMessage;
mockRequest.mockImplementation(() => {
const mssqlError = new MSSQLError({
name: '',
message: '',
});
// not totally sure why I have to set the error in this way to get it to work.
mssqlError.name = 'TestMSSQLMessage';
mssqlError.message = expectedErrorMessage;
mssqlError.code = '1';
mssqlError.originalError = { message: 'original error', name: 'originalError' };
throw mssqlError;
});

// method under test
const queryResult: QueryResults = await client.executeQuery(context, 'SELECT * FROM bar', []);

expect(mockRequest).toThrow(expectedErrorMessage);
expect(queryResult.success).toBeFalsy();
});

test('should handle known mssql ConnectionError exceptions', async () => {
const expectedErrorMessage = 'Test ConnectionError exception';
const context = await createMockApplicationContext();
const connectionError = new ConnectionError(expectedErrorMessage);
connectionError.code = '';
connectionError.name = 'ConnectionError';
connectionError.message = expectedErrorMessage;
mockConnect.mockImplementation(() => {
throw connectionError;
});

// method under test
const queryResult: QueryResults = await client.executeQuery(context, 'SELECT * FROM bar', []);
expect(mockConnect).toThrow(expectedErrorMessage);
expect(queryResult.success).toBeFalsy();
});

test('should handle known mssql ConnectionError exceptions with AggregateErrors', async () => {
const expectedErrorMessage = 'Test ConnectionError exception with AggregateErrors';
const connectionError = new ConnectionError(expectedErrorMessage);
connectionError.code = '';
connectionError.name = 'ConnectionError';
connectionError.message = expectedErrorMessage;
connectionError.originalError = {
message: '',
name: 'AggregateError',
errors: [
{ message: 'Something happen 01', name: '01', code: '' } as MSSQLError,
{
message: 'Something happen 02',
name: '02',
code: '',
errors: [
{
message: 'Agreggate error sub error 1',
name: '02.1',
code: '',
},
],
originalError: {
name: '03',
message: 'Nested aggregate errors',
errors: [
{ message: 'Nested aggregate error 04', name: '04', code: '' } as MSSQLError,
{ message: 'Nested aggregate error 05', name: '05', code: '' } as MSSQLError,
],
} as AggregateError,
} as ConnectionError,
],
} as AggregateError;
mockConnect.mockImplementation(() => {
throw connectionError;
});

const context = await createMockApplicationContext();
// method under test
const queryResult: QueryResults = await client.executeQuery(context, 'SELECT * FROM bar', []);

expect(mockConnect).toThrow(expectedErrorMessage);
expect(queryResult.success).toBeFalsy();
});

test('should close connection pool when calling close', async () => {
const context = await createMockApplicationContext();
const client = new MssqlClient(context);

await client.close();
expect(mockConnectionPoolClose).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { QueryResults, IDbConfig } from '../types/database';
import { createMockApplicationContext } from '../../testing/testing-utilities';
import { AbstractMssqlClient } from './abstract-mssql-client';
import { ApplicationContext } from '../types/basic';
import { IResult } from 'mssql';

type sqlConnect = {
request: () => void;
close: () => void;
query: () => void;
};

jest.mock('mssql', () => {
return {
ConnectionPool: jest.fn().mockImplementation(() => {
return {
connect: jest.fn().mockImplementation(
(): Promise<sqlConnect> =>
Promise.resolve({
request: jest.fn().mockImplementation(() => ({
input: jest.fn(),
query: jest
.fn()
.mockImplementation(
(): Promise<IResult<string>> =>
Promise.resolve({ recordset: 'test string' } as unknown as IResult<string>),
),
})),
close: jest.fn(),
query: jest
.fn()
.mockImplementation(
(): Promise<string> =>
Promise.resolve("this is not the string you're looking for"),
),
}),
),
};
}),
};
});

describe('Abstract MS-SQL client', () => {
test('should get appropriate results', async () => {
const applicationContext = await createMockApplicationContext();
// setup test
// execute method under test
const context = applicationContext;
const query = 'SELECT * FROM foo WHERE data = @param1 AND name=@param2';
const input = [
{
name: 'param1',
type: null,
value: 'dataValue',
},
{
name: 'param2',
type: null,
value: 'nameValue',
},
];

// execute method under test
class TestDbClient extends AbstractMssqlClient {
constructor(context: ApplicationContext, config: IDbConfig, childModuleName: string) {
super(context, config, childModuleName);
}
}
const client = new TestDbClient(context, context.config.dxtrDbConfig, 'TEST_MODULE');
const queryResult: QueryResults = await client.executeQuery<string>(context, query, input);

// assert
expect(queryResult).toEqual({ results: 'test string', message: '', success: true });
});
});
113 changes: 113 additions & 0 deletions backend/functions/lib/adapters/gateways/abstract-mssql-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { config, ConnectionError, ConnectionPool, MSSQLError, IResult } from 'mssql';
import { DbTableFieldSpec, IDbConfig, QueryResults } from '../types/database';
import { Closable, deferClose } from '../../defer-close';
import { ApplicationContext } from '../types/basic';

export abstract class AbstractMssqlClient implements Closable {
private connectionPool: ConnectionPool;
private readonly moduleName: string;

protected constructor(context: ApplicationContext, dbConfig: IDbConfig, childModuleName: string) {
this.moduleName = `ABSTRACT-MSSQL-CLIENT (${childModuleName})`;
this.connectionPool = new ConnectionPool(dbConfig as config);
deferClose(context, this);
}

public async executeQuery<T = unknown>(
context: ApplicationContext,
query: string,
input?: DbTableFieldSpec[],
): Promise<QueryResults> {
// we should do some sanitization here to eliminate sql injection issues
try {
const connection = await this.connectionPool.connect();
const request = connection.request();

if (typeof input != 'undefined') {
input.forEach((item) => {
request.input(item.name, item.type, item.value);
});
}
const result = (await request.query(query)) as IResult<T>;

const queryResults: QueryResults = {
results: result.recordset,
message: '',
success: true,
};

context.logger.info(this.moduleName, 'Closing connection.');

await connection.close();

return queryResults;
} catch (error) {
if (isConnectionError(error)) {
const errorMessages = [];
// No recursive function here. Limiting this to just 2 "errors" lists deep.
if (isAggregateError(error.originalError)) {
error.originalError.errors.reduce((acc, e) => {
if (isAggregateError(e)) {
e.errors.forEach((lowestE) => {
acc.push(lowestE.message);
});
} else {
acc.push(e.message);
}
return acc;
}, errorMessages);
}
errorMessages.push(error.message);
context.logger.error(this.moduleName, 'ConnectionError', { errorMessages });
} else if (isMssqlError(error)) {
const newError = {
error: {
name: error.name, // RequestError
description: error.message, // Timeout: Request failed to complete in 15000ms
},
originalError: {},
query,
input,
};
if (error.originalError) {
newError.originalError = {
name: error.originalError.name,
description: error.originalError.name,
};
}

context.logger.error(this.moduleName, 'MssqlError', newError);
} else {
context.logger.error(this.moduleName, error.message, { error, query, input });
}

// TODO May want to refactor to throw CamsError and remove returning QueryResults
const queryResult: QueryResults = {
results: {},
message: (error as Error).message,
success: false,
};
return queryResult;
}
}

public async close(): Promise<void> {
await this.connectionPool.close();
}
}

function isMssqlError(e): e is MSSQLError {
return e instanceof MSSQLError;
}

function isConnectionError(e): e is ConnectionError {
return e instanceof ConnectionError;
}

type AggregateError = Error & {
errors?: Error[];
};

function isAggregateError(e: unknown): e is AggregateError {
return e && 'errors' in (e as object);
}
Loading
Loading