Repository: amplify-education/serverless-domain-manager (ts)

Issue:
Failed to find a stack (v7.0.4)
<!--- Please keep this note for the community --->

### Community Note

* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
* Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
* If you are interested in working on this issue or have submitted a pull request, please leave a comment

<!--- Thank you for keeping this note for the community --->

## Bug Report

**Error Description**

When using release v7.0.4, deployment results in an error like:

> Error: Failed to find a stack my-service-stage

And a warning like:

> Warning: V1 - 'instance-stage.mydomain.io' does not exist.

Downgrading to v6.4.1 fixes the issue

**Command Run**

```sh
serverless deploy --verbose --stage stage
```

**Console Output**

This has been cleaned up somewhat, to remove private info:

```
Deploying my-service to stage stage (us-east-1)

Packaging
Using Python specified in "pythonBin": python3
Packaging Python WSGI handler...
Generated requirements from /path/to/my/app/requirements/deployment.txt in /path/to/my/app/.serverless/requirements.txt
Using static cache of requirements found at /Users/me/Library/Caches/serverless-python-requirements/843c0abdd474f831940064857629a06e5f5e461d3b8e4f8ad0cbff00790599f7_x86_64_slspyc
Excluding development dependencies for service package
Injecting required Python packages to package
Warning: V1 - 'instance-stage.mydomain.io' does not exist.
Warning: V1 - 'instance-stage.mydomain.io' does not exist.
Retrieving CloudFormation stack
Uploading
Uploading CloudFormation file to S3
Uploading State file to S3
Uploading service my-service.zip file to S3 (22.08 MB)
Updating CloudFormation stack
Creating new change set
Waiting for new change set to be created
Change Set did not reach desired state, retrying
Change Set did not reach desired state, retrying
Executing created change set
  UPDATE_IN_PROGRESS - AWS::CloudFormation::Stack - my-service-stage
  ....
  UPDATE_COMPLETE_CLEANUP_IN_PROGRESS - AWS::CloudFormation::Stack - my-service-stage
  ....
  UPDATE_COMPLETE - AWS::CloudFormation::Stack - my-service-stage
Retrieving CloudFormation stack


Warning: V1 - 'instance-stage.mydomain.io' does not exist.

✖ Stack my-service-stage failed to deploy (89s)
Environment: darwin, node 16.17.1, framework 3.33.0 (local), plugin 6.2.3, SDK 4.3.2
Credentials: Local, "mfa" profile
Docs:        docs.serverless.com
Support:     forum.serverless.com
Bugs:        github.com/serverless/serverless/issues

Error:
Error: Failed to find a stack my-service-stage

    at CloudFormationWrapper.<anonymous> (/path/to/my/app/node_modules/serverless-domain-manager/dist/src/aws/cloud-formation-wrapper.js:100:23)
    at Generator.next (<anonymous>)
    at fulfilled (/path/to/my/app/node_modules/serverless-domain-manager/dist/src/aws/cloud-formation-wrapper.js:8:58)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

2 deprecations found: run 'serverless doctor' for more details
```

**Domain Manager Configuration**
Replace this with your own `serverless.yml` file (anonymized, of course) to help us better resolve your issue.
```yaml
custom:
  customDomain:
    domainName: "instance-${self:provider.stage}.mydomain.io"
    basePath: ""
    stage: ${self:provider.stage}
    createRoute53Record: true
```

**Versions**
- Domain Manager version(s): v7.0.4 (bug does _not_ appear in v6.4.1)
- Node/npm version: Node v16.17.1, npm v8.15.0
- Serverless Version: v3.33.0
- Lambda Code: Python

**Possible Solution**
<!--- Only if you have suggestions on a fix for the bug -->

Downgrading `serverless-domain-manager` to v6.4.1

```
npm install serverless-domain-manager@6.4.1
```

**Additional context/Screenshots**

Seems to be similar to #551, and [a number of other related issues](https://github.com/amplify-education/serverless-domain-manager/issues?q=is%3Aissue+failed+to+find+a+stack)

I have verified that all the resources it is looking for in AWS do in fact exist and are named appropriately, including:
- Lambda application
- CloudFormation stack
- API Gateway
- Route53 domain, pointing at the CloudFront distro associated with API Gateway
- ACM certificate (verified + active), associated with the API gateway's CF distro


Pull request:
CHANGE - fixed not getting all paginated results from AWS clients calls
<!--- Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests --->
Fixes #587

**Description of Issue Fixed**
Fixed not getting all paginated results from AWS clients calls if there are more results then returned initially.

<!--- Please remember to allow edits from maintainers: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork --->


Patch:
diff --git a/package-lock.json b/package-lock.json
index 7678a7b..0debd57 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "serverless-domain-manager",
-  "version": "7.1.0",
+  "version": "7.1.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "serverless-domain-manager",
-      "version": "7.1.0",
+      "version": "7.1.1",
       "license": "MIT",
       "dependencies": {
         "@aws-sdk/client-acm": "^3.370.0",
diff --git a/src/aws/acm-wrapper.ts b/src/aws/acm-wrapper.ts
index 7af6fdb..12b11a2 100644
--- a/src/aws/acm-wrapper.ts
+++ b/src/aws/acm-wrapper.ts
@@ -1,10 +1,13 @@
 import {
-    ACMClient,
-    ListCertificatesCommand,
-    ListCertificatesCommandOutput,
+  ACMClient,
+  CertificateSummary,
+  ListCertificatesCommand,
+  ListCertificatesCommandInput,
+  ListCertificatesCommandOutput
 } from "@aws-sdk/client-acm";
 import Globals from "../globals";
 import DomainConfig = require("../models/domain-config");
+import { getAWSPagedResults } from "../utils";
 
 const certStatuses = ["PENDING_VALIDATION", "ISSUED", "INACTIVE"];
 
@@ -15,7 +18,8 @@ class ACMWrapper {
         const isEdge = endpointType === Globals.endpointTypes.edge;
         this.acm = new ACMClient({
             credentials,
-            region: isEdge ? Globals.defaultRegion : Globals.getRegion()
+            region: isEdge ? Globals.defaultRegion : Globals.getRegion(),
+            retryStrategy: Globals.getRetryStrategy()
         });
     }
 
@@ -24,16 +28,20 @@ class ACMWrapper {
         let certificateName = domain.certificateName; // The certificate name
 
         try {
-            const response: ListCertificatesCommandOutput = await this.acm.send(
-                new ListCertificatesCommand({CertificateStatuses: certStatuses})
+            const certificates = await getAWSPagedResults<CertificateSummary, ListCertificatesCommandInput, ListCertificatesCommandOutput>(
+              this.acm,
+              "CertificateSummaryList",
+              "NextToken",
+              "NextToken",
+              new ListCertificatesCommand({ CertificateStatuses: certStatuses })
             );
             // enhancement idea: weight the choice of cert so longer expires
             // and RenewalEligibility = ELIGIBLE is more preferable
             if (certificateName) {
-                certificateArn = this.getCertArnByCertName(response.CertificateSummaryList, certificateName);
+                certificateArn = this.getCertArnByCertName(certificates, certificateName);
             } else {
                 certificateName = domain.givenDomainName;
-                certificateArn = this.getCertArnByDomainName(response.CertificateSummaryList, certificateName);
+                certificateArn = this.getCertArnByDomainName(certificates, certificateName);
             }
         } catch (err) {
             throw Error(`Could not search certificates in Certificate Manager.\n${err.message}`);
diff --git a/src/aws/api-gateway-v1-wrapper.ts b/src/aws/api-gateway-v1-wrapper.ts
index 59f6280..df83d15 100644
--- a/src/aws/api-gateway-v1-wrapper.ts
+++ b/src/aws/api-gateway-v1-wrapper.ts
@@ -5,24 +5,27 @@ import DomainConfig = require("../models/domain-config");
 import DomainInfo = require("../models/domain-info");
 import Globals from "../globals";
 import {
-    APIGatewayClient,
-    CreateBasePathMappingCommand,
-    CreateDomainNameCommand,
-    CreateDomainNameCommandOutput,
-    DeleteBasePathMappingCommand,
-    DeleteDomainNameCommand,
-    GetBasePathMappingsCommand,
-    GetBasePathMappingsCommandOutput,
-    GetDomainNameCommand,
-    GetDomainNameCommandOutput,
-    UpdateBasePathMappingCommand,
+  APIGatewayClient,
+  BasePathMapping,
+  CreateBasePathMappingCommand,
+  CreateDomainNameCommand,
+  CreateDomainNameCommandOutput,
+  DeleteBasePathMappingCommand,
+  DeleteDomainNameCommand,
+  GetBasePathMappingsCommand,
+  GetBasePathMappingsCommandInput,
+  GetBasePathMappingsCommandOutput,
+  GetDomainNameCommand,
+  GetDomainNameCommandOutput,
+  UpdateBasePathMappingCommand
 } from "@aws-sdk/client-api-gateway";
 import ApiGatewayMap = require("../models/api-gateway-map");
 import APIGatewayBase = require("../models/apigateway-base");
 import Logging from "../logging";
+import { getAWSPagedResults } from "../utils";
 
 class APIGatewayV1Wrapper extends APIGatewayBase {
-    constructor(credentials?: any,) {
+    constructor(credentials?: any) {
         super();
         this.apiGateway = new APIGatewayClient({
             credentials,
@@ -124,12 +127,15 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
 
     public async getBasePathMappings(domain: DomainConfig): Promise<ApiGatewayMap[]> {
         try {
-            const response: GetBasePathMappingsCommandOutput = await this.apiGateway.send(
+            const items = await getAWSPagedResults<BasePathMapping, GetBasePathMappingsCommandInput, GetBasePathMappingsCommandOutput>(
+                this.apiGateway,
+                "items",
+                "position",
+                "position",
                 new GetBasePathMappingsCommand({
-                    domainName: domain.givenDomainName
+                    domainName: domain.givenDomainName,
                 })
             );
-            const items = response.items || [];
             return items.map((item) => {
                     return new ApiGatewayMap(item.restApiId, item.basePath, item.stage, null);
                 }
diff --git a/src/aws/api-gateway-v2-wrapper.ts b/src/aws/api-gateway-v2-wrapper.ts
index 18ea71d..f3bd0eb 100644
--- a/src/aws/api-gateway-v2-wrapper.ts
+++ b/src/aws/api-gateway-v2-wrapper.ts
@@ -7,22 +7,24 @@ import Globals from "../globals";
 import ApiGatewayMap = require("../models/api-gateway-map");
 import APIGatewayBase = require("../models/apigateway-base");
 import {
-    ApiGatewayV2Client,
-    CreateApiMappingCommand,
-    CreateDomainNameCommand,
-    CreateDomainNameCommandOutput,
-    DeleteApiMappingCommand,
-    DeleteDomainNameCommand,
-    GetApiMappingsCommand,
-    GetApiMappingsCommandOutput,
-    GetDomainNameCommand,
-    GetDomainNameCommandOutput,
-    UpdateApiMappingCommand,
+  ApiGatewayV2Client,
+  ApiMapping,
+  CreateApiMappingCommand,
+  CreateDomainNameCommand,
+  CreateDomainNameCommandOutput,
+  DeleteApiMappingCommand,
+  DeleteDomainNameCommand,
+  GetApiMappingsCommand,
+  GetApiMappingsCommandInput,
+  GetApiMappingsCommandOutput,
+  GetDomainNameCommand,
+  GetDomainNameCommandOutput,
+  UpdateApiMappingCommand
 } from "@aws-sdk/client-apigatewayv2";
 import Logging from "../logging";
+import { getAWSPagedResults } from "../utils";
 
 class APIGatewayV2Wrapper extends APIGatewayBase {
-
     constructor(credentials?: any) {
         super();
         this.apiGateway = new ApiGatewayV2Client({
@@ -153,12 +155,15 @@ class APIGatewayV2Wrapper extends APIGatewayBase {
      */
     public async getBasePathMappings(domain: DomainConfig): Promise<ApiGatewayMap[]> {
         try {
-            const response: GetApiMappingsCommandOutput = await this.apiGateway.send(
-                new GetApiMappingsCommand({
-                    DomainName: domain.givenDomainName
-                })
+            const items = await getAWSPagedResults<ApiMapping, GetApiMappingsCommandInput, GetApiMappingsCommandOutput>(
+              this.apiGateway,
+              "Items",
+              "NextToken",
+              "NextToken",
+              new GetApiMappingsCommand({
+                DomainName: domain.givenDomainName
+              })
             );
-            const items = response.Items || [];
             return items.map(
                 (item) => new ApiGatewayMap(item.ApiId, item.ApiMappingKey, item.Stage, item.ApiMappingId)
             );
diff --git a/src/aws/cloud-formation-wrapper.ts b/src/aws/cloud-formation-wrapper.ts
index 4f298a1..180d011 100644
--- a/src/aws/cloud-formation-wrapper.ts
+++ b/src/aws/cloud-formation-wrapper.ts
@@ -2,17 +2,22 @@
  * Wrapper class for AWS CloudFormation provider
  */
 
-import Globals from "../globals";
-import Logging from "../logging";
 import {
-    CloudFormationClient,
-    DescribeStackResourceCommand,
-    DescribeStackResourceCommandOutput,
-    DescribeStacksCommand,
-    DescribeStacksCommandOutput,
-    ListExportsCommand,
-    ListExportsCommandOutput
+  CloudFormationClient,
+  DescribeStackResourceCommand,
+  DescribeStackResourceCommandOutput,
+  DescribeStacksCommand,
+  DescribeStacksCommandInput,
+  DescribeStacksCommandOutput,
+  Export,
+  ListExportsCommand,
+  ListExportsCommandInput,
+  ListExportsCommandOutput,
+  Stack
 } from "@aws-sdk/client-cloudformation";
+import Globals from "../globals";
+import Logging from "../logging";
+import { getAWSPagedResults } from "../utils";
 
 class CloudFormationWrapper {
     public cloudFormation: CloudFormationClient;
@@ -24,7 +29,8 @@ class CloudFormationWrapper {
         this.stackName = Globals.serverless.service.provider.stackName || defaultStackName;
         this.cloudFormation = new CloudFormationClient({
             credentials,
-            region: Globals.getRegion()
+            region: Globals.getRegion(),
+            retryStrategy: Globals.getRetryStrategy()
         });
     }
 
@@ -120,10 +126,13 @@ class CloudFormationWrapper {
      * Gets values by names from cloudformation exports
      */
     public async getImportValues(names: string[]): Promise<any> {
-        const response: ListExportsCommandOutput = await this.cloudFormation.send(
-            new ListExportsCommand({})
+        const exports = await getAWSPagedResults<Export, ListExportsCommandInput, ListExportsCommandOutput>(
+          this.cloudFormation,
+          "Exports",
+          "NextToken",
+          "NextToken",
+          new ListExportsCommand({})
         );
-        const exports = response.Exports || [];
         // filter Exports by names which we need
         const filteredExports = exports.filter((item) => names.indexOf(item.Name) !== -1);
         // converting a list of unique values to dict
@@ -152,10 +161,13 @@ class CloudFormationWrapper {
      */
     public async getNestedStack(logicalResourceId: string, stackName: string) {
         // get all stacks from the CloudFormation
-        const response: DescribeStacksCommandOutput = await this.cloudFormation.send(
-            new DescribeStacksCommand({})
+        const stacks = await getAWSPagedResults<Stack, DescribeStacksCommandInput, DescribeStacksCommandOutput>(
+          this.cloudFormation,
+          "Stacks",
+          "NextToken",
+          "NextToken",
+          new DescribeStacksCommand({})
         );
-        const stacks = response.Stacks || [];
 
         // filter stacks by given stackName and check by nested stack RootId
         const regex = new RegExp("/" + stackName + "/");
diff --git a/src/aws/route53-wrapper.ts b/src/aws/route53-wrapper.ts
index 37b67d4..b8cb19b 100644
--- a/src/aws/route53-wrapper.ts
+++ b/src/aws/route53-wrapper.ts
@@ -2,11 +2,14 @@ import Globals from "../globals";
 import DomainConfig = require("../models/domain-config");
 import Logging from "../logging";
 import {
-    ChangeResourceRecordSetsCommand,
-    ListHostedZonesCommand,
-    ListHostedZonesCommandOutput,
-    Route53Client
+  ChangeResourceRecordSetsCommand,
+  HostedZone,
+  ListHostedZonesCommand,
+  ListHostedZonesCommandInput,
+  ListHostedZonesCommandOutput,
+  Route53Client
 } from "@aws-sdk/client-route-53";
+import { getAWSPagedResults } from "../utils";
 
 class Route53Wrapper {
     public route53: Route53Client;
@@ -16,10 +19,14 @@ class Route53Wrapper {
         if (credentials) {
             this.route53 = new Route53Client({
                 credentials,
-                region: region || Globals.getRegion()
+                region: region || Globals.getRegion(),
+                retryStrategy: Globals.getRetryStrategy()
             });
         } else {
-            this.route53 = new Route53Client({region: Globals.getRegion()});
+            this.route53 = new Route53Client({
+                region: Globals.getRegion(),
+                retryStrategy: Globals.getRetryStrategy()
+            });
         }
     }
 
@@ -40,10 +47,13 @@ class Route53Wrapper {
 
         let hostedZones = [];
         try {
-            const response: ListHostedZonesCommandOutput = await this.route53.send(
-                new ListHostedZonesCommand({})
+            hostedZones = await getAWSPagedResults<HostedZone, ListHostedZonesCommandInput, ListHostedZonesCommandOutput>(
+              this.route53,
+              "HostedZones",
+              "Marker",
+              "NextMarker",
+              new ListHostedZonesCommand({})
             );
-            hostedZones = response.HostedZones || hostedZones;
         } catch (err) {
             throw new Error(`Unable to list hosted zones in Route53.\n${err.message}`);
         }
diff --git a/src/utils.ts b/src/utils.ts
index f1c762e..97ace62 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,3 +1,5 @@
+import { Client, Command } from "@smithy/smithy-client";
+import { MetadataBearer } from "@smithy/types";
 import Globals from "./globals";
 
 /**
@@ -5,7 +7,7 @@ import Globals from "./globals";
  * @param seconds
  * @returns {Promise<void>} Resolves after given number of seconds.
  */
-async function sleep(seconds) {
+async function sleep(seconds: number) {
     return new Promise((resolve) => setTimeout(resolve, 1000 * seconds));
 }
 
@@ -37,7 +39,34 @@ function evaluateBoolean(value: any, defaultValue: boolean): boolean {
     throw new Error(`${Globals.pluginName}: Ambiguous boolean config: "${value}"`);
 }
 
-export {
-    evaluateBoolean,
-    sleep,
-};
+/**
+ * Iterate through the pages of a AWS SDK response and collect them into a single array
+ *
+ * @param client - The AWS service instance to use to make the calls
+ * @param resultsKey - The key name in the response that contains the items to return
+ * @param nextTokenKey - The request key name to append to the request that has the paging token value
+ * @param nextRequestTokenKey - The response key name that has the next paging token value
+ * @param params - Parameters to send in the request
+ */
+async function getAWSPagedResults<ClientOutput, ClientInputCommand extends object, ClientOutputCommand extends MetadataBearer>(
+  client: Client<any, any, any, any>,
+  resultsKey: keyof ClientOutputCommand,
+  nextTokenKey: keyof ClientInputCommand,
+  nextRequestTokenKey: keyof ClientOutputCommand,
+  params: Command<any, any, any>
+): Promise<ClientOutput[]> {
+  let results = [];
+  let response = await client.send(params);
+  results = results.concat(response[resultsKey] || results);
+  while (
+    response.hasOwnProperty(nextRequestTokenKey) &&
+    response[nextRequestTokenKey]
+  ) {
+    params.input[nextTokenKey] = response[nextRequestTokenKey];
+    response = await client.send(params);
+    results = results.concat(response[resultsKey]);
+  }
+  return results;
+}
+
+export { evaluateBoolean, sleep, getAWSPagedResults };


Test patch:
diff --git a/test/unit-tests/aws/acm-wrapper.test.ts b/test/unit-tests/aws/acm-wrapper.test.ts
index a9e39ed..27e08e0 100644
--- a/test/unit-tests/aws/acm-wrapper.test.ts
+++ b/test/unit-tests/aws/acm-wrapper.test.ts
@@ -60,6 +60,39 @@ describe("ACM Wrapper checks", () => {
         const actualResult = await acmWrapper.getCertArn(dc);
         expect(actualResult).to.equal(testCertificateArnByDomain);
     });
+    
+    it("getCertArn by domain name by getting all paginated certificates from AWS", async () => { 
+        const ACMCMock = mockClient(ACMClient);
+        ACMCMock.on(ListCertificatesCommand)
+        .resolvesOnce({
+          CertificateSummaryList: [
+            {
+              CertificateArn: "test_domain_arn",
+              DomainName: "test_domain",
+              Status: "ISSUED",
+            },
+          ],
+          NextToken: 'NextToken',
+        })
+        .resolves({
+          CertificateSummaryList: [
+            {
+              CertificateArn: "test_domain_arn2",
+              DomainName: "test_domain2",
+              Status: "ISSUED",
+            },
+          ],
+        });
+
+        const acmWrapper = new ACMWrapper(null, Globals.endpointTypes.regional);
+        const dc = new DomainConfig(getDomainConfig({
+            domainName: "test_domain",
+        }));
+
+        const actualResult = await acmWrapper.getCertArn(dc);
+        expect(actualResult).to.equal(testCertificateArnByDomain);
+        expect(ACMCMock.calls().length).to.equal(2);
+    });
 
     it("empty getCertArn by certificate name", async () => {
         const ACMCMock = mockClient(ACMClient);
diff --git a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts
index d5f85b4..22635f8 100644
--- a/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts
+++ b/test/unit-tests/aws/api-gateway-v1-wrapper.test.ts
@@ -361,6 +361,44 @@ describe("API Gateway V1 wrapper checks", () => {
 
             expect(commandCalls.length).to.equal(1);
         });
+        
+        it("get all base path mappings", async () => {
+            const APIGatewayCMock = mockClient(APIGatewayClient);
+            APIGatewayCMock.on(GetBasePathMappingsCommand)
+              .resolvesOnce({
+                items: [
+                  {
+                    restApiId: "test_rest_api_id",
+                    basePath: "test",
+                    stage: "test"
+                  },
+                ],
+                position: "position",
+              })
+              .resolves({
+                items: [
+                  {
+                    restApiId: "test_rest_api_id2",
+                    basePath: "test2",
+                    stage: "test",
+                  },
+                ],
+              });
+      
+            const apiGatewayV1Wrapper = new APIGatewayV1Wrapper();
+            const dc = new DomainConfig(getDomainConfig({
+                domainName: "test_domain"
+            }));
+
+            const actualResult = await apiGatewayV1Wrapper.getBasePathMappings(dc);
+            const expectedResult = [
+                new ApiGatewayMap("test_rest_api_id", "test", "test", null),
+                new ApiGatewayMap("test_rest_api_id2", "test2", "test", null),
+            ]
+
+            expect(actualResult).to.eql(expectedResult);
+            expect(APIGatewayCMock.calls().length).to.equal(2);
+        });
 
         it("get base path mapping failure", async () => {
             const APIGatewayMock = mockClient(APIGatewayClient);
diff --git a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts
index 5932e13..45af267 100644
--- a/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts
+++ b/test/unit-tests/aws/api-gateway-v2-wrapper.test.ts
@@ -395,6 +395,41 @@ describe("API Gateway V2 wrapper checks", () => {
 
             expect(commandCalls.length).to.equal(1);
         });
+        
+        it("get all base path mappings", async () => {
+            const APIGatewayMock = mockClient(ApiGatewayV2Client);
+            APIGatewayMock.on(GetApiMappingsCommand).resolvesOnce({
+                Items: [{
+                    ApiId: "test_rest_api_id",
+                    ApiMappingKey: "test",
+                    Stage: "test",
+                    ApiMappingId: "test_id"
+                }],
+                NextToken: "NextToken"
+            })
+            .resolves({
+                Items: [{
+                    ApiId: "test_rest_api_id2",
+                    ApiMappingKey: "test2",
+                    Stage: "test",
+                    ApiMappingId: "test_id2"
+                }]
+            });
+
+            const apiGatewayV2Wrapper = new APIGatewayV2Wrapper();
+            const dc = new DomainConfig(getDomainConfig({
+                domainName: "test_domain"
+            }));
+
+            const actualResult = await apiGatewayV2Wrapper.getBasePathMappings(dc);
+            const expectedResult = [
+                new ApiGatewayMap("test_rest_api_id", "test", "test", "test_id"),
+                new ApiGatewayMap("test_rest_api_id2", "test2", "test", "test_id2")
+            ]
+
+            expect(actualResult).to.eql(expectedResult);
+            expect(APIGatewayMock.calls().length).to.equal(2);
+        });
 
         it("get base path mapping failure", async () => {
             const APIGatewayMock = mockClient(ApiGatewayV2Client);
diff --git a/test/unit-tests/aws/cloud-formation-wrapper.test.ts b/test/unit-tests/aws/cloud-formation-wrapper.test.ts
index a3a1888..d08330b 100644
--- a/test/unit-tests/aws/cloud-formation-wrapper.test.ts
+++ b/test/unit-tests/aws/cloud-formation-wrapper.test.ts
@@ -56,6 +56,38 @@ describe("Cloud Formation wrapper checks", () => {
         const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true);
         expect(commandCalls.length).to.equal(1);
     });
+    
+    it("findApiId for the rest api type via Fn::ImportValue paginated", async () => {
+        const fnImportValue = "test-value";
+        const CloudFormationMock = mockClient(CloudFormationClient);
+        CloudFormationMock.on(ListExportsCommand).resolvesOnce({
+            Exports: [
+                {Name: "test-name", Value: fnImportValue},
+                {Name: "dummy-name", Value: "dummy-value"},
+            ],
+            NextToken: "NextToken"
+        })
+        .resolves({
+            Exports: [
+                {Name: "test-name2", Value: "test-name2"},
+                {Name: "dummy-name2", Value: "dummy-value2"},
+            ]
+        });
+
+        const cloudFormationWrapper = new CloudFormationWrapper();
+        Globals.serverless.service.provider.apiGateway.restApiId = {
+            [Globals.CFFuncNames.fnImport]: "test-name"
+        };
+
+        const actualResult = await cloudFormationWrapper.findApiId(Globals.apiTypes.rest)
+        expect(actualResult).to.equal(fnImportValue);
+
+        const expectedParams = {
+            NextToken: "NextToken"
+        };
+        const commandCalls = CloudFormationMock.commandCalls(ListExportsCommand, expectedParams, true);
+        expect(commandCalls.length).to.equal(2);
+    });
 
     it("findApiId for the rest api type via Fn::ImportValue not found", async () => {
         const fnImportValue = "test-value";
@@ -265,6 +297,72 @@ describe("Cloud Formation wrapper checks", () => {
         const allCommandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand);
         expect(allCommandCalls.length).to.equal(2);
     });
+    
+    it("findApiId for the rest api type with paginated nested stacks", async () => {
+        const physicalResourceId = "test_rest_api_id";
+        const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK";
+        const CloudFormationMock = mockClient(CloudFormationClient);
+        CloudFormationMock.on(DescribeStackResourceCommand).rejectsOnce()
+            .resolves({
+                StackResourceDetail: {
+                    LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest],
+                    PhysicalResourceId: physicalResourceId,
+                    ResourceType: "",
+                    LastUpdatedTimestamp: undefined,
+                    ResourceStatus: ResourceStatus.CREATE_COMPLETE,
+                },
+            });
+        CloudFormationMock.on(DescribeStacksCommand).resolvesOnce({
+            Stacks: [
+                {
+                    StackName: "custom-stage-name-NestedStackOne-U89W84TQIHJK",
+                    RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+                    CreationTime: null,
+                    StackStatus: StackStatus.CREATE_COMPLETE
+                },
+                {
+                    StackName: nestedStackName,
+                    RootId: `arn:aws:cloudformation:us-east-1:000000000000:stack/${Globals.serverless.service.provider.stackName}/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`,
+                    CreationTime: null,
+                    StackStatus: StackStatus.CREATE_COMPLETE
+                },
+                {
+                    StackName: "outside-stack-NestedStackZERO-U89W84TQIHJK",
+                    RootId: null,
+                    CreationTime: null,
+                    StackStatus: StackStatus.CREATE_COMPLETE
+                },
+            ],
+            NextToken: "NextToken"
+        })
+        .resolves({
+            Stacks: [
+                {
+                    StackName: "custom-stage-name-NestedStackOne-U89W84TQ1235",
+                    RootId: "arn:aws:cloudformation:us-east-1:000000000000:stack/dummy-name2/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+                    CreationTime: null,
+                    StackStatus: StackStatus.CREATE_COMPLETE
+                }                
+            ],
+        });
+
+        const actualResult = await new CloudFormationWrapper().findApiId(Globals.apiTypes.rest)
+        expect(actualResult).to.equal(physicalResourceId);
+
+        const expectedParams = {
+            LogicalResourceId: Globals.CFResourceIds[Globals.apiTypes.rest],
+            StackName: nestedStackName,
+        };
+
+        const commandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand, expectedParams, true);
+        expect(commandCalls.length).to.equal(1);
+
+        const allCommandCalls = CloudFormationMock.commandCalls(DescribeStackResourceCommand);
+        expect(allCommandCalls.length).to.equal(2);
+        
+        const describeStacksCommandCalls = CloudFormationMock.commandCalls(DescribeStacksCommand);
+        expect(describeStacksCommandCalls.length).to.equal(2);
+    });
 
     it("findApiId for the rest api type with nested stacks failure", async () => {
         const nestedStackName = "custom-stage-name-NestedStackTwo-U89W84TQIHJK";
diff --git a/test/unit-tests/aws/route53-wrapper.test.ts b/test/unit-tests/aws/route53-wrapper.test.ts
index ca4a4f1..576892a 100644
--- a/test/unit-tests/aws/route53-wrapper.test.ts
+++ b/test/unit-tests/aws/route53-wrapper.test.ts
@@ -70,6 +70,87 @@ describe("Route53 wrapper checks", () => {
         actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc);
         expect(actualId).to.equal(dc.hostedZoneId);
     });
+    
+    it("get route53 hosted zone id paginated", async () => {
+        const testId = "test_host_id"
+        const Route53Mock = mockClient(Route53Client);
+        Route53Mock.on(ListHostedZonesCommand).resolvesOnce({
+            HostedZones: [
+                {
+                    CallerReference: "1",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "test_domain",
+                }, {
+                    CallerReference: "2",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "dummy_test_domain",
+                }, {
+                    CallerReference: "3",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "domain",
+                }                
+            ],
+            NextMarker: "NextMarker"
+        })
+        .resolvesOnce({
+            HostedZones: [
+                {
+                    CallerReference: "4",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "test_domain2",
+                }, {
+                    CallerReference: "5",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "dummy_test_domain2",
+                }, {
+                    CallerReference: "6",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "domain2",
+                }
+            ],
+            NextMarker: "NextMarker"
+        })
+        .resolves({
+            HostedZones: [
+                {
+                    CallerReference: "7",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "test_domain3",
+                }, {
+                    CallerReference: "8",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "dummy_test_domain3",
+                }, {
+                    CallerReference: "9",
+                    Config: {PrivateZone: false},
+                    Id: testId,
+                    Name: "domain3",
+                }
+            ]
+        });
+
+        const dc = new DomainConfig(getDomainConfig({
+            domainName: "test_domain"
+        }));
+
+        let actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc);
+        expect(actualId).to.equal(testId);
+
+        const commandCalls = Route53Mock.commandCalls(ListHostedZonesCommand, {});
+        expect(commandCalls.length).to.equal(3);
+
+        dc.hostedZoneId = "test_id"
+        actualId = await new Route53Wrapper().getRoute53HostedZoneId(dc);
+        expect(actualId).to.equal(dc.hostedZoneId);
+    });
 
     it("get route53 hosted zone id public", async () => {
         const testId = "test_host_id"
@@ -101,7 +182,7 @@ describe("Route53 wrapper checks", () => {
         expect(commandCalls.length).to.equal(1);
     });
 
-    it("get route53 hosted zone id privet", async () => {
+    it("get route53 hosted zone id private", async () => {
         const testId = "test_host_id"
         const Route53Mock = mockClient(Route53Client);
         Route53Mock.on(ListHostedZonesCommand).resolves({