Skip to content

Commit 5a090e1

Browse files
CCM-10555: client config (#546)
Co-authored-by: ben.hansell1 <[email protected]>
1 parent 40f42d1 commit 5a090e1

File tree

66 files changed

+2768
-338
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2768
-338
lines changed

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ vale 3.6.0
2323
# docker/jdkato/vale v3.6.0@sha256:0ef22c8d537f079633cfff69fc46f69a2196072f69cab1ab232e8a79a388e425 # SEE: https://hub.docker.com/r/jdkato/vale/tags
2424
# docker/koalaman/shellcheck latest@sha256:e40388688bae0fcffdddb7e4dea49b900c18933b452add0930654b2dea3e7d5c # SEE: https://hub.docker.com/r/koalaman/shellcheck/tags
2525
# docker/mstruebing/editorconfig-checker 2.7.1@sha256:dd3ca9ea50ef4518efe9be018d669ef9cf937f6bb5cfe2ef84ff2a620b5ddc24 # SEE: https://hub.docker.com/r/mstruebing/editorconfig-checker/tags
26-
# docker/sonarsource/sonar-scanner-cli 5.0.1@sha256:494ecc3b5b1ee1625bd377b3905c4284e4f0cc155cff397805a244dee1c7d575 # SEE: https://hub.docker.com/r/sonarsource/sonar-scanner-cli/tags
26+
# docker/sonarsource/sonar-scanner-cli 11.3@sha256:7462f132388135e32b948f8f18ff0db9ae28a87c6777f1df5b2207e04a6d7c5c # SEE: https://hub.docker.com/r/sonarsource/sonar-scanner-cli/tags

frontend/src/__tests__/app/request-proof-of-template/page.test.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,25 @@ import {
1414
NHS_APP_TEMPLATE,
1515
SMS_TEMPLATE,
1616
} from '../../helpers';
17+
import { serverIsFeatureEnabled } from '@utils/server-features';
1718
import content from '@content/content';
1819

1920
const { pageTitle } = content.components.requestProof;
2021

2122
jest.mock('@utils/form-actions');
2223
jest.mock('next/navigation');
2324
jest.mock('@forms/RequestProof/RequestProof');
25+
jest.mock('@utils/server-features');
2426

2527
const getTemplateMock = jest.mocked(getTemplate);
2628
const redirectMock = jest.mocked(redirect);
29+
const serverIsFeatureEnabledMock = jest.mocked(serverIsFeatureEnabled);
2730

2831
describe('RequestProofPage', () => {
29-
beforeEach(jest.resetAllMocks);
32+
beforeEach(() => {
33+
jest.resetAllMocks();
34+
serverIsFeatureEnabledMock.mockResolvedValueOnce(true);
35+
});
3036

3137
test('should load page', async () => {
3238
const state = {
@@ -104,4 +110,17 @@ describe('RequestProofPage', () => {
104110
expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace');
105111
}
106112
);
113+
114+
test('should forbid user from requesting a proof when client does not have feature enabled', async () => {
115+
serverIsFeatureEnabledMock.mockReset();
116+
serverIsFeatureEnabledMock.mockResolvedValueOnce(false);
117+
118+
await RequestProofPage({
119+
params: Promise.resolve({
120+
templateId: 'template-id',
121+
}),
122+
});
123+
124+
expect(redirectMock).toHaveBeenCalledWith('/invalid-template', 'replace');
125+
});
107126
});

frontend/src/__tests__/app/submit-letter-template/page.test.tsx

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,85 @@ import {
1515
SMS_TEMPLATE,
1616
} from '../../helpers';
1717
import { LetterTemplate } from 'nhs-notify-web-template-management-utils';
18+
import { serverIsFeatureEnabled } from '@utils/server-features';
1819

1920
jest.mock('@utils/form-actions');
2021
jest.mock('next/navigation');
2122
jest.mock('@forms/SubmitTemplate/SubmitLetterTemplate');
23+
jest.mock('@utils/server-features');
2224

2325
const getTemplateMock = jest.mocked(getTemplate);
2426
const redirectMock = jest.mocked(redirect);
27+
const serverIsFeatureEnabledMock = jest.mocked(serverIsFeatureEnabled);
2528

2629
describe('SubmitLetterTemplatePage', () => {
27-
beforeEach(jest.resetAllMocks);
30+
const OLD_ENV = { ...process.env };
2831

29-
test('should load page', async () => {
30-
getTemplateMock.mockResolvedValue({
31-
...LETTER_TEMPLATE,
32-
createdAt: 'today',
33-
updatedAt: 'today',
34-
});
35-
36-
const page = await SubmitLetterTemplatePage({
37-
params: Promise.resolve({
38-
templateId: 'template-id',
39-
}),
40-
});
32+
beforeEach(() => {
33+
jest.resetAllMocks();
34+
process.env.NEXT_PUBLIC_ENABLE_PROOFING = 'true';
35+
});
4136

42-
expect(page).toEqual(
43-
<SubmitLetterTemplate
44-
templateName={LETTER_TEMPLATE.name}
45-
templateId={LETTER_TEMPLATE.id}
46-
/>
47-
);
37+
afterAll(() => {
38+
process.env = OLD_ENV;
4839
});
4940

41+
test.each([
42+
{
43+
globalProofing: true,
44+
clientProofing: true,
45+
expectedProofingEnabled: true,
46+
},
47+
{
48+
globalProofing: false,
49+
clientProofing: true,
50+
expectedProofingEnabled: false,
51+
},
52+
{
53+
globalProofing: true,
54+
clientProofing: false,
55+
expectedProofingEnabled: false,
56+
},
57+
{
58+
globalProofing: false,
59+
clientProofing: false,
60+
expectedProofingEnabled: false,
61+
},
62+
])(
63+
'should load page with proofingEnabled $expectedProofingEnabled when global proofing is $globalProofing and client proofing is $clientProofing',
64+
async ({
65+
globalProofing,
66+
clientProofing,
67+
expectedProofingEnabled: proofingEnabled,
68+
}) => {
69+
process.env.NEXT_PUBLIC_ENABLE_PROOFING = String(globalProofing);
70+
71+
getTemplateMock.mockResolvedValue({
72+
...LETTER_TEMPLATE,
73+
createdAt: 'today',
74+
updatedAt: 'today',
75+
});
76+
77+
serverIsFeatureEnabledMock.mockResolvedValueOnce(clientProofing);
78+
79+
const page = await SubmitLetterTemplatePage({
80+
params: Promise.resolve({
81+
templateId: 'template-id',
82+
}),
83+
});
84+
85+
expect(page).toEqual(
86+
<SubmitLetterTemplate
87+
templateName={LETTER_TEMPLATE.name}
88+
templateId={LETTER_TEMPLATE.id}
89+
proofingEnabled={proofingEnabled}
90+
/>
91+
);
92+
93+
expect(serverIsFeatureEnabledMock).toHaveBeenCalledWith('proofing');
94+
}
95+
);
96+
5097
test('should handle invalid template', async () => {
5198
getTemplateMock.mockResolvedValue(undefined);
5299

frontend/src/__tests__/components/forms/SubmitTemplate/SubmitLetterTemplate.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ describe('SubmitLetterTemplate component', () => {
3434
<SubmitLetterTemplate
3535
templateId='template-id'
3636
templateName='template-name'
37+
proofingEnabled={true}
3738
/>
3839
);
3940

@@ -47,6 +48,7 @@ describe('SubmitLetterTemplate component', () => {
4748
<SubmitLetterTemplate
4849
templateId='template-id'
4950
templateName='template-name'
51+
proofingEnabled={false}
5052
/>
5153
);
5254

frontend/src/__tests__/utils/form-actions.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import {
1818
} from '@utils/form-actions';
1919
import { getSessionServer } from '@utils/amplify-utils';
2020
import { TemplateDto } from 'nhs-notify-backend-client';
21-
import { templateClient } from 'nhs-notify-backend-client/src/template-api-client';
21+
import { templateApiClient } from 'nhs-notify-backend-client/src/template-api-client';
2222

23-
const mockedTemplateClient = jest.mocked(templateClient);
23+
const mockedTemplateClient = jest.mocked(templateApiClient);
2424
const authIdTokenServerMock = jest.mocked(getSessionServer);
2525

2626
jest.mock('@utils/amplify-utils');
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { serverIsFeatureEnabled } from '@utils/server-features';
2+
import { getSessionServer } from '@utils/amplify-utils';
3+
import { clientConfigurationApiClient } from 'nhs-notify-backend-client';
4+
5+
jest.mock('@utils/amplify-utils');
6+
jest.mock('nhs-notify-backend-client');
7+
jest.mock('nhs-notify-web-template-management-utils/logger');
8+
9+
const getSessionServerMock = jest.mocked(getSessionServer);
10+
const clientConfigurationApiClientMock = jest.mocked(
11+
clientConfigurationApiClient
12+
);
13+
14+
describe('serverIsFeatureEnabled', () => {
15+
beforeAll(() => {
16+
jest.resetAllMocks();
17+
});
18+
19+
it('should return false when no accessToken', async () => {
20+
getSessionServerMock.mockResolvedValueOnce({
21+
accessToken: undefined,
22+
userSub: undefined,
23+
});
24+
25+
const enabled = await serverIsFeatureEnabled('proofing');
26+
27+
expect(enabled).toEqual(false);
28+
});
29+
30+
it('should return false when no client', async () => {
31+
getSessionServerMock.mockResolvedValueOnce({
32+
accessToken: 'token',
33+
userSub: 'user',
34+
});
35+
36+
clientConfigurationApiClientMock.fetch.mockResolvedValueOnce({
37+
data: null,
38+
});
39+
40+
const enabled = await serverIsFeatureEnabled('proofing');
41+
42+
expect(enabled).toEqual(false);
43+
44+
expect(clientConfigurationApiClientMock.fetch).toHaveBeenCalledWith(
45+
'token'
46+
);
47+
});
48+
49+
it('returns false if fetching configuration fails unexpectedly', async () => {
50+
getSessionServerMock.mockResolvedValueOnce({
51+
accessToken: 'token',
52+
userSub: 'user',
53+
});
54+
55+
clientConfigurationApiClientMock.fetch.mockResolvedValueOnce({
56+
error: { code: 500, message: 'server error' },
57+
});
58+
59+
const enabled = await serverIsFeatureEnabled('proofing');
60+
61+
expect(enabled).toEqual(false);
62+
63+
expect(clientConfigurationApiClientMock.fetch).toHaveBeenCalledWith(
64+
'token'
65+
);
66+
});
67+
68+
it('should return false when feature is not enabled', async () => {
69+
clientConfigurationApiClientMock.fetch.mockResolvedValueOnce({
70+
data: {
71+
features: { proofing: false },
72+
},
73+
});
74+
75+
getSessionServerMock.mockResolvedValueOnce({
76+
accessToken: 'token',
77+
userSub: 'user',
78+
});
79+
80+
const enabled = await serverIsFeatureEnabled('proofing');
81+
82+
expect(enabled).toEqual(false);
83+
84+
expect(clientConfigurationApiClientMock.fetch).toHaveBeenCalledWith(
85+
'token'
86+
);
87+
});
88+
89+
it('should return true when feature is enabled', async () => {
90+
clientConfigurationApiClientMock.fetch.mockResolvedValueOnce({
91+
data: {
92+
features: { proofing: true },
93+
},
94+
});
95+
96+
getSessionServerMock.mockResolvedValueOnce({
97+
accessToken: 'token',
98+
userSub: 'user',
99+
});
100+
101+
const enabled = await serverIsFeatureEnabled('proofing');
102+
103+
expect(enabled).toEqual(true);
104+
105+
expect(clientConfigurationApiClientMock.fetch).toHaveBeenCalledWith(
106+
'token'
107+
);
108+
});
109+
});

frontend/src/app/request-proof-of-template/[templateId]/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
} from 'nhs-notify-web-template-management-utils';
1010
import { getTemplate } from '@utils/form-actions';
1111
import content from '@content/content';
12+
import { serverIsFeatureEnabled } from '@utils/server-features';
13+
1214
const { pageTitle } = content.components.requestProof;
1315

1416
export async function generateMetadata(): Promise<Metadata> {
@@ -20,6 +22,12 @@ export async function generateMetadata(): Promise<Metadata> {
2022
const RequestProofPage = async (props: PageProps) => {
2123
const { templateId } = await props.params;
2224

25+
const proofingEnabled = await serverIsFeatureEnabled('proofing');
26+
27+
if (!proofingEnabled) {
28+
return redirect('/invalid-template', RedirectType.replace);
29+
}
30+
2331
const template = await getTemplate(templateId);
2432

2533
const validatedTemplate = validateLetterTemplate(template);

frontend/src/app/submit-letter-template/[templateId]/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from 'nhs-notify-web-template-management-utils';
99
import { getTemplate } from '@utils/form-actions';
1010
import { SubmitLetterTemplate } from '@forms/SubmitTemplate/SubmitLetterTemplate';
11+
import { serverIsFeatureEnabled } from '@utils/server-features';
1112

1213
export async function generateMetadata(): Promise<Metadata> {
1314
return {
@@ -26,10 +27,17 @@ const SubmitLetterTemplatePage = async (props: PageProps) => {
2627
return redirect('/invalid-template', RedirectType.replace);
2728
}
2829

30+
const clientProofingEnabled = await serverIsFeatureEnabled('proofing');
31+
const globalProofingEnabled =
32+
process.env.NEXT_PUBLIC_ENABLE_PROOFING === 'true';
33+
34+
const proofingEnabled = clientProofingEnabled && globalProofingEnabled;
35+
2936
return (
3037
<SubmitLetterTemplate
3138
templateName={validatedTemplate.name}
3239
templateId={validatedTemplate.id}
40+
proofingEnabled={proofingEnabled}
3341
/>
3442
);
3543
};

frontend/src/components/forms/SubmitTemplate/SubmitLetterTemplate.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ export const SubmitLetterTemplateProofingDisabled: FC<{
8888
export const SubmitLetterTemplate: FC<{
8989
templateName: string;
9090
templateId: string;
91-
}> = ({ templateName, templateId }) => {
91+
proofingEnabled: boolean;
92+
}> = ({ templateName, templateId, proofingEnabled }) => {
9293
const {
9394
buttonText,
9495
goBackButtonText,
@@ -105,7 +106,7 @@ export const SubmitLetterTemplate: FC<{
105106

106107
const [_, action] = useActionState(submitTemplate, 'LETTER');
107108

108-
if (process.env.NEXT_PUBLIC_ENABLE_PROOFING !== 'true') {
109+
if (!proofingEnabled) {
109110
return (
110111
<SubmitLetterTemplateProofingDisabled
111112
templateName={templateName}

frontend/src/hooks/use-text-input.hook.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { useState } from 'react';
33
type InputValue = HTMLTextAreaElement | HTMLInputElement | HTMLSelectElement;
44

55
export const useTextInput = <T extends InputValue, S extends string = string>(
6-
initialState: string
6+
initialState: S
77
): [S, React.ChangeEventHandler<T>] => {
8-
const [value, setValue] = useState<S>(initialState as S);
8+
const [value, setValue] = useState<S>(initialState);
99

1010
const handleChange: React.ChangeEventHandler<T> = (e) => {
1111
setValue(e.target.value as S);

0 commit comments

Comments
 (0)