Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 1 addition & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn lint-staged
yarn lint-staged
2 changes: 1 addition & 1 deletion charts/sptribs-frontend/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ appVersion: '1.0'
description: A Helm chart for sptribs-frontend App
name: sptribs-frontend
home: https://github.com/hmcts/sptribs-frontend
version: 0.0.43
version: 0.0.44
dependencies:
- name: nodejs
version: 3.2.0
Expand Down
4 changes: 2 additions & 2 deletions charts/sptribs-frontend/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ nodejs:
IDAM_API_URL: 'https://idam-api.{{ .Values.global.environment }}.platform.hmcts.net/o/token'
REDIS_HOST: 'sptribs-{{ .Values.global.environment }}.redis.cache.windows.net'
EQUALITY_URL: 'http://pcq.{{ .Values.global.environment }}.platform.hmcts.net'
CCD_API_URL: 'http://ccd-data-store-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal'
CASE_DOCUMENT_API_URL: 'http://ccd-case-document-am-api-{{ .Values.global.environment }}.service.core-compute-{{ .Values.global.environment }}.internal'
CCD_API_URL: 'https://ccd-data-store-api-pr-2486.preview.platform.hmcts.net'
CASE_DOCUMENT_API_URL: 'https://ccd-case-document-am-api-pr-2486.preview.platform.hmcts.net'
DSS_UPDATE_URL: 'http://sptribs-dss-update-case-web.{{ .Values.global.environment }}.platform.hmcts.net/'
keyVaults:
sptribs:
Expand Down
2 changes: 2 additions & 0 deletions config/custom-environment-variables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ services:
url: CCD_API_URL
cdam:
url: CASE_DOCUMENT_API_URL
sptribs:
url: SPTRIBS_CASE_API_URL
idam:
authorizationURL: IDAM_WEB_URL
tokenURL: IDAM_API_URL
Expand Down
6 changes: 3 additions & 3 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ services:
systemUsername: 'dummy_user'
systemPassword: 'dummy_password'
sptribs:
url: 'http://sptribs-case-api-aat.service.core-compute-aat.internal'
url: 'https://sptribs-case-api-pr-2486.preview.platform.hmcts.net'
ccd:
url: 'http://ccd-data-store-api-aat.service.core-compute-aat.internal'
url: 'https://ccd-data-store-api-pr-2486.preview.platform.hmcts.net'
cdam:
url: 'http://ccd-case-document-am-api-aat.service.core-compute-aat.internal'
url: 'https://ccd-case-document-am-api-pr-2486.preview.platform.hmcts.net'
equalityAndDiversity:
url: 'https://pcq.aat.platform.hmcts.net'
path: '/service-endpoint'
Expand Down
6 changes: 6 additions & 0 deletions config/development.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
port: 3001
timeout: 120000
services:
sptribs:
url: 'http://localhost:4013'
ccd:
url: 'http://localhost:4452'
cdam:
url: 'http://localhost:4455'
feeLookup:
url: 'http://fees-register-api-aat.service.core-compute-aat.internal/fees-register/fees/lookup'
dssUpdate:
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@
"@hmcts/nodejs-healthcheck": "^1.8.0",
"@hmcts/nodejs-logging": "^4.0.4",
"@hmcts/properties-volume": "^1.0.0",
"@types/autobind-decorator": "^2.1.0",
"@types/config": "^3.0.0",
"@types/cookie-parser": "^1.4.2",
"@types/csurf": "^1.11.4",
"@types/es6-promisify": "^6.0.3",
"@types/express": "^4.17.20",
"@types/express": "^5.0.6",
"@types/express-session": "^1.17.4",
"@types/glob": "^8.0.0",
"@types/lodash": "^4.14.201",
Expand All @@ -64,7 +62,7 @@
"connect-redis": "^8.0.3",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"csurf": "^1.11.0",
"csrf-sync": "^4.0.0",
"dayjs": "^1.11.1",
"email-validator": "^2.0.4",
"express": "^4.20.0",
Expand All @@ -88,6 +86,7 @@
"otplib": "^12.0.1",
"redis": "^5.0.1",
"require-directory": "^2.1.1",
"sass-mq": "^7.0.0",
"serve-favicon": "^2.5.0",
"session-file-store": "^1.5.0",
"terser-webpack-plugin": "^5.3.10",
Expand All @@ -103,7 +102,7 @@
"@babel/preset-env": "^7.23.2",
"@babel/preset-typescript": "^7.23.2",
"@sonar/scan": "^4.3.4",
"@types/copy-webpack-plugin": "^10.1.0",
"@types/copy-webpack-plugin": "^10.1.3",
"@types/cors": "^2.8.17",
"@types/jest": "^29.5.4",
"@types/mini-css-extract-plugin": "^2.5.1",
Expand Down
135 changes: 124 additions & 11 deletions src/main/app/case/CaseApi.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import axios from 'axios';
import { LoggerInstance } from 'winston';

import { UserDetails } from '../controller/AppRequest';

import { CaseApi } from './CaseApi';
import { fromApiFormat } from './from-api-format';

const { Logger } = require('@hmcts/nodejs-logging');
const logger: LoggerInstance = Logger.getLogger('app');
jest.mock('axios');
jest.mock('./from-api-format');

test('Should return case roles for userId and caseId passed', async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
Expand All @@ -22,18 +22,11 @@ test('Should return case roles for userId and caseId passed', async () => {
],
},
});
const userDetails: UserDetails = {
accessToken: 'string',
id: '372ff9c1-9930-46d9-8bd2-88dd26ba2475',
email: 'harry@hog.com',
givenName: 'harry',
familyName: 'potter',
roles: ['citizen'],
};

const case_id = '1624351572550045';
const user_id = '372ff9c1-9930-46d9-8bd2-88dd26ba2475';
const caseApiInstance: CaseApi = new CaseApi(mockedAxios, logger);
const result = await caseApiInstance.getCaseUserRoles(case_id, userDetails.id);
const result = await caseApiInstance.getCaseUserRoles(case_id, user_id);
expect(result).toEqual({
case_users: [
{
Expand Down Expand Up @@ -62,3 +55,123 @@ test('Should throw error when case roles could not be fetched', async () => {

await expect(caseApiInstance.getCaseUserRoles(case_id, user_id)).rejects.toThrow(expectedError);
});

test('Should return case by CICA reference', async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
const mockedSptribsAxios = axios as jest.Mocked<typeof axios>;
const mockedFromApiFormat = fromApiFormat as jest.MockedFunction<typeof fromApiFormat>;

mockedFromApiFormat.mockReturnValueOnce({
tribunalFormDocuments: [],
supportingDocuments: [],
otherInfoDocuments: [],
} as any);

mockedSptribsAxios.get.mockResolvedValueOnce({
data: {
id: '1624351572550045',
state: 'Submitted',
data: {
dssCaseDataTribunalFormDocuments: [],
dssCaseDataSupportingDocuments: [],
dssCaseDataOtherInfoDocuments: [],
},
},
});

const cicaReference = 'X12345';
const caseApiInstance: CaseApi = new CaseApi(mockedAxios, logger, mockedSptribsAxios);
const result = await caseApiInstance.getCaseByCicaReference(cicaReference);

expect(result).not.toBeNull();
expect(result!.id).toBe('1624351572550045');
expect(result!.state).toBe('Submitted');
expect(mockedSptribsAxios.get).toHaveBeenCalledWith(`/cases/cica/${cicaReference}`);
});

test('Should return null when no case found by CICA reference', async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
const mockedSptribsAxios = axios as jest.Mocked<typeof axios>;

mockedSptribsAxios.get.mockRejectedValueOnce({
response: { status: 404 },
config: { method: 'GET', url: 'https://example.com/cases/cica/X99999' },
});

const cicaReference = 'X99999';
const caseApiInstance: CaseApi = new CaseApi(mockedAxios, logger, mockedSptribsAxios);
const result = await caseApiInstance.getCaseByCicaReference(cicaReference);

expect(result).toBeNull();
});

test('Should throw error when case lookup by CICA reference fails', async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
const mockedSptribsAxios = axios as jest.Mocked<typeof axios>;

mockedSptribsAxios.get.mockRejectedValueOnce({
response: { status: 500 },
config: { method: 'GET', url: 'https://example.com/cases/cica/X12345' },
});

const cicaReference = 'X12345';
const caseApiInstance: CaseApi = new CaseApi(mockedAxios, logger, mockedSptribsAxios);
const expectedError = 'Case could not be fetched.';

await expect(caseApiInstance.getCaseByCicaReference(cicaReference)).rejects.toThrow(expectedError);
});

test('Should throw error when sptribs client not configured', async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;

const cicaReference = 'X12345';
const caseApiInstance: CaseApi = new CaseApi(mockedAxios, logger);
const expectedError = 'Sptribs backend client not configured';

await expect(caseApiInstance.getCaseByCicaReference(cicaReference)).rejects.toThrow(expectedError);
});

test('Should return case by ID', async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
const mockedFromApiFormat = fromApiFormat as jest.MockedFunction<typeof fromApiFormat>;

mockedFromApiFormat.mockReturnValueOnce({
tribunalFormDocuments: [],
supportingDocuments: [],
otherInfoDocuments: [],
} as any);

mockedAxios.get.mockResolvedValue({
data: {
id: '1624351572550045',
state: 'Submitted',
data: {
dssCaseDataTribunalFormDocuments: [],
dssCaseDataSupportingDocuments: [],
dssCaseDataOtherInfoDocuments: [],
},
},
});

const case_id = '1624351572550045';
const caseApiInstance: CaseApi = new CaseApi(mockedAxios, logger);
const result = await caseApiInstance.getCaseById(case_id);

expect(result.id).toBe('1624351572550045');
expect(result.state).toBe('Submitted');
expect(mockedAxios.get).toHaveBeenCalledWith(`/cases/${case_id}`);
});

test('Should throw error when case could not be fetched by ID', async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
mockedAxios.get.mockRejectedValue({
config: { method: 'GET', url: 'https://example.com/cases/123' },
request: 'mock request',
});

const case_id = '123';
const caseApiInstance: CaseApi = new CaseApi(mockedAxios, logger);
const expectedError = 'Case could not be fetched.';

await expect(caseApiInstance.getCaseById(case_id)).rejects.toThrow(expectedError);
});
80 changes: 74 additions & 6 deletions src/main/app/case/CaseApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class CaseApi {

constructor(
private readonly client: AxiosInstance,
private readonly logger: LoggerInstance
private readonly logger: LoggerInstance,
private readonly sptribsClient?: AxiosInstance
) {}

public async createCase(data: Partial<Case>): Promise<CaseWithId> {
Expand Down Expand Up @@ -52,6 +53,59 @@ export class CaseApi {
}
}

public async getCaseByCicaReference(cicaReference: string): Promise<CaseWithId | null> {
if (!this.sptribsClient) {
throw new Error('Sptribs backend client not configured');
}

try {
const response = await this.sptribsClient.get<SptribsCaseResponse>(
`/cases/cica/${encodeURIComponent(cicaReference)}`
);
return {
id: response.data.id,
state: response.data.state,
...fromApiFormat(response.data.data),
};
} catch (err) {
if ((err as AxiosError).response?.status === 404) {
return null;
}
this.logError(err);
throw new Error('Case could not be fetched.');
}
}

public async downloadDocument(documentId: string): Promise<AxiosResponse> {
if (!this.sptribsClient) {
throw new Error('Sptribs backend client not configured');
}

try {
const response = await this.sptribsClient.get(`/case/document/downloadDocument/${documentId}`, {
responseType: 'stream',
});
return response;
} catch (err) {
const error = err as AxiosError;
// Extract only primitive values to avoid circular reference issues when logging
const status = error.response?.status || 'unknown';
const message = error.message || 'Unknown error';
this.logger.error(`Document download failed for documentId=${documentId}: status=${status}, message=${message}`);
throw new Error('Document could not be downloaded.');
}
}

public async getCaseById(caseId: string): Promise<CaseWithId> {
try {
const response = await this.client.get<CcdV2Response>(`/cases/${caseId}`);
return { id: response.data.id, state: response.data.state, ...fromApiFormat(response.data.data) };
} catch (err) {
this.logError(err);
throw new Error('Case could not be fetched.');
}
}

public async getEventTrigger(caseId: string, eventName: string): Promise<CcdEventTriggerResponse> {
try {
const response = await this.client.get<CcdEventTriggerResponse>(`/cases/${caseId}/event-triggers/${eventName}`);
Expand Down Expand Up @@ -106,18 +160,26 @@ export class CaseApi {
}

export const getCaseApi = (userDetails: UserDetails, logger: LoggerInstance): CaseApi => {
const authHeaders = {
Authorization: 'Bearer ' + userDetails.accessToken,
ServiceAuthorization: getServiceAuthToken(),
Accept: '*/*',
'Content-Type': 'application/json',
};

return new CaseApi(
axios.create({
baseURL: config.get('services.ccd.url'),
headers: {
Authorization: 'Bearer ' + userDetails.accessToken,
ServiceAuthorization: getServiceAuthToken(),
...authHeaders,
experimental: 'true',
Accept: '*/*',
'Content-Type': 'application/json',
},
}),
logger
logger,
axios.create({
baseURL: config.get('services.sptribs.url'),
headers: authHeaders,
})
);
};

Expand All @@ -138,3 +200,9 @@ interface CcdEventTriggerResponse extends CcdTokenResponse {
data: CaseData;
};
}

interface SptribsCaseResponse {
id: string;
state: string;
data: CaseData;
}
1 change: 1 addition & 0 deletions src/main/app/case/case.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ describe('Case interface', () => {
tribunalFormDocuments: [],
supportingDocuments: [],
otherInfoDocuments: [],
applicantDocuments: [],
languagePreference: LanguagePreference.ENGLISH,
};

Expand Down
Loading
Loading