diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef642d0..e4fbf52f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [5.6.0] - 2021-01-26 +- Added config option to avoid automatically deleting an APIGW domain when other base path mappings exist. Thank you @straticJeff ([389](https://github.com/amplify-education/serverless-domain-manager/pull/389)) + ## [5.5.0] - 2021-01-24 - Added proxy support. Thank you @mscharp ([405](https://github.com/amplify-education/serverless-domain-manager/pull/405)) - Fixed issue with disabling createRoute53Record. Thank you @albinlundmark ([476](https://github.com/amplify-education/serverless-domain-manager/pull/476)) diff --git a/README.md b/README.md index 950a819c..93aae2d1 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,8 @@ allowPathMatching | false | When updating an existing api mapping this will matc | route53Params:
  weight | `200` | Sets the weight for weighted routing. Ignored for `simple` and `latency` routing. | | route53Params:
  setIdentifier | | A unique identifier for records in a set of Route 53 records with the same domain name. Only relevant for `latency` and `weighted` routing. Defaults to the regional endpoint if not provided. | | route53Params:
  healthCheckId | | An optional id for a Route 53 health check. If it is failing, Route 53 will stop routing to it. Only relevant for `latency` and `weighted` routing. If it is not provided, no health check will be associated with the record. | +| preserveExternalPathMappings | `false` | When `autoDomain` is set to true, and a deployment is removed, setting this to `true` checks for additional API Gateway base path mappings before automatically deleting the domain, and avoids doing so if they exist. | + ## Running diff --git a/package-lock.json b/package-lock.json index c0b58c22..51577a74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@types/mocha": "^9.1.0", - "@types/node": "^17.0.10", + "@types/node": "^17.0.12", "aws-sdk-mock": "^5.6.0", "chai": "^4.3.4", "chai-spies": "^1.0.0", @@ -24,7 +24,7 @@ "randomstring": "^1.2.2", "request": "^2.88.2", "request-promise-native": "^1.0.9", - "serverless": "^2.72.1", + "serverless": "^2.72.2", "serverless-plugin-split-stacks": "^1.11.3", "shelljs": "^0.8.5", "ts-node": "^10.4.0", @@ -1373,9 +1373,9 @@ } }, "node_modules/@sindresorhus/is": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.3.0.tgz", - "integrity": "sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz", + "integrity": "sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ==", "dev": true, "engines": { "node": ">=10" @@ -1525,9 +1525,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", - "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz", + "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==", "dev": true }, "node_modules/@types/request": { @@ -3019,13 +3019,13 @@ "dev": true }, "node_modules/crc-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", + "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", "dev": true, "dependencies": { "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" + "printj": "~1.3.1" }, "bin": { "crc32": "bin/crc32.njs" @@ -3591,9 +3591,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.51", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz", - "integrity": "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ==", + "version": "1.4.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.52.tgz", + "integrity": "sha512-JGkh8HEh5PnVrhU4HbpyyO0O791dVY6k7AdqfDeqbcRMeoGxtNHWT77deR2nhvbLe4dKpxjlDEvdEwrvRLGu2Q==", "dev": true }, "node_modules/emoji-regex": { @@ -5971,15 +5971,16 @@ "dev": true }, "node_modules/ncjsm": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.2.0.tgz", - "integrity": "sha512-L2Qij4PTy7Bs4TB24zs7FLIAYJTaR5JPvSig5hIcO059LnMCNgy6MfHHNyg8s/aekPKrTqKX90gBGt3NNGvhdw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.3.0.tgz", + "integrity": "sha512-oah6YGwb4Ern2alojiMFcjPhE4wvQBw1Ur/kUr2P0ovKdzaF5pCIsGjs0f2y+iZeej0/5Y6OOhQ8j30cTDMEGw==", "dev": true, "dependencies": { "builtin-modules": "^3.2.0", "deferred": "^0.7.11", "es5-ext": "^0.10.53", "es6-set": "^0.1.5", + "ext": "^1.6.0", "find-requires": "^1.0.0", "fs2": "^0.3.9", "type": "^2.5.0" @@ -7049,9 +7050,9 @@ "dev": true }, "node_modules/printj": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", + "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", "dev": true, "bin": { "printj": "bin/printj.njs" @@ -7682,9 +7683,9 @@ } }, "node_modules/serverless": { - "version": "2.72.1", - "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.1.tgz", - "integrity": "sha512-SxmxyBgWQvcKvEXdP0fR3Y+nljOQ+nWCPDZXnhic//w+k0kNQ/bHcd3S1VZQqU3m7nZZwMsdC5lxB26EpUFELA==", + "version": "2.72.2", + "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.2.tgz", + "integrity": "sha512-xlxaWyq4b58DIX3prwIikBmXj0z4R+YI9zcDPYgQc78mKJ0qTgCK7BMoy6dlVuHXnfhBkhT3uT/EhFvjKIdk6g==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -7696,7 +7697,7 @@ "ajv": "^6.12.6", "ajv-keywords": "^3.5.2", "archiver": "^5.3.0", - "aws-sdk": "^2.1061.0", + "aws-sdk": "^2.1062.0", "bluebird": "^3.7.2", "boxen": "^5.1.2", "cachedir": "^2.3.0", @@ -7726,7 +7727,7 @@ "lodash": "^4.17.21", "memoizee": "^0.4.15", "micromatch": "^4.0.4", - "ncjsm": "^4.2.0", + "ncjsm": "^4.3.0", "node-fetch": "^2.6.7", "open": "^7.4.2", "path2": "^0.1.0", @@ -11007,9 +11008,9 @@ } }, "@sindresorhus/is": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.3.0.tgz", - "integrity": "sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.4.0.tgz", + "integrity": "sha512-QppPM/8l3Mawvh4rn9CNEYIU9bxpXUCRMaX9yUpvBk1nMKusLKpfXGDEKExKaPhLzcn3lzil7pR6rnJ11HgeRQ==", "dev": true }, "@sinonjs/commons": { @@ -11150,9 +11151,9 @@ "dev": true }, "@types/node": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.10.tgz", - "integrity": "sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==", + "version": "17.0.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.12.tgz", + "integrity": "sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==", "dev": true }, "@types/request": { @@ -12360,13 +12361,13 @@ "dev": true }, "crc-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", + "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", "dev": true, "requires": { "exit-on-epipe": "~1.0.1", - "printj": "~1.1.0" + "printj": "~1.3.1" } }, "crc32-stream": { @@ -12828,9 +12829,9 @@ } }, "electron-to-chromium": { - "version": "1.4.51", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz", - "integrity": "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ==", + "version": "1.4.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.52.tgz", + "integrity": "sha512-JGkh8HEh5PnVrhU4HbpyyO0O791dVY6k7AdqfDeqbcRMeoGxtNHWT77deR2nhvbLe4dKpxjlDEvdEwrvRLGu2Q==", "dev": true }, "emoji-regex": { @@ -14728,15 +14729,16 @@ "dev": true }, "ncjsm": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.2.0.tgz", - "integrity": "sha512-L2Qij4PTy7Bs4TB24zs7FLIAYJTaR5JPvSig5hIcO059LnMCNgy6MfHHNyg8s/aekPKrTqKX90gBGt3NNGvhdw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ncjsm/-/ncjsm-4.3.0.tgz", + "integrity": "sha512-oah6YGwb4Ern2alojiMFcjPhE4wvQBw1Ur/kUr2P0ovKdzaF5pCIsGjs0f2y+iZeej0/5Y6OOhQ8j30cTDMEGw==", "dev": true, "requires": { "builtin-modules": "^3.2.0", "deferred": "^0.7.11", "es5-ext": "^0.10.53", "es6-set": "^0.1.5", + "ext": "^1.6.0", "find-requires": "^1.0.0", "fs2": "^0.3.9", "type": "^2.5.0" @@ -15575,9 +15577,9 @@ } }, "printj": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", + "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", "dev": true }, "process-nextick-args": { @@ -16040,9 +16042,9 @@ } }, "serverless": { - "version": "2.72.1", - "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.1.tgz", - "integrity": "sha512-SxmxyBgWQvcKvEXdP0fR3Y+nljOQ+nWCPDZXnhic//w+k0kNQ/bHcd3S1VZQqU3m7nZZwMsdC5lxB26EpUFELA==", + "version": "2.72.2", + "resolved": "https://registry.npmjs.org/serverless/-/serverless-2.72.2.tgz", + "integrity": "sha512-xlxaWyq4b58DIX3prwIikBmXj0z4R+YI9zcDPYgQc78mKJ0qTgCK7BMoy6dlVuHXnfhBkhT3uT/EhFvjKIdk6g==", "dev": true, "requires": { "@serverless/cli": "^1.6.0", @@ -16053,7 +16055,7 @@ "ajv": "^6.12.6", "ajv-keywords": "^3.5.2", "archiver": "^5.3.0", - "aws-sdk": "^2.1061.0", + "aws-sdk": "^2.1062.0", "bluebird": "^3.7.2", "boxen": "^5.1.2", "cachedir": "^2.3.0", @@ -16083,7 +16085,7 @@ "lodash": "^4.17.21", "memoizee": "^0.4.15", "micromatch": "^4.0.4", - "ncjsm": "^4.2.0", + "ncjsm": "^4.3.0", "node-fetch": "^2.6.7", "open": "^7.4.2", "path2": "^0.1.0", diff --git a/package.json b/package.json index 0fd1679b..2cb299d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-domain-manager", - "version": "5.5.0", + "version": "5.6.0", "engines": { "node": ">=12" }, @@ -47,7 +47,7 @@ }, "devDependencies": { "@types/mocha": "^9.1.0", - "@types/node": "^17.0.10", + "@types/node": "^17.0.12", "aws-sdk-mock": "^5.6.0", "chai": "^4.3.4", "chai-spies": "^1.0.0", @@ -57,7 +57,7 @@ "randomstring": "^1.2.2", "request": "^2.88.2", "request-promise-native": "^1.0.9", - "serverless": "^2.72.1", + "serverless": "^2.72.2", "serverless-plugin-split-stacks": "^1.11.3", "shelljs": "^0.8.5", "ts-node": "^10.4.0", diff --git a/src/aws/api-gateway-wrapper.ts b/src/aws/api-gateway-wrapper.ts index a0ed66f8..a9b9cbee 100644 --- a/src/aws/api-gateway-wrapper.ts +++ b/src/aws/api-gateway-wrapper.ts @@ -148,12 +148,9 @@ class APIGatewayWrapper { } } - /** - * Get basepath mapping - */ - public async getBasePathMapping(domain: DomainConfig): Promise { + public async getApiMappings(domain: DomainConfig): Promise { try { - const mappings = await getAWSPagedResults( + return await getAWSPagedResults( this.apiGatewayV2, "getApiMappings", "Items", @@ -161,12 +158,6 @@ class APIGatewayWrapper { "NextToken", {DomainName: domain.givenDomainName}, ); - for (const mapping of mappings) { - if (mapping.ApiId === domain.apiId - || (mapping.ApiMappingKey === domain.basePath && domain.allowPathMatching)) { - return mapping; - } - } } catch (err) { Globals.logError(err, domain.givenDomainName); throw new Error(`Unable to get API Mappings for ${domain.givenDomainName}`); diff --git a/src/domain-config.ts b/src/domain-config.ts index 071daf1f..4187af15 100644 --- a/src/domain-config.ts +++ b/src/domain-config.ts @@ -28,6 +28,7 @@ class DomainConfig { public autoDomain: boolean | undefined; public autoDomainWaitFor: string | undefined; public route53Params: Route53Params; + public preserveExternalPathMappings: boolean | undefined; public domainInfo: DomainInfo | undefined; public apiId: string | undefined; @@ -50,6 +51,7 @@ class DomainConfig { this.allowPathMatching = config.allowPathMatching; this.autoDomain = config.autoDomain; this.autoDomainWaitFor = config.autoDomainWaitFor; + this.preserveExternalPathMappings = this.evaluateBoolean(config.preserveExternalPathMappings, false); let basePath = config.basePath; if (basePath == null || basePath.trim() === "") { diff --git a/src/index.ts b/src/index.ts index 7083105b..2ef904b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -301,7 +301,13 @@ class ServerlessCustomDomain { await Promise.all(this.domains.map(async (domain) => { try { domain.apiId = await this.getApiId(domain); - domain.apiMapping = await this.apiGatewayWrapper.getBasePathMapping(domain); + const mappings = await this.apiGatewayWrapper.getApiMappings(domain); + const filteredMappings = mappings.filter((mapping) => { + return mapping.ApiId === domain.apiId || ( + mapping.ApiMappingKey === domain.basePath && domain.allowPathMatching + ) + }); + domain.apiMapping = filteredMappings ? filteredMappings[0] : null; domain.domainInfo = await this.apiGatewayWrapper.getCustomDomainInfo(domain); if (!domain.apiMapping) { @@ -328,6 +334,7 @@ class ServerlessCustomDomain { */ public async removeBasePathMappings(): Promise { await Promise.all(this.domains.map(async (domain) => { + let externalBasePathExists = false; try { domain.apiId = await this.getApiId(domain); @@ -336,7 +343,17 @@ class ServerlessCustomDomain { Globals.logInfo(`Unable to find corresponding API for ${domain.givenDomainName}, API Mappings may need to be manually removed.`); } else { - domain.apiMapping = await this.apiGatewayWrapper.getBasePathMapping(domain); + const mappings = await this.apiGatewayWrapper.getApiMappings(domain); + const filteredMappings = mappings.filter((mapping) => { + return mapping.ApiId === domain.apiId || ( + mapping.ApiMappingKey === domain.basePath && domain.allowPathMatching + ) + }); + if (domain.preserveExternalPathMappings) { + externalBasePathExists = mappings.length > filteredMappings.length; + } + + domain.apiMapping = filteredMappings ? filteredMappings[0] : null; await this.apiGatewayWrapper.deleteBasePathMapping(domain); } } catch (err) { @@ -351,8 +368,7 @@ class ServerlessCustomDomain { } } - const autoDomain = domain.autoDomain; - if (autoDomain === true) { + if (domain.autoDomain === true && !externalBasePathExists) { Globals.logInfo("Deleting domain name after removing base path mapping."); await this.deleteDomain(domain); } diff --git a/src/types.ts b/src/types.ts index d93fafd3..8b5457d0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,6 +19,7 @@ export interface CustomDomain { // tslint:disable-line autoDomainWaitFor: string | undefined; allowPathMatching: boolean | undefined; route53Params: Route53Params | undefined + preserveExternalPathMappings: boolean | undefined; } export interface ServerlessInstance { // tslint:disable-line diff --git a/test/integration-tests/basic.test.ts b/test/integration-tests/basic.test.ts index 7fe8fdf6..c749cad0 100644 --- a/test/integration-tests/basic.test.ts +++ b/test/integration-tests/basic.test.ts @@ -94,7 +94,10 @@ describe("Integration Tests", function () { expect(basePath).to.equal("hello-world"); } finally { // should destroy the last created config folder ( import config ) - await utilities.destroyResources(`${testExportName} & ${testImportName}`); + await utilities.destroyResources(testImportName); + // temp dir are empty and we need to update it with export config for the proper cleanup + await utilities.createTempDir(TEMP_DIR, configExportFolder); + await utilities.destroyResources(testExportName); } }); diff --git a/test/unit-tests/index.test.ts b/test/unit-tests/index.test.ts index 25896e6c..bc4df785 100644 --- a/test/unit-tests/index.test.ts +++ b/test/unit-tests/index.test.ts @@ -55,6 +55,7 @@ const constructPlugin = (customDomainOptions, multiple: boolean = false) => { hostedZonePrivate: customDomainOptions.hostedZonePrivate, route53Profile: customDomainOptions.route53Profile, route53Region: customDomainOptions.route53Region, + preserveExternalPathMappings: customDomainOptions.preserveExternalPathMappings, securityPolicy: customDomainOptions.securityPolicy, stage: customDomainOptions.stage, route53Params: customDomainOptions.route53Params @@ -731,35 +732,6 @@ describe("Custom Domain Plugin", () => { }); describe("Gets existing basepath mappings correctly", () => { - it("Returns undefined if no basepaths map to current api", async () => { - AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => { - // @ts-ignore - callback(null, { - Items: [ - { - ApiId: "someother_api_id", - ApiMappingId: "test_rest_api_id_one", - MappingKey: "test", - Stage: "test", - }, - ], - }); - }); - - const plugin = constructPlugin({ - domainName: "test_domain", - }); - - const dc: DomainConfig = new DomainConfig(plugin.serverless.service.custom.customDomain); - dc.apiMapping = {ApiMappingId: "api_id"}; - - plugin.initializeVariables(); - plugin.initAWSResources(); - - const result = await plugin.apiGatewayWrapper.getBasePathMapping(dc); - expect(result).to.equal(undefined); - }); - it("Returns current api mapping", async () => { AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => { callback(null, { @@ -781,8 +753,8 @@ describe("Custom Domain Plugin", () => { plugin.initializeVariables(); plugin.initAWSResources(); - const result = await plugin.apiGatewayWrapper.getBasePathMapping(dc); - expect(result).to.eql({ + const result = await plugin.apiGatewayWrapper.getApiMappings(dc); + expect(result[0]).to.eql({ ApiId: "test_rest_api_id", ApiMappingId: "fake_id", ApiMappingKey: "api", @@ -1938,7 +1910,118 @@ describe("Custom Domain Plugin", () => { expect(spy).to.have.not.been.called(); }); + it("removeBasePathMapping should not call deleteDomain when preserveExternalPathMappings is true and " + + "external mappings exist", async () => { + AWS.mock("CloudFormation", "describeStackResource", (params, callback) => { + // @ts-ignore + callback(null, { + StackResourceDetail: + { + LogicalResourceId: "ApiGatewayRestApi", + PhysicalResourceId: "test_rest_api_id", + }, + }); + }); + AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => { + // @ts-ignore + callback(null, { + Items: [ + {ApiId: "test_rest_api_id", MappingKey: "test", ApiMappingId: "test_mapping_id", Stage: "test"}, + { + ApiId: "test_rest_api_id_2", + ApiMappingId: "test_mapping_id", + MappingKey: "test", + Stage: "test", + }, + ], + }); + }); + AWS.mock("ApiGatewayV2", "deleteApiMapping", (params, callback) => { + callback(null, params); + }); + AWS.mock("ApiGatewayV2", "deleteDomainName", (params, callback) => { + callback(null, params); + }); + AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => { + callback(null, params); + }); + + const plugin = constructPlugin({ + autoDomain: true, + basePath: "test_basepath", + createRoute53Record: false, + domainName: "test_domain", + preserveExternalPathMappings: true, + restApiId: "test_rest_api_id", + }); + plugin.initializeVariables(); + plugin.initAWSResources(); + + plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"}; + + const spy = chai.spy.on(plugin.apiGatewayWrapper.apiGatewayV2, "deleteDomainName"); + + await plugin.removeBasePathMappings(); + + expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(true); + expect(plugin.serverless.service.custom.customDomain.preserveExternalPathMappings).to.equal(true); + expect(spy).to.have.not.been.called(); + }); + + it("removeBasePathMapping should call deleteDomain when preserveExternalPathMappings is true and " + + "external mappings don't exist", async () => { + AWS.mock("CloudFormation", "describeStackResource", (params, callback) => { + // @ts-ignore + callback(null, { + StackResourceDetail: + { + LogicalResourceId: "ApiGatewayRestApi", + PhysicalResourceId: "test_rest_api_id", + }, + }); + }); + AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => { + // @ts-ignore + callback(null, { + Items: [ + {ApiId: "test_rest_api_id", MappingKey: "test", ApiMappingId: "test_mapping_id", Stage: "test"}, + ], + }); + }); + AWS.mock("ApiGatewayV2", "deleteApiMapping", (params, callback) => { + callback(null, params); + }); + AWS.mock("ApiGatewayV2", "deleteDomainName", (params, callback) => { + callback(null, params); + }); + AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => { + callback(null, params); + }); + + const plugin = constructPlugin({ + autoDomain: true, + basePath: "test_basepath", + createRoute53Record: false, + domainName: "test_domain", + preserveExternalPathMappings: true, + restApiId: "test_rest_api_id", + }); + plugin.initializeVariables(); + plugin.initAWSResources(); + + plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"}; + + const spy = chai.spy.on(plugin.apiGatewayWrapper.apiGatewayV2, "deleteDomainName"); + + await plugin.removeBasePathMappings(); + + expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(true); + expect(plugin.serverless.service.custom.customDomain.preserveExternalPathMappings).to.equal(true); + expect(spy).to.have.been.called(); + }); + afterEach(() => { + AWS.restore(); consoleOutput = []; }); });