Skip to content

Commit 7b39613

Browse files
committed
CCM-12438: api level routing feature flag
1 parent 3bd2401 commit 7b39613

File tree

10 files changed

+323
-9
lines changed

10 files changed

+323
-9
lines changed

infrastructure/terraform/modules/backend-api/module_delete_routing_config_lambda.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,15 @@ data "aws_iam_policy_document" "delete_routing_config_lambda_policy" {
6565
var.kms_key_arn
6666
]
6767
}
68+
69+
statement {
70+
sid = "AllowSSMParameterRead"
71+
effect = "Allow"
72+
73+
actions = [
74+
"ssm:GetParameter",
75+
]
76+
77+
resources = [local.client_ssm_path_pattern]
78+
}
6879
}

infrastructure/terraform/modules/backend-api/module_submit_routing_config_lambda.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,15 @@ data "aws_iam_policy_document" "submit_routing_config_lambda_policy" {
6565
var.kms_key_arn
6666
]
6767
}
68+
69+
statement {
70+
sid = "AllowSSMParameterRead"
71+
effect = "Allow"
72+
73+
actions = [
74+
"ssm:GetParameter",
75+
]
76+
77+
resources = [local.client_ssm_path_pattern]
78+
}
6879
}

lambdas/backend-api/src/__tests__/templates/app/routing-config-client.test.ts

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ describe('RoutingConfigClient', () => {
275275
};
276276

277277
mocks.clientConfigRepository.get.mockResolvedValueOnce({
278-
data: { features: {}, campaignIds: [campaignId] },
278+
data: { features: { routing: true }, campaignIds: [campaignId] },
279279
});
280280

281281
mocks.routingConfigRepository.create.mockResolvedValueOnce({
@@ -356,7 +356,7 @@ describe('RoutingConfigClient', () => {
356356
};
357357

358358
mocks.clientConfigRepository.get.mockResolvedValueOnce({
359-
data: { features: {}, campaignIds: ['campaign'] },
359+
data: { features: { routing: true }, campaignIds: ['campaign'] },
360360
});
361361

362362
mocks.routingConfigRepository.create.mockResolvedValueOnce({
@@ -420,6 +420,41 @@ describe('RoutingConfigClient', () => {
420420
});
421421
});
422422

423+
test('returns failure if routing feature is disabled for the client', async () => {
424+
const { client, mocks } = setup();
425+
426+
const input: CreateUpdateRoutingConfig = {
427+
name: 'rc',
428+
campaignId: 'campaign',
429+
cascade: [
430+
{
431+
cascadeGroups: ['standard'],
432+
channel: 'SMS',
433+
channelType: 'primary',
434+
defaultTemplateId: 'sms',
435+
},
436+
],
437+
cascadeGroupOverrides: [{ name: 'standard' }],
438+
};
439+
440+
mocks.clientConfigRepository.get.mockResolvedValueOnce({
441+
data: { features: { routing: false }, campaignIds: ['campaign'] },
442+
});
443+
444+
const result = await client.createRoutingConfig(input, user);
445+
446+
expect(mocks.routingConfigRepository.create).not.toHaveBeenCalled();
447+
448+
expect(result).toEqual({
449+
error: {
450+
errorMeta: {
451+
code: 400,
452+
description: 'Routing feature is disabled',
453+
},
454+
},
455+
});
456+
});
457+
423458
test('returns failure if campaignId is not allowed for the client', async () => {
424459
const { client, mocks } = setup();
425460

@@ -438,7 +473,10 @@ describe('RoutingConfigClient', () => {
438473
};
439474

440475
mocks.clientConfigRepository.get.mockResolvedValueOnce({
441-
data: { features: {}, campaignIds: ['another campaign'] },
476+
data: {
477+
features: { routing: true },
478+
campaignIds: ['another campaign'],
479+
},
442480
});
443481

444482
const result = await client.createRoutingConfig(input, user);
@@ -460,6 +498,10 @@ describe('RoutingConfigClient', () => {
460498
test('returns completed routing config', async () => {
461499
const { client, mocks } = setup();
462500

501+
mocks.clientConfigRepository.get.mockResolvedValueOnce({
502+
data: { features: { routing: true } },
503+
});
504+
463505
const id = '2cb1c52d-befa-42f4-8628-06cfe63aa64d';
464506

465507
const completed: RoutingConfig = {
@@ -482,12 +524,37 @@ describe('RoutingConfigClient', () => {
482524
data: completed,
483525
});
484526
});
527+
528+
test('returns failure if routing feature is disabled for the client', async () => {
529+
const { client, mocks } = setup();
530+
531+
mocks.clientConfigRepository.get.mockResolvedValueOnce({
532+
data: { features: { routing: false } },
533+
});
534+
535+
const result = await client.submitRoutingConfig('some-id', user);
536+
537+
expect(mocks.routingConfigRepository.submit).not.toHaveBeenCalled();
538+
539+
expect(result).toEqual({
540+
error: {
541+
errorMeta: {
542+
code: 400,
543+
description: 'Routing feature is disabled',
544+
},
545+
},
546+
});
547+
});
485548
});
486549

487550
describe('deleteRoutingConfig', () => {
488551
test('returns undefined after deleting routing config', async () => {
489552
const { client, mocks } = setup();
490553

554+
mocks.clientConfigRepository.get.mockResolvedValueOnce({
555+
data: { features: { routing: true } },
556+
});
557+
491558
const id = '2cb1c52d-befa-42f4-8628-06cfe63aa64d';
492559

493560
const deleted: RoutingConfig = {
@@ -514,6 +581,10 @@ describe('RoutingConfigClient', () => {
514581
test('returns error response from repository', async () => {
515582
const { client, mocks } = setup();
516583

584+
mocks.clientConfigRepository.get.mockResolvedValueOnce({
585+
data: { features: { routing: true } },
586+
});
587+
517588
const id = '2cb1c52d-befa-42f4-8628-06cfe63aa64d';
518589

519590
const errorResponse = {
@@ -531,6 +602,27 @@ describe('RoutingConfigClient', () => {
531602

532603
expect(result).toEqual(errorResponse);
533604
});
605+
606+
test('returns failure if routing feature is disabled for the client', async () => {
607+
const { client, mocks } = setup();
608+
609+
mocks.clientConfigRepository.get.mockResolvedValueOnce({
610+
data: { features: { routing: false }, campaignIds: ['campaign'] },
611+
});
612+
613+
const result = await client.deleteRoutingConfig('some-id', user);
614+
615+
expect(mocks.routingConfigRepository.delete).not.toHaveBeenCalled();
616+
617+
expect(result).toEqual({
618+
error: {
619+
errorMeta: {
620+
code: 400,
621+
description: 'Routing feature is disabled',
622+
},
623+
},
624+
});
625+
});
534626
});
535627

536628
describe('updateRoutingConfig', () => {
@@ -550,7 +642,10 @@ describe('RoutingConfigClient', () => {
550642
};
551643

552644
mocks.clientConfigRepository.get.mockResolvedValueOnce({
553-
data: { features: {}, campaignIds: [routingConfig.campaignId] },
645+
data: {
646+
features: { routing: true },
647+
campaignIds: [routingConfig.campaignId],
648+
},
554649
});
555650

556651
mocks.routingConfigRepository.update.mockResolvedValueOnce({
@@ -648,6 +743,41 @@ describe('RoutingConfigClient', () => {
648743
});
649744
});
650745

746+
test('returns failure if routing feature is disabled for the client', async () => {
747+
const { client, mocks } = setup();
748+
749+
const update: CreateUpdateRoutingConfig = {
750+
cascade: routingConfig.cascade,
751+
cascadeGroupOverrides: routingConfig.cascadeGroupOverrides,
752+
name: routingConfig.name,
753+
campaignId: 'this campaign',
754+
};
755+
756+
mocks.clientConfigRepository.get.mockResolvedValueOnce({
757+
data: {
758+
features: { routing: false },
759+
campaignIds: ['this campaign'],
760+
},
761+
});
762+
763+
const result = await client.updateRoutingConfig(
764+
routingConfig.id,
765+
update,
766+
user
767+
);
768+
769+
expect(mocks.routingConfigRepository.update).not.toHaveBeenCalled();
770+
771+
expect(result).toEqual({
772+
error: {
773+
errorMeta: {
774+
code: 400,
775+
description: 'Routing feature is disabled',
776+
},
777+
},
778+
});
779+
});
780+
651781
test('returns failure if campaignId is not allowed for the client', async () => {
652782
const { client, mocks } = setup();
653783

@@ -659,7 +789,10 @@ describe('RoutingConfigClient', () => {
659789
};
660790

661791
mocks.clientConfigRepository.get.mockResolvedValueOnce({
662-
data: { features: {}, campaignIds: ['another campaign'] },
792+
data: {
793+
features: { routing: true },
794+
campaignIds: ['another campaign'],
795+
},
663796
});
664797

665798
const result = await client.updateRoutingConfig(

lambdas/backend-api/src/templates/app/routing-config-client.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ export class RoutingConfigClient {
4040

4141
if (clientConfigurationError) return clientConfigurationResult;
4242

43+
if (!clientConfiguration?.features.routing) {
44+
return failure(
45+
ErrorCase.VALIDATION_FAILED,
46+
'Routing feature is disabled'
47+
);
48+
}
49+
4350
if (!clientConfiguration?.campaignIds?.includes(validated.campaignId)) {
4451
return failure(
4552
ErrorCase.VALIDATION_FAILED,
@@ -73,6 +80,13 @@ export class RoutingConfigClient {
7380

7481
if (clientConfigurationError) return clientConfigurationResult;
7582

83+
if (!clientConfiguration?.features.routing) {
84+
return failure(
85+
ErrorCase.VALIDATION_FAILED,
86+
'Routing feature is disabled'
87+
);
88+
}
89+
7690
if (!clientConfiguration?.campaignIds?.includes(validated.campaignId)) {
7791
return failure(
7892
ErrorCase.VALIDATION_FAILED,
@@ -91,13 +105,45 @@ export class RoutingConfigClient {
91105
routingConfigId: string,
92106
user: User
93107
): Promise<Result<RoutingConfig>> {
108+
const clientConfigurationResult = await this.clientConfigRepository.get(
109+
user.clientId
110+
);
111+
112+
const { data: clientConfiguration, error: clientConfigurationError } =
113+
clientConfigurationResult;
114+
115+
if (clientConfigurationError) return clientConfigurationResult;
116+
117+
if (!clientConfiguration?.features.routing) {
118+
return failure(
119+
ErrorCase.VALIDATION_FAILED,
120+
'Routing feature is disabled'
121+
);
122+
}
123+
94124
return this.routingConfigRepository.submit(routingConfigId, user);
95125
}
96126

97127
async deleteRoutingConfig(
98128
routingConfigId: string,
99129
user: User
100130
): Promise<Result<undefined>> {
131+
const clientConfigurationResult = await this.clientConfigRepository.get(
132+
user.clientId
133+
);
134+
135+
const { data: clientConfiguration, error: clientConfigurationError } =
136+
clientConfigurationResult;
137+
138+
if (clientConfigurationError) return clientConfigurationResult;
139+
140+
if (!clientConfiguration?.features.routing) {
141+
return failure(
142+
ErrorCase.VALIDATION_FAILED,
143+
'Routing feature is disabled'
144+
);
145+
}
146+
101147
const result = await this.routingConfigRepository.delete(
102148
routingConfigId,
103149
user

tests/test-team/helpers/auth/cognito-auth-helper.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ export const testUsers: Record<string, TestUserStaticDetails> = {
122122
userId: 'UserWithFallbackCampaignId',
123123
clientKey: 'ClientWithFallbackCampaignId',
124124
},
125+
126+
/**
127+
* UserRoutingEnabled belongs to an alternate client with routing enabled
128+
*/
129+
UserRoutingEnabled: {
130+
userId: 'UserWithRoutingEnabled',
131+
clientKey: 'ClientRoutingEnabled',
132+
},
125133
};
126134

127135
export type TestUser = TestUserStaticDetails &

tests/test-team/helpers/client/client-helper.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type ClientConfiguration = {
1515
};
1616

1717
export type ClientKey =
18-
`Client${1 | 2 | 3 | 4 | 5 | 6 | 'WithMultipleCampaigns' | 'WithFallbackCampaignId'}`;
18+
`Client${1 | 2 | 3 | 4 | 5 | 6 | 'WithMultipleCampaigns' | 'WithFallbackCampaignId' | 'RoutingEnabled'}`;
1919

2020
type TestClients = Record<ClientKey, ClientConfiguration | undefined>;
2121

@@ -96,6 +96,15 @@ export const testClients: TestClients = {
9696
routing: false,
9797
},
9898
},
99+
100+
/**
101+
* ClientRoutingEnabled is an alternative client with routing enabled
102+
*/
103+
ClientRoutingEnabled: {
104+
campaignIds: ['RoutingEnabledCampaign'],
105+
name: 'Routing Enabled Client',
106+
features: { proofing: false, routing: true },
107+
},
99108
};
100109

101110
export class ClientConfigurationHelper {

0 commit comments

Comments
 (0)