From fa13297aab80f76cfe2875591cbc4b6094ca4bc9 Mon Sep 17 00:00:00 2001 From: Nadeem Patwekar Date: Fri, 5 Dec 2025 17:02:48 +0530 Subject: [PATCH 1/3] Refactor and revert region handling in client and remove regions.json file --- lib/assets/regions.json | 211 ------------------ lib/contentstack.js | 22 +- lib/core/Util.js | 14 +- lib/core/contentstackHTTPClient.js | 30 ++- package.json | 4 +- test/unit/ContentstackHTTPClient-test.js | 165 -------------- test/unit/Util-test.js | 260 ----------------------- test/unit/contentstack-test.js | 169 --------------- 8 files changed, 34 insertions(+), 841 deletions(-) delete mode 100644 lib/assets/regions.json diff --git a/lib/assets/regions.json b/lib/assets/regions.json deleted file mode 100644 index eafbba92..00000000 --- a/lib/assets/regions.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "regions": [ - { - "id": "na", - "name": "AWS North America", - "cloudProvider": "AWS", - "location": "North America", - "alias": [ - "na", - "us", - "aws-na", - "aws_na" - ], - "isDefault": true, - "endpoints": { - "application": "https://app.contentstack.com", - "contentDelivery": "https://cdn.contentstack.io", - "contentManagement": "https://api.contentstack.io", - "auth": "https://auth-api.contentstack.com", - "graphqlDelivery": "https://graphql.contentstack.com", - "preview": "https://rest-preview.contentstack.com", - "graphqlPreview": "https://graphql-preview.contentstack.com", - "images": "https://images.contentstack.io", - "assets": "https://assets.contentstack.io", - "automate": "https://automations-api.contentstack.com", - "launch": "https://launch-api.contentstack.com", - "developerHub": "https://developerhub-api.contentstack.com", - "brandKit": "https://brand-kits-api.contentstack.com", - "genAI": "https://ai.contentstack.com", - "personalize": "https://personalize-api.contentstack.com", - "personalizeEdge": "https://personalize-edge.contentstack.com" - } - }, - { - "id": "eu", - "name": "AWS Europe", - "cloudProvider": "AWS", - "location": "Europe", - "alias": [ - "eu", - "aws-eu", - "aws_eu" - ], - "isDefault": false, - "endpoints": { - "application": "https://eu-app.contentstack.com", - "contentDelivery": "https://eu-cdn.contentstack.com", - "contentManagement": "https://eu-api.contentstack.com", - "auth": "https://eu-auth-api.contentstack.com", - "graphqlDelivery": "https://eu-graphql.contentstack.com", - "preview": "https://eu-rest-preview.contentstack.com", - "graphqlPreview": "https://eu-graphql-preview.contentstack.com", - "images": "https://eu-images.contentstack.com", - "assets": "https://eu-assets.contentstack.com", - "automate": "https://eu-prod-automations-api.contentstack.com", - "launch": "https://eu-launch-api.contentstack.com", - "developerHub": "https://eu-developerhub-api.contentstack.com", - "brandKit": "https://eu-brand-kits-api.contentstack.com", - "genAI": "https://eu-ai.contentstack.com", - "personalize": "https://eu-personalize-api.contentstack.com", - "personalizeEdge": "https://eu-personalize-edge.contentstack.com" - } - }, - { - "id": "au", - "name": "AWS Australia", - "cloudProvider": "AWS", - "location": "Australia", - "alias": [ - "au", - "aws-au", - "aws_au" - ], - "isDefault": false, - "endpoints": { - "application": "https://au-app.contentstack.com", - "contentDelivery": "https://au-cdn.contentstack.com", - "contentManagement": "https://au-api.contentstack.com", - "auth": "https://au-auth-api.contentstack.com", - "graphqlDelivery": "https://au-graphql.contentstack.com", - "preview": "https://au-rest-preview.contentstack.com", - "graphqlPreview": "https://au-graphql-preview.contentstack.com", - "images": "https://au-images.contentstack.com", - "assets": "https://au-assets.contentstack.com", - "automate": "https://au-prod-automations-api.contentstack.com", - "launch": "https://au-launch-api.contentstack.com", - "developerHub": "https://au-developerhub-api.contentstack.com", - "brandKit": "https://au-brand-kits-api.contentstack.com", - "genAI": "https://au-ai.contentstack.com", - "personalize": "https://au-personalize-api.contentstack.com", - "personalizeEdge": "https://au-personalize-edge.contentstack.com" - } - }, - { - "id": "azure-na", - "name": "Azure North America", - "cloudProvider": "Azure", - "location": "North America", - "alias": [ - "azure-na", - "azure_na" - ], - "isDefault": false, - "endpoints": { - "application": "https://azure-na-app.contentstack.com", - "contentDelivery": "https://azure-na-cdn.contentstack.com", - "contentManagement": "https://azure-na-api.contentstack.com", - "auth": "https://azure-na-auth-api.contentstack.com", - "graphqlDelivery": "https://azure-na-graphql.contentstack.com", - "preview": "https://azure-na-rest-preview.contentstack.com", - "graphqlPreview": "https://azure-na-graphql-preview.contentstack.com", - "images": "https://azure-na-images.contentstack.com", - "assets": "https://azure-na-assets.contentstack.com", - "automate": "https://azure-na-automations-api.contentstack.com", - "launch": "https://azure-na-launch-api.contentstack.com", - "developerHub": "https://azure-na-developerhub-api.contentstack.com", - "brandKit": "https://azure-na-brand-kits-api.contentstack.com", - "genAI": "https://azure-na-ai.contentstack.com", - "personalize": "https://azure-na-personalize-api.contentstack.com", - "personalizeEdge": "https://azure-na-personalize-edge.contentstack.com" - } - }, - { - "id": "azure-eu", - "name": "Azure Europe", - "cloudProvider": "Azure", - "location": "Europe", - "alias": [ - "azure-eu", - "azure_eu" - ], - "isDefault": false, - "endpoints": { - "application": "https://azure-eu-app.contentstack.com", - "contentDelivery": "https://azure-eu-cdn.contentstack.com", - "contentManagement": "https://azure-eu-api.contentstack.com", - "auth": "https://azure-eu-auth-api.contentstack.com", - "graphqlDelivery": "https://azure-eu-graphql.contentstack.com", - "preview": "https://azure-eu-rest-preview.contentstack.com", - "graphqlPreview": "https://azure-eu-graphql-preview.contentstack.com", - "images": "https://azure-eu-images.contentstack.com", - "assets": "https://azure-eu-assets.contentstack.com", - "automate": "https://azure-eu-automations-api.contentstack.com", - "launch": "https://azure-eu-launch-api.contentstack.com", - "developerHub": "https://azure-eu-developerhub-api.contentstack.com", - "brandKit": "https://azure-eu-brand-kits-api.contentstack.com", - "genAI": "https://azure-eu-ai.contentstack.com", - "personalize": "https://azure-eu-personalize-api.contentstack.com", - "personalizeEdge": "https://azure-eu-personalize-edge.contentstack.com" - } - }, - { - "id": "gcp-na", - "name": "GCP North America", - "cloudProvider": "GCP", - "location": "North America", - "alias": [ - "gcp-na", - "gcp_na" - ], - "isDefault": false, - "endpoints": { - "application": "https://gcp-na-app.contentstack.com", - "contentDelivery": "https://gcp-na-cdn.contentstack.com", - "contentManagement": "https://gcp-na-api.contentstack.com", - "auth": "https://gcp-na-auth-api.contentstack.com", - "graphqlDelivery": "https://gcp-na-graphql.contentstack.com", - "preview": "https://gcp-na-rest-preview.contentstack.com", - "graphqlPreview": "https://gcp-na-graphql-preview.contentstack.com", - "images": "https://gcp-na-images.contentstack.com", - "assets": "https://gcp-na-assets.contentstack.com", - "automate": "https://gcp-na-automations-api.contentstack.com", - "launch": "https://gcp-na-launch-api.contentstack.com", - "developerHub": "https://gcp-na-developerhub-api.contentstack.com", - "brandKit": "https://gcp-na-brand-kits-api.contentstack.com", - "genAI": "https://gcp-na-brand-kits-api.contentstack.com", - "personalize": "https://gcp-na-personalize-api.contentstack.com", - "personalizeEdge": "https://gcp-na-personalize-edge.contentstack.com" - } - }, - { - "id": "gcp-eu", - "name": "GCP Europe", - "cloudProvider": "GCP", - "location": "Europe", - "alias": [ - "gcp-eu", - "gcp_eu" - ], - "isDefault": false, - "endpoints": { - "application": "https://gcp-eu-app.contentstack.com", - "contentDelivery": "https://gcp-eu-cdn.contentstack.com", - "contentManagement": "https://gcp-eu-api.contentstack.com", - "auth": "https://gcp-eu-auth-api.contentstack.com", - "graphqlDelivery": "https://gcp-eu-graphql.contentstack.com", - "preview": "https://gcp-eu-rest-preview.contentstack.com", - "graphqlPreview": "https://gcp-eu-graphql-preview.contentstack.com", - "images": "https://gcp-eu-images.contentstack.com", - "assets": "https://gcp-eu-assets.contentstack.com", - "automate": "https://gcp-eu-automations-api.contentstack.com", - "launch": "https://gcp-eu-launch-api.contentstack.com", - "developerHub": "https://gcp-eu-developerhub-api.contentstack.com", - "brandKit": "https://gcp-eu-brand-kits-api.contentstack.com", - "genAI": "https://gcp-eu-brand-kits-api.contentstack.com", - "personalize": "https://gcp-eu-personalize-api.contentstack.com", - "personalizeEdge": "https://gcp-eu-personalize-edge.contentstack.com" - } - } - ] -} diff --git a/lib/contentstack.js b/lib/contentstack.js index 641b99a3..33d6dbec 100644 --- a/lib/contentstack.js +++ b/lib/contentstack.js @@ -7,6 +7,15 @@ import clonedeep from 'lodash/cloneDeep' import getUserAgent, { getRegionEndpoint } from './core/Util.js' import contentstackClient from './contentstackClient.js' import httpClient from './core/contentstackHTTPClient.js' +const regionHostMap = { + NA: 'api.contentstack.io', + EU: 'eu-api.contentstack.com', + AU: 'au-api.contentstack.com', + AZURE_NA: 'azure-na-api.contentstack.com', + AZURE_EU: 'azure-eu-api.contentstack.com', + GCP_NA: 'gcp-na-api.contentstack.com', + GCP_EU: 'gcp-eu-api.contentstack.com' +} /** * Create client instance @@ -170,11 +179,18 @@ import httpClient from './core/contentstackHTTPClient.js' * @returns {ContentstackClient} Instance of ContentstackClient */ export function client (params = {}) { - let defaultHostName = getRegionEndpoint('na') + let defaultHostName if (params.region) { - params.region = params.region.toLowerCase() - defaultHostName = getRegionEndpoint(params.region) + const region = params.region.toLowerCase() + if (!regionHostMap[region]) { + throw new Error(`Invalid region '${params.region}' provided. Allowed regions are: ${Object.keys(regionHostMap).join(', ')}`) + } + defaultHostName = regionHostMap[region] + } else if (params.host) { + defaultHostName = params.host + } else { + defaultHostName = regionHostMap['NA'] } const defaultParameter = { diff --git a/lib/core/Util.js b/lib/core/Util.js index 21aa43b4..f7229e4b 100644 --- a/lib/core/Util.js +++ b/lib/core/Util.js @@ -1,5 +1,4 @@ import { platform, release } from 'os' -import regionHostMap from '../assets/regions.json' const HOST_REGEX = /^(?!(?:(?:https?|ftp):\/\/|internal|localhost|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))(?:[\w-]+\.contentstack\.(?:io|com)(?::[^\/\s:]+)?|[\w-]+(?:\.[\w-]+)*(?::[^\/\s:]+)?)(?![\/?#])$/ // eslint-disable-line @@ -236,15 +235,4 @@ export const validateAndSanitizeConfig = (config) => { ...config, url: config.url.trim() // Sanitize URL by removing whitespace } -} - -export const getRegionEndpoint = (region, service = 'contentManagement') => { - const regionData = regionHostMap.regions.find(r => - r.id === region || - r.alias.some(alias => alias === region) - ) - if (!regionData) { - throw new Error(`Invalid region '${region}' provided. Allowed regions are: ${regionHostMap.regions.map(r => r.id).join(', ')}`) - } - return regionData.endpoints[service]?.replace(/^https?:\/\//, '') -} +} \ No newline at end of file diff --git a/lib/core/contentstackHTTPClient.js b/lib/core/contentstackHTTPClient.js index adb09788..ca41ea8c 100644 --- a/lib/core/contentstackHTTPClient.js +++ b/lib/core/contentstackHTTPClient.js @@ -2,7 +2,7 @@ import axios from 'axios' import clonedeep from 'lodash/cloneDeep' import Qs from 'qs' import { ConcurrencyQueue } from './concurrency-queue' -import { getRegionEndpoint, isHost } from './Util' +import { isHost } from './Util' export default function contentstackHttpClient (options) { const defaultConfig = { @@ -68,28 +68,24 @@ export default function contentstackHttpClient (options) { config.basePath = `/${config.basePath.split('/').filter(Boolean).join('/')}` } const baseURL = config.endpoint || `${protocol}://${hostname}:${port}${config.basePath}/{api-version}` + let uiHostName = hostname + let developerHubBaseUrl = hostname - let region = config.region || 'na' - if (!config.region && config.host) { - const hostRegionMatch = config.host.match(/^([a-z]+-?[a-z]*)-api\./) - if (hostRegionMatch) { - region = hostRegionMatch[1] - } + if (uiHostName?.endsWith('io')) { + uiHostName = uiHostName.replace('io', 'com') } - let uiHostName, developerHubBaseUrl - if (config.host && (config.host.startsWith('dev') || config.host.startsWith('stag'))) { - uiHostName = config.host.replace('-api.', '-app.') - const transformedHost = config.host - .replace(/^dev\d+/, 'dev') - .replace(/^stag\d+/, 'stag') - developerHubBaseUrl = `https://${transformedHost.replace('-api.', '-developerhub-api.')}` - } else { - uiHostName = getRegionEndpoint(region, 'application') - developerHubBaseUrl = `https://${getRegionEndpoint(region, 'developerHub')}` + if (uiHostName) { + uiHostName = uiHostName.replace('api', 'app') } const uiBaseUrl = config.endpoint || `${protocol}://${uiHostName}` + developerHubBaseUrl = developerHubBaseUrl + ?.replace('api', 'developerhub-api') + .replace(/^dev\d+/, 'dev') // Replaces any 'dev1', 'dev2', etc. with 'dev' + .replace('io', 'com') + .replace(/^http/, '') // Removing `http` if already present + .replace(/^/, 'https://') // Adds 'https://' at the start if not already there // set ui host name const axiosOptions = { diff --git a/package.json b/package.json index 8ffabdef..2ddcfc3e 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,7 @@ "pre-commit": "npm run lint && husky install && husky && chmod +x .husky/pre-commit && ./.husky/pre-commit", "prepush": "npm run test:unit", "generate:docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --readme README.md --verbose", - "husky-check": "npx husky && chmod +x .husky/pre-commit", - "postinstall": "curl -s --max-time 30 --fail https://artifacts.contentstack.com/regions.json -o lib/assets/regions.json || echo 'Warning: Failed to download regions.json, using existing file if available'", - "postupdate": "curl -s --max-time 30 --fail https://artifacts.contentstack.com/regions.json -o lib/assets/regions.json || echo 'Warning: Failed to download regions.json, using existing file if available'" + "husky-check": "npx husky && chmod +x .husky/pre-commit" }, "engines": { "node": ">=8.0.0" diff --git a/test/unit/ContentstackHTTPClient-test.js b/test/unit/ContentstackHTTPClient-test.js index 685575f3..1b78a290 100644 --- a/test/unit/ContentstackHTTPClient-test.js +++ b/test/unit/ContentstackHTTPClient-test.js @@ -167,169 +167,4 @@ describe('Contentstack HTTP Client', () => { expect(axiosInstance.defaults.headers['x-header-ea']).to.be.equal('ea1,ea2') done() }) - - describe('Region-based endpoint configuration', () => { - it('should configure endpoints for NA region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'api.contentstack.io', - region: 'na' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://app.contentstack.com', 'NA UI base URL should match') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('developerhub-api.contentstack.com', 'NA developer hub should match') - done() - }) - - it('should configure endpoints for EU region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'eu-api.contentstack.com', - region: 'eu' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://eu-app.contentstack.com', 'EU UI base URL should match') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('eu-developerhub-api.contentstack.com', 'EU developer hub should match') - done() - }) - - it('should configure endpoints for AU region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'au-api.contentstack.com', - region: 'au' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://au-app.contentstack.com', 'AU UI base URL should match') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('au-developerhub-api.contentstack.com', 'AU developer hub should match') - done() - }) - - it('should configure endpoints for Azure NA region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'azure-na-api.contentstack.com', - region: 'azure-na' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://azure-na-app.contentstack.com', 'Azure NA UI base URL should match') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('azure-na-developerhub-api.contentstack.com', 'Azure NA developer hub should match') - done() - }) - - it('should configure endpoints for Azure EU region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'azure-eu-api.contentstack.com', - region: 'azure-eu' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://azure-eu-app.contentstack.com', 'Azure EU UI base URL should match') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('azure-eu-developerhub-api.contentstack.com', 'Azure EU developer hub should match') - done() - }) - - it('should configure endpoints for GCP NA region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'gcp-na-api.contentstack.com', - region: 'gcp-na' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://gcp-na-app.contentstack.com', 'GCP NA UI base URL should match') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('gcp-na-developerhub-api.contentstack.com', 'GCP NA developer hub should match') - done() - }) - - it('should configure endpoints for GCP EU region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'gcp-eu-api.contentstack.com', - region: 'gcp-eu' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://gcp-eu-app.contentstack.com', 'GCP EU UI base URL should match') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('gcp-eu-developerhub-api.contentstack.com', 'GCP EU developer hub should match') - done() - }) - - it('should include https protocol in developer hub base URL', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'api.contentstack.io', - region: 'na' - }) - expect(axiosInstance.defaults.developerHubBaseUrl).to.match(/^https:\/\//, 'Developer hub URL should start with https://') - done() - }) - - it('should include https protocol in UI base URL', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'api.contentstack.io', - region: 'na' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.match(/^https:\/\//, 'UI base URL should start with https://') - done() - }) - - it('should configure UI base URL with protocol for NA region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'api.contentstack.io', - region: 'na' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://app.contentstack.com', 'NA UI base URL should include protocol') - done() - }) - - it('should configure UI base URL with protocol for EU region', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'eu-api.contentstack.com', - region: 'eu' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://eu-app.contentstack.com', 'EU UI base URL should include protocol') - done() - }) - - it('should handle region aliases when configuring endpoints', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'api.contentstack.io', - region: 'us' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://app.contentstack.com', 'US alias should map to NA endpoints') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('developerhub-api.contentstack.com', 'US alias should map to NA developer hub') - done() - }) - - it('should handle azure_na alias when configuring endpoints', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'azure-na-api.contentstack.com', - region: 'azure_na' - }) - expect(axiosInstance.defaults.uiBaseUrl).to.be.equal('https://azure-na-app.contentstack.com', 'azure_na alias should work') - expect(axiosInstance.defaults.developerHubBaseUrl).to.contain('azure-na-developerhub-api.contentstack.com', 'azure_na alias should work for developer hub') - done() - }) - - it('should configure region property in config', done => { - var axiosInstance = contentstackHTTPClient({ - apiKey: 'apiKey', - accessToken: 'accessToken', - defaultHostName: 'api.contentstack.io', - region: 'eu' - }) - expect(axiosInstance.defaults.region).to.be.equal('eu', 'Region should be stored in defaults') - done() - }) - }) }) diff --git a/test/unit/Util-test.js b/test/unit/Util-test.js index eddffa1c..93411376 100644 --- a/test/unit/Util-test.js +++ b/test/unit/Util-test.js @@ -166,264 +166,4 @@ describe('Get User Agent', () => { done() }) }) - - describe('Region Endpoint Retrieval', () => { - describe('Valid regions with ID', () => { - it('should return correct endpoint for NA region', done => { - const endpoint = getRegionEndpoint('na', 'contentManagement') - expect(endpoint).to.be.equal('api.contentstack.io', 'NA region endpoint should match') - done() - }) - - it('should return correct endpoint for EU region', done => { - const endpoint = getRegionEndpoint('eu', 'contentManagement') - expect(endpoint).to.be.equal('eu-api.contentstack.com', 'EU region endpoint should match') - done() - }) - - it('should return correct endpoint for AU region', done => { - const endpoint = getRegionEndpoint('au', 'contentManagement') - expect(endpoint).to.be.equal('au-api.contentstack.com', 'AU region endpoint should match') - done() - }) - - it('should return correct endpoint for Azure NA region', done => { - const endpoint = getRegionEndpoint('azure-na', 'contentManagement') - expect(endpoint).to.be.equal('azure-na-api.contentstack.com', 'Azure NA region endpoint should match') - done() - }) - - it('should return correct endpoint for Azure EU region', done => { - const endpoint = getRegionEndpoint('azure-eu', 'contentManagement') - expect(endpoint).to.be.equal('azure-eu-api.contentstack.com', 'Azure EU region endpoint should match') - done() - }) - - it('should return correct endpoint for GCP NA region', done => { - const endpoint = getRegionEndpoint('gcp-na', 'contentManagement') - expect(endpoint).to.be.equal('gcp-na-api.contentstack.com', 'GCP NA region endpoint should match') - done() - }) - - it('should return correct endpoint for GCP EU region', done => { - const endpoint = getRegionEndpoint('gcp-eu', 'contentManagement') - expect(endpoint).to.be.equal('gcp-eu-api.contentstack.com', 'GCP EU region endpoint should match') - done() - }) - }) - - describe('Valid regions with aliases', () => { - it('should return correct endpoint for US alias', done => { - const endpoint = getRegionEndpoint('us', 'contentManagement') - expect(endpoint).to.be.equal('api.contentstack.io', 'US alias should map to NA region') - done() - }) - - it('should return correct endpoint for aws-na alias', done => { - const endpoint = getRegionEndpoint('aws-na', 'contentManagement') - expect(endpoint).to.be.equal('api.contentstack.io', 'aws-na alias should map to NA region') - done() - }) - - it('should return correct endpoint for aws_na alias', done => { - const endpoint = getRegionEndpoint('aws_na', 'contentManagement') - expect(endpoint).to.be.equal('api.contentstack.io', 'aws_na alias should map to NA region') - done() - }) - - it('should return correct endpoint for aws-eu alias', done => { - const endpoint = getRegionEndpoint('aws-eu', 'contentManagement') - expect(endpoint).to.be.equal('eu-api.contentstack.com', 'aws-eu alias should map to EU region') - done() - }) - - it('should return correct endpoint for azure_na alias', done => { - const endpoint = getRegionEndpoint('azure_na', 'contentManagement') - expect(endpoint).to.be.equal('azure-na-api.contentstack.com', 'azure_na alias should map to Azure NA region') - done() - }) - - it('should return correct endpoint for gcp_na alias', done => { - const endpoint = getRegionEndpoint('gcp_na', 'contentManagement') - expect(endpoint).to.be.equal('gcp-na-api.contentstack.com', 'gcp_na alias should map to GCP NA region') - done() - }) - }) - - describe('Different service endpoints', () => { - it('should return correct application endpoint for NA region', done => { - const endpoint = getRegionEndpoint('na', 'application') - expect(endpoint).to.be.equal('app.contentstack.com', 'NA application endpoint should match') - done() - }) - - it('should return correct developerHub endpoint for NA region', done => { - const endpoint = getRegionEndpoint('na', 'developerHub') - expect(endpoint).to.be.equal('developerhub-api.contentstack.com', 'NA developerHub endpoint should match') - done() - }) - - it('should return correct contentDelivery endpoint for EU region', done => { - const endpoint = getRegionEndpoint('eu', 'contentDelivery') - expect(endpoint).to.be.equal('eu-cdn.contentstack.com', 'EU contentDelivery endpoint should match') - done() - }) - - it('should return correct auth endpoint for Azure NA region', done => { - const endpoint = getRegionEndpoint('azure-na', 'auth') - expect(endpoint).to.be.equal('azure-na-auth-api.contentstack.com', 'Azure NA auth endpoint should match') - done() - }) - - it('should return correct graphqlDelivery endpoint for GCP EU region', done => { - const endpoint = getRegionEndpoint('gcp-eu', 'graphqlDelivery') - expect(endpoint).to.be.equal('gcp-eu-graphql.contentstack.com', 'GCP EU graphqlDelivery endpoint should match') - done() - }) - - it('should return correct preview endpoint for AU region', done => { - const endpoint = getRegionEndpoint('au', 'preview') - expect(endpoint).to.be.equal('au-rest-preview.contentstack.com', 'AU preview endpoint should match') - done() - }) - - it('should return correct images endpoint for NA region', done => { - const endpoint = getRegionEndpoint('na', 'images') - expect(endpoint).to.be.equal('images.contentstack.io', 'NA images endpoint should match') - done() - }) - - it('should return correct automate endpoint for Azure EU region', done => { - const endpoint = getRegionEndpoint('azure-eu', 'automate') - expect(endpoint).to.be.equal('azure-eu-automations-api.contentstack.com', 'Azure EU automate endpoint should match') - done() - }) - - it('should return correct personalize endpoint for GCP NA region', done => { - const endpoint = getRegionEndpoint('gcp-na', 'personalizeManagement') - expect(endpoint).to.be.equal('gcp-na-personalize-api.contentstack.com', 'GCP NA personalize endpoint should match') - done() - }) - }) - - describe('Default service parameter', () => { - it('should default to contentManagement service when service parameter is not provided', done => { - const endpoint = getRegionEndpoint('na') - expect(endpoint).to.be.equal('api.contentstack.io', 'Should default to contentManagement service') - done() - }) - - it('should default to contentManagement for EU region', done => { - const endpoint = getRegionEndpoint('eu') - expect(endpoint).to.be.equal('eu-api.contentstack.com', 'Should default to contentManagement service for EU') - done() - }) - }) - - describe('URL protocol stripping', () => { - it('should strip https:// protocol from endpoint', done => { - const endpoint = getRegionEndpoint('na', 'contentManagement') - expect(endpoint).to.not.contain('https://', 'Endpoint should not contain https://') - expect(endpoint).to.not.contain('http://', 'Endpoint should not contain http://') - done() - }) - - it('should strip protocol from application endpoint', done => { - const endpoint = getRegionEndpoint('eu', 'application') - expect(endpoint).to.not.contain('https://', 'Application endpoint should not contain https://') - expect(endpoint).to.not.contain('http://', 'Application endpoint should not contain http://') - done() - }) - }) - - describe('Invalid regions', () => { - it('should throw error for invalid region', done => { - try { - getRegionEndpoint('invalid-region', 'contentManagement') - done(new Error('Should have thrown an error')) - } catch (error) { - expect(error.message).to.contain('Invalid region', 'Error message should indicate invalid region') - expect(error.message).to.contain('invalid-region', 'Error message should contain the invalid region name') - done() - } - }) - - it('should throw error for empty region', done => { - try { - getRegionEndpoint('', 'contentManagement') - done(new Error('Should have thrown an error')) - } catch (error) { - expect(error.message).to.contain('Invalid region', 'Error message should indicate invalid region') - done() - } - }) - - it('should throw error for null region', done => { - try { - getRegionEndpoint(null, 'contentManagement') - done(new Error('Should have thrown an error')) - } catch (error) { - expect(error.message).to.contain('Invalid region', 'Error message should indicate invalid region') - done() - } - }) - - it('should throw error for undefined region', done => { - try { - getRegionEndpoint(undefined, 'contentManagement') - done(new Error('Should have thrown an error')) - } catch (error) { - expect(error.message).to.contain('Invalid region', 'Error message should indicate invalid region') - done() - } - }) - - it('should include available regions in error message', done => { - try { - getRegionEndpoint('invalid', 'contentManagement') - done(new Error('Should have thrown an error')) - } catch (error) { - expect(error.message).to.contain('Allowed regions are:', 'Error should list allowed regions') - expect(error.message).to.contain('na', 'Error should include NA region') - expect(error.message).to.contain('eu', 'Error should include EU region') - expect(error.message).to.contain('au', 'Error should include AU region') - done() - } - }) - }) - - describe('Case sensitivity', () => { - it('should handle lowercase region names', done => { - const endpoint = getRegionEndpoint('na', 'contentManagement') - expect(endpoint).to.be.equal('api.contentstack.io', 'Lowercase region should work') - done() - }) - - it.skip('should be case-sensitive for region names', done => { - try { - getRegionEndpoint('NA', 'contentManagement') - done(new Error('Should have thrown an error for uppercase region')) - } catch (error) { - expect(error.message).to.contain('Invalid region', 'Should throw error for uppercase region') - done() - } - }) - - it.skip('should be case-sensitive for aliases', done => { - try { - getRegionEndpoint('US', 'contentManagement') - done(new Error('Should have thrown an error for uppercase alias')) - } catch (error) { - expect(error.message).to.contain('Invalid region', 'Should throw error for uppercase alias') - done() - } - }) - - it('should accept lowercase Azure region', done => { - const endpoint = getRegionEndpoint('azure-na', 'contentManagement') - expect(endpoint).to.be.equal('azure-na-api.contentstack.com', 'Lowercase Azure region should work') - done() - }) - }) - }) }) diff --git a/test/unit/contentstack-test.js b/test/unit/contentstack-test.js index 3cb06916..067d6460 100644 --- a/test/unit/contentstack-test.js +++ b/test/unit/contentstack-test.js @@ -108,173 +108,4 @@ describe('Contentstack HTTP Client', () => { createClientRewireApi.__ResetDependency__('contentstackClient') done() }) - - describe('Region Configuration', () => { - it('should use default NA region when no region is specified', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client() - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('api.contentstack.io', 'Should default to NA region') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should set EU region endpoint when region is eu', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'eu' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('eu-api.contentstack.com', 'Should use EU region endpoint') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should set AU region endpoint when region is au', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'au' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('au-api.contentstack.com', 'Should use AU region endpoint') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should set Azure NA region endpoint when region is azure-na', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'azure-na' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('azure-na-api.contentstack.com', 'Should use Azure NA region endpoint') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should set Azure EU region endpoint when region is azure-eu', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'azure-eu' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('azure-eu-api.contentstack.com', 'Should use Azure EU region endpoint') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should set GCP NA region endpoint when region is gcp-na', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'gcp-na' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('gcp-na-api.contentstack.com', 'Should use GCP NA region endpoint') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should set GCP EU region endpoint when region is gcp-eu', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'gcp-eu' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('gcp-eu-api.contentstack.com', 'Should use GCP EU region endpoint') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should accept region aliases - us', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'us' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('api.contentstack.io', 'Should accept us alias for NA') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should accept region aliases - aws-na', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'aws-na' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('api.contentstack.io', 'Should accept aws-na alias for NA') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should accept region aliases - aws_na', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'aws_na' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('api.contentstack.io', 'Should accept aws_na alias for NA') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should accept region aliases - azure_na', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'azure_na' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('azure-na-api.contentstack.com', 'Should accept azure_na alias for Azure NA') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should throw error for invalid region', done => { - try { - client({ region: 'invalid-region' }) - done(new Error('Should have thrown an error for invalid region')) - } catch (error) { - expect(error.message).to.contain('Invalid region', 'Error message should indicate invalid region') - expect(error.message).to.contain('invalid-region', 'Error message should include the invalid region name') - done() - } - }) - - it('should handle region parameter case-sensitively', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'na' }) - expect(createHttpClientStub.args[0][0].defaultHostName).to.be.equal('api.contentstack.io', 'Lowercase region should work') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - - it('should pass region to HTTP client configuration', done => { - createClientRewireApi.__Rewire__('client', { create: sinon.stub() }) - const createHttpClientStub = sinon.stub() - createClientRewireApi.__Rewire__('httpClient', createHttpClientStub) - createClientRewireApi.__Rewire__('contentstackClient', sinon.stub().returns({})) - client({ region: 'eu' }) - expect(createHttpClientStub.args[0][0].region).to.be.equal('eu', 'Region should be passed to HTTP client') - createClientRewireApi.__ResetDependency__('httpClient') - createClientRewireApi.__ResetDependency__('contentstackClient') - done() - }) - }) }) From c888fe43bb3221a194301b8d483a4988cd19e1a4 Mon Sep 17 00:00:00 2001 From: Nadeem Patwekar Date: Fri, 5 Dec 2025 17:04:16 +0530 Subject: [PATCH 2/3] Refactor import statement for getUserAgent in contentstack.js --- lib/contentstack.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contentstack.js b/lib/contentstack.js index 33d6dbec..4349e23f 100644 --- a/lib/contentstack.js +++ b/lib/contentstack.js @@ -4,7 +4,7 @@ */ import packages from '../package.json' import clonedeep from 'lodash/cloneDeep' -import getUserAgent, { getRegionEndpoint } from './core/Util.js' +import getUserAgent from './core/Util.js' import contentstackClient from './contentstackClient.js' import httpClient from './core/contentstackHTTPClient.js' const regionHostMap = { From 0fb6b1501dbf37fe59e04a3a2a64780128f3a20b Mon Sep 17 00:00:00 2001 From: Nadeem Patwekar Date: Fri, 5 Dec 2025 17:21:49 +0530 Subject: [PATCH 3/3] Remove console log from ContentstackClient tests and ensure newline at end of Util.js file --- lib/core/Util.js | 2 +- test/unit/ContentstackClient-test.js | 1 - test/unit/Util-test.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/core/Util.js b/lib/core/Util.js index f7229e4b..23a2f449 100644 --- a/lib/core/Util.js +++ b/lib/core/Util.js @@ -235,4 +235,4 @@ export const validateAndSanitizeConfig = (config) => { ...config, url: config.url.trim() // Sanitize URL by removing whitespace } -} \ No newline at end of file +} diff --git a/test/unit/ContentstackClient-test.js b/test/unit/ContentstackClient-test.js index f09784da..2e38198b 100644 --- a/test/unit/ContentstackClient-test.js +++ b/test/unit/ContentstackClient-test.js @@ -273,7 +273,6 @@ describe('Contentstack Client', () => { it('should prioritize tfa_token over mfaSecret', done => { mock.onPost('/user-session').reply(config => { const data = JSON.parse(config.data) - console.log(data) expect(data.user).to.deep.equal({ email: 'test@example.com', password: 'password123', diff --git a/test/unit/Util-test.js b/test/unit/Util-test.js index 93411376..7d0d8dcf 100644 --- a/test/unit/Util-test.js +++ b/test/unit/Util-test.js @@ -1,4 +1,4 @@ -import getUserAgent, { __RewireAPI__ as getUserAgentRewireApi, isHost, getRegionEndpoint } from '../../lib/core/Util.js' +import getUserAgent, { __RewireAPI__ as getUserAgentRewireApi, isHost } from '../../lib/core/Util.js' import { expect } from 'chai' import { describe, it } from 'mocha' const headerRegEx = /(app|sdk|platform|integration|os) \S+(\/\d+.\d+.\d+(-[\w\d-]+)?)?;/igm