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

feat(GIFT-3546): add endpoint to fetch customer data #1127

Open
wants to merge 17 commits into
base: feat/FN-3543/integrate-with-ods
Choose a base branch
from

Conversation

ChristianMcc
Copy link

@ChristianMcc ChristianMcc commented Jan 3, 2025

Introduction ✏️

Create an endpoint and new ODS service/controller/module to query ODS for the customer.
Added types and functionality for new endpoints for other ODS entities to be fetched in the future.

All entities from ODS will be fetched in a similar way using the same stored procedure hence lumping it all together in a single ODS service, we could look at splitting up controllers and services in the future if we feel they are getting too bloated.

No full payload validation has been done of the response from the ODS stored procedures - only validation of the fields that is required for the endpoint.

An API test will be added separately to this PR in a following PR

@@ -10,7 +10,7 @@ run-name: Executing test QA on ${{ github.repository }} 🚀

on:
pull_request:
branches: [main]
branches: [main, feat/FN-3543/integrate-with-ods]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

temporarily leaving this in while the feature branch is open to run tests, will remove before feature branch is merged to main

pattern: regexToString(UKEFID.PARTY_ID.REGEX),
})
@Matches(UKEFID.PARTY_ID.REGEX)
public customerUrn: string;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint and ODS refer to the entity as customer so I've used customer URN instead of party URN here but I'm happy to change back to partyUrn if people think that is more logical/consistent

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opted to just call it urn while in the namespace of customer

@ApiProperty({
description: 'The unique UKEF id of the customer',
})
readonly customerUrn: string;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto on customerUrn comment on the other dto

import { OdsController } from './ods.controller';
import { OdsService } from './ods.service';

describe('OdsController', () => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted a test to check the validation of the dto (e.g. valid when a urn matching the regex is passed through) but that would require setting up a validationPipe for this test which no other test in the codebase has done. For now I'm going to leave out to stay consistent with the other test suites but one to look at across the board

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment DTO validations are tested as part of api/e2e tests (root/tests). Most likely these tests are not part of github pipelines yet.

Would be nice to create api/e2e test for osd.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep will add this in a new PR straight after this one (don't have a tonne of time today/tomorrow but keen to get my feature branch tested in dev so have split up the work)

@Injectable()
export class OdsService {
constructor(
@InjectDataSource(DATABASE.ODS)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This black box magic of adding the name of the data source to pick the right one is amazing, I don't fully understand it but I like it

customerName: storedProcedureJson.results[0]?.customer_name,
};
} catch (err) {
if (err instanceof NotFoundException) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the catching and rethrowing feels a little clunky here but is the best way to get better logging for debugging which I think is worth it. Any recommendations on refactoring this are welcome

const queryRunner = this.odsDataSource.createQueryRunner();
try {
// Use the query runner to call a stored procedure
const result = await queryRunner.query(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortuantely typeorm doesn't support any calling of stored procedures outside of just a SQL query so I've written a parameterised SQL query to stop SQL injection but has to be hard coded like this

@@ -135,6 +135,11 @@ jobs:
"DATABASE_NUMBER_GENERATOR_NAME=${{ secrets.DATABASE_NUMBER_GENERATOR_NAME }}" \
"DATABASE_CEDAR_NAME=${{ secrets.DATABASE_CEDAR_NAME }}" \
"DATABASE_CIS_NAME=${{ secrets.DATABASE_CIS_NAME }}" \
"DATABASE_ODS_HOST=${{ secrets.DATABASE_ODS_HOST }}" \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need DATABASE_ODS_PORT here?

Comment on lines 4 to 12
@ApiProperty({
description: 'The unique UKEF id of the customer',
})
readonly urn: string;

@ApiProperty({
description: 'Customer name',
})
readonly name: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth adding the example property to these?

Comment on lines 1 to 7
export class odsCustomerStoredProcedureQueryParams {
public customer_party_unique_reference_number: string;
}

export type odsStoredProcedureQueryParams = odsCustomerStoredProcedureQueryParams;

export class odsStoredProcedureInput {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Types and classes will typically start with a capital letter. Any reason for these to be different?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, could these two classes just be plain types?


export type odsStoredProcedureQueryParams = odsCustomerStoredProcedureQueryParams;

export class odsStoredProcedureInput {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth a link here so it's easy to remember where these came from.

Similarly for odsCustomerStoredProcedureQueryParams above we could link to this

@@ -0,0 +1,31 @@
import { CUSTOMERS } from '@ukef/constants';
import { when } from 'jest-when';

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove new line here.

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove new line here.

service = new OdsService(mockDataSource, null);
});

it('callOdsPrcedure should call the stored procedure with the query runner', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it tests should generally read as a sentence starting with it. e.g.

it('does a thing', async () => { ...

*
* @returns {Promise<any>} The result of the stored procedure
*/
async callOdsStoredProcedure(storedProcedureInput: odsStoredProcedureInput): Promise<any> {
Copy link
Collaborator

@toddym42 toddym42 Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be able to give a type for the output, maybe an array of something like this?

output_body: {
  query_request_id: string;
  message: string;
  status: 'SUCCESS' | 'ERROR';
  total_result_count: number;
  results: Record<string, unknown>;
}

or if not sure if this is definitely returned could make the values optional?

Could link to here if we do want to store this type somewhere.

Could go further and make this a generic with the expected the type of results based on the particular query were running. Something like:

type OutputBody<TResults extends object = object> = {
  query_request_id: string;
  message: string;
  status: 'SUCCESS' | 'ERROR';
  total_result_count: number;
  results: TResults;
}

throw new InternalServerErrorException('Error trying to find a customer');
}

if (storedProcedureJson?.total_result_count == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to use == here instead of ===?


return result;
} finally {
queryRunner.release();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs an await?


expect(mockDataSource.createQueryRunner).toHaveBeenCalled();
expect(mockQueryRunner.query).toHaveBeenCalledWith(
expect.stringMatching(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to have to match indentation in the string matching so there is a regex match instead. Thoughts @toddym42 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants