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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @jest-environment node
*/
import { reviewAndApproveLetterTemplateAction } from '@app/review-and-approve-letter-template/[templateId]/server-action';
import { redirect } from 'next/navigation';
import { redirect, RedirectType } from 'next/navigation';
import { approveTemplate } from '@utils/form-actions';

jest.mock('next/navigation');
Expand Down Expand Up @@ -93,6 +93,38 @@ describe('reviewAndApproveLetterTemplateAction', () => {
expect(redirectMock).not.toHaveBeenCalled();
});

it('should redirect to preview page when lockNumber is invalid for API error 409', async () => {
approveTemplateMock.mockRejectedValueOnce(new Error('CONFLICT'));
const formData = new FormData();
formData.append('templateId', validTemplateId);
formData.append('lockNumber', '0');

await reviewAndApproveLetterTemplateAction({}, formData);

expect(approveTemplateMock).toHaveBeenCalledWith(validTemplateId, 0);
expect(redirectMock).toHaveBeenCalledWith(
`/preview-letter-template/${validTemplateId}`,
RedirectType.push
);
});

test('should throw failed to approve template error for API error 404', async () => {
approveTemplateMock.mockRejectedValueOnce(
new Error('Failed to approve template')
);

const formData = new FormData();
formData.append('templateId', validTemplateId);
formData.append('lockNumber', '1');

await expect(
reviewAndApproveLetterTemplateAction({}, formData)
).rejects.toThrow('Failed to approve template');

expect(approveTemplateMock).toHaveBeenCalledWith(validTemplateId, 1);
expect(redirectMock).not.toHaveBeenCalled();
});

it('should return error state when templateId is empty', async () => {
const formData = new FormData();
formData.append('templateId', '');
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/__tests__/utils/form-actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,35 @@ describe('form-actions', () => {
'Failed to get access token'
);
});

test('approveTemplate - should throw CONFLICT error when lockNumber is invalid', async () => {
const error = {
errorMeta: {
code: 409,
description:
'Lock number mismatch - Template has been modified since template',
},
};

mockedTemplateClient.approveTemplate.mockResolvedValueOnce({
error,
});

await expect(approveTemplate('template-id', 0)).rejects.toThrow(
'CONFLICT'
);

expect(mockedTemplateClient.approveTemplate).toHaveBeenCalledWith(
'template-id',
'token',
0
);

expect(loggerMock.error).toHaveBeenCalledWith(
'Failed to approve template',
error
);
});
});

describe('setTemplateToDeleted', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { z } from 'zod/v4';
import type { FormState } from '@utils/types';
import { redirect } from 'next/navigation';
import { redirect, RedirectType } from 'next/navigation';
import { approveTemplate } from '@utils/form-actions';
import { $LockNumber } from 'nhs-notify-backend-client/schemas';

Expand All @@ -25,7 +25,17 @@ export async function reviewAndApproveLetterTemplateAction(

const { templateId, lockNumber } = result.data;

await approveTemplate(templateId, lockNumber);
try {
await approveTemplate(templateId, lockNumber);
redirect(`/letter-template-approved/${templateId}`);
} catch (error) {
if (error instanceof Error && error.message === 'CONFLICT') {
return redirect(
`/preview-letter-template/${templateId}`,
RedirectType.push
);
}

redirect(`/letter-template-approved/${templateId}`);
throw error;
}
}
5 changes: 5 additions & 0 deletions frontend/src/utils/form-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ export async function approveTemplate(

if (error) {
logger.error('Failed to approve template', error);

if (error.errorMeta?.code && error.errorMeta.code === 409) {
throw new Error('CONFLICT');
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I set the error to use the CONFLICT description as it defines what is being targeted which is also the error that is triggered of the lockNumber doesn't match.

}

throw new Error('Failed to approve template');
}

Expand Down
Loading