-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/vr 56 Verify Company Name, Check in to see progress (#22)
* init commit for VR-56 Install dotenv Created model for company house api Created tests for functions in company house api model Created mock for company house api Created `/verify-input` endpoint for htmx view Created `/verify-company` endpoint for htmx view Added company house api to `.env` linting Some more linting VR-56: added await to company profile call which returns a promise VR-56 - applied `npm run lint:fix` VR-56 - fixed build time errors VR-56 - lint fix VR-56 - fixes some bugs in build VR-56 - crude working example Added `/` route to load form into template Added query param to `/verify-company` route that takes input field Added regex check on input to match pattern taken from https://gist.github.com/rob-murray/01d43581114a6b319034732bcbda29e1 VR-56 - lint fix VR-56 - fixed various comments on PR * VR-56 version update * VR-56 - fixed env import for test * VR-56 * VR-56 - removed duplicate dependancy - changed `Error` to use `Internal Error`
- Loading branch information
1 parent
2741a4e
commit 559a239
Showing
12 changed files
with
403 additions
and
10 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Get, Produces, Query, Route, Security, SuccessResponse } from 'tsoa' | ||
import { inject, injectable, singleton } from 'tsyringe' | ||
|
||
import { Logger, type ILogger } from '../../logger.js' | ||
import CompantHouseEntity from '../../models/companyHouseEntity.js' | ||
import NewConnectionTemplates from '../../views/newConnection.js' | ||
import { HTML, HTMLController } from '../HTMLController.js' | ||
|
||
@singleton() | ||
@injectable() | ||
@Security('oauth2') | ||
@Route('/connection/new') | ||
@Produces('text/html') | ||
export class NewConnectionController extends HTMLController { | ||
constructor( | ||
private companyHouseEntity: CompantHouseEntity, | ||
private newConnection: NewConnectionTemplates, | ||
@inject(Logger) private logger: ILogger | ||
) { | ||
super() | ||
this.logger = logger.child({ controller: '/connection/new' }) | ||
} | ||
|
||
/** | ||
* | ||
* @returns The new connections form page | ||
*/ | ||
@SuccessResponse(200) | ||
@Get('/') | ||
public async newConnectionForm(): Promise<HTML> { | ||
this.logger.debug('new connection page requested') | ||
return this.html( | ||
this.newConnection.formPage({ | ||
targetBox: await this.newConnection.companyEmptyTextBox({ | ||
errorMessage: 'Please type in company number to populate information', | ||
}), | ||
}) | ||
) | ||
} | ||
|
||
/** | ||
* Returns a company from a validated company number | ||
*/ | ||
@SuccessResponse(200) | ||
@Get('/verify-company') | ||
public async verifyCompanyForm(@Query() companyNumber: string): Promise<HTML> { | ||
this.logger.debug('connections page requested') | ||
const regex = | ||
/^(((AC|CE|CS|FC|FE|GE|GS|IC|LP|NC|NF|NI|NL|NO|NP|OC|OE|PC|R0|RC|SA|SC|SE|SF|SG|SI|SL|SO|SR|SZ|ZC|\d{2})\d{6})|((IP|SP|RS)[A-Z\d]{6})|(SL\d{5}[\dA]))$/ | ||
if (!regex.test(`${companyNumber}`)) { | ||
return this.html( | ||
this.newConnection.companyNumberInput({ | ||
targetBox: await this.newConnection.companyEmptyTextBox({ errorMessage: 'Company number format incorrect' }), | ||
}) | ||
) | ||
} | ||
let company | ||
try { | ||
company = await this.companyHouseEntity.getCompanyProfileByCompanyNumber(companyNumber) | ||
} catch (err) { | ||
return this.html( | ||
this.newConnection.companyNumberInput({ | ||
targetBox: await this.newConnection.companyEmptyTextBox({ errorMessage: 'Company number does not exist' }), | ||
}) | ||
) | ||
} | ||
|
||
return this.html( | ||
this.newConnection.companyNumberInput({ | ||
targetBox: await this.newConnection.companyFilledTextBox({ company }), | ||
}) | ||
) | ||
} | ||
|
||
/** | ||
* submits the company number for | ||
*/ | ||
@SuccessResponse(200) | ||
@Get('/submit') | ||
public async submitCompanyNumber(): Promise<HTML> { | ||
// do some regex if there is a match on the whole regex return true else return false | ||
return this.html(this.newConnection.companyEmptyTextBox({ errorMessage: 'Company does not exist' })) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { describe, it } from 'mocha' | ||
import { Env } from '../../env' | ||
import CompanyHouseEntity from '../companyHouseEntity' | ||
import { | ||
invalidCompanyNumber, | ||
noCompanyNumber, | ||
successResponse, | ||
validCompanyNumber, | ||
} from './fixtures/companyHouseFixtures' | ||
import { withCompanyHouseMock } from './helpers/mockCompanyHouse' | ||
|
||
describe('companyHouseEntity', () => { | ||
let expect: Chai.ExpectStatic | ||
before(async () => { | ||
expect = (await import('chai')).expect | ||
}) | ||
|
||
withCompanyHouseMock() | ||
|
||
describe('getCompanyProfileByCompanyNumber', () => { | ||
it('should give back a json in format of companyProfileSchema', async () => { | ||
const environment = new Env() | ||
const companyHouseObject = new CompanyHouseEntity(environment) | ||
const response = await companyHouseObject.getCompanyProfileByCompanyNumber(validCompanyNumber) | ||
expect(response).deep.equal(successResponse) | ||
}) | ||
it('should give back a empty json', async () => { | ||
const environment = new Env() | ||
const companyHouseObject = new CompanyHouseEntity(environment) | ||
let errorMessage: unknown | ||
try { | ||
await companyHouseObject.getCompanyProfileByCompanyNumber(noCompanyNumber) | ||
} catch (err) { | ||
errorMessage = err | ||
} | ||
|
||
expect(errorMessage).instanceOf(Error) | ||
expect((errorMessage as Error).message).equals(`Error calling CompanyHouse API`) | ||
}) | ||
it('should throw an error saying Error calling CompanyHouse API', async () => { | ||
const environment = new Env() | ||
const companyHouseObject = new CompanyHouseEntity(environment) | ||
let errorMessage: unknown | ||
try { | ||
await companyHouseObject.getCompanyProfileByCompanyNumber(invalidCompanyNumber) | ||
} catch (err) { | ||
errorMessage = err | ||
} | ||
|
||
expect(errorMessage).instanceOf(Error) | ||
expect((errorMessage as Error).message).equals(`Error calling CompanyHouse API`) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export const validCompanyNumber = '10592650' | ||
export const invalidCompanyNumber = '105926502' | ||
export const noCompanyNumber = '' | ||
|
||
export const successResponse = { | ||
registered_office_address: { | ||
address_line_1: '51 Mornington Crescent', | ||
locality: 'Hounslow', | ||
postal_code: 'TW5 9ST', | ||
country: 'United Kingdom', | ||
}, | ||
company_status: 'dissolved', | ||
registered_office_is_in_dispute: false, | ||
company_name: 'SMH IOT SOLUTIONS LTD', | ||
company_number: '10592650', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { container } from 'tsyringe' | ||
import { Dispatcher, MockAgent, getGlobalDispatcher, setGlobalDispatcher } from 'undici' | ||
import { Env } from '../../../env' | ||
import { | ||
invalidCompanyNumber, | ||
noCompanyNumber, | ||
successResponse, | ||
validCompanyNumber, | ||
} from '../fixtures/companyHouseFixtures' | ||
|
||
const env = container.resolve(Env) | ||
|
||
export function withCompanyHouseMock() { | ||
let originalDispatcher: Dispatcher | ||
let agent: MockAgent | ||
beforeEach(function () { | ||
originalDispatcher = getGlobalDispatcher() | ||
agent = new MockAgent() | ||
setGlobalDispatcher(agent) | ||
|
||
const client = agent.get(env.get('COMPANY_HOUSE_API_URL')) | ||
client | ||
.intercept({ | ||
path: `/company/${validCompanyNumber}`, | ||
method: 'GET', | ||
}) | ||
.reply(200, successResponse) | ||
|
||
client | ||
.intercept({ | ||
path: `/company/${noCompanyNumber}`, | ||
method: 'GET', | ||
}) | ||
.reply(404, {}) | ||
|
||
client | ||
.intercept({ | ||
path: `/company/${invalidCompanyNumber}`, | ||
method: 'GET', | ||
}) | ||
.reply(404, { | ||
errors: [ | ||
{ | ||
type: 'ch:service', | ||
error: 'company-profile-not-found', | ||
}, | ||
], | ||
}) | ||
}) | ||
afterEach(function () { | ||
setGlobalDispatcher(originalDispatcher) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { injectable, singleton } from 'tsyringe' | ||
import { z } from 'zod' | ||
import { Env } from '../env' | ||
import { InternalError } from '../errors' | ||
|
||
const companyProfileSchema = z.object({ | ||
company_number: z.string(), | ||
// .regex( | ||
// new RegExp( | ||
// /^((AC|ZC|FC|GE|LP|OC|SE|SA|SZ|SF|GS|SL|SO|SC|ES|NA|NZ|NF|GN|NL|NC|R0|NI|EN|\d{2}|SG|FE)\d{5}(\d|C|R))|((RS|SO)\d{3}(\d{3}|\d{2}[WSRCZF]|\d(FI|RS|SA|IP|US|EN|AS)|CUS))|((NI|SL)\d{5}[\dA])|(OC(([\dP]{5}[CWERTB])|([\dP]{4}(OC|CU))))$/ | ||
// ) | ||
// ), | ||
company_name: z.string(), | ||
registered_office_address: z.object({ | ||
address_line_1: z.string().optional(), | ||
address_line_2: z.string().optional(), | ||
care_of: z.string().optional(), | ||
country: z.string().optional(), | ||
locality: z.string().optional(), | ||
po_box: z.string().optional(), | ||
postal_code: z.string().optional(), | ||
premises: z.string().optional(), | ||
region: z.string().optional(), | ||
}), | ||
registered_office_is_in_dispute: z.boolean(), | ||
company_status: z.union([ | ||
z.literal('active'), | ||
z.literal('dissolved'), | ||
z.literal('liquidation'), | ||
z.literal('receivership'), | ||
z.literal('converted-closed'), | ||
z.literal('voluntary-arrangement'), | ||
z.literal('insolvency-proceedings'), | ||
z.literal('administration'), | ||
z.literal('open'), | ||
z.literal('closed'), | ||
z.literal('registered'), | ||
z.literal('removed'), | ||
]), | ||
}) | ||
export type CompanyProfile = z.infer<typeof companyProfileSchema> | ||
|
||
@singleton() | ||
@injectable() | ||
export default class CompanyHouseEntity { | ||
constructor(private env: Env) {} | ||
|
||
private async makeCompanyProfileRequest(route: string): Promise<unknown> { | ||
const url = new URL(route) | ||
|
||
const response = await fetch(url.toString(), { | ||
method: 'GET', | ||
headers: new Headers({ | ||
Authorization: this.env.get('COMPANY_PROFILE_API_KEY'), | ||
}), | ||
}) | ||
if (!response.ok) { | ||
throw new InternalError(`Error calling CompanyHouse API`) | ||
} | ||
|
||
return response.json() | ||
} | ||
|
||
/* | ||
This function will return a companyProfile object | ||
*/ | ||
async getCompanyProfileByCompanyNumber(companyNumber: string): Promise<CompanyProfile> { | ||
const endpoint = `${this.env.get('COMPANY_HOUSE_API_URL')}/company/${encodeURIComponent(companyNumber)}` | ||
|
||
const companyProfile = await this.makeCompanyProfileRequest(endpoint) | ||
|
||
return companyProfileSchema.parse(companyProfile) | ||
} | ||
} |
Oops, something went wrong.