diff --git a/e2e/.env.example b/e2e/.env.example index 6485c8e..48cefa3 100644 --- a/e2e/.env.example +++ b/e2e/.env.example @@ -2,5 +2,4 @@ TEST_NAMESPACE_1='11111-test' TEST_AUTH_1='testauth' TEST_AUTH_2='testauth2' TEST_NAMESPACE_2='12345-test-stage' -# do not use the scheme for endpoints -ADOBE_STATE_STORE_ENDPOINT_PROD='127.0.0.1:8080' +AIO_STATE_ENDPOINT='http://127.0.0.1:8080' diff --git a/e2e/e2e.md b/e2e/e2e.md index 32bfc76..8187f58 100644 --- a/e2e/e2e.md +++ b/e2e/e2e.md @@ -14,8 +14,7 @@ Copy the `.env.example` to your own `.env` in this folder. For local testing, add the environment variable: ```sh -# do not use the scheme for endpoints -ADOBE_STATE_STORE_ENDPOINT_PROD=127.0.0.1:8080 +AIO_STATE_ENDPOINT='http://127.0.0.1:8080' ``` Substitute the host with `host.docker.internal` if you are testing with the Dockerized version of the e2e tests. @@ -24,22 +23,8 @@ Substitute the host with `host.docker.internal` if you are testing with the Dock You might have to connect to internal servers for your e2e testing. -For `prod`, use these two environment variables: - -```sh -# do not use the scheme for endpoints -ADOBE_STATE_STORE_ENDPOINT_PROD=my-prod-server-here.com -# can be omitted as well, since it defaults to prod -AIO_CLI_ENV=prod -``` - -For `stage`, use these two environment variables: - ```sh -# do not use the scheme for endpoints -ADOBE_STATE_STORE_ENDPOINT_STAGE=my-stage-server-here.com -# set the env -AIO_CLI_ENV=stage +AIO_STATE_ENDPOINT=https://my-server-here.com ``` ## Local Run diff --git a/lib/AdobeState.js b/lib/AdobeState.js index b8eb8dc..f85ebd1 100644 --- a/lib/AdobeState.js +++ b/lib/AdobeState.js @@ -16,7 +16,7 @@ const logger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-state', { const { HttpExponentialBackoff } = require('@adobe/aio-lib-core-networking') const url = require('node:url') const { getCliEnv } = require('@adobe/aio-lib-env') -const { ADOBE_STATE_STORE_ENDPOINT, REGEX_PATTERN_STORE_KEY, API_VERSION, ADOBE_STATE_STORE_REGIONS, HEADER_KEY_EXPIRES } = require('./constants') +const { REGEX_PATTERN_STORE_KEY, API_VERSION, HEADER_KEY_EXPIRES, CUSTOM_ENDPOINT, ENDPOINTS, ALLOWED_REGIONS } = require('./constants') const Ajv = require('ajv') /* *********************************** typedefs *********************************** */ @@ -135,25 +135,11 @@ class AdobeState { /** @private */ this.region = region /** @private */ - this.endpoint = this.getRegionalEndpoint(ADOBE_STATE_STORE_ENDPOINT[getCliEnv()], region) + this.endpoint = this.getRegionalEndpoint(ENDPOINTS[getCliEnv()], region) /** @private */ this.fetchRetry = new HttpExponentialBackoff() } - /** - * Tests if an endpoint is a local endpoint. - * - * @param {string} endpoint the endpoint to test - * @returns {boolean} true if it is a local endpoint - */ - isLocalEndpoint (endpoint) { - return ( - endpoint.startsWith('localhost') || - endpoint.startsWith('127.0.0.1') || - endpoint.startsWith('host.docker.internal') - ) - } - /** * Gets the regional endpoint for an endpoint. * @@ -162,13 +148,11 @@ class AdobeState { * @returns {string} the endpoint, with the correct region */ getRegionalEndpoint (endpoint, region) { - if (this.isLocalEndpoint(endpoint) || region === ADOBE_STATE_STORE_REGIONS[0]) { - return endpoint + if (CUSTOM_ENDPOINT) { + return CUSTOM_ENDPOINT } - const pattern = /-amer/gi - const replacement = `-${region}` - return endpoint.replaceAll(pattern, replacement) + return endpoint.replaceAll(//gi, region) } /** @@ -180,14 +164,12 @@ class AdobeState { * @returns {string} the constructed request url */ createRequestUrl (key, queryObject = {}) { - const isLocal = this.isLocalEndpoint(this.endpoint) - const protocol = isLocal ? 'http' : 'https' let urlString if (key) { - urlString = `${protocol}://${this.endpoint}/${API_VERSION}/containers/${this.namespace}/data/${key}` + urlString = `${this.endpoint}/${API_VERSION}/containers/${this.namespace}/data/${key}` } else { - urlString = `${protocol}://${this.endpoint}/${API_VERSION}/containers/${this.namespace}` + urlString = `${this.endpoint}/${API_VERSION}/containers/${this.namespace}` } logger.debug('requestUrl string', urlString) @@ -230,7 +212,7 @@ class AdobeState { logger.debug(`init AdobeState with ${JSON.stringify(cloned, null, 2)}`) if (!credentials.region) { - credentials.region = ADOBE_STATE_STORE_REGIONS.at(0) // first item is the default + credentials.region = ALLOWED_REGIONS.at(0) // first item is the default } const schema = { @@ -238,7 +220,7 @@ class AdobeState { properties: { region: { type: 'string', - enum: ADOBE_STATE_STORE_REGIONS + enum: ALLOWED_REGIONS }, apikey: { type: 'string' }, namespace: { type: 'string' } diff --git a/lib/constants.js b/lib/constants.js index 1578ede..f1e0205 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -13,30 +13,31 @@ governing permissions and limitations under the License. const { PROD_ENV, STAGE_ENV } = require('@adobe/aio-lib-env') const { isInternalToAdobeRuntime } = require('./utils') -// gets these values if the keys are set in the environment, if not it will use the defaults set -// omit the protocol (https) -// the endpoints must have the region encoded as '-region', in this case it is the default region 'amer' -// (see ADOBE_STATE_STORE_REGIONS first element default) +// Default endpoints with protocol +// the endpoints must have the region encoded as '-' +const ENDPOINT_PROD = 'https://storage-state-.app-builder.adp.adobe.io' +const ENDPOINT_PROD_INTERNAL = 'https://storage-state-.app-builder.int.adp.adobe.io' +/// we always use the stage public endpoint, as Runtime Prod doesn't have access to State Stage internal +/// see https://jira.corp.adobe.com/browse/ACNA-2699 +const ENDPOINT_STAGE = 'https://storage-state-.stg.app-builder.adp.adobe.io' +const ENDPOINT_STAGE_INTERNAL = 'https://storage-state-.stg.app-builder.adp.adobe.io' + +const ALLOWED_REGIONS = [ // first region is the default region + 'amer', + 'apac', + 'emea' +] + +// can be overwritten by env const { - ADOBE_STATE_STORE_ENDPOINT_PROD = 'storage-state-amer.app-builder.adp.adobe.io', - ADOBE_STATE_STORE_ENDPOINT_PROD_INTERNAL = 'storage-state-amer.app-builder.int.adp.adobe.io', - - // we always use the stage public endpoint, as Runtime Prod doesn't have access to State Stage internal - // see https://jira.corp.adobe.com/browse/ACNA-2699 - ADOBE_STATE_STORE_ENDPOINT_STAGE = 'storage-state-amer.stg.app-builder.adp.adobe.io', - ADOBE_STATE_STORE_ENDPOINT_STAGE_INTERNAL = 'storage-state-amer.stg.app-builder.adp.adobe.io', - - API_VERSION = 'v1beta1', - ADOBE_STATE_STORE_REGIONS = [ // first region is the default region - 'amer', - 'apac', - 'emea' - ] + AIO_STATE_API_VERSION: API_VERSION = 'v1beta1', + // needs protocol + AIO_STATE_ENDPOINT: CUSTOM_ENDPOINT = null // make sure users can point to any instance (e.g. for testing) } = process.env -const ADOBE_STATE_STORE_ENDPOINT = { - [PROD_ENV]: isInternalToAdobeRuntime() ? ADOBE_STATE_STORE_ENDPOINT_PROD_INTERNAL : ADOBE_STATE_STORE_ENDPOINT_PROD, - [STAGE_ENV]: isInternalToAdobeRuntime() ? ADOBE_STATE_STORE_ENDPOINT_STAGE_INTERNAL : ADOBE_STATE_STORE_ENDPOINT_STAGE +const ENDPOINTS = { + [PROD_ENV]: isInternalToAdobeRuntime() ? ENDPOINT_PROD_INTERNAL : ENDPOINT_PROD, + [STAGE_ENV]: isInternalToAdobeRuntime() ? ENDPOINT_STAGE_INTERNAL : ENDPOINT_STAGE } const MAX_KEY_SIZE = 1024 * 1 // 1KB @@ -48,16 +49,18 @@ const REGEX_PATTERN_STORE_NAMESPACE = '^(development-)?([0-9]{3,10})-([a-z0-9]{1 const REGEX_PATTERN_STORE_KEY = `^[a-zA-Z0-9-_.]{1,${MAX_KEY_SIZE}}$` module.exports = { - ADOBE_STATE_STORE_REGIONS, - ADOBE_STATE_STORE_ENDPOINT_PROD, - ADOBE_STATE_STORE_ENDPOINT_STAGE, - ADOBE_STATE_STORE_ENDPOINT_PROD_INTERNAL, - ADOBE_STATE_STORE_ENDPOINT_STAGE_INTERNAL, + ALLOWED_REGIONS, API_VERSION, + ENDPOINTS, + CUSTOM_ENDPOINT, MAX_KEY_SIZE, MAX_TTL_SECONDS, REGEX_PATTERN_STORE_NAMESPACE, REGEX_PATTERN_STORE_KEY, - ADOBE_STATE_STORE_ENDPOINT, - HEADER_KEY_EXPIRES + HEADER_KEY_EXPIRES, + // for testing only + ENDPOINT_PROD, + ENDPOINT_PROD_INTERNAL, + ENDPOINT_STAGE, + ENDPOINT_STAGE_INTERNAL } diff --git a/test/AdobeState.test.js b/test/AdobeState.test.js index 4fe285a..5d055cb 100644 --- a/test/AdobeState.test.js +++ b/test/AdobeState.test.js @@ -11,12 +11,12 @@ governing permissions and limitations under the License. */ // @ts-nocheck -const { getCliEnv, DEFAULT_ENV, PROD_ENV, STAGE_ENV } = require('@adobe/aio-lib-env') +const { DEFAULT_ENV, PROD_ENV, STAGE_ENV } = require('@adobe/aio-lib-env') const { HttpExponentialBackoff } = require('@adobe/aio-lib-core-networking') const { AdobeState } = require('../lib/AdobeState') const querystring = require('node:querystring') const { Buffer } = require('node:buffer') -const { API_VERSION, ADOBE_STATE_STORE_REGIONS, HEADER_KEY_EXPIRES } = require('../lib/constants') +const { ALLOWED_REGIONS, HEADER_KEY_EXPIRES, API_VERSION } = require('../lib/constants') // constants ////////////////////////////////////////////////////////// @@ -32,13 +32,6 @@ const fakeCredentials = { namespace: 'some-namespace' } -const myConstants = { - ADOBE_STATE_STORE_ENDPOINT: { - prod: 'prod-server-amer', - stage: 'stage-server-amer' - } -} - // helpers ////////////////////////////////////////////////////////// const wrapInFetchResponse = (body, options = {}) => { @@ -69,26 +62,23 @@ const wrapInFetchError = (status, body) => { // mocks ////////////////////////////////////////////////////////// -jest.mock('@adobe/aio-lib-core-networking') +const mockCLIEnv = jest.fn() -jest.mock('../lib/constants', () => { - return { - ...jest.requireActual('../lib/constants'), - ...myConstants - } -}) +jest.mock('@adobe/aio-lib-core-networking') jest.mock('@adobe/aio-lib-env', () => { return { ...jest.requireActual('@adobe/aio-lib-env'), - getCliEnv: jest.fn() + getCliEnv: () => mockCLIEnv() } }) // jest globals ////////////////////////////////////////////////////////// beforeEach(() => { - getCliEnv.mockReturnValue(DEFAULT_ENV) + delete process.env.AIO_STATE_ENDPOINT + delete process.env.AIO_STATE_API_VERSION + mockCLIEnv.mockReturnValue(DEFAULT_ENV) mockExponentialBackoff.mockReset() }) @@ -366,7 +356,7 @@ describe('any', () => { }) describe('private methods', () => { - const DEFAULT_REGION = ADOBE_STATE_STORE_REGIONS.at(0) + const DEFAULT_REGION = ALLOWED_REGIONS.at(0) test('getAuthorizationHeaders (private)', async () => { const expectedHeaders = { @@ -380,49 +370,37 @@ describe('private methods', () => { describe('createRequestUrl (private)', () => { test('no params', async () => { const env = PROD_ENV - getCliEnv.mockReturnValue(env) - - // need to instantiate a new store, when env changes - const store = await AdobeState.init(fakeCredentials) - - const url = store.createRequestUrl() - expect(url).toEqual(`https://prod-server-${DEFAULT_REGION}/${API_VERSION}/containers/${fakeCredentials.namespace}`) - }) - - test('no params, localhost endpoint', async () => { - const env = PROD_ENV - getCliEnv.mockReturnValue(env) + mockCLIEnv.mockReturnValue(env) // need to instantiate a new store, when env changes const store = await AdobeState.init(fakeCredentials) - store.endpoint = 'localhost' const url = store.createRequestUrl() - expect(url).toEqual(`http://${store.endpoint}/${API_VERSION}/containers/${fakeCredentials.namespace}`) + expect(url).toEqual(`https://storage-state-${DEFAULT_REGION}.app-builder.adp.adobe.io/${API_VERSION}/containers/${fakeCredentials.namespace}`) }) - test('no params, 127.0.0.1 endpoint', async () => { + test('no params, custom endpoint', async () => { const env = PROD_ENV - getCliEnv.mockReturnValue(env) + mockCLIEnv.mockReturnValue(env) // need to instantiate a new store, when env changes const store = await AdobeState.init(fakeCredentials) - store.endpoint = '127.0.0.1' + store.endpoint = 'http://localhost' const url = store.createRequestUrl() - expect(url).toEqual(`http://${store.endpoint}/${API_VERSION}/containers/${fakeCredentials.namespace}`) + expect(url).toEqual(`${store.endpoint}/${API_VERSION}/containers/${fakeCredentials.namespace}`) }) test('key set, no query params', async () => { const key = 'some-key' const env = STAGE_ENV - getCliEnv.mockReturnValue(env) + mockCLIEnv.mockReturnValue(env) // need to instantiate a new store, when env changes const store = await AdobeState.init(fakeCredentials) const url = store.createRequestUrl(key) - expect(url).toEqual(`https://stage-server-${DEFAULT_REGION}/${API_VERSION}/containers/${fakeCredentials.namespace}/data/${key}`) + expect(url).toEqual(`https://storage-state-${DEFAULT_REGION}.stg.app-builder.adp.adobe.io/${API_VERSION}/containers/${fakeCredentials.namespace}/data/${key}`) }) test('key set, some query params', async () => { @@ -432,34 +410,100 @@ describe('private methods', () => { } const key = 'some-key' const env = STAGE_ENV - getCliEnv.mockReturnValue(env) + mockCLIEnv.mockReturnValue(env) // need to instantiate a new store, when env changes const store = await AdobeState.init(fakeCredentials) const url = store.createRequestUrl(key, queryParams) - expect(url).toEqual(`https://stage-server-${DEFAULT_REGION}/${API_VERSION}/containers/${fakeCredentials.namespace}/data/${key}?${querystring.stringify(queryParams)}`) + expect(url).toEqual(`https://storage-state-${DEFAULT_REGION}.stg.app-builder.adp.adobe.io/${API_VERSION}/containers/${fakeCredentials.namespace}/data/${key}?${querystring.stringify(queryParams)}`) }) test('no params, region set', async () => { const region = 'apac' const env = PROD_ENV - getCliEnv.mockReturnValue(env) + mockCLIEnv.mockReturnValue(env) // need to instantiate a new store, when env changes const store = await AdobeState.init({ ...fakeCredentials, region }) const url = store.createRequestUrl() - expect(url).toEqual(`https://prod-server-${region}/${API_VERSION}/containers/${fakeCredentials.namespace}`) + expect(url).toEqual(`https://storage-state-${region}.app-builder.adp.adobe.io/${API_VERSION}/containers/${fakeCredentials.namespace}`) }) test('no params, region invalid', async () => { const region = 'some-invalid-region' const env = PROD_ENV - getCliEnv.mockReturnValue(env) + mockCLIEnv.mockReturnValue(env) await expect(AdobeState.init({ ...fakeCredentials, region })).rejects .toThrow('[AdobeStateLib:ERROR_BAD_ARGUMENT] /region must be equal to one of the allowed values: amer, apac, emea') }) }) + + test('custom AIO_STATE_API_VERSION, default env and region', async () => { + jest.resetModules() + process.env.AIO_STATE_API_VERSION = 'v5' + const env = PROD_ENV + mockCLIEnv.mockReturnValue(env) + + // need to instantiate a new store, when env changes + const customAdobeState = require('../lib/AdobeState').AdobeState + const store = await customAdobeState.init({ ...fakeCredentials }) + const url = store.createRequestUrl() + expect(url).toEqual(`https://storage-state-amer.app-builder.adp.adobe.io/v5/containers/${fakeCredentials.namespace}`) + }) + + test('custom AIO_STATE_API_VERSION, stage, emea', async () => { + jest.resetModules() + process.env.AIO_STATE_API_VERSION = 'v5' + const region = 'emea' + const env = STAGE_ENV + mockCLIEnv.mockReturnValue(env) + + // need to instantiate a new store, when env changes + const customAdobeState = require('../lib/AdobeState').AdobeState + const store = await customAdobeState.init({ ...fakeCredentials, region }) + const url = store.createRequestUrl() + expect(url).toEqual(`https://storage-state-${region}.stg.app-builder.adp.adobe.io/v5/containers/${fakeCredentials.namespace}`) + }) + + test('custom AIO_STATE_ENDPOINT', async () => { + jest.resetModules() + process.env.AIO_STATE_ENDPOINT = 'https://custom.abc.com' + + // need to instantiate a new store, when env changes + const customAdobeState = require('../lib/AdobeState').AdobeState + const store = await customAdobeState.init({ ...fakeCredentials }) + const url = store.createRequestUrl() + expect(url).toEqual(`https://custom.abc.com/${API_VERSION}/containers/${fakeCredentials.namespace}`) + }) + + test('custom AIO_STATE_ENDPOINT, env and region should have no effect', async () => { + jest.resetModules() + process.env.AIO_STATE_ENDPOINT = 'https://custom.abc.com' + const env = STAGE_ENV + const region = 'apac' + mockCLIEnv.mockReturnValue(env) + + // need to instantiate a new store, when env changes + const customAdobeState = require('../lib/AdobeState').AdobeState + const store = await customAdobeState.init({ ...fakeCredentials, region }) + const url = store.createRequestUrl() + expect(url).toEqual(`https://custom.abc.com/${API_VERSION}/containers/${fakeCredentials.namespace}`) + }) + + test('custom AIO_STATE_ENDPOINT, custom AIO_STATE_API_VERSION', async () => { + jest.resetModules() + process.env.AIO_STATE_ENDPOINT = 'https://custom.abc.com' + process.env.AIO_STATE_API_VERSION = 'v5' + const env = PROD_ENV + mockCLIEnv.mockReturnValue(env) + + // need to instantiate a new store, when env changes + const customAdobeState = require('../lib/AdobeState').AdobeState + const store = await customAdobeState.init({ ...fakeCredentials }) + const url = store.createRequestUrl() + expect(url).toEqual(`https://custom.abc.com/v5/containers/${fakeCredentials.namespace}`) + }) }) diff --git a/test/utils.test.js b/test/utils.test.js index 452307d..1cd1b66 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -79,8 +79,8 @@ describe('isInternalToAdobeRuntime', () => { expect(isInternalToAdobeRuntime()).toBeTruthy() jest.isolateModules(() => { const constants = require('../lib/constants') - expect(constants.ADOBE_STATE_STORE_ENDPOINT.prod).toEqual(constants.ADOBE_STATE_STORE_ENDPOINT_PROD_INTERNAL) - expect(constants.ADOBE_STATE_STORE_ENDPOINT.stage).toEqual(constants.ADOBE_STATE_STORE_ENDPOINT_STAGE_INTERNAL) + expect(constants.ENDPOINTS.prod).toEqual(constants.ENDPOINT_PROD_INTERNAL) + expect(constants.ENDPOINTS.stage).toEqual(constants.ENDPOINT_STAGE_INTERNAL) }) }) @@ -93,8 +93,8 @@ describe('isInternalToAdobeRuntime', () => { expect(isInternalToAdobeRuntime()).toBeFalsy() jest.isolateModules(() => { const constants = require('../lib/constants') - expect(constants.ADOBE_STATE_STORE_ENDPOINT.prod).toEqual(constants.ADOBE_STATE_STORE_ENDPOINT_PROD) - expect(constants.ADOBE_STATE_STORE_ENDPOINT.stage).toEqual(constants.ADOBE_STATE_STORE_ENDPOINT_STAGE) + expect(constants.ENDPOINTS.prod).toEqual(constants.ENDPOINT_PROD) + expect(constants.ENDPOINTS.stage).toEqual(constants.ENDPOINT_STAGE) }) }) })