diff --git a/infrastructure/terraform/modules/backend-api/README.md b/infrastructure/terraform/modules/backend-api/README.md
index d9bd38dc8..1ef7b2c65 100644
--- a/infrastructure/terraform/modules/backend-api/README.md
+++ b/infrastructure/terraform/modules/backend-api/README.md
@@ -38,6 +38,7 @@ No requirements.
| Name | Source | Version |
|------|--------|---------|
| [authorizer\_lambda](#module\_authorizer\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.22/terraform-lambda.zip | n/a |
+| [count\_routing\_configs\_lambda](#module\_count\_routing\_configs\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.22/terraform-lambda.zip | n/a |
| [create\_template\_lambda](#module\_create\_template\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.22/terraform-lambda.zip | n/a |
| [delete\_template\_lambda](#module\_delete\_template\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.22/terraform-lambda.zip | n/a |
| [get\_client\_lambda](#module\_get\_client\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.22/terraform-lambda.zip | n/a |
diff --git a/infrastructure/terraform/modules/backend-api/iam_role_api_gateway_execution_role.tf b/infrastructure/terraform/modules/backend-api/iam_role_api_gateway_execution_role.tf
index b6e45176d..db31b3e2e 100644
--- a/infrastructure/terraform/modules/backend-api/iam_role_api_gateway_execution_role.tf
+++ b/infrastructure/terraform/modules/backend-api/iam_role_api_gateway_execution_role.tf
@@ -50,6 +50,7 @@ data "aws_iam_policy_document" "api_gateway_execution_policy" {
resources = [
module.authorizer_lambda.function_arn,
module.upload_letter_template_lambda.function_arn,
+ module.count_routing_configs_lambda.function_arn,
module.create_template_lambda.function_arn,
module.delete_template_lambda.function_arn,
module.get_client_lambda.function_arn,
diff --git a/infrastructure/terraform/modules/backend-api/locals.tf b/infrastructure/terraform/modules/backend-api/locals.tf
index 05cb2d62e..368c2fafd 100644
--- a/infrastructure/terraform/modules/backend-api/locals.tf
+++ b/infrastructure/terraform/modules/backend-api/locals.tf
@@ -11,20 +11,21 @@ locals {
client_ssm_path_pattern = "arn:aws:ssm:${var.region}:${var.aws_account_id}:parameter${local.client_ssm_path_prefix}/*"
openapi_spec = templatefile("${path.module}/spec.tmpl.json", {
- APIG_EXECUTION_ROLE_ARN = aws_iam_role.api_gateway_execution_role.arn
- AUTHORIZER_LAMBDA_ARN = module.authorizer_lambda.function_arn
- AWS_REGION = var.region
- CREATE_LAMBDA_ARN = module.create_template_lambda.function_arn
- DELETE_LAMBDA_ARN = module.delete_template_lambda.function_arn
- GET_CLIENT_LAMBDA_ARN = module.get_client_lambda.function_arn
- GET_LAMBDA_ARN = module.get_template_lambda.function_arn
- GET_ROUTING_CONFIG_LAMBDA_ARN = module.get_routing_config_lambda.function_arn
- LIST_LAMBDA_ARN = module.list_template_lambda.function_arn
- LIST_ROUTING_CONFIGS_LAMBDA_ARN = module.list_routing_configs_lambda.function_arn
- REQUEST_PROOF_LAMBDA_ARN = module.request_proof_lambda.function_arn
- SUBMIT_LAMBDA_ARN = module.submit_template_lambda.function_arn
- UPDATE_LAMBDA_ARN = module.update_template_lambda.function_arn
- UPLOAD_LETTER_LAMBDA_ARN = module.upload_letter_template_lambda.function_arn
+ APIG_EXECUTION_ROLE_ARN = aws_iam_role.api_gateway_execution_role.arn
+ AUTHORIZER_LAMBDA_ARN = module.authorizer_lambda.function_arn
+ AWS_REGION = var.region
+ COUNT_ROUTING_CONFIGS_LAMBDA_ARN = module.count_routing_configs_lambda.function_arn
+ CREATE_LAMBDA_ARN = module.create_template_lambda.function_arn
+ DELETE_LAMBDA_ARN = module.delete_template_lambda.function_arn
+ GET_CLIENT_LAMBDA_ARN = module.get_client_lambda.function_arn
+ GET_LAMBDA_ARN = module.get_template_lambda.function_arn
+ GET_ROUTING_CONFIG_LAMBDA_ARN = module.get_routing_config_lambda.function_arn
+ LIST_LAMBDA_ARN = module.list_template_lambda.function_arn
+ LIST_ROUTING_CONFIGS_LAMBDA_ARN = module.list_routing_configs_lambda.function_arn
+ REQUEST_PROOF_LAMBDA_ARN = module.request_proof_lambda.function_arn
+ SUBMIT_LAMBDA_ARN = module.submit_template_lambda.function_arn
+ UPDATE_LAMBDA_ARN = module.update_template_lambda.function_arn
+ UPLOAD_LETTER_LAMBDA_ARN = module.upload_letter_template_lambda.function_arn
})
backend_lambda_environment_variables = {
diff --git a/infrastructure/terraform/modules/backend-api/module_count_routing_configs_lambda.tf b/infrastructure/terraform/modules/backend-api/module_count_routing_configs_lambda.tf
new file mode 100644
index 000000000..238184c3e
--- /dev/null
+++ b/infrastructure/terraform/modules/backend-api/module_count_routing_configs_lambda.tf
@@ -0,0 +1,68 @@
+module "count_routing_configs_lambda" {
+ source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.22/terraform-lambda.zip"
+
+ project = var.project
+ environment = var.environment
+ component = var.component
+ aws_account_id = var.aws_account_id
+ region = var.region
+
+ kms_key_arn = var.kms_key_arn
+
+ function_name = "count-routing-configs"
+
+ function_module_name = "count-routing-configs"
+ handler_function_name = "handler"
+ description = "Count Routing Configs API endpoint"
+
+ memory = 512
+ timeout = 3
+ runtime = "nodejs20.x"
+
+ log_retention_in_days = var.log_retention_in_days
+
+ iam_policy_document = {
+ body = data.aws_iam_policy_document.count_routing_configs_lambda_policy.json
+ }
+
+ lambda_env_vars = local.backend_lambda_environment_variables
+ function_s3_bucket = var.function_s3_bucket
+ function_code_base_path = local.lambdas_dir
+ function_code_dir = "backend-api/dist/count-routing-configs"
+
+ send_to_firehose = var.send_to_firehose
+ log_destination_arn = var.log_destination_arn
+ log_subscription_role_arn = var.log_subscription_role_arn
+}
+
+data "aws_iam_policy_document" "count_routing_configs_lambda_policy" {
+ statement {
+ sid = "AllowDynamoAccess"
+ effect = "Allow"
+
+ actions = [
+ "dynamodb:Query",
+ ]
+
+ resources = [
+ aws_dynamodb_table.routing_configuration.arn,
+ ]
+ }
+
+ statement {
+ sid = "AllowKMSAccess"
+ effect = "Allow"
+
+ actions = [
+ "kms:Decrypt",
+ "kms:DescribeKey",
+ "kms:Encrypt",
+ "kms:GenerateDataKey*",
+ "kms:ReEncrypt*",
+ ]
+
+ resources = [
+ var.kms_key_arn
+ ]
+ }
+}
diff --git a/infrastructure/terraform/modules/backend-api/spec.tmpl.json b/infrastructure/terraform/modules/backend-api/spec.tmpl.json
index 9786c0ea7..1599f8905 100644
--- a/infrastructure/terraform/modules/backend-api/spec.tmpl.json
+++ b/infrastructure/terraform/modules/backend-api/spec.tmpl.json
@@ -376,6 +376,29 @@
],
"type": "object"
},
+ "CountSuccess": {
+ "properties": {
+ "data": {
+ "properties": {
+ "count": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "count"
+ ],
+ "type": "object"
+ },
+ "statusCode": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "data",
+ "statusCode"
+ ],
+ "type": "object"
+ },
"CreateUpdateTemplate": {
"allOf": [
{
@@ -1117,6 +1140,70 @@
}
}
},
+ "/v1/routing-configurations/count": {
+ "get": {
+ "description": "Get a count of routing configs",
+ "parameters": [
+ {
+ "description": "Filter by a single active status",
+ "in": "query",
+ "name": "status",
+ "schema": {
+ "$ref": "#/components/schemas/RoutingConfigStatusActive"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CountSuccess"
+ }
+ }
+ },
+ "description": "200 response",
+ "headers": {
+ "Content-Type": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "default": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Failure"
+ }
+ }
+ },
+ "description": "Error"
+ }
+ },
+ "security": [
+ {
+ "authorizer": []
+ }
+ ],
+ "summary": "Count routing configs",
+ "x-amazon-apigateway-integration": {
+ "contentHandling": "CONVERT_TO_TEXT",
+ "credentials": "${APIG_EXECUTION_ROLE_ARN}",
+ "httpMethod": "POST",
+ "passthroughBehavior": "WHEN_NO_TEMPLATES",
+ "responses": {
+ ".*": {
+ "statusCode": "200"
+ }
+ },
+ "timeoutInMillis": 29000,
+ "type": "AWS_PROXY",
+ "uri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${COUNT_ROUTING_CONFIGS_LAMBDA_ARN}/invocations"
+ }
+ }
+ },
"/v1/template": {
"post": {
"description": "Create a template",
diff --git a/lambdas/backend-api/build.sh b/lambdas/backend-api/build.sh
index 2b46f0547..21ec3a323 100755
--- a/lambdas/backend-api/build.sh
+++ b/lambdas/backend-api/build.sh
@@ -15,12 +15,12 @@ npx esbuild \
--outdir=dist \
--external:pdfjs-dist \
src/templates/copy-scanned-object-to-internal.ts \
- src/templates/upload-letter.ts \
+ src/templates/count-routing-configs.ts \
src/templates/create.ts \
- src/templates/delete-failed-scanned-object.ts \
src/templates/delete.ts \
- src/templates/get-client.ts \
+ src/templates/delete-failed-scanned-object.ts \
src/templates/get.ts \
+ src/templates/get-client.ts \
src/templates/get-routing-config.ts \
src/templates/list.ts \
src/templates/list-routing-configs.ts \
@@ -29,6 +29,7 @@ npx esbuild \
src/templates/set-letter-upload-virus-scan-status.ts \
src/templates/submit.ts \
src/templates/update.ts \
+ src/templates/upload-letter.ts \
src/templates/validate-letter-template-files.ts
cp -r ../../utils/utils/src/email-templates ./dist/submit
diff --git a/lambdas/backend-api/src/__tests__/templates/api/count-routing-configs.test.ts b/lambdas/backend-api/src/__tests__/templates/api/count-routing-configs.test.ts
new file mode 100644
index 000000000..156b28233
--- /dev/null
+++ b/lambdas/backend-api/src/__tests__/templates/api/count-routing-configs.test.ts
@@ -0,0 +1,117 @@
+import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
+import { mock } from 'jest-mock-extended';
+import type { Logger } from 'nhs-notify-web-template-management-utils/logger';
+import { createHandler } from '@backend-api/templates/api/count-routing-configs';
+import { RoutingConfigClient } from '@backend-api/templates/app/routing-config-client';
+
+jest.mock('nhs-notify-web-template-management-utils/logger', () => ({
+ logger: mock({
+ child: jest.fn().mockReturnThis(),
+ }),
+}));
+
+const setup = () => {
+ const routingConfigClient = mock();
+
+ const handler = createHandler({ routingConfigClient });
+
+ return { handler, mocks: { routingConfigClient } };
+};
+
+describe('CountRoutingConfigs handler', () => {
+ test.each([
+ ['undefined', undefined],
+ ['missing user', { clientId: 'client-id', user: undefined }],
+ ['missing client', { clientId: undefined, user: 'user-id' }],
+ ])(
+ 'should return 400 - Invalid request when requestContext is %s',
+ async (_, ctx) => {
+ const { handler, mocks } = setup();
+
+ const event = mock({
+ requestContext: { authorizer: ctx },
+ });
+
+ const result = await handler(event, mock(), jest.fn());
+
+ expect(result).toEqual({
+ statusCode: 400,
+ body: JSON.stringify({
+ statusCode: 400,
+ technicalMessage: 'Invalid request',
+ }),
+ });
+
+ expect(
+ mocks.routingConfigClient.countRoutingConfigs
+ ).not.toHaveBeenCalled();
+ }
+ );
+
+ test('should return error when counting routing configs fails', async () => {
+ const { handler, mocks } = setup();
+
+ mocks.routingConfigClient.countRoutingConfigs.mockResolvedValueOnce({
+ error: {
+ errorMeta: {
+ code: 500,
+ description: 'Internal server error',
+ },
+ },
+ });
+
+ const event = mock();
+ event.requestContext.authorizer = {
+ user: 'sub',
+ clientId: 'nhs-notify-client-id',
+ };
+
+ event.queryStringParameters = {
+ status: 'DRAFT',
+ };
+
+ const result = await handler(event, mock(), jest.fn());
+
+ expect(result).toEqual({
+ statusCode: 500,
+ body: JSON.stringify({
+ statusCode: 500,
+ technicalMessage: 'Internal server error',
+ }),
+ });
+
+ expect(mocks.routingConfigClient.countRoutingConfigs).toHaveBeenCalledWith(
+ 'nhs-notify-client-id',
+ { status: 'DRAFT' }
+ );
+ });
+
+ test('should return count of routing configs', async () => {
+ const { handler, mocks } = setup();
+
+ mocks.routingConfigClient.countRoutingConfigs.mockResolvedValueOnce({
+ data: { count: 99 },
+ });
+
+ const event = mock();
+ event.requestContext.authorizer = {
+ user: 'sub',
+ clientId: 'nhs-notify-client-id',
+ };
+ event.queryStringParameters = {
+ status: 'COMPLETED',
+ };
+
+ const result = await handler(event, mock(), jest.fn());
+
+ expect(result).toEqual({
+ statusCode: 200,
+ body: JSON.stringify({ statusCode: 200, data: { count: 99 } }),
+ });
+
+ expect(mocks.routingConfigClient.countRoutingConfigs).toHaveBeenCalledWith(
+ 'nhs-notify-client-id',
+ { status: 'COMPLETED' }
+ );
+ });
+});
diff --git a/lambdas/backend-api/src/__tests__/templates/app/routing-config-client.test.ts b/lambdas/backend-api/src/__tests__/templates/app/routing-config-client.test.ts
index e6979e858..7891ea859 100644
--- a/lambdas/backend-api/src/__tests__/templates/app/routing-config-client.test.ts
+++ b/lambdas/backend-api/src/__tests__/templates/app/routing-config-client.test.ts
@@ -159,4 +159,70 @@ describe('RoutingConfigClient', () => {
expect(query.status).toHaveBeenCalledWith('DRAFT');
});
});
+
+ describe('countRoutingConfigs', () => {
+ it('queries for non-deleted configs with the given owner and returns the count', async () => {
+ const { mocks, client } = setup();
+
+ const query = mockQuery();
+
+ mocks.routingConfigRepository.query.mockReturnValueOnce(query);
+
+ query.count.mockResolvedValueOnce({ data: { count: 3 } });
+
+ const result = await client.countRoutingConfigs('nhs-notify-client-id');
+
+ expect(result).toEqual({ data: { count: 3 } });
+
+ expect(mocks.routingConfigRepository.query).toHaveBeenCalledWith(
+ 'nhs-notify-client-id'
+ );
+ expect(query.excludeStatus).toHaveBeenCalledWith('DELETED');
+ expect(query.status).not.toHaveBeenCalled();
+ });
+
+ it('validates status filter parameter', async () => {
+ const { client, mocks } = setup();
+
+ const result = await client.countRoutingConfigs('nhs-notify-client-id', {
+ status: 'INVALID',
+ });
+
+ expect(result).toEqual({
+ error: expect.objectContaining({
+ errorMeta: {
+ code: 400,
+ description: 'Request failed validation',
+ details: {
+ status: 'Invalid option: expected one of "COMPLETED"|"DRAFT"',
+ },
+ },
+ }),
+ });
+
+ expect(mocks.routingConfigRepository.query).not.toHaveBeenCalled();
+ });
+
+ it('uses the given status filter', async () => {
+ const { client, mocks } = setup();
+
+ const query = mockQuery();
+
+ mocks.routingConfigRepository.query.mockReturnValueOnce(query);
+
+ query.count.mockResolvedValueOnce({ data: { count: 18 } });
+
+ const result = await client.countRoutingConfigs('nhs-notify-client-id', {
+ status: 'DRAFT',
+ });
+
+ expect(result).toEqual({ data: { count: 18 } });
+
+ expect(mocks.routingConfigRepository.query).toHaveBeenCalledWith(
+ 'nhs-notify-client-id'
+ );
+ expect(query.excludeStatus).toHaveBeenCalledWith('DELETED');
+ expect(query.status).toHaveBeenCalledWith('DRAFT');
+ });
+ });
});
diff --git a/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/query.test.ts b/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/query.test.ts
index 2f745544b..4f394943d 100644
--- a/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/query.test.ts
+++ b/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/query.test.ts
@@ -32,194 +32,285 @@ function setup() {
}
describe('RoutingConfigRepo#query', () => {
- test('queries by owner, paginates across pages, returns all items', async () => {
- const { repo, mocks } = setup();
-
- const page1: RoutingConfig[] = [config1, config2];
- const page2: RoutingConfig[] = [config3];
-
- mocks.dynamo
- .on(QueryCommand)
- .resolvesOnce({
- Items: page1,
- LastEvaluatedKey: { owner, id: config2.id },
- })
- .resolvesOnce({
- Items: page2,
+ describe('list', () => {
+ test('queries by owner, paginates across pages, returns all items', async () => {
+ const { repo, mocks } = setup();
+
+ const page1: RoutingConfig[] = [config1, config2];
+ const page2: RoutingConfig[] = [config3];
+
+ mocks.dynamo
+ .on(QueryCommand)
+ .resolvesOnce({
+ Items: page1,
+ LastEvaluatedKey: { owner, id: config2.id },
+ })
+ .resolvesOnce({
+ Items: page2,
+ });
+
+ const result = await repo.query(owner).list();
+
+ expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 2);
+ expect(mocks.dynamo).toHaveReceivedNthCommandWith(1, QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ },
+ ExclusiveStartKey: { owner, id: config2.id },
+ });
+ expect(mocks.dynamo).toHaveReceivedNthCommandWith(2, QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ },
});
- const result = await repo.query(owner).list();
-
- expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 2);
- expect(mocks.dynamo).toHaveReceivedNthCommandWith(1, QueryCommand, {
- TableName: TABLE_NAME,
- KeyConditionExpression: '#owner = :owner',
- ExpressionAttributeNames: {
- '#owner': 'owner',
- },
- ExpressionAttributeValues: {
- ':owner': owner,
- },
- ExclusiveStartKey: { owner, id: config2.id },
- });
- expect(mocks.dynamo).toHaveReceivedNthCommandWith(2, QueryCommand, {
- TableName: TABLE_NAME,
- KeyConditionExpression: '#owner = :owner',
- ExpressionAttributeNames: {
- '#owner': 'owner',
- },
- ExpressionAttributeValues: {
- ':owner': owner,
- },
+ expect(result.data).toEqual([config1, config2, config3]);
});
- expect(result.data).toEqual([config1, config2, config3]);
- });
+ test('supports filtering by status (chainable)', async () => {
+ const { repo, mocks } = setup();
- test('supports filtering by status (chainable)', async () => {
- const { repo, mocks } = setup();
+ mocks.dynamo.on(QueryCommand).resolvesOnce({
+ Items: [],
+ });
- mocks.dynamo.on(QueryCommand).resolvesOnce({
- Items: [],
+ await repo
+ .query(owner)
+ .status('COMPLETED', 'DELETED')
+ .status('DRAFT')
+ .list();
+
+ expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
+ expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ FilterExpression: '(#status IN (:status0, :status1, :status2))',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ '#status': 'status',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ ':status0': 'COMPLETED',
+ ':status1': 'DELETED',
+ ':status2': 'DRAFT',
+ },
+ });
});
- await repo
- .query(owner)
- .status('COMPLETED', 'DELETED')
- .status('DRAFT')
- .list();
-
- expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
- expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
- TableName: TABLE_NAME,
- KeyConditionExpression: '#owner = :owner',
- FilterExpression: '(#status IN (:status0, :status1, :status2))',
- ExpressionAttributeNames: {
- '#owner': 'owner',
- '#status': 'status',
- },
- ExpressionAttributeValues: {
- ':owner': owner,
- ':status0': 'COMPLETED',
- ':status1': 'DELETED',
- ':status2': 'DRAFT',
- },
- });
- });
+ test('supports excluding statuses (chainable)', async () => {
+ const { repo, mocks } = setup();
- test('supports excluding statuses (chainable)', async () => {
- const { repo, mocks } = setup();
+ mocks.dynamo.on(QueryCommand).resolvesOnce({
+ Items: [],
+ });
- mocks.dynamo.on(QueryCommand).resolvesOnce({
- Items: [],
+ await repo
+ .query(owner)
+ .excludeStatus('COMPLETED', 'DELETED')
+ .excludeStatus('DRAFT')
+ .list();
+
+ expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
+ expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ FilterExpression:
+ '(#status <> :notStatus0 AND #status <> :notStatus1 AND #status <> :notStatus2)',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ '#status': 'status',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ ':notStatus0': 'COMPLETED',
+ ':notStatus1': 'DELETED',
+ ':notStatus2': 'DRAFT',
+ },
+ });
});
- await repo
- .query(owner)
- .excludeStatus('COMPLETED', 'DELETED')
- .excludeStatus('DRAFT')
- .list();
-
- expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
- expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
- TableName: TABLE_NAME,
- KeyConditionExpression: '#owner = :owner',
- FilterExpression:
- '(#status <> :notStatus0 AND #status <> :notStatus1 AND #status <> :notStatus2)',
- ExpressionAttributeNames: {
- '#owner': 'owner',
- '#status': 'status',
- },
- ExpressionAttributeValues: {
- ':owner': owner,
- ':notStatus0': 'COMPLETED',
- ':notStatus1': 'DELETED',
- ':notStatus2': 'DRAFT',
- },
+ test('supports mixed filters', async () => {
+ const { repo, mocks } = setup();
+
+ mocks.dynamo.on(QueryCommand).resolvesOnce({
+ Items: [],
+ });
+
+ await repo.query(owner).status('DRAFT').excludeStatus('DELETED').list();
+
+ expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
+ expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ FilterExpression:
+ '(#status IN (:status0)) AND (#status <> :notStatus0)',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ '#status': 'status',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ ':notStatus0': 'DELETED',
+ ':status0': 'DRAFT',
+ },
+ });
});
- });
- test('supports mixed filters', async () => {
- const { repo, mocks } = setup();
+ test('dedupes status filters', async () => {
+ const { repo, mocks } = setup();
- mocks.dynamo.on(QueryCommand).resolvesOnce({
- Items: [],
+ mocks.dynamo.on(QueryCommand).resolvesOnce({
+ Items: [],
+ });
+
+ await repo
+ .query(owner)
+ .status('DRAFT')
+ .status('DRAFT')
+ .excludeStatus('DELETED')
+ .excludeStatus('DELETED')
+ .list();
+
+ expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
+ expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ FilterExpression:
+ '(#status IN (:status0)) AND (#status <> :notStatus0)',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ '#status': 'status',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ ':notStatus0': 'DELETED',
+ ':status0': 'DRAFT',
+ },
+ });
});
- await repo.query(owner).status('DRAFT').excludeStatus('DELETED').list();
-
- expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
- expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
- TableName: TABLE_NAME,
- KeyConditionExpression: '#owner = :owner',
- FilterExpression: '(#status IN (:status0)) AND (#status <> :notStatus0)',
- ExpressionAttributeNames: {
- '#owner': 'owner',
- '#status': 'status',
- },
- ExpressionAttributeValues: {
- ':owner': owner,
- ':notStatus0': 'DELETED',
- ':status0': 'DRAFT',
- },
+ test('filters out invalid routing config items', async () => {
+ const { repo, mocks } = setup();
+
+ mocks.dynamo.on(QueryCommand).resolvesOnce({
+ Items: [
+ config1,
+ { owner, id: '2eb0b8f5-63f0-4512-8a95-5b82e7c4b07b' },
+ config2,
+ ],
+ });
+
+ const result = await repo.query(owner).list();
+
+ expect(result.data).toEqual([config1, config2]);
});
- });
- test('dedupes status filters', async () => {
- const { repo, mocks } = setup();
+ test('handles no items from dynamo', async () => {
+ const { repo, mocks } = setup();
+
+ mocks.dynamo.on(QueryCommand).resolvesOnce({});
+
+ const result = await repo.query(owner).list();
- mocks.dynamo.on(QueryCommand).resolvesOnce({
- Items: [],
+ expect(result.data).toEqual([]);
});
- await repo
- .query(owner)
- .status('DRAFT')
- .status('DRAFT')
- .excludeStatus('DELETED')
- .excludeStatus('DELETED')
- .list();
-
- expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 1);
- expect(mocks.dynamo).toHaveReceivedCommandWith(QueryCommand, {
- TableName: TABLE_NAME,
- KeyConditionExpression: '#owner = :owner',
- FilterExpression: '(#status IN (:status0)) AND (#status <> :notStatus0)',
- ExpressionAttributeNames: {
- '#owner': 'owner',
- '#status': 'status',
- },
- ExpressionAttributeValues: {
- ':owner': owner,
- ':notStatus0': 'DELETED',
- ':status0': 'DRAFT',
- },
+ test('handles exceptions from dynamodb', async () => {
+ const { repo, mocks } = setup();
+
+ const e = new Error('oh no');
+
+ mocks.dynamo.on(QueryCommand).rejectsOnce(e);
+
+ const result = await repo.query(owner).list();
+
+ expect(result.error).toMatchObject({
+ actualError: e,
+ errorMeta: expect.objectContaining({ code: 500 }),
+ });
+ expect(result.data).toBeUndefined();
});
});
- test('filters out invalid routing config items', async () => {
- const { repo, mocks } = setup();
+ describe('count', () => {
+ test('queries by owner, paginates across pages, returns count of items', async () => {
+ const { repo, mocks } = setup();
+
+ mocks.dynamo
+ .on(QueryCommand)
+ .resolvesOnce({
+ Count: 2,
+ LastEvaluatedKey: { owner, id: config2.id },
+ })
+ .resolvesOnce({
+ Count: 1,
+ });
+
+ const result = await repo.query(owner).count();
+
+ expect(mocks.dynamo).toHaveReceivedCommandTimes(QueryCommand, 2);
+ expect(mocks.dynamo).toHaveReceivedNthCommandWith(1, QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ },
+ Select: 'COUNT',
+ ExclusiveStartKey: { owner, id: config2.id },
+ });
+ expect(mocks.dynamo).toHaveReceivedNthCommandWith(2, QueryCommand, {
+ TableName: TABLE_NAME,
+ KeyConditionExpression: '#owner = :owner',
+ ExpressionAttributeNames: {
+ '#owner': 'owner',
+ },
+ ExpressionAttributeValues: {
+ ':owner': owner,
+ },
+ Select: 'COUNT',
+ });
- mocks.dynamo.on(QueryCommand).resolvesOnce({
- Items: [
- config1,
- { owner, id: '2eb0b8f5-63f0-4512-8a95-5b82e7c4b07b' },
- config2,
- ],
+ expect(result.data).toEqual({ count: 3 });
});
- const result = await repo.query(owner).list();
+ test('handles no items from dynamo', async () => {
+ const { repo, mocks } = setup();
- expect(result.data).toEqual([config1, config2]);
- });
+ mocks.dynamo.on(QueryCommand).resolvesOnce({});
- test('handles no items from dynamo', async () => {
- const { repo, mocks } = setup();
+ const result = await repo.query(owner).count();
+
+ expect(result.data).toEqual({ count: 0 });
+ });
- mocks.dynamo.on(QueryCommand).resolvesOnce({});
+ test('handles exceptions from dynamodb', async () => {
+ const { repo, mocks } = setup();
- const result = await repo.query(owner).list();
+ const e = new Error('oh no');
- expect(result.data).toEqual([]);
+ mocks.dynamo.on(QueryCommand).rejectsOnce(e);
+
+ const result = await repo.query(owner).count();
+
+ expect(result.error).toMatchObject({
+ actualError: e,
+ errorMeta: expect.objectContaining({ code: 500 }),
+ });
+ expect(result.data).toBeUndefined();
+ });
});
});
diff --git a/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/repository.test.ts b/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/repository.test.ts
index a03ef29ae..b1ddfbe16 100644
--- a/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/repository.test.ts
+++ b/lambdas/backend-api/src/__tests__/templates/infra/routing-config-repository/repository.test.ts
@@ -1,6 +1,7 @@
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
import 'aws-sdk-client-mock-jest';
import { mockClient } from 'aws-sdk-client-mock';
+import { ZodError } from 'zod';
import { RoutingConfigRepository } from '@backend-api/templates/infra/routing-config-repository';
import { routingConfig } from '../../fixtures/routing-config';
@@ -83,7 +84,12 @@ describe('RoutingConfigRepository', () => {
'nhs-notify-client-id'
);
- expect(result.error).not.toBeUndefined();
+ expect(result.error).toMatchObject({
+ actualError: expect.any(ZodError),
+ errorMeta: expect.objectContaining({
+ code: 500,
+ }),
+ });
expect(result.data).toBeUndefined();
expect(mocks.dynamo).toHaveReceivedCommandWith(GetCommand, {
@@ -94,5 +100,27 @@ describe('RoutingConfigRepository', () => {
},
});
});
+
+ test('returns errors if the database call fails', async () => {
+ const { repo, mocks } = setup();
+
+ const e = new Error('Oh No');
+
+ mocks.dynamo.on(GetCommand).rejectsOnce(e);
+
+ const result = await repo.get(
+ 'b9b6d56b-421e-462f-9ce5-3012e3fdb27f',
+ 'nhs-notify-client-id'
+ );
+
+ expect(result.error).toMatchObject({
+ actualError: e,
+ errorMeta: expect.objectContaining({
+ code: 500,
+ }),
+ });
+
+ expect(result.data).toBeUndefined();
+ });
});
});
diff --git a/lambdas/backend-api/src/templates/api/count-routing-configs.ts b/lambdas/backend-api/src/templates/api/count-routing-configs.ts
new file mode 100644
index 000000000..db14d1bf8
--- /dev/null
+++ b/lambdas/backend-api/src/templates/api/count-routing-configs.ts
@@ -0,0 +1,42 @@
+import type { APIGatewayProxyHandler } from 'aws-lambda';
+import { apiFailure, apiSuccess } from './responses';
+import type { RoutingConfigClient } from '../app/routing-config-client';
+import { logger } from 'nhs-notify-web-template-management-utils/logger';
+
+export function createHandler({
+ routingConfigClient,
+}: {
+ routingConfigClient: RoutingConfigClient;
+}): APIGatewayProxyHandler {
+ return async function handler(event) {
+ const { user: userId, clientId } = event.requestContext.authorizer ?? {};
+
+ if (!clientId || !userId) {
+ return apiFailure(400, 'Invalid request');
+ }
+
+ const log = logger.child({
+ clientId,
+ userId,
+ });
+
+ const { data, error } = await routingConfigClient.countRoutingConfigs(
+ clientId,
+ event.queryStringParameters
+ );
+
+ if (error) {
+ log
+ .child(error.errorMeta)
+ .error('Failed to count routing configs', error.actualError);
+
+ return apiFailure(
+ error.errorMeta.code,
+ error.errorMeta.description,
+ error.errorMeta.details
+ );
+ }
+
+ return apiSuccess(200, data);
+ };
+}
diff --git a/lambdas/backend-api/src/templates/api/responses.ts b/lambdas/backend-api/src/templates/api/responses.ts
index 04eb84198..31789153c 100644
--- a/lambdas/backend-api/src/templates/api/responses.ts
+++ b/lambdas/backend-api/src/templates/api/responses.ts
@@ -4,8 +4,15 @@ import type {
RoutingConfig,
} from 'nhs-notify-backend-client';
+type Count = { count: number };
+
export const apiSuccess = <
- T extends TemplateDto | TemplateDto[] | RoutingConfig | RoutingConfig[],
+ T extends
+ | Count
+ | RoutingConfig
+ | RoutingConfig[]
+ | TemplateDto
+ | TemplateDto[],
>(
statusCode: number,
result: T
diff --git a/lambdas/backend-api/src/templates/app/routing-config-client.ts b/lambdas/backend-api/src/templates/app/routing-config-client.ts
index c214f79d2..afad79849 100644
--- a/lambdas/backend-api/src/templates/app/routing-config-client.ts
+++ b/lambdas/backend-api/src/templates/app/routing-config-client.ts
@@ -53,4 +53,31 @@ export class RoutingConfigClient {
return query.list();
}
+
+ async countRoutingConfigs(
+ owner: string,
+ filters?: unknown
+ ): Promise> {
+ let parsedFilters: ListRoutingConfigFilters = {};
+
+ if (filters) {
+ const validation = await validate($ListRoutingConfigFilters, filters);
+
+ if (validation.error) {
+ return validation;
+ }
+
+ parsedFilters = validation.data;
+ }
+
+ const query = this.routingConfigRepository
+ .query(owner)
+ .excludeStatus('DELETED');
+
+ if (parsedFilters.status) {
+ query.status(parsedFilters.status);
+ }
+
+ return query.count();
+ }
}
diff --git a/lambdas/backend-api/src/templates/count-routing-configs.ts b/lambdas/backend-api/src/templates/count-routing-configs.ts
new file mode 100644
index 000000000..9b443dc6d
--- /dev/null
+++ b/lambdas/backend-api/src/templates/count-routing-configs.ts
@@ -0,0 +1,4 @@
+import { createHandler } from './api/count-routing-configs';
+import { createContainer } from './container';
+
+export const handler = createHandler(createContainer());
diff --git a/lambdas/backend-api/src/templates/infra/routing-config-repository/query.ts b/lambdas/backend-api/src/templates/infra/routing-config-repository/query.ts
index 25a43b2fe..7f56e430a 100644
--- a/lambdas/backend-api/src/templates/infra/routing-config-repository/query.ts
+++ b/lambdas/backend-api/src/templates/infra/routing-config-repository/query.ts
@@ -4,9 +4,10 @@ import {
type NativeAttributeValue,
type QueryCommandInput,
} from '@aws-sdk/lib-dynamodb';
-import { ApplicationResult, success } from '@backend-api/utils/result';
+import { ApplicationResult, failure, success } from '@backend-api/utils/result';
import {
$RoutingConfig,
+ ErrorCase,
type RoutingConfig,
type RoutingConfigStatus,
} from 'nhs-notify-backend-client';
@@ -15,6 +16,7 @@ import { logger } from 'nhs-notify-web-template-management-utils/logger';
export class RoutingConfigQuery {
private includeStatuses: RoutingConfigStatus[] = [];
private excludeStatuses: RoutingConfigStatus[] = [];
+ private returnCount = false;
constructor(
private readonly docClient: DynamoDBDocumentClient,
@@ -36,28 +38,65 @@ export class RoutingConfigQuery {
/** Execute the query and return a list of all matching RoutingConfigs */
async list(): Promise> {
- const query = this.build();
-
- const collected: RoutingConfig[] = [];
-
- const paginator = paginateQuery({ client: this.docClient }, query);
-
- for await (const page of paginator) {
- for (const item of page.Items ?? []) {
- const parsed = $RoutingConfig.safeParse(item);
- if (parsed.success) {
- collected.push(parsed.data);
- } else {
- logger.warn('Filtered out invalid RoutingConfig item', {
- owner: this.owner,
- id: item.id,
- issues: parsed.error.issues,
- });
+ try {
+ this.returnCount = false;
+
+ const query = this.build();
+
+ const collected: RoutingConfig[] = [];
+
+ const paginator = paginateQuery({ client: this.docClient }, query);
+
+ for await (const page of paginator) {
+ for (const item of page.Items ?? []) {
+ const parsed = $RoutingConfig.safeParse(item);
+ if (parsed.success) {
+ collected.push(parsed.data);
+ } else {
+ logger.warn('Filtered out invalid RoutingConfig item', {
+ owner: this.owner,
+ id: item.id,
+ issues: parsed.error.issues,
+ });
+ }
}
}
+
+ return success(collected);
+ } catch (error) {
+ return failure(
+ ErrorCase.INTERNAL,
+ 'Error listing Routing Configs',
+ error
+ );
}
+ }
+
+ /** Execute the query and return a count of all matching RoutingConfigs */
+ async count(): Promise> {
+ try {
+ this.returnCount = true;
+
+ const query = this.build();
+
+ let count = 0;
+
+ const paginator = paginateQuery({ client: this.docClient }, query);
- return success(collected);
+ for await (const page of paginator) {
+ if (page.Count) {
+ count += page.Count;
+ }
+ }
+
+ return success({ count });
+ } catch (error) {
+ return failure(
+ ErrorCase.INTERNAL,
+ 'Error counting Routing Configs',
+ error
+ );
+ }
}
private build() {
@@ -110,6 +149,10 @@ export class RoutingConfigQuery {
query.FilterExpression = filters.join(' AND ');
}
+ if (this.returnCount) {
+ query.Select = 'COUNT';
+ }
+
return query;
}
}
diff --git a/lambdas/backend-api/src/templates/infra/routing-config-repository/repository.ts b/lambdas/backend-api/src/templates/infra/routing-config-repository/repository.ts
index 38e102582..6d7e16524 100644
--- a/lambdas/backend-api/src/templates/infra/routing-config-repository/repository.ts
+++ b/lambdas/backend-api/src/templates/infra/routing-config-repository/repository.ts
@@ -17,31 +17,31 @@ export class RoutingConfigRepository {
id: string,
owner: string
): Promise> {
- const result = await this.client.send(
- new GetCommand({
- TableName: this.tableName,
- Key: {
- id,
- owner,
- },
- })
- );
+ try {
+ const result = await this.client.send(
+ new GetCommand({
+ TableName: this.tableName,
+ Key: {
+ id,
+ owner,
+ },
+ })
+ );
- if (!result.Item) {
- return failure(ErrorCase.NOT_FOUND, 'Routing Config not found');
- }
+ if (!result.Item) {
+ return failure(ErrorCase.NOT_FOUND, 'Routing Config not found');
+ }
- const parsed = $RoutingConfig.safeParse(result.Item);
+ const parsed = $RoutingConfig.parse(result.Item);
- if (!parsed.success) {
+ return success(parsed);
+ } catch (error) {
return failure(
ErrorCase.INTERNAL,
'Error retrieving Routing Config',
- parsed.error
+ error
);
}
-
- return success(parsed.data);
}
query(owner: string): RoutingConfigQuery {
diff --git a/lambdas/backend-client/src/types/generated/types.gen.ts b/lambdas/backend-client/src/types/generated/types.gen.ts
index 53e2464da..6cbc16005 100644
--- a/lambdas/backend-client/src/types/generated/types.gen.ts
+++ b/lambdas/backend-client/src/types/generated/types.gen.ts
@@ -95,6 +95,13 @@ export type ConditionalTemplateLanguage = {
templateId: string;
};
+export type CountSuccess = {
+ data: {
+ count: number;
+ };
+ statusCode: number;
+};
+
export type CreateUpdateTemplate = BaseTemplate &
(SmsProperties | EmailProperties | NhsAppProperties | UploadLetterProperties);
@@ -365,6 +372,38 @@ export type GetV1RoutingConfigurationsResponses = {
export type GetV1RoutingConfigurationsResponse =
GetV1RoutingConfigurationsResponses[keyof GetV1RoutingConfigurationsResponses];
+export type GetV1RoutingConfigurationsCountData = {
+ body?: never;
+ path?: never;
+ query?: {
+ /**
+ * Filter by a single active status
+ */
+ status?: RoutingConfigStatusActive;
+ };
+ url: '/v1/routing-configurations/count';
+};
+
+export type GetV1RoutingConfigurationsCountErrors = {
+ /**
+ * Error
+ */
+ default: Failure;
+};
+
+export type GetV1RoutingConfigurationsCountError =
+ GetV1RoutingConfigurationsCountErrors[keyof GetV1RoutingConfigurationsCountErrors];
+
+export type GetV1RoutingConfigurationsCountResponses = {
+ /**
+ * 200 response
+ */
+ 200: CountSuccess;
+};
+
+export type GetV1RoutingConfigurationsCountResponse =
+ GetV1RoutingConfigurationsCountResponses[keyof GetV1RoutingConfigurationsCountResponses];
+
export type PostV1TemplateData = {
/**
* Template to create
diff --git a/package-lock.json b/package-lock.json
index 4b051c588..9a233dd57 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17651,12 +17651,12 @@
"license": "(Unlicense OR Apache-2.0)"
},
"node_modules/@smithy/abort-controller": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.1.1.tgz",
- "integrity": "sha512-vkzula+IwRvPR6oKQhMYioM3A/oX/lFCZiwuxkQbRhqJS2S4YRY2k7k/SyR2jMf3607HLtbEwlRxi0ndXHMjRg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.0.tgz",
+ "integrity": "sha512-PLUYa+SUKOEZtXFURBu/CNxlsxfaFGxSBPcStL13KpVeVWIfdezWyDqkz7iDLmwnxojXD0s5KzuB5HGHvt4Aeg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17689,15 +17689,15 @@
}
},
"node_modules/@smithy/config-resolver": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.2.2.tgz",
- "integrity": "sha512-IT6MatgBWagLybZl1xQcURXRICvqz1z3APSCAI9IqdvfCkrA7RaQIEfgC6G/KvfxnDfQUDqFV+ZlixcuFznGBQ==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.3.0.tgz",
+ "integrity": "sha512-9oH+n8AVNiLPK/iK/agOsoWfrKZ3FGP3502tkksd6SRsKMYiu7AFX0YXo6YBADdsAj7C+G/aLKdsafIJHxuCkQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.2.2",
- "@smithy/types": "^4.5.0",
- "@smithy/util-config-provider": "^4.1.0",
- "@smithy/util-middleware": "^4.1.1",
+ "@smithy/node-config-provider": "^4.3.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-config-provider": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17705,20 +17705,20 @@
}
},
"node_modules/@smithy/core": {
- "version": "3.13.0",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.13.0.tgz",
- "integrity": "sha512-BI6ALLPOKnPOU1Cjkc+1TPhOlP3JXSR/UH14JmnaLq41t3ma+IjuXrKfhycVjr5IQ0XxRh2NnQo3olp+eCVrGg==",
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.14.0.tgz",
+ "integrity": "sha512-XJ4z5FxvY/t0Dibms/+gLJrI5niRoY0BCmE02fwmPcRYFPI4KI876xaE79YGWIKnEslMbuQPsIEsoU/DXa0DoA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/middleware-serde": "^4.1.1",
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/types": "^4.5.0",
- "@smithy/util-base64": "^4.1.0",
- "@smithy/util-body-length-browser": "^4.1.0",
- "@smithy/util-middleware": "^4.1.1",
- "@smithy/util-stream": "^4.3.2",
- "@smithy/util-utf8": "^4.1.0",
- "@smithy/uuid": "^1.0.0",
+ "@smithy/middleware-serde": "^4.2.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-base64": "^4.2.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.0",
+ "@smithy/util-stream": "^4.4.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "@smithy/uuid": "^1.1.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17726,15 +17726,15 @@
}
},
"node_modules/@smithy/credential-provider-imds": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.1.2.tgz",
- "integrity": "sha512-JlYNq8TShnqCLg0h+afqe2wLAwZpuoSgOyzhYvTgbiKBWRov+uUve+vrZEQO6lkdLOWPh7gK5dtb9dS+KGendg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.0.tgz",
+ "integrity": "sha512-SOhFVvFH4D5HJZytb0bLKxCrSnwcqPiNlrw+S4ZXjMnsC+o9JcUQzbZOEQcA8yv9wJFNhfsUiIUKiEnYL68Big==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.2.2",
- "@smithy/property-provider": "^4.1.1",
- "@smithy/types": "^4.5.0",
- "@smithy/url-parser": "^4.1.1",
+ "@smithy/node-config-provider": "^4.3.0",
+ "@smithy/property-provider": "^4.2.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/url-parser": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17812,15 +17812,15 @@
}
},
"node_modules/@smithy/fetch-http-handler": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.2.1.tgz",
- "integrity": "sha512-5/3wxKNtV3wO/hk1is+CZUhL8a1yy/U+9u9LKQ9kZTkMsHaQjJhc3stFfiujtMnkITjzWfndGA2f7g9Uh9vKng==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.0.tgz",
+ "integrity": "sha512-BG3KSmsx9A//KyIfw+sqNmWFr1YBUr+TwpxFT7yPqAk0yyDh7oSNgzfNH7pS6OC099EGx2ltOULvumCFe8bcgw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/querystring-builder": "^4.1.1",
- "@smithy/types": "^4.5.0",
- "@smithy/util-base64": "^4.1.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/querystring-builder": "^4.2.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-base64": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17843,14 +17843,14 @@
}
},
"node_modules/@smithy/hash-node": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.1.1.tgz",
- "integrity": "sha512-H9DIU9WBLhYrvPs9v4sYvnZ1PiAI0oc8CgNQUJ1rpN3pP7QADbTOUjchI2FB764Ub0DstH5xbTqcMJu1pnVqxA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.0.tgz",
+ "integrity": "sha512-ugv93gOhZGysTctZh9qdgng8B+xO0cj+zN0qAZ+Sgh7qTQGPOJbMdIuyP89KNfUyfAqFSNh5tMvC+h2uCpmTtA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
- "@smithy/util-buffer-from": "^4.1.0",
- "@smithy/util-utf8": "^4.1.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17872,12 +17872,12 @@
}
},
"node_modules/@smithy/invalid-dependency": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.1.1.tgz",
- "integrity": "sha512-1AqLyFlfrrDkyES8uhINRlJXmHA2FkG+3DY8X+rmLSqmFwk3DJnvhyGzyByPyewh2jbmV+TYQBEfngQax8IFGg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.0.tgz",
+ "integrity": "sha512-ZmK5X5fUPAbtvRcUPtk28aqIClVhbfcmfoS4M7UQBTnDdrNxhsrxYVv0ZEl5NaPSyExsPWqL4GsPlRvtlwg+2A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17885,9 +17885,9 @@
}
},
"node_modules/@smithy/is-array-buffer": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.1.0.tgz",
- "integrity": "sha512-ePTYUOV54wMogio+he4pBybe8fwg4sDvEVDBU8ZlHOZXbXK3/C0XfJgUCu6qAZcawv05ZhZzODGUerFBPsPUDQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
+ "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -17911,13 +17911,13 @@
}
},
"node_modules/@smithy/middleware-content-length": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.1.1.tgz",
- "integrity": "sha512-9wlfBBgTsRvC2JxLJxv4xDGNBrZuio3AgSl0lSFX7fneW2cGskXTYpFxCdRYD2+5yzmsiTuaAJD1Wp7gWt9y9w==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.0.tgz",
+ "integrity": "sha512-6ZAnwrXFecrA4kIDOcz6aLBhU5ih2is2NdcZtobBDSdSHtE9a+MThB5uqyK4XXesdOCvOcbCm2IGB95birTSOQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/types": "^4.5.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17925,18 +17925,18 @@
}
},
"node_modules/@smithy/middleware-endpoint": {
- "version": "4.2.5",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.2.5.tgz",
- "integrity": "sha512-DdOIpssQ5LFev7hV6GX9TMBW5ChTsQBxqgNW1ZGtJNSAi5ksd5klwPwwMY0ejejfEzwXXGqxgVO3cpaod4veiA==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.0.tgz",
+ "integrity": "sha512-jFVjuQeV8TkxaRlcCNg0GFVgg98tscsmIrIwRFeC74TIUyLE3jmY9xgc1WXrPQYRjQNK3aRoaIk6fhFRGOIoGw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.13.0",
- "@smithy/middleware-serde": "^4.1.1",
- "@smithy/node-config-provider": "^4.2.2",
- "@smithy/shared-ini-file-loader": "^4.2.0",
- "@smithy/types": "^4.5.0",
- "@smithy/url-parser": "^4.1.1",
- "@smithy/util-middleware": "^4.1.1",
+ "@smithy/core": "^3.14.0",
+ "@smithy/middleware-serde": "^4.2.0",
+ "@smithy/node-config-provider": "^4.3.0",
+ "@smithy/shared-ini-file-loader": "^4.3.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/url-parser": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17944,19 +17944,19 @@
}
},
"node_modules/@smithy/middleware-retry": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.3.1.tgz",
- "integrity": "sha512-aH2bD1bzb6FB04XBhXA5mgedEZPKx3tD/qBuYCAKt5iieWvWO1Y2j++J9uLqOndXb9Pf/83Xka/YjSnMbcPchA==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.0.tgz",
+ "integrity": "sha512-yaVBR0vQnOnzex45zZ8ZrPzUnX73eUC8kVFaAAbn04+6V7lPtxn56vZEBBAhgS/eqD6Zm86o6sJs6FuQVoX5qg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.2.2",
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/service-error-classification": "^4.1.2",
- "@smithy/smithy-client": "^4.6.5",
- "@smithy/types": "^4.5.0",
- "@smithy/util-middleware": "^4.1.1",
- "@smithy/util-retry": "^4.1.2",
- "@smithy/uuid": "^1.0.0",
+ "@smithy/node-config-provider": "^4.3.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/service-error-classification": "^4.2.0",
+ "@smithy/smithy-client": "^4.7.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-middleware": "^4.2.0",
+ "@smithy/util-retry": "^4.2.0",
+ "@smithy/uuid": "^1.1.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17964,13 +17964,13 @@
}
},
"node_modules/@smithy/middleware-serde": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.1.1.tgz",
- "integrity": "sha512-lh48uQdbCoj619kRouev5XbWhCwRKLmphAif16c4J6JgJ4uXjub1PI6RL38d3BLliUvSso6klyB/LTNpWSNIyg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.0.tgz",
+ "integrity": "sha512-rpTQ7D65/EAbC6VydXlxjvbifTf4IH+sADKg6JmAvhkflJO2NvDeyU9qsWUNBelJiQFcXKejUHWRSdmpJmEmiw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/types": "^4.5.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17978,12 +17978,12 @@
}
},
"node_modules/@smithy/middleware-stack": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.1.1.tgz",
- "integrity": "sha512-ygRnniqNcDhHzs6QAPIdia26M7e7z9gpkIMUe/pK0RsrQ7i5MblwxY8078/QCnGq6AmlUUWgljK2HlelsKIb/A==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.0.tgz",
+ "integrity": "sha512-G5CJ//eqRd9OARrQu9MK1H8fNm2sMtqFh6j8/rPozhEL+Dokpvi1Og+aCixTuwDAGZUkJPk6hJT5jchbk/WCyg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -17991,14 +17991,14 @@
}
},
"node_modules/@smithy/node-config-provider": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.2.2.tgz",
- "integrity": "sha512-SYGTKyPvyCfEzIN5rD8q/bYaOPZprYUPD2f5g9M7OjaYupWOoQFYJ5ho+0wvxIRf471i2SR4GoiZ2r94Jq9h6A==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.0.tgz",
+ "integrity": "sha512-5QgHNuWdT9j9GwMPPJCKxy2KDxZ3E5l4M3/5TatSZrqYVoEiqQrDfAq8I6KWZw7RZOHtVtCzEPdYz7rHZixwcA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/property-provider": "^4.1.1",
- "@smithy/shared-ini-file-loader": "^4.2.0",
- "@smithy/types": "^4.5.0",
+ "@smithy/property-provider": "^4.2.0",
+ "@smithy/shared-ini-file-loader": "^4.3.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18006,15 +18006,15 @@
}
},
"node_modules/@smithy/node-http-handler": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.2.1.tgz",
- "integrity": "sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.3.0.tgz",
+ "integrity": "sha512-RHZ/uWCmSNZ8cneoWEVsVwMZBKy/8123hEpm57vgGXA3Irf/Ja4v9TVshHK2ML5/IqzAZn0WhINHOP9xl+Qy6Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/abort-controller": "^4.1.1",
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/querystring-builder": "^4.1.1",
- "@smithy/types": "^4.5.0",
+ "@smithy/abort-controller": "^4.2.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/querystring-builder": "^4.2.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18022,12 +18022,12 @@
}
},
"node_modules/@smithy/property-provider": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.1.1.tgz",
- "integrity": "sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.0.tgz",
+ "integrity": "sha512-rV6wFre0BU6n/tx2Ztn5LdvEdNZ2FasQbPQmDOPfV9QQyDmsCkOAB0osQjotRCQg+nSKFmINhyda0D3AnjSBJw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18035,12 +18035,12 @@
}
},
"node_modules/@smithy/protocol-http": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.2.1.tgz",
- "integrity": "sha512-T8SlkLYCwfT/6m33SIU/JOVGNwoelkrvGjFKDSDtVvAXj/9gOT78JVJEas5a+ETjOu4SVvpCstKgd0PxSu/aHw==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.0.tgz",
+ "integrity": "sha512-6POSYlmDnsLKb7r1D3SVm7RaYW6H1vcNcTWGWrF7s9+2noNYvUsm7E4tz5ZQ9HXPmKn6Hb67pBDRIjrT4w/d7Q==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18048,13 +18048,13 @@
}
},
"node_modules/@smithy/querystring-builder": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.1.1.tgz",
- "integrity": "sha512-J9b55bfimP4z/Jg1gNo+AT84hr90p716/nvxDkPGCD4W70MPms0h8KF50RDRgBGZeL83/u59DWNqJv6tEP/DHA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.0.tgz",
+ "integrity": "sha512-Q4oFD0ZmI8yJkiPPeGUITZj++4HHYCW3pYBYfIobUCkYpI6mbkzmG1MAQQ3lJYYWj3iNqfzOenUZu+jqdPQ16A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
- "@smithy/util-uri-escape": "^4.1.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-uri-escape": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18062,12 +18062,12 @@
}
},
"node_modules/@smithy/querystring-parser": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.1.1.tgz",
- "integrity": "sha512-63TEp92YFz0oQ7Pj9IuI3IgnprP92LrZtRAkE3c6wLWJxfy/yOPRt39IOKerVr0JS770olzl0kGafXlAXZ1vng==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.0.tgz",
+ "integrity": "sha512-BjATSNNyvVbQxOOlKse0b0pSezTWGMvA87SvoFoFlkRsKXVsN3bEtjCxvsNXJXfnAzlWFPaT9DmhWy1vn0sNEA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18075,24 +18075,24 @@
}
},
"node_modules/@smithy/service-error-classification": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.1.2.tgz",
- "integrity": "sha512-Kqd8wyfmBWHZNppZSMfrQFpc3M9Y/kjyN8n8P4DqJJtuwgK1H914R471HTw7+RL+T7+kI1f1gOnL7Vb5z9+NgQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.0.tgz",
+ "integrity": "sha512-Ylv1ttUeKatpR0wEOMnHf1hXMktPUMObDClSWl2TpCVT4DwtJhCeighLzSLbgH3jr5pBNM0LDXT5yYxUvZ9WpA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0"
+ "@smithy/types": "^4.6.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@smithy/shared-ini-file-loader": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.2.0.tgz",
- "integrity": "sha512-OQTfmIEp2LLuWdxa8nEEPhZmiOREO6bcB6pjs0AySf4yiZhl6kMOfqmcwcY8BaBPX+0Tb+tG7/Ia/6mwpoZ7Pw==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.3.0.tgz",
+ "integrity": "sha512-VCUPPtNs+rKWlqqntX0CbVvWyjhmX30JCtzO+s5dlzzxrvSfRh5SY0yxnkirvc1c80vdKQttahL71a9EsdolSQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18100,18 +18100,18 @@
}
},
"node_modules/@smithy/signature-v4": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.2.1.tgz",
- "integrity": "sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.0.tgz",
+ "integrity": "sha512-MKNyhXEs99xAZaFhm88h+3/V+tCRDQ+PrDzRqL0xdDpq4gjxcMmf5rBA3YXgqZqMZ/XwemZEurCBQMfxZOWq/g==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.1.0",
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/types": "^4.5.0",
- "@smithy/util-hex-encoding": "^4.1.0",
- "@smithy/util-middleware": "^4.1.1",
- "@smithy/util-uri-escape": "^4.1.0",
- "@smithy/util-utf8": "^4.1.0",
+ "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.0",
+ "@smithy/util-uri-escape": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18119,17 +18119,17 @@
}
},
"node_modules/@smithy/smithy-client": {
- "version": "4.6.5",
- "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.6.5.tgz",
- "integrity": "sha512-6J2hhuWu7EjnvLBIGltPCqzNswL1cW/AkaZx6i56qLsQ0ix17IAhmDD9aMmL+6CN9nCJODOXpBTCQS6iKAA7/g==",
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.7.0.tgz",
+ "integrity": "sha512-3BDx/aCCPf+kkinYf5QQhdQ9UAGihgOVqI3QO5xQfSaIWvUE4KYLtiGRWsNe1SR7ijXC0QEPqofVp5Sb0zC8xQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/core": "^3.13.0",
- "@smithy/middleware-endpoint": "^4.2.5",
- "@smithy/middleware-stack": "^4.1.1",
- "@smithy/protocol-http": "^5.2.1",
- "@smithy/types": "^4.5.0",
- "@smithy/util-stream": "^4.3.2",
+ "@smithy/core": "^3.14.0",
+ "@smithy/middleware-endpoint": "^4.3.0",
+ "@smithy/middleware-stack": "^4.2.0",
+ "@smithy/protocol-http": "^5.3.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-stream": "^4.4.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18137,9 +18137,9 @@
}
},
"node_modules/@smithy/types": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.5.0.tgz",
- "integrity": "sha512-RkUpIOsVlAwUIZXO1dsz8Zm+N72LClFfsNqf173catVlvRZiwPy0x2u0JLEA4byreOPKDZPGjmPDylMoP8ZJRg==",
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.6.0.tgz",
+ "integrity": "sha512-4lI9C8NzRPOv66FaY1LL1O/0v0aLVrq/mXP/keUa9mJOApEeae43LsLd2kZRUJw91gxOQfLIrV3OvqPgWz1YsA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -18149,13 +18149,13 @@
}
},
"node_modules/@smithy/url-parser": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.1.1.tgz",
- "integrity": "sha512-bx32FUpkhcaKlEoOMbScvc93isaSiRM75pQ5IgIBaMkT7qMlIibpPRONyx/0CvrXHzJLpOn/u6YiDX2hcvs7Dg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.0.tgz",
+ "integrity": "sha512-AlBmD6Idav2ugmoAL6UtR6ItS7jU5h5RNqLMZC7QrLCoITA9NzIN3nx9GWi8g4z1pfWh2r9r96SX/jHiNwPJ9A==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/querystring-parser": "^4.1.1",
- "@smithy/types": "^4.5.0",
+ "@smithy/querystring-parser": "^4.2.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18163,13 +18163,13 @@
}
},
"node_modules/@smithy/util-base64": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.1.0.tgz",
- "integrity": "sha512-RUGd4wNb8GeW7xk+AY5ghGnIwM96V0l2uzvs/uVHf+tIuVX2WSvynk5CxNoBCsM2rQRSZElAo9rt3G5mJ/gktQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.2.0.tgz",
+ "integrity": "sha512-+erInz8WDv5KPe7xCsJCp+1WCjSbah9gWcmUXc9NqmhyPx59tf7jqFz+za1tRG1Y5KM1Cy1rWCcGypylFp4mvA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.1.0",
- "@smithy/util-utf8": "^4.1.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18177,9 +18177,9 @@
}
},
"node_modules/@smithy/util-body-length-browser": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.1.0.tgz",
- "integrity": "sha512-V2E2Iez+bo6bUMOTENPr6eEmepdY8Hbs+Uc1vkDKgKNA/brTJqOW/ai3JO1BGj9GbCeLqw90pbbH7HFQyFotGQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz",
+ "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -18189,9 +18189,9 @@
}
},
"node_modules/@smithy/util-body-length-node": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.1.0.tgz",
- "integrity": "sha512-BOI5dYjheZdgR9XiEM3HJcEMCXSoqbzu7CzIgYrx0UtmvtC3tC2iDGpJLsSRFffUpy8ymsg2ARMP5fR8mtuUQQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.0.tgz",
+ "integrity": "sha512-U8q1WsSZFjXijlD7a4wsDQOvOwV+72iHSfq1q7VD+V75xP/pdtm0WIGuaFJ3gcADDOKj2MIBn4+zisi140HEnQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -18201,12 +18201,12 @@
}
},
"node_modules/@smithy/util-buffer-from": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.1.0.tgz",
- "integrity": "sha512-N6yXcjfe/E+xKEccWEKzK6M+crMrlwaCepKja0pNnlSkm6SjAeLKKA++er5Ba0I17gvKfN/ThV+ZOx/CntKTVw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
+ "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/is-array-buffer": "^4.1.0",
+ "@smithy/is-array-buffer": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18214,9 +18214,9 @@
}
},
"node_modules/@smithy/util-config-provider": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.1.0.tgz",
- "integrity": "sha512-swXz2vMjrP1ZusZWVTB/ai5gK+J8U0BWvP10v9fpcFvg+Xi/87LHvHfst2IgCs1i0v4qFZfGwCmeD/KNCdJZbQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz",
+ "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -18226,14 +18226,14 @@
}
},
"node_modules/@smithy/util-defaults-mode-browser": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.1.5.tgz",
- "integrity": "sha512-FGBhlmFZVSRto816l6IwrmDcQ9pUYX6ikdR1mmAhdtSS1m77FgADukbQg7F7gurXfAvloxE/pgsrb7SGja6FQA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.2.0.tgz",
+ "integrity": "sha512-qzHp7ZDk1Ba4LDwQVCNp90xPGqSu7kmL7y5toBpccuhi3AH7dcVBIT/pUxYcInK4jOy6FikrcTGq5wxcka8UaQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/property-provider": "^4.1.1",
- "@smithy/smithy-client": "^4.6.5",
- "@smithy/types": "^4.5.0",
+ "@smithy/property-provider": "^4.2.0",
+ "@smithy/smithy-client": "^4.7.0",
+ "@smithy/types": "^4.6.0",
"bowser": "^2.11.0",
"tslib": "^2.6.2"
},
@@ -18242,17 +18242,17 @@
}
},
"node_modules/@smithy/util-defaults-mode-node": {
- "version": "4.1.5",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.1.5.tgz",
- "integrity": "sha512-Gwj8KLgJ/+MHYjVubJF0EELEh9/Ir7z7DFqyYlwgmp4J37KE+5vz6b3pWUnSt53tIe5FjDfVjDmHGYKjwIvW0Q==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.0.tgz",
+ "integrity": "sha512-FxUHS3WXgx3bTWR6yQHNHHkQHZm/XKIi/CchTnKvBulN6obWpcbzJ6lDToXn+Wp0QlVKd7uYAz2/CTw1j7m+Kg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/config-resolver": "^4.2.2",
- "@smithy/credential-provider-imds": "^4.1.2",
- "@smithy/node-config-provider": "^4.2.2",
- "@smithy/property-provider": "^4.1.1",
- "@smithy/smithy-client": "^4.6.5",
- "@smithy/types": "^4.5.0",
+ "@smithy/config-resolver": "^4.3.0",
+ "@smithy/credential-provider-imds": "^4.2.0",
+ "@smithy/node-config-provider": "^4.3.0",
+ "@smithy/property-provider": "^4.2.0",
+ "@smithy/smithy-client": "^4.7.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18260,13 +18260,13 @@
}
},
"node_modules/@smithy/util-endpoints": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.1.2.tgz",
- "integrity": "sha512-+AJsaaEGb5ySvf1SKMRrPZdYHRYSzMkCoK16jWnIMpREAnflVspMIDeCVSZJuj+5muZfgGpNpijE3mUNtjv01Q==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.0.tgz",
+ "integrity": "sha512-TXeCn22D56vvWr/5xPqALc9oO+LN+QpFjrSM7peG/ckqEPoI3zaKZFp+bFwfmiHhn5MGWPaLCqDOJPPIixk9Wg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/node-config-provider": "^4.2.2",
- "@smithy/types": "^4.5.0",
+ "@smithy/node-config-provider": "^4.3.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18274,9 +18274,9 @@
}
},
"node_modules/@smithy/util-hex-encoding": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.1.0.tgz",
- "integrity": "sha512-1LcueNN5GYC4tr8mo14yVYbh/Ur8jHhWOxniZXii+1+ePiIbsLZ5fEI0QQGtbRRP5mOhmooos+rLmVASGGoq5w==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz",
+ "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -18286,12 +18286,12 @@
}
},
"node_modules/@smithy/util-middleware": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.1.1.tgz",
- "integrity": "sha512-CGmZ72mL29VMfESz7S6dekqzCh8ZISj3B+w0g1hZFXaOjGTVaSqfAEFAq8EGp8fUL+Q2l8aqNmt8U1tglTikeg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.0.tgz",
+ "integrity": "sha512-u9OOfDa43MjagtJZ8AapJcmimP+K2Z7szXn8xbty4aza+7P1wjFmy2ewjSbhEiYQoW1unTlOAIV165weYAaowA==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/types": "^4.5.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18299,13 +18299,13 @@
}
},
"node_modules/@smithy/util-retry": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.1.2.tgz",
- "integrity": "sha512-NCgr1d0/EdeP6U5PSZ9Uv5SMR5XRRYoVr1kRVtKZxWL3tixEL3UatrPIMFZSKwHlCcp2zPLDvMubVDULRqeunA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.0.tgz",
+ "integrity": "sha512-BWSiuGbwRnEE2SFfaAZEX0TqaxtvtSYPM/J73PFVm+A29Fg1HTPiYFb8TmX1DXp4hgcdyJcNQmprfd5foeORsg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/service-error-classification": "^4.1.2",
- "@smithy/types": "^4.5.0",
+ "@smithy/service-error-classification": "^4.2.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18313,18 +18313,18 @@
}
},
"node_modules/@smithy/util-stream": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.3.2.tgz",
- "integrity": "sha512-Ka+FA2UCC/Q1dEqUanCdpqwxOFdf5Dg2VXtPtB1qxLcSGh5C1HdzklIt18xL504Wiy9nNUKwDMRTVCbKGoK69g==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.4.0.tgz",
+ "integrity": "sha512-vtO7ktbixEcrVzMRmpQDnw/Ehr9UWjBvSJ9fyAbadKkC4w5Cm/4lMO8cHz8Ysb8uflvQUNRcuux/oNHKPXkffg==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/fetch-http-handler": "^5.2.1",
- "@smithy/node-http-handler": "^4.2.1",
- "@smithy/types": "^4.5.0",
- "@smithy/util-base64": "^4.1.0",
- "@smithy/util-buffer-from": "^4.1.0",
- "@smithy/util-hex-encoding": "^4.1.0",
- "@smithy/util-utf8": "^4.1.0",
+ "@smithy/fetch-http-handler": "^5.3.0",
+ "@smithy/node-http-handler": "^4.3.0",
+ "@smithy/types": "^4.6.0",
+ "@smithy/util-base64": "^4.2.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18332,9 +18332,9 @@
}
},
"node_modules/@smithy/util-uri-escape": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.1.0.tgz",
- "integrity": "sha512-b0EFQkq35K5NHUYxU72JuoheM6+pytEVUGlTwiFxWFpmddA+Bpz3LgsPRIpBk8lnPE47yT7AF2Egc3jVnKLuPg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz",
+ "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
@@ -18344,12 +18344,12 @@
}
},
"node_modules/@smithy/util-utf8": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.1.0.tgz",
- "integrity": "sha512-mEu1/UIXAdNYuBcyEPbjScKi/+MQVXNIuY/7Cm5XLIWe319kDrT5SizBE95jqtmEXoDbGoZxKLCMttdZdqTZKQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
+ "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/util-buffer-from": "^4.1.0",
+ "@smithy/util-buffer-from": "^4.2.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18357,13 +18357,13 @@
}
},
"node_modules/@smithy/util-waiter": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.1.1.tgz",
- "integrity": "sha512-PJBmyayrlfxM7nbqjomF4YcT1sApQwZio0NHSsT0EzhJqljRmvhzqZua43TyEs80nJk2Cn2FGPg/N8phH6KeCQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.0.tgz",
+ "integrity": "sha512-0Z+nxUU4/4T+SL8BCNN4ztKdQjToNvUYmkF1kXO5T7Yz3Gafzh0HeIG6mrkN8Fz3gn9hSyxuAT+6h4vM+iQSBQ==",
"license": "Apache-2.0",
"dependencies": {
- "@smithy/abort-controller": "^4.1.1",
- "@smithy/types": "^4.5.0",
+ "@smithy/abort-controller": "^4.2.0",
+ "@smithy/types": "^4.6.0",
"tslib": "^2.6.2"
},
"engines": {
@@ -18371,9 +18371,9 @@
}
},
"node_modules/@smithy/uuid": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.0.0.tgz",
- "integrity": "sha512-OlA/yZHh0ekYFnbUkmYBDQPE6fGfdrvgz39ktp8Xf+FA6BfxLejPTMDOG0Nfk5/rDySAz1dRbFf24zaAFYVXlQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz",
+ "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
diff --git a/tests/test-team/template-mgmt-api-tests/count-routing-configs.api.spec.ts b/tests/test-team/template-mgmt-api-tests/count-routing-configs.api.spec.ts
new file mode 100644
index 000000000..aecb5efb0
--- /dev/null
+++ b/tests/test-team/template-mgmt-api-tests/count-routing-configs.api.spec.ts
@@ -0,0 +1,214 @@
+import { test, expect } from '@playwright/test';
+import type { RoutingConfig } from 'nhs-notify-backend-client';
+import {
+ createAuthHelper,
+ type TestUser,
+ testUsers,
+} from '../helpers/auth/cognito-auth-helper';
+import { RoutingConfigStorageHelper } from '../helpers/db/routing-config-storage-helper';
+import { RoutingConfigFactory } from '../helpers/factories/routing-config-factory';
+
+test.describe('GET /v1/routing-configurations/count', () => {
+ const authHelper = createAuthHelper();
+ const storageHelper = new RoutingConfigStorageHelper();
+ let user1: TestUser;
+ let user2: TestUser;
+ let userSharedClient: TestUser;
+ let draftRoutingConfig: RoutingConfig;
+ let completedRoutingConfig: RoutingConfig;
+ let deletedRoutingConfig: RoutingConfig;
+
+ test.beforeAll(async () => {
+ user1 = await authHelper.getTestUser(testUsers.User1.userId);
+ user2 = await authHelper.getTestUser(testUsers.User2.userId);
+ userSharedClient = await authHelper.getTestUser(testUsers.User7.userId);
+
+ draftRoutingConfig = RoutingConfigFactory.create({
+ owner: user1.clientId,
+ clientId: user1.clientId,
+ status: 'DRAFT',
+ createdBy: user1.userId,
+ updatedBy: user1.userId,
+ });
+
+ completedRoutingConfig = RoutingConfigFactory.create({
+ owner: user1.clientId,
+ clientId: user1.clientId,
+ status: 'COMPLETED',
+ createdBy: user1.userId,
+ updatedBy: user1.userId,
+ });
+
+ deletedRoutingConfig = RoutingConfigFactory.create({
+ owner: user1.clientId,
+ clientId: user1.clientId,
+ status: 'DELETED',
+ createdBy: user1.userId,
+ updatedBy: user1.userId,
+ });
+
+ await storageHelper.seed([
+ draftRoutingConfig,
+ completedRoutingConfig,
+ deletedRoutingConfig,
+ ]);
+ });
+
+ test.afterAll(async () => {
+ await storageHelper.deleteSeeded();
+ });
+
+ test('returns 401 if no auth token', async ({ request }) => {
+ const response = await request.get(
+ `${process.env.API_BASE_URL}/v1/routing-configurations/count`
+ );
+ expect(response.status()).toBe(401);
+ expect(await response.json()).toEqual({
+ message: 'Unauthorized',
+ });
+ });
+
+ test('counts active routing configs belonging to the authenticated owner', async ({
+ request,
+ }) => {
+ // exercise - request user 1 routing configs
+ const response = await request.get(
+ `${process.env.API_BASE_URL}/v1/routing-configurations/count`,
+ {
+ headers: {
+ Authorization: await user1.getAccessToken(),
+ },
+ }
+ );
+
+ // assert on user 1 response - should filter out deleted
+ expect(response.status()).toBe(200);
+
+ const body = await response.json();
+
+ expect(body).toEqual({
+ statusCode: 200,
+ data: { count: 2 },
+ });
+ });
+
+ test('does not return routing configs belonging to other clients besides the authenticated one', async ({
+ request,
+ }) => {
+ // exercise - request user 2 routing configs (they have no routing configs)
+ const response = await request.get(
+ `${process.env.API_BASE_URL}/v1/routing-configurations/count`,
+ {
+ headers: {
+ Authorization: await user2.getAccessToken(),
+ },
+ }
+ );
+
+ // assert that user 2 gets an empty list
+ expect(response.status()).toBe(200);
+
+ const body = await response.json();
+
+ expect(body).toEqual({
+ statusCode: 200,
+ data: { count: 0 },
+ });
+ });
+
+ test('different users on the same client can fetch the clients routing configs', async ({
+ request,
+ }) => {
+ // exercise - request shared user routing configs (same client as user 1)
+ const response = await request.get(
+ `${process.env.API_BASE_URL}/v1/routing-configurations/count`,
+ {
+ headers: {
+ Authorization: await userSharedClient.getAccessToken(),
+ },
+ }
+ );
+
+ // assert that the user gets the full list of configs belonging to their client
+ expect(response.status()).toBe(200);
+
+ const body = await response.json();
+
+ expect(body).toEqual({
+ statusCode: 200,
+ data: { count: 2 },
+ });
+ });
+
+ test('can filter by DRAFT status', async ({ request }) => {
+ // exercise - request routing configs with DRAFT status
+ const response = await request.get(
+ `${process.env.API_BASE_URL}/v1/routing-configurations/count`,
+ {
+ headers: {
+ Authorization: await user1.getAccessToken(),
+ },
+ params: { status: 'DRAFT' },
+ }
+ );
+
+ // assert that response only contains the drafts
+ expect(response.status()).toBe(200);
+
+ const body = await response.json();
+
+ expect(body).toEqual({
+ statusCode: 200,
+ data: { count: 1 },
+ });
+ });
+
+ test('can filter by COMPLETED status', async ({ request }) => {
+ // exercise - request routing configs with COMPLETED status
+ const response = await request.get(
+ `${process.env.API_BASE_URL}/v1/routing-configurations/count`,
+ {
+ headers: {
+ Authorization: await user1.getAccessToken(),
+ },
+ params: { status: 'COMPLETED' },
+ }
+ );
+
+ // assert that response only contains the completed configs
+ expect(response.status()).toBe(200);
+
+ const body = await response.json();
+
+ expect(body).toEqual({
+ statusCode: 200,
+ data: { count: 1 },
+ });
+ });
+
+ test('cannot filter by DELETED status', async ({ request }) => {
+ // exercise - request routing configs with DELETED status
+ const response = await request.get(
+ `${process.env.API_BASE_URL}/v1/routing-configurations/count`,
+ {
+ headers: {
+ Authorization: await user1.getAccessToken(),
+ },
+ params: { status: 'DELETED' },
+ }
+ );
+
+ // assert that response contains an error
+ expect(response.status()).toBe(400);
+
+ const body = await response.json();
+
+ expect(body).toEqual({
+ details: {
+ status: 'Invalid option: expected one of "COMPLETED"|"DRAFT"',
+ },
+ statusCode: 400,
+ technicalMessage: 'Request failed validation',
+ });
+ });
+});