Skip to content

Commit

Permalink
Update create authorization to use SSI
Browse files Browse the repository at this point in the history
  • Loading branch information
jleiva-gap committed Oct 17, 2023
1 parent dd333c1 commit 6000f0b
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

import { Logger } from '@edfi/meadowlark-utilities';
import retry from 'async-retry';
import { Logger, Config } from '@edfi/meadowlark-utilities';
import { CreateAuthorizationClientRequest, CreateAuthorizationClientResult } from '@edfi/meadowlark-authz-server';
import { PoolClient } from 'pg';
import { AuthorizationDocument, authorizationDocumentFromCreate } from '../../model/AuthorizationDocument';
Expand All @@ -16,24 +17,57 @@ export async function createAuthorizationClientDocument(
client: PoolClient,
): Promise<CreateAuthorizationClientResult> {
const createResult: CreateAuthorizationClientResult = { response: 'UNKNOWN_FAILURE' };
let retryCount = 0;
try {
await beginTransaction(client);
const authorizationClient: AuthorizationDocument = authorizationDocumentFromCreate(request);
const hasResults = await insertOrUpdateAuthorization(authorizationClient, client);
if (hasResults) {
createResult.response = 'CREATE_SUCCESS';
} else {
const msg = 'Error inserting or updating the authorization client in the PostgreSQL database.';
Logger.error(functionName, request.traceId, msg);
await rollbackTransaction(client);
}
await commitTransaction(client);
const numberOfRetries: number = Config.get('POSTGRES_MAX_NUMBER_OF_RETRIES');
const createProcess = await retry(
async (bail) => {
try {
await beginTransaction(client);
const authorizationClient: AuthorizationDocument = authorizationDocumentFromCreate(request);
const hasResults = await insertOrUpdateAuthorization(authorizationClient, client);
if (hasResults) {
createResult.response = 'CREATE_SUCCESS';
} else {
const msg = 'Error inserting or updating the authorization client in the PostgreSQL database.';
Logger.error(functionName, request.traceId, msg);
await rollbackTransaction(client);
}
await commitTransaction(client);
return createResult;
} catch (error) {
retryCount += 1;
await rollbackTransaction(client);
// If there's a serialization failure, rollback the transaction
if (error.code === '40001') {
if (retryCount >= numberOfRetries) {
throw Error('Error after maximum retries');
}
// Throws the error to be handled by async-retry
throw error;
} else {
// If it's not a serialization failure, don't retry and rethrow the error
Logger.error(`${functionName}.createAuthorizationClientDocument`, request.traceId, error);
// Throws the error to be handled by the caller
bail(error);
}
}
return createResult;
},
{
retries: numberOfRetries,
onRetry: (error, attempt) => {
if (attempt === numberOfRetries) {
Logger.error('Error after maximum retries', error);
} else {
Logger.error('Retrying transaction due to error:', error);
}
},
},
);
return createProcess;
} catch (e) {
if (client) {
await rollbackTransaction(client);
}
Logger.error(functionName, request.traceId, e);
}

return createResult;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
// See the LICENSE and NOTICES files in the project root for more information.

import { CreateAuthorizationClientRequest, CreateAuthorizationClientResult } from '@edfi/meadowlark-authz-server';
import * as utilities from '@edfi/meadowlark-utilities';
import type { PoolClient } from 'pg';
import { getSharedClient, resetSharedClient } from '../../../src/repository/Db';
import { createAuthorizationClientDocument } from '../../../src/repository/authorization/CreateAuthorizationClient';
import { deleteAllAuthorizations } from '../TestHelper';
import { getAuthorizationClientDocumentById } from '../../../src/repository/SqlHelper';
import * as SqlHelper from '../../../src/repository/SqlHelper';

const clientId = 'clientId';

jest.setTimeout(70000);
const newCreateAuthorizationClientRequest = (): CreateAuthorizationClientRequest => ({
clientId,
clientSecretHashed: 'clientSecretHashed',
Expand Down Expand Up @@ -62,3 +64,70 @@ describe('given the create of a new authorization client', () => {
`);
});
});

describe('given the create of a new authorization client with Postgresql SSI', () => {
let client: PoolClient;
let createClientRequest: CreateAuthorizationClientResult;
const retryNumberOfTimes = 2;

beforeEach(async () => {
client = await getSharedClient();
});

afterEach(async () => {
if (client) {
await deleteAllAuthorizations(client);
client.release();
await resetSharedClient();
}
});
it('should retry on error 40001', async () => {
jest.spyOn(utilities.Config, 'get').mockImplementationOnce(() => retryNumberOfTimes);
const mockError = {
code: '40001',
message: 'Could not serialize access due to read/write dependencies among transactions',
};
jest
.spyOn(SqlHelper, 'insertOrUpdateAuthorization')
.mockImplementationOnce(() => {
throw mockError;
})
.mockImplementationOnce(() => {
throw mockError;
});
createClientRequest = await createAuthorizationClientDocument(newCreateAuthorizationClientRequest(), client);
expect(createClientRequest.response).toBe('CREATE_SUCCESS');
});

it('should not retry on error not equal to 40001', async () => {
jest.spyOn(utilities.Config, 'get').mockImplementationOnce(() => retryNumberOfTimes);
const mockError = { code: '50000', message: 'Any exception different of SSI 40001' };
// Mock selectReferences to throw an error
jest.spyOn(SqlHelper, 'insertOrUpdateAuthorization').mockImplementation(() => {
throw mockError;
});
createClientRequest = await createAuthorizationClientDocument(newCreateAuthorizationClientRequest(), client);
expect(createClientRequest.response).toBe('UNKNOWN_FAILURE');
});

it('should Throw Error When Retry Limit Exceeded', async () => {
jest.spyOn(utilities.Config, 'get').mockImplementationOnce(() => retryNumberOfTimes);
const mockError = {
code: '40001',
message: 'Could not serialize access due to read/write dependencies among transactions',
};
jest
.spyOn(SqlHelper, 'insertOrUpdateAuthorization')
.mockImplementationOnce(() => {
throw mockError;
})
.mockImplementationOnce(() => {
throw mockError;
})
.mockImplementationOnce(() => {
throw mockError;
});
createClientRequest = await createAuthorizationClientDocument(newCreateAuthorizationClientRequest(), client);
expect(createClientRequest.response).toBe('UNKNOWN_FAILURE');
});
});

0 comments on commit 6000f0b

Please sign in to comment.