diff --git a/docs/openapi/llmo-api.yaml b/docs/openapi/llmo-api.yaml index ec4d202d8..2fe75684a 100644 --- a/docs/openapi/llmo-api.yaml +++ b/docs/openapi/llmo-api.yaml @@ -1432,26 +1432,23 @@ site-llmo-edge-optimize-config: site-llmo-edge-optimize-routing: parameters: - $ref: './parameters.yaml#/siteId' - - name: x-promise-token - in: header - required: true - description: Promise token exchanged for IMS user token to authorize the CDN API call. - schema: - type: string post: operationId: enableEdgeOptimize summary: Update edge optimize routing for a site description: | Updates edge optimize routing for the site via the internal CDN API. Only available when ENV is prod. The CDN API to call is selected by the required cdnType request body parameter. + Authenticated via standard auth (JWT/IMS) in the Authorization header; no promise token required. Flow: - 1. Validates request (x-promise-token header and cdnType required; enabled must be boolean if provided). - 2. Probes the site with User-Agent `AdobeEdgeOptimize-Test`. If 2xx, flow continues with probe URL domain. + 1. Validates request (cdnType required; enabled must be boolean if provided). + 2. Loads site and checks access; extracts the site's organization IMS org ID for the CDN API. + 3. Probes the site with User-Agent `AdobeEdgeOptimize-Test`. If 2xx, flow continues with probe URL domain. If 301, the Location header is checked: the probe URL hostname and the Location URL hostname are compared (lowercased, with leading www. stripped); subdomains are considered, so only the same host is allowed. If they match, the domain from the Location URL is used for the CDN API; otherwise the flow fails. Other non-2xx responses fail. - 3. Exchanges the promise token for IMS user token, then calls internal CDN API for the chosen cdnType to set routing for the domain. + 4. Calls internal CDN API for the chosen cdnType with a static token from env (EDGE_OPTIMIZE_CDN_API_TOKEN) + in the Authorization header and the site's organization IMS org ID in the x-ims-org-id header. tags: - llmo @@ -1495,7 +1492,7 @@ site-llmo-edge-optimize-routing: type: string example: aem-cs-fastly '400': - description: Bad request (e.g. non-prod ENV, missing/invalid x-promise-token or cdnType, invalid enabled, probe failed, non-2xx/301, or 301 Location domain mismatch) + description: Bad request (e.g. non-prod ENV, missing/invalid cdnType, invalid enabled, site org missing or missing IMS org ID, probe failed, non-2xx/301, or 301 Location domain mismatch) content: application/json: schema: @@ -1504,7 +1501,7 @@ site-llmo-edge-optimize-routing: message: type: string '401': - description: Authentication failed with upstream IMS service (missing or invalid promise token) + description: CDN API returned 401 Unauthorized content: application/json: schema: @@ -1512,7 +1509,7 @@ site-llmo-edge-optimize-routing: properties: message: type: string - example: Authentication failed with upstream IMS service + example: User is not authorized to update CDN routing '403': $ref: './responses.yaml#/403' '404': @@ -1528,7 +1525,7 @@ site-llmo-edge-optimize-routing: type: string example: Upstream call failed with status 503 '503': - description: API not available (EDGE_OPTIMIZE_ROUTING_CONFIG not set or invalid, or missing cdnRoutingUrl for cdnType) + description: API not available (EDGE_OPTIMIZE_ROUTING_CONFIG or EDGE_OPTIMIZE_CDN_API_TOKEN not set or invalid, or missing cdnRoutingUrl for cdnType) content: application/json: schema: diff --git a/src/controllers/llmo/llmo.js b/src/controllers/llmo/llmo.js index 1ce6ac623..7db857a30 100644 --- a/src/controllers/llmo/llmo.js +++ b/src/controllers/llmo/llmo.js @@ -30,7 +30,6 @@ import crypto from 'crypto'; import { Entitlement as EntitlementModel } from '@adobe/spacecat-shared-data-access'; import TokowakaClient, { calculateForwardedHost } from '@adobe/spacecat-shared-tokowaka-client'; import AccessControlUtil from '../../support/access-control-util.js'; -import { exchangePromiseToken } from '../../support/utils.js'; import { triggerBrandProfileAgent } from '../../support/brand-profile-trigger.js'; import { applyFilters, @@ -1261,11 +1260,11 @@ function LlmoController(ctx) { /** * POST /sites/{siteId}/llmo/edge-optimize-routing * Updates edge optimize routing for the site via the internal CDN API. - * - Requires x-promise-token header and request body cdnType. + * - Extracts the site's organization IMS org ID and sends it as x-ims-org-id to the CDN API. + * - Uses static token from env (EDGE_OPTIMIZE_CDN_API_TOKEN) in CDN API Authorization header. * - Probes the site with custom User-Agent (2xx continues; 301: if Location domain * normalizes to same as probe URL domain, use Location domain for CDN API; otherwise break). - * - Exchanges promise token for IMS user token, then calls internal CDN API. - * @param {object} context - Request context (context.request for headers) + * @param {object} context - Request context * @returns {Promise} */ const updateEdgeOptimizeCDNRouting = async (context) => { @@ -1273,8 +1272,9 @@ function LlmoController(ctx) { const { siteId } = context.params; const { Site } = dataAccess; const { cdnType, enabled = true } = context.data || {}; - const promiseToken = context.request?.headers?.get?.('x-promise-token'); - log.info(`Edge optimize routing update request received for site ${siteId}`); + const profile = context.attributes?.authInfo?.getProfile?.(); + const requester = profile?.email || 'unknown'; + log.info(`Edge optimize routing update request received for site ${siteId} by ${requester}`); if (env?.ENV && env.ENV !== 'prod') { return createResponse( @@ -1283,10 +1283,6 @@ function LlmoController(ctx) { ); } - if (!hasText(promiseToken)) { - return badRequest('x-promise-token header is required and must be a non-empty string'); - } - if (!hasText(cdnType)) { return badRequest('cdnType is required and must be a non-empty string'); } @@ -1316,6 +1312,15 @@ function LlmoController(ctx) { ); } + const cdnApiToken = env?.EDGE_OPTIMIZE_CDN_API_TOKEN; + if (!hasText(cdnApiToken)) { + log.error('EDGE_OPTIMIZE_CDN_API_TOKEN is not set'); + return createResponse( + { message: 'API is missing mandatory environment variable' }, + 503, + ); + } + const strategy = EDGE_OPTIMIZE_CDN_STRATEGIES[cdnTypeNormalized]; if (enabled !== undefined && typeof enabled !== 'boolean') { @@ -1331,6 +1336,17 @@ function LlmoController(ctx) { return forbidden('User does not have access to this site'); } + const org = await site.getOrganization(); + if (!isObject(org)) { + log.error(`Site ${siteId} has no organization`); + return badRequest('Site organization is missing'); + } + const imsOrgId = org.getImsOrgId?.(); + if (!hasText(imsOrgId)) { + log.error(`Site ${siteId} organization has no IMS org ID`); + return badRequest('Site organization has no IMS org ID'); + } + const overrideBaseURL = site.getConfig()?.getFetchConfig?.()?.overrideBaseURL; const effectiveBaseUrl = isValidUrl(overrideBaseURL) ? overrideBaseURL : site.getBaseURL(); log.info(`Effective base URL for site ${siteId}: ${effectiveBaseUrl}`); @@ -1377,16 +1393,6 @@ function LlmoController(ctx) { return badRequest(msg); } - let imsUserToken; - try { - log.debug(`Getting IMS user token for site ${siteId}`); - imsUserToken = await exchangePromiseToken(context, promiseToken); - log.info('IMS user token obtained successfully'); - } catch (tokenError) { - log.warn(`Fetching IMS user token for site ${siteId} failed: ${tokenError.status} ${tokenError.message}`); - return createResponse({ message: 'Authentication failed with upstream IMS service' }, 401); - } - try { const cdnUrl = strategy.buildUrl(cdnConfig, domain); const cdnBody = strategy.buildBody(enabled); @@ -1395,7 +1401,8 @@ function LlmoController(ctx) { method: strategy.method, headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${imsUserToken}`, + Authorization: `Bearer ${cdnApiToken}`, + 'x-ims-org-id': imsOrgId, }, body: JSON.stringify(cdnBody), signal: AbortSignal.timeout(5000), diff --git a/test/controllers/llmo/llmo.test.js b/test/controllers/llmo/llmo.test.js index 330790335..7e255321a 100644 --- a/test/controllers/llmo/llmo.test.js +++ b/test/controllers/llmo/llmo.test.js @@ -4562,25 +4562,16 @@ describe('LlmoController', () => { describe('enableEdgeOptimize', () => { let enableEdgeContext; const validSiteId = '12345678-1234-4123-8123-123456789012'; - const FAKE_PROMISE_TOKEN = 'fake-promise-token'; + const STATIC_CDN_TOKEN = 'static-cdn-token'; const routingConfigFastly = JSON.stringify({ [LOG_SOURCES.AEM_CS_FASTLY]: { cdnRoutingUrl: 'https://internal-cdn.example.com' }, }); - function mockRequestWithPromiseToken(token = FAKE_PROMISE_TOKEN) { - return { - headers: { - get: (name) => (name === 'x-promise-token' ? token : null), - }, - }; - } - beforeEach(() => { enableEdgeContext = { ...mockContext, params: { siteId: validSiteId }, data: { cdnType: LOG_SOURCES.AEM_CS_FASTLY }, - request: mockRequestWithPromiseToken(), }; mockDataAccess.Site.findById.resetBehavior(); mockDataAccess.Site.findById.resolves(mockSite); @@ -4597,63 +4588,45 @@ describe('LlmoController', () => { }); it('returns 503 when EDGE_OPTIMIZE_ROUTING_CONFIG has no entry for cdnType', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: '{}' }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: '{}', EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(503); expect((await result.json()).message).to.include('API is missing mandatory environment variable'); }); - it('returns 400 when x-promise-token header is missing', async () => { - enableEdgeContext.data = { cdnType: LOG_SOURCES.AEM_CS_FASTLY }; - enableEdgeContext.request = mockRequestWithPromiseToken(''); - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); - expect(result.status).to.equal(400); - expect((await result.json()).message).to.include('x-promise-token header is required'); - }); - - it('returns 400 when x-promise-token header is undefined (null/undefined branch)', async () => { - enableEdgeContext.data = { cdnType: LOG_SOURCES.AEM_CS_FASTLY }; - enableEdgeContext.request = { headers: { get: () => undefined } }; - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); - expect(result.status).to.equal(400); - expect((await result.json()).message).to.include('x-promise-token header is required'); - }); - it('returns 400 when cdnType is missing', async () => { enableEdgeContext.data = {}; - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(400); expect((await result.json()).message).to.include('cdnType is required'); }); - it('returns 400 when context.data is undefined and no promise token in header', async () => { - const ctxNoData = { ...enableEdgeContext, data: undefined, request: mockRequestWithPromiseToken('') }; - ctxNoData.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + it('returns 400 when context.data is undefined and cdnType is missing', async () => { + const ctxNoData = { ...enableEdgeContext, data: undefined }; + ctxNoData.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; const result = await controller.updateEdgeOptimizeCDNRouting(ctxNoData); expect(result.status).to.equal(400); - expect((await result.json()).message).to.include('x-promise-token header is required'); + expect((await result.json()).message).to.include('cdnType is required'); }); it('returns 400 when cdnType is not supported', async () => { enableEdgeContext.data = { cdnType: 'unknown' }; - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(400); expect((await result.json()).message).to.include('cdnType must be one of'); }); it('returns 400 when ENV is set and not prod', async () => { - const ctxNonProd = { ...enableEdgeContext, env: { ENV: 'stage', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly } }; + const ctxNonProd = { ...enableEdgeContext, env: { ENV: 'stage', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN } }; const result = await controller.updateEdgeOptimizeCDNRouting(ctxNonProd); expect(result.status).to.equal(400); expect((await result.json()).message).to.equal('API is not available in stage environment'); }); it('returns 400 when enabled is not a boolean', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; enableEdgeContext.data = { cdnType: LOG_SOURCES.AEM_CS_FASTLY, enabled: 'true' }; const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(400); @@ -4661,7 +4634,7 @@ describe('LlmoController', () => { }); it('returns 404 when site not found', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockDataAccess.Site.findById.resolves(null); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(404); @@ -4669,34 +4642,30 @@ describe('LlmoController', () => { }); it('returns 403 when user does not have access to site', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; const result = await controllerWithAccessDenied(mockContext) .updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(403); expect((await result.json()).message).to.equal('User does not have access to this site'); }); - it('returns 401 when exchangePromiseToken fails', async () => { + it('returns 503 when EDGE_OPTIMIZE_CDN_API_TOKEN is not set', async () => { enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - tracingFetchStub.onFirstCall().resolves({ ok: true }); - exchangePromiseTokenStub.rejects(new Error('Missing promise token')); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); - expect(result.status).to.equal(401); - expect((await result.json()).message).to.equal('Authentication failed with upstream IMS service'); + expect(result.status).to.equal(503); + expect((await result.json()).message).to.include('API is missing mandatory environment variable'); }); - it('returns 401 when exchangePromiseToken throws without status and message', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - tracingFetchStub.onFirstCall().resolves({ ok: true }); - exchangePromiseTokenStub.rejects(Object.assign(new Error(), { message: '', status: undefined })); + it('returns 400 when site organization has no IMS org ID', async () => { + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; + mockSite.getOrganization = sinon.stub().resolves({ getId: () => 'org-1', getImsOrgId: () => '' }); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); - expect(result.status).to.equal(401); - expect((await result.json()).message).to.equal('Authentication failed with upstream IMS service'); + expect(result.status).to.equal(400); + expect((await result.json()).message).to.include('Site organization has no IMS org ID'); }); it('returns 400 when site probe returns non-200 and non-301', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: false, status: 404 }); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(400); @@ -4704,7 +4673,7 @@ describe('LlmoController', () => { }); it('returns 400 when probe returns 301 without Location header', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: false, status: 301, @@ -4716,7 +4685,7 @@ describe('LlmoController', () => { }); it('returns 400 when probe returns 301 with invalid Location header', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockSite.getBaseURL.returns('https://example.com'); tracingFetchStub.onFirstCall().resolves({ ok: false, @@ -4729,7 +4698,7 @@ describe('LlmoController', () => { }); it('returns 400 when probe returns 301 but probe URL is invalid for domain check', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockSite.getBaseURL.returns('https://['); tracingFetchStub.onFirstCall().resolves({ ok: false, @@ -4742,7 +4711,7 @@ describe('LlmoController', () => { }); it('returns 400 when probe returns 301 and Location domain does not match probe domain', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockSite.getBaseURL.returns('https://example.com'); tracingFetchStub.onFirstCall().resolves({ ok: false, @@ -4758,14 +4727,13 @@ describe('LlmoController', () => { }); it('301 with Location without scheme exercises hostname-without-www branch', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockSite.getBaseURL.returns('https://example.com'); tracingFetchStub.onFirstCall().resolves({ ok: false, status: 301, headers: { get: (n) => (n === 'location' ? 'example.com' : null) }, }); - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); tracingFetchStub.onSecondCall().resolves({ ok: true }); try { const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); @@ -4777,14 +4745,13 @@ describe('LlmoController', () => { }); it('returns 200 with domain from Location when probe returns 301 and root domains match (www vs non-www)', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockSite.getBaseURL.returns('https://example.com'); tracingFetchStub.onFirstCall().resolves({ ok: false, status: 301, headers: { get: (n) => (n === 'location' ? 'https://www.example.com/' : null) }, }); - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); tracingFetchStub.onSecondCall().resolves({ ok: true }); try { const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); @@ -4800,14 +4767,13 @@ describe('LlmoController', () => { }); it('returns 200 with domain from Location when probe returns 301 with adobe.com -> www.adobe.com', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockSite.getBaseURL.returns('https://adobe.com'); tracingFetchStub.onFirstCall().resolves({ ok: false, status: 301, headers: { get: (n) => (n === 'location' ? 'https://www.adobe.com/' : null) }, }); - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); tracingFetchStub.onSecondCall().resolves({ ok: true }); try { const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); @@ -4819,8 +4785,7 @@ describe('LlmoController', () => { }); it('returns 400 when site probe fetch throws', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().rejects(new Error('Network error')); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); expect(result.status).to.equal(400); @@ -4828,8 +4793,7 @@ describe('LlmoController', () => { }); it('returns 500 when CDN API returns 503', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().resolves({ ok: false, @@ -4843,8 +4807,7 @@ describe('LlmoController', () => { }); it('returns Forbidden when CDN API returns 403', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().resolves({ ok: false, @@ -4858,8 +4821,7 @@ describe('LlmoController', () => { }); it('returns Unauthorized when CDN API returns 401', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().resolves({ ok: false, @@ -4873,14 +4835,13 @@ describe('LlmoController', () => { }); it('returns 200 with enabled, domain and cdnType when probe and CDN succeed', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; enableEdgeContext.data = { cdnType: LOG_SOURCES.AEM_CS_FASTLY, enabled: true, }; mockSite.getBaseURL.returns('example.com'); mockConfig.getFetchConfig.returns({}); - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().resolves({ ok: true }); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); @@ -4891,6 +4852,8 @@ describe('LlmoController', () => { cdnType: LOG_SOURCES.AEM_CS_FASTLY, }); expect(tracingFetchStub.firstCall.args[0]).to.equal('https://example.com'); + expect(tracingFetchStub.secondCall.args[1].headers.Authorization).to.equal(`Bearer ${STATIC_CDN_TOKEN}`); + expect(tracingFetchStub.secondCall.args[1].headers['x-ims-org-id']).to.equal(TEST_IMS_ORG_ID); }); it('defaults enabled to true when context.data has only cdnType', async () => { @@ -4901,8 +4864,7 @@ describe('LlmoController', () => { enabled: true, }, }; - ctxOnlyCdnType.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + ctxOnlyCdnType.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().resolves({ ok: true }); const result = await controller.updateEdgeOptimizeCDNRouting(ctxOnlyCdnType); @@ -4916,9 +4878,8 @@ describe('LlmoController', () => { }); it('returns 200 using overrideBaseURL from site config when valid', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; mockConfig.getFetchConfig.returns({ overrideBaseURL: 'https://override.example.com' }); - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().resolves({ ok: true }); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); @@ -4927,12 +4888,11 @@ describe('LlmoController', () => { }); it('returns 200 with enabled false when data.enabled is false', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; enableEdgeContext.data = { cdnType: LOG_SOURCES.AEM_CS_FASTLY, enabled: false, }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().resolves({ ok: true }); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext); @@ -4946,8 +4906,7 @@ describe('LlmoController', () => { }); it('returns error.status when thrown error has status property', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: true }); const err = new Error('CDN request failed'); err.status = 418; @@ -4958,8 +4917,7 @@ describe('LlmoController', () => { }); it('returns 500 when unexpected error has no status property', async () => { - enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly }; - exchangePromiseTokenStub.resolves({ access_token: 'fake-token' }); + enableEdgeContext.env = { ENV: 'prod', EDGE_OPTIMIZE_ROUTING_CONFIG: routingConfigFastly, EDGE_OPTIMIZE_CDN_API_TOKEN: STATIC_CDN_TOKEN }; tracingFetchStub.onFirstCall().resolves({ ok: true }); tracingFetchStub.onSecondCall().rejects(new Error('Network error')); const result = await controller.updateEdgeOptimizeCDNRouting(enableEdgeContext);