Skip to content

Commit f147483

Browse files
authored
Merge pull request #764 from companieshouse/feature/add-psc-verification-state-endpoint
IDVA3-2455 Add endpoint for PSC VerificationState
2 parents 3d0750c + de33a33 commit f147483

File tree

4 files changed

+132
-80
lines changed

4 files changed

+132
-80
lines changed

src/services/psc/service.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,14 @@ export default class PscService {
1010
constructor (private readonly client: IHttpClient) { }
1111

1212
/**
13-
* Get the PSC details for an individual person.
13+
* Get the PSC individual details including their optional verification state.
1414
*
15-
* @param companyNumber the company number to look up
16-
* @param notificationId the PSC Notification Id to retrieve
15+
* @param companyNumber the Company Number to look up
16+
* @param pscNotificationId the PSC Notification ID to retrieve
1717
*/
18-
public async getPscIndividual (companyNumber: string, notificationId: string): Promise<Resource<PersonWithSignificantControl> | ApiErrorResponse> {
19-
const response = await this.client.httpGet(`/company/${companyNumber}/persons-with-significant-control/individual/${notificationId}`);
20-
21-
const resource: Resource<PersonWithSignificantControl> = {
22-
httpStatusCode: response.status
23-
};
18+
public async getPscIndividual (companyNumber: string, pscNotificationId: string): Promise<Resource<PersonWithSignificantControl> | ApiErrorResponse> {
19+
const resourceUri = `/company/${companyNumber}/persons-with-significant-control/individual/${pscNotificationId}/verification-state`;
20+
const response = await this.client.httpGet(resourceUri);
2421

2522
if (response.error) {
2623
return {
@@ -29,9 +26,13 @@ export default class PscService {
2926
}
3027
}
3128

29+
const frontEndResource: Resource<PersonWithSignificantControl> = {
30+
httpStatusCode: response.status
31+
};
32+
3233
const body = response.body as PersonWithSignificantControlResource;
33-
resource.resource = Mapping.camelCaseKeys<PersonWithSignificantControl>(body);
34+
frontEndResource.resource = Mapping.camelCaseKeys<PersonWithSignificantControl>(body);
3435

35-
return resource;
36+
return frontEndResource;
3637
}
3738
}

src/services/psc/types.ts

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* PersonWithSignificantControlResource is what is returned from the api.
2+
* PersonWithSignificantControlResource is what is returned from the PSC data API, and will apply to individual PSC types only.
33
*/
44

55
export interface PersonWithSignificantControlResource {
@@ -15,14 +15,15 @@ export interface PersonWithSignificantControlResource {
1515
natures_of_control: string[],
1616
notified_on: string,
1717
is_sanctioned?: boolean,
18-
kind?: string,
19-
identification?: IdentificationResource
18+
kind?: KindEnum,
19+
verification_state?: VerificationStateResource
2020
};
2121

2222
export interface AddressResource {
2323
address_line_1: string;
2424
address_line_2?: string;
2525
careOf?: string;
26+
country?: string;
2627
locality: string;
2728
poBox?: string;
2829
postal_code?: string;
@@ -44,15 +45,6 @@ export interface NameElementsResource {
4445
surname: string;
4546
};
4647

47-
export interface IdentificationResource {
48-
identification_type?: string,
49-
legal_authority?: string,
50-
legal_form?: string,
51-
place_registered?: string,
52-
registration_number?: string,
53-
country_registered?: string
54-
};
55-
5648
export interface ResultsLinksResource {
5749
self: string,
5850
persons_with_significant_control_statements_list?: string;
@@ -63,6 +55,22 @@ export interface ItemLinksResource {
6355
statement?: string;
6456
};
6557

58+
export interface VerificationStateResource {
59+
verification_status?: VerificationStatusEnum;
60+
verification_start_date?: Date;
61+
verification_statement_due_date?: Date;
62+
}
63+
64+
export enum VerificationStatusEnum {
65+
UNVERIFIED = "UNVERIFIED",
66+
VERIFIED = "VERIFIED",
67+
PENDING = "PENDING"
68+
}
69+
70+
export enum KindEnum {
71+
INDIVIDUAL_PERSON_WITH_SIGNIFICANT_CONTROL = "individual-person-with-significant-control"
72+
}
73+
6674
export interface PersonWithSignificantControl {
6775
address: any,
6876
countryOfResidence: string,
@@ -76,14 +84,15 @@ export interface PersonWithSignificantControl {
7684
naturesOfControl: string[],
7785
notifiedOn: string,
7886
isSanctioned?: boolean,
79-
kind?: string,
80-
identification?: Identification
87+
kind?: KindEnum,
88+
verificationState?: VerificationState
8189
};
8290

8391
export interface Address {
8492
addressLine1: string;
8593
addressLine2?: string;
8694
careOf?: string;
95+
country?: string;
8796
locality: string;
8897
poBox?: string;
8998
postalCode?: string;
@@ -105,15 +114,6 @@ export interface NameElements {
105114
surname: string;
106115
};
107116

108-
export interface Identification {
109-
identificationType?: string,
110-
legalAuthority?: string,
111-
legalForm?: string,
112-
placeRegistered?: string,
113-
registrationNumber?: string,
114-
countryRegistered?: string
115-
};
116-
117117
export interface ResultsLinks {
118118
self: string,
119119
personsWithSignificantControlStatementsList?: string;
@@ -123,3 +123,10 @@ export interface ItemLinks {
123123
self: string,
124124
statement?: string;
125125
};
126+
127+
export interface VerificationState {
128+
verificationStatus?: VerificationStatusEnum;
129+
verificationStartDate?: Date;
130+
verificationStatementDueDate?: Date;
131+
132+
}

test/services/psc/service.mock.ts

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,101 @@
11
import { ReasonPhrases, StatusCodes } from "http-status-codes";
22
import { RequestClient } from "../../../src";
3-
import { DateOfBirthResource, PscVerificationDataResource, PscVerificationResource } from "../../../src/services/psc-verification-link/types";
4-
import { PersonWithSignificantControlResource } from "../../../src/services/psc/types";
3+
import { DateOfBirthResource } from "../../../src/services/psc-verification-link/types";
4+
import { KindEnum, PersonWithSignificantControl, PersonWithSignificantControlResource, VerificationState, VerificationStateResource, VerificationStatusEnum } from "../../../src/services/psc/types";
55

66
export const requestClient = new RequestClient({ baseUrl: "URL_NOT_USED", oauthToken: "123" });
7-
87
export const COMPANY_NUMBER = "12345678";
98
export const PSC_NOTIFICATION_ID = "67edfE436y35hetsie6zuAZtr"
10-
export const TRANSACTION_ID = "12345";
11-
export const FILING_ID = "00112233";
12-
export const FIRST_DATE = new Date("2024-03-13T10:08:42Z");
13-
export const UPDATE_DATE = new Date("2024-04-13T10:08:42Z");
14-
export const DOB_DATE = new Date("1970-01-01");
15-
export const SELF_LINK = `/company/${COMPANY_NUMBER}/persons-with-significant-control/individual/${PSC_NOTIFICATION_ID}`;
16-
export const KIND_INDIVIDUAL = "individual-person-with-significant-control";
17-
export const NATURE_OF_CONTROL = ["ownership-of-shares-75-to-100-percent", "voting-rights-75-to-100-percent-as-trust"]
18-
export const NAME = "Sir Forename Middlename Surname";
19-
export const NATIONALITY = "British";
20-
export const NAME_ELEMENTS = {
9+
const SELF_LINK = `/company/${COMPANY_NUMBER}/persons-with-significant-control/individual/${PSC_NOTIFICATION_ID}`;
10+
const NATURE_OF_CONTROL = ["ownership-of-shares-75-to-100-percent", "voting-rights-75-to-100-percent-as-trust"]
11+
const NAME = "Sir Forename Middlename Surname";
12+
const NATIONALITY = "British";
13+
const NOTIFICATION_DATE = "2023-01-02";
14+
const ETAG = "etag";
15+
16+
const NAME_ELEMENTS = {
2117
title: "Sir",
2218
forename: "Forename",
2319
middlename: "Middlename",
2420
surname: "Surname"
2521
};
26-
export const ADDRESS = {
22+
23+
const ADDRESS_RESOURCE = {
2724
postal_code: "CF14 3UZ",
2825
locality: "Cardiff",
2926
region: "South Glamorgan",
3027
address_line_1: "Crown Way"
3128
};
32-
export const COUNTRY_OF_RESIDENCE = "Wales";
3329

34-
export const PSC_VERIFICATION_CREATED_RESOURCE: PscVerificationDataResource = {
35-
company_number: COMPANY_NUMBER
30+
const ADDRESS = {
31+
postalCode: "CF14 3UZ",
32+
locality: "Cardiff",
33+
region: "South Glamorgan",
34+
addressLine1: "Crown Way"
3635
};
3736

37+
const COUNTRY_OF_RESIDENCE = "Wales";
38+
39+
const VERIFICATION_START_DATE = new Date("2024-04-13");
40+
const VERIFICATION_DUE_DATE = new Date("2024-04-27");
41+
const VERIFICATION_STATUS = VerificationStatusEnum.UNVERIFIED;
42+
43+
const VERIFICATION_STATE: VerificationState = {
44+
verificationStatus: VERIFICATION_STATUS,
45+
verificationStartDate: VERIFICATION_START_DATE,
46+
verificationStatementDueDate: VERIFICATION_DUE_DATE
47+
}
48+
49+
const VERIFICATION_STATE_RESOURCE: VerificationStateResource = {
50+
verification_status: VERIFICATION_STATUS,
51+
verification_start_date: VERIFICATION_START_DATE,
52+
verification_statement_due_date: VERIFICATION_DUE_DATE
53+
}
54+
3855
const PSC_INDIVIDUAL_DOB: DateOfBirthResource = {
3956
day: undefined,
4057
month: "10",
4158
year: "21"
4259
}
4360

44-
export const PSC_INDIVIDUAL: PersonWithSignificantControlResource = {
61+
const PSC_INDIVIDUAL_RESOURCE: PersonWithSignificantControlResource = {
4562
natures_of_control: NATURE_OF_CONTROL,
46-
kind: KIND_INDIVIDUAL,
63+
kind: KindEnum.INDIVIDUAL_PERSON_WITH_SIGNIFICANT_CONTROL,
4764
name: NAME,
4865
name_elements: NAME_ELEMENTS,
4966
nationality: NATIONALITY,
50-
address: ADDRESS,
67+
address: ADDRESS_RESOURCE,
5168
country_of_residence: COUNTRY_OF_RESIDENCE,
5269
links: {
5370
self: SELF_LINK
5471
},
5572
date_of_birth: PSC_INDIVIDUAL_DOB,
56-
etag: "",
57-
notified_on: ""
73+
etag: ETAG,
74+
notified_on: NOTIFICATION_DATE,
75+
verification_state: VERIFICATION_STATE_RESOURCE
5876
};
5977

60-
export const mockPscVerificationCreatedResource: PscVerificationResource = {
61-
created_at: FIRST_DATE,
62-
updated_at: FIRST_DATE,
63-
data: PSC_VERIFICATION_CREATED_RESOURCE,
78+
export const PSC_INDIVIDUAL: PersonWithSignificantControl = {
79+
naturesOfControl: NATURE_OF_CONTROL,
80+
kind: KindEnum.INDIVIDUAL_PERSON_WITH_SIGNIFICANT_CONTROL,
81+
name: NAME,
82+
nameElements: NAME_ELEMENTS,
83+
nationality: NATIONALITY,
84+
address: ADDRESS,
85+
countryOfResidence: COUNTRY_OF_RESIDENCE,
6486
links: {
65-
self: SELF_LINK,
66-
validation_status: `${SELF_LINK}/validation_status`
67-
}
68-
};
69-
70-
export const PSC_VERIFICATION_INDV_PATCH: PscVerificationDataResource = {
71-
psc_notification_id: PSC_NOTIFICATION_ID
72-
};
73-
74-
export const mockPscVerificationCreatedResponse = {
75-
201: { status: StatusCodes.CREATED, body: mockPscVerificationCreatedResource },
76-
400: { status: StatusCodes.BAD_REQUEST, error: ReasonPhrases.BAD_REQUEST },
77-
401: { status: StatusCodes.UNAUTHORIZED, error: ReasonPhrases.UNAUTHORIZED }
87+
self: SELF_LINK
88+
},
89+
dateOfBirth: PSC_INDIVIDUAL_DOB,
90+
etag: ETAG,
91+
notifiedOn: NOTIFICATION_DATE,
92+
verificationState: VERIFICATION_STATE
7893
};
7994

8095
export const mockIndividualResponse = {
81-
200: { status: StatusCodes.OK, body: PSC_INDIVIDUAL },
96+
200: { status: StatusCodes.OK, body: PSC_INDIVIDUAL_RESOURCE },
97+
400: { status: StatusCodes.BAD_REQUEST, error: ReasonPhrases.BAD_REQUEST },
8298
401: { status: StatusCodes.UNAUTHORIZED, error: ReasonPhrases.UNAUTHORIZED },
83-
404: { status: StatusCodes.NOT_FOUND, error: ReasonPhrases.NOT_FOUND }
99+
404: { status: StatusCodes.NOT_FOUND, error: ReasonPhrases.NOT_FOUND },
100+
500: { status: StatusCodes.INTERNAL_SERVER_ERROR, error: ReasonPhrases.INTERNAL_SERVER_ERROR }
84101
};

test/services/psc/service.spec.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as sinon from "sinon";
55
import PscService from "../../../src/services/psc/service";
66
import { PersonWithSignificantControl } from "../../../src/services/psc/types";
77
import Resource, { ApiErrorResponse } from "../../../src/services/resource";
8-
import { COMPANY_NUMBER, PSC_NOTIFICATION_ID, PSC_INDIVIDUAL, mockIndividualResponse, requestClient } from "./service.mock";
8+
import { COMPANY_NUMBER, PSC_NOTIFICATION_ID, mockIndividualResponse, requestClient, PSC_INDIVIDUAL } from "./service.mock";
99
import Mapping from "../../../src/mapping/mapping";
1010

1111
describe("PSC details", () => {
@@ -22,7 +22,7 @@ describe("PSC details", () => {
2222
expect(response.resource).to.eql(Mapping.camelCaseKeys(PSC_INDIVIDUAL));
2323
});
2424

25-
it("should return status 401 Unauthorised on unauthorised access", async () => {
25+
it("should return status 401 Unauthorised when access is unauthorised", async () => {
2626
sinon.stub(requestClient, "httpGet").resolves(mockIndividualResponse[401]);
2727

2828
const response = await pscService.getPscIndividual(COMPANY_NUMBER, PSC_NOTIFICATION_ID) as ApiErrorResponse;
@@ -31,13 +31,40 @@ describe("PSC details", () => {
3131
expect(response.errors?.[0]).to.equal(ReasonPhrases.UNAUTHORIZED);
3232
});
3333

34-
it("should return status 404 Not Found if resource id not found", async () => {
34+
it("should return status 400 Bad Request when the resource ID is null in the request", async () => {
35+
sinon.stub(requestClient, "httpGet").resolves(mockIndividualResponse[400]);
36+
37+
const response = (await pscService.getPscIndividual(
38+
null as unknown as string, null as unknown as string
39+
40+
)) as ApiErrorResponse;
41+
42+
expect(response.httpStatusCode).to.equal(StatusCodes.BAD_REQUEST);
43+
expect(response.errors?.[0]).to.equal(ReasonPhrases.BAD_REQUEST);
44+
});
45+
46+
it("should return status 404 Not Found when the resource ID is not found", async () => {
3547
sinon.stub(requestClient, "httpGet").resolves(mockIndividualResponse[404]);
3648

37-
const response = await pscService.getPscIndividual(COMPANY_NUMBER, PSC_NOTIFICATION_ID) as ApiErrorResponse;
49+
const response = (await pscService.getPscIndividual(
50+
COMPANY_NUMBER, PSC_NOTIFICATION_ID
51+
52+
)) as ApiErrorResponse;
3853

3954
expect(response.httpStatusCode).to.equal(StatusCodes.NOT_FOUND);
4055
expect(response.errors?.[0]).to.equal(ReasonPhrases.NOT_FOUND);
4156
});
57+
58+
it("should return status 500 Internal Server Error if a server error occurs", async () => {
59+
sinon.stub(requestClient, "httpGet").resolves(mockIndividualResponse[500]);
60+
61+
const response = (await pscService.getPscIndividual(
62+
COMPANY_NUMBER, PSC_NOTIFICATION_ID
63+
64+
)) as ApiErrorResponse;
65+
66+
expect(response.httpStatusCode).to.equal(StatusCodes.INTERNAL_SERVER_ERROR);
67+
expect(response.errors?.[0]).to.equal(ReasonPhrases.INTERNAL_SERVER_ERROR);
68+
});
4269
});
4370
});

0 commit comments

Comments
 (0)