@@ -2,9 +2,13 @@ import { randomUUID } from 'node:crypto';
22import { removeTemplateFromMessagePlan } from './actions' ;
33import { getRoutingConfig , updateRoutingConfig } from '@utils/message-plans' ;
44import {
5+ CascadeGroupAccessible ,
56 CascadeGroupName ,
7+ CascadeGroupTranslations ,
68 Channel ,
79 ChannelType ,
10+ Language ,
11+ LetterType ,
812 RoutingConfigStatus ,
913} from 'nhs-notify-backend-client' ;
1014import { redirect } from 'next/navigation' ;
@@ -18,6 +22,13 @@ const mockGetRoutingConfig = jest.mocked(getRoutingConfig);
1822const mockUpdateRoutingConfig = jest . mocked ( updateRoutingConfig ) ;
1923
2024const routingConfigId = randomUUID ( ) ;
25+ const emailTemplateId = randomUUID ( ) ;
26+ const smsTemplateId = randomUUID ( ) ;
27+ const polishTemplateId = randomUUID ( ) ;
28+ const frenchTemplateId = randomUUID ( ) ;
29+ const accessibleFormatId = randomUUID ( ) ;
30+ const largePrintId = randomUUID ( ) ;
31+
2132const baseConfig = {
2233 id : routingConfigId ,
2334 campaignId : 'campaign1' ,
@@ -31,13 +42,13 @@ const baseConfig = {
3142 channel : 'EMAIL' as Channel ,
3243 channelType : 'primary' as ChannelType ,
3344 cascadeGroups : [ 'standard' as CascadeGroupName ] ,
34- defaultTemplateId : 'template-1' ,
45+ defaultTemplateId : emailTemplateId ,
3546 } ,
3647 {
3748 channel : 'SMS' as Channel ,
3849 channelType : 'primary' as ChannelType ,
3950 cascadeGroups : [ 'standard' as CascadeGroupName ] ,
40- defaultTemplateId : 'template-2' ,
51+ defaultTemplateId : smsTemplateId ,
4152 } ,
4253 ] ,
4354 cascadeGroupOverrides : [ ] ,
@@ -48,12 +59,12 @@ describe('removeTemplateFromMessagePlan', () => {
4859 jest . clearAllMocks ( ) ;
4960 } ) ;
5061
51- it ( 'removes the template from the correct channel and updates the routing configuration' , async ( ) => {
62+ it ( 'removes the correct template from the cascade item and updates the routing configuration' , async ( ) => {
5263 mockGetRoutingConfig . mockResolvedValue ( baseConfig ) ;
5364
5465 const formData = new FormData ( ) ;
5566 formData . set ( 'routingConfigId' , routingConfigId ) ;
56- formData . set ( 'channel ' , 'EMAIL' ) ;
67+ formData . set ( 'templateId ' , emailTemplateId ) ;
5768
5869 await removeTemplateFromMessagePlan ( formData ) ;
5970
@@ -69,20 +80,272 @@ describe('removeTemplateFromMessagePlan', () => {
6980 } ) ,
7081 expect . objectContaining ( {
7182 channel : 'SMS' ,
72- defaultTemplateId : 'template-2' ,
83+ defaultTemplateId : smsTemplateId ,
84+ } ) ,
85+ ] ,
86+ } )
87+ ) ;
88+ } ) ;
89+
90+ it ( 'removes multiple templates at once' , async ( ) => {
91+ const configWithConditionalTemplates = {
92+ ...baseConfig ,
93+ cascade : [
94+ {
95+ ...baseConfig . cascade [ 0 ] ,
96+ conditionalTemplates : [
97+ {
98+ accessibleFormat : 'x1' as LetterType ,
99+ templateId : largePrintId ,
100+ } ,
101+ { language : 'pl' as Language , templateId : polishTemplateId } ,
102+ { language : 'fr' as Language , templateId : frenchTemplateId } ,
103+ ] ,
104+ } ,
105+ baseConfig . cascade [ 1 ] ,
106+ ] ,
107+ } ;
108+
109+ mockGetRoutingConfig . mockResolvedValue ( configWithConditionalTemplates ) ;
110+
111+ const formData = new FormData ( ) ;
112+ formData . set ( 'routingConfigId' , routingConfigId ) ;
113+ formData . append ( 'templateId' , polishTemplateId ) ;
114+ formData . append ( 'templateId' , frenchTemplateId ) ;
115+
116+ await removeTemplateFromMessagePlan ( formData ) ;
117+
118+ expect ( mockUpdateRoutingConfig ) . toHaveBeenCalledWith (
119+ routingConfigId ,
120+ expect . objectContaining ( {
121+ cascade : [
122+ expect . objectContaining ( {
123+ channel : 'EMAIL' ,
124+ defaultTemplateId : emailTemplateId ,
125+ conditionalTemplates : [
126+ {
127+ accessibleFormat : 'x1' ,
128+ templateId : largePrintId ,
129+ } ,
130+ ] ,
131+ } ) ,
132+ expect . objectContaining ( {
133+ channel : 'SMS' ,
134+ } ) ,
135+ ] ,
136+ } )
137+ ) ;
138+ } ) ;
139+
140+ it ( 'removes conditional templates from cascade items' , async ( ) => {
141+ const configWithConditionalTemplate = {
142+ ...baseConfig ,
143+ cascade : [
144+ {
145+ ...baseConfig . cascade [ 0 ] ,
146+ channel : 'LETTER' as Channel ,
147+ conditionalTemplates : [
148+ { accessibleFormat : 'x1' as LetterType , templateId : largePrintId } ,
149+ ] ,
150+ } ,
151+ ] ,
152+ } ;
153+
154+ mockGetRoutingConfig . mockResolvedValue ( configWithConditionalTemplate ) ;
155+
156+ const formData = new FormData ( ) ;
157+ formData . set ( 'routingConfigId' , routingConfigId ) ;
158+ formData . set ( 'templateId' , largePrintId ) ;
159+
160+ await removeTemplateFromMessagePlan ( formData ) ;
161+
162+ expect ( mockUpdateRoutingConfig ) . toHaveBeenCalledWith (
163+ routingConfigId ,
164+ expect . objectContaining ( {
165+ cascade : [
166+ expect . objectContaining ( {
167+ channel : 'LETTER' ,
168+ } ) ,
169+ ] ,
170+ } )
171+ ) ;
172+
173+ // Verify conditionalTemplates was removed
174+ const [ [ , updateConfig ] ] = mockUpdateRoutingConfig . mock . calls ;
175+ expect ( updateConfig . cascade ?. [ 0 ] . conditionalTemplates ) . toBeUndefined ( ) ;
176+ } ) ;
177+
178+ it ( 'updates cascadeGroupOverrides when templates are removed' , async ( ) => {
179+ const configWithOverrides = {
180+ ...baseConfig ,
181+ cascade : [
182+ {
183+ channel : 'LETTER' as Channel ,
184+ channelType : 'primary' as ChannelType ,
185+ cascadeGroups : [
186+ 'standard' as CascadeGroupName ,
187+ 'accessible' as CascadeGroupName ,
188+ 'translations' as CascadeGroupName ,
189+ ] ,
190+ defaultTemplateId : emailTemplateId ,
191+ conditionalTemplates : [
192+ {
193+ accessibleFormat : 'q4' as LetterType ,
194+ templateId : accessibleFormatId ,
195+ } ,
196+ {
197+ accessibleFormat : 'x1' as LetterType ,
198+ templateId : largePrintId ,
199+ } ,
200+ { language : 'pl' as Language , templateId : polishTemplateId } ,
201+ { language : 'fr' as Language , templateId : frenchTemplateId } ,
202+ ] ,
203+ } ,
204+ ] ,
205+ cascadeGroupOverrides : [
206+ {
207+ name : 'accessible' as CascadeGroupName ,
208+ accessibleFormat : [ 'q4' as LetterType , 'x1' as LetterType ] ,
209+ } as CascadeGroupAccessible ,
210+ {
211+ name : 'translations' as CascadeGroupName ,
212+ language : [ 'pl' as Language , 'fr' as Language ] ,
213+ } as CascadeGroupTranslations ,
214+ ] ,
215+ } ;
216+
217+ mockGetRoutingConfig . mockResolvedValue ( configWithOverrides ) ;
218+
219+ const formData = new FormData ( ) ;
220+ formData . set ( 'routingConfigId' , routingConfigId ) ;
221+ formData . append ( 'templateId' , accessibleFormatId ) ;
222+ formData . append ( 'templateId' , polishTemplateId ) ;
223+ formData . append ( 'templateId' , frenchTemplateId ) ;
224+
225+ await removeTemplateFromMessagePlan ( formData ) ;
226+
227+ expect ( mockUpdateRoutingConfig ) . toHaveBeenCalledWith (
228+ routingConfigId ,
229+ expect . objectContaining ( {
230+ cascadeGroupOverrides : [
231+ {
232+ name : 'accessible' ,
233+ accessibleFormat : [ 'x1' ] ,
234+ } ,
235+ ] ,
236+ } )
237+ ) ;
238+ } ) ;
239+
240+ it ( 'updates cascadeGroups on cascade items when templates are removed' , async ( ) => {
241+ const configWithConditionalTemplates = {
242+ ...baseConfig ,
243+ cascade : [
244+ {
245+ channel : 'LETTER' as Channel ,
246+ channelType : 'primary' as ChannelType ,
247+ cascadeGroups : [
248+ 'standard' as CascadeGroupName ,
249+ 'accessible' as CascadeGroupName ,
250+ 'translations' as CascadeGroupName ,
251+ ] ,
252+ defaultTemplateId : emailTemplateId ,
253+ conditionalTemplates : [
254+ {
255+ accessibleFormat : 'x1' as LetterType ,
256+ templateId : largePrintId ,
257+ } ,
258+ { language : 'pl' as Language , templateId : polishTemplateId } ,
259+ { language : 'fr' as Language , templateId : frenchTemplateId } ,
260+ ] ,
261+ } ,
262+ ] ,
263+ cascadeGroupOverrides : [ ] ,
264+ } ;
265+
266+ mockGetRoutingConfig . mockResolvedValue ( configWithConditionalTemplates ) ;
267+
268+ const formData = new FormData ( ) ;
269+ formData . set ( 'routingConfigId' , routingConfigId ) ;
270+ formData . append ( 'templateId' , polishTemplateId ) ;
271+ formData . append ( 'templateId' , frenchTemplateId ) ;
272+
273+ await removeTemplateFromMessagePlan ( formData ) ;
274+
275+ expect ( mockUpdateRoutingConfig ) . toHaveBeenCalledWith (
276+ routingConfigId ,
277+ expect . objectContaining ( {
278+ cascade : [
279+ expect . objectContaining ( {
280+ channel : 'LETTER' ,
281+ cascadeGroups : [ 'standard' , 'accessible' ] ,
282+ conditionalTemplates : [
283+ {
284+ accessibleFormat : 'x1' ,
285+ templateId : largePrintId ,
286+ } ,
287+ ] ,
73288 } ) ,
74289 ] ,
75290 } )
76291 ) ;
77292 } ) ;
78293
294+ it ( 'updates cascadeGroups to only standard when all conditional templates are removed' , async ( ) => {
295+ const configWithConditionalTemplates = {
296+ ...baseConfig ,
297+ cascade : [
298+ {
299+ channel : 'LETTER' as Channel ,
300+ channelType : 'primary' as ChannelType ,
301+ cascadeGroups : [
302+ 'standard' as CascadeGroupName ,
303+ 'accessible' as CascadeGroupName ,
304+ ] ,
305+ defaultTemplateId : emailTemplateId ,
306+ conditionalTemplates : [
307+ {
308+ accessibleFormat : 'x1' as LetterType ,
309+ templateId : largePrintId ,
310+ } ,
311+ ] ,
312+ } ,
313+ ] ,
314+ cascadeGroupOverrides : [ ] ,
315+ } ;
316+
317+ mockGetRoutingConfig . mockResolvedValue ( configWithConditionalTemplates ) ;
318+
319+ const formData = new FormData ( ) ;
320+ formData . set ( 'routingConfigId' , routingConfigId ) ;
321+ formData . set ( 'templateId' , largePrintId ) ;
322+
323+ await removeTemplateFromMessagePlan ( formData ) ;
324+
325+ expect ( mockUpdateRoutingConfig ) . toHaveBeenCalledWith (
326+ routingConfigId ,
327+ expect . objectContaining ( {
328+ cascade : [
329+ expect . objectContaining ( {
330+ channel : 'LETTER' ,
331+ cascadeGroups : [ 'standard' ] ,
332+ } ) ,
333+ ] ,
334+ } )
335+ ) ;
336+
337+ // Verify conditionalTemplates was removed
338+ const [ [ , updateConfig ] ] = mockUpdateRoutingConfig . mock . calls ;
339+ expect ( updateConfig . cascade ?. [ 0 ] . conditionalTemplates ) . toBeUndefined ( ) ;
340+ } ) ;
341+
79342 it ( 'refreshes the choose-templates page after successful removal' , async ( ) => {
80343 mockGetRoutingConfig . mockResolvedValue ( baseConfig ) ;
81344 mockUpdateRoutingConfig . mockResolvedValue ( undefined ) ;
82345
83346 const formData = new FormData ( ) ;
84347 formData . set ( 'routingConfigId' , routingConfigId ) ;
85- formData . set ( 'channel ' , 'EMAIL' ) ;
348+ formData . set ( 'templateId ' , emailTemplateId ) ;
86349
87350 await removeTemplateFromMessagePlan ( formData ) ;
88351
@@ -96,7 +359,7 @@ describe('removeTemplateFromMessagePlan', () => {
96359
97360 const formData = new FormData ( ) ;
98361 formData . set ( 'routingConfigId' , routingConfigId ) ;
99- formData . set ( 'channel ' , 'EMAIL' ) ;
362+ formData . set ( 'templateId ' , emailTemplateId ) ;
100363
101364 await expect ( removeTemplateFromMessagePlan ( formData ) ) . rejects . toThrow (
102365 / n o t f o u n d /
@@ -114,7 +377,7 @@ describe('removeTemplateFromMessagePlan', () => {
114377 it ( 'throws an error if form data is invalid' , async ( ) => {
115378 const formData = new FormData ( ) ;
116379 formData . set ( 'routingConfigId' , 'invalid-id' ) ;
117- formData . set ( 'channel ' , 'test ' ) ;
380+ formData . set ( 'templateId ' , '' ) ;
118381
119382 await expect ( removeTemplateFromMessagePlan ( formData ) ) . rejects . toThrow (
120383 / I n v a l i d f o r m d a t a /
0 commit comments