From 24e0ca27523f8ac409f82b75ede1bef7a8b4b3c7 Mon Sep 17 00:00:00 2001 From: Tej Kotthakota Date: Thu, 4 Dec 2025 17:05:19 -0600 Subject: [PATCH 1/5] Refactor to provide clear message when bot protection occures --- .../opportunity-status-processor/handler.js | 84 ++++++++++++------- test/index.test.js | 24 ++++++ .../opportunity-status-processor.test.js | 15 ++-- 3 files changed, 85 insertions(+), 38 deletions(-) diff --git a/src/tasks/opportunity-status-processor/handler.js b/src/tasks/opportunity-status-processor/handler.js index 179a80c..25bcc50 100644 --- a/src/tasks/opportunity-status-processor/handler.js +++ b/src/tasks/opportunity-status-processor/handler.js @@ -470,46 +470,68 @@ export async function runOpportunityStatusProcessor(message, context) { const needsGSC = requiredDependencies.has('GSC'); // Only check data sources that are needed + // Resolve canonical URL once (used by RUM, GSC, and scraping) + let resolvedUrl = null; + let domain = null; + if (siteUrl && (needsRUM || needsGSC || needsScraping)) { try { - const resolvedUrl = await resolveCanonicalUrl(siteUrl); + resolvedUrl = await resolveCanonicalUrl(siteUrl); log.info(`Resolved URL: ${resolvedUrl}`); - const domain = new URL(resolvedUrl).hostname; - if (needsRUM) { - rumAvailable = await isRUMAvailable(domain, context); + if (!resolvedUrl) { + log.warn(`Could not resolve canonical URL for: ${siteUrl}. Site may be unreachable. Skipping data source checks.`); + if (slackContext) { + await say(env, log, slackContext, `:warning: Could not resolve canonical URL for \`${siteUrl}\`. Site may be unreachable. Skipping RUM, GSC, and scraping checks.`); + } + } else { + domain = new URL(resolvedUrl).hostname; + log.info(`Extracted domain: ${domain}`); } - - if (needsGSC) { - gscConfigured = await isGSCConfigured(resolvedUrl, context); + /* c8 ignore start */ + // URL parsing error is very rare (resolveCanonicalUrl returns null on failures) + } catch (error) { + log.error(`Failed to resolve URL or extract domain for ${siteUrl}:`, error); + if (slackContext) { + await say(env, log, slackContext, `:x: Failed to resolve URL for \`${siteUrl}\`: ${error.message}. Skipping RUM, GSC, and scraping checks.`); } + } + /* c8 ignore stop */ + } + + // Only proceed with data source checks if we have a valid resolved URL + if (resolvedUrl && domain) { + if (needsRUM) { + rumAvailable = await isRUMAvailable(domain, context); + } - if (needsScraping) { - const scrapingCheck = await isScrapingAvailable(siteUrl, context); - scrapingAvailable = scrapingCheck.available; - - // Send Slack notification with scraping statistics if available - if (scrapingCheck.stats && slackContext) { - const { completed, failed, total } = scrapingCheck.stats; - const statsMessage = `:mag: *Scraping Statistics for ${siteUrl}*\n` - + `✅ Completed: ${completed}\n` - + `❌ Failed: ${failed}\n` - + `📊 Total: ${total}`; - - if (failed > 0) { - await say( - env, - log, - slackContext, - `${statsMessage}\n:information_source: _${failed} failed URLs will be retried on re-onboarding._`, - ); - } else { - await say(env, log, slackContext, statsMessage); - } + if (needsGSC) { + gscConfigured = await isGSCConfigured(resolvedUrl, context); + } + + if (needsScraping) { + const scrapingCheck = await isScrapingAvailable(siteUrl, context); + scrapingAvailable = scrapingCheck.available; + + // Send Slack notification with scraping statistics if available + if (scrapingCheck.stats && slackContext) { + const { completed, failed, total } = scrapingCheck.stats; + const statsMessage = `:mag: *Scraping Statistics for ${siteUrl}*\n` + + `✅ Completed: ${completed}\n` + + `❌ Failed: ${failed}\n` + + `📊 Total: ${total}`; + + if (failed > 0) { + await say( + env, + log, + slackContext, + `${statsMessage}\n:information_source: _${failed} failed URLs will be retried on re-onboarding._`, + ); + } else { + await say(env, log, slackContext, statsMessage); } } - } catch (error) { - log.warn(`Could not resolve canonical URL or parse siteUrl for data source checks: ${siteUrl}`, error); } } diff --git a/test/index.test.js b/test/index.test.js index 08f238f..7b44e00 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -203,5 +203,29 @@ describe('Index Tests', () => { expect(resp.status).to.equal(200); expect(directContext.log.info.calledWith(sinon.match(/Received message with type: dummy/))).to.be.true; }); + + it('detects wrapped SQS events with messageId in context.invocation.event.Records', async () => { + // Test case where event.Records is not present, but context.invocation.event.Records is + // This covers lines 111-112 in src/index.js + const wrappedSqsContext = { + ...context, + invocation: { + event: { + Records: [{ + messageId: 'test-message-id-123', + body: JSON.stringify({ + type: 'dummy', + siteId: 'wrapped-site', + }), + }], + }, + }, + }; + + // Pass an empty event object (no top-level Records) + const resp = await main({}, wrappedSqsContext); + expect(resp.status).to.equal(200); + expect(wrappedSqsContext.log.info.calledWith(sinon.match(/Received message with type: dummy/))).to.be.true; + }); }); }); diff --git a/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js b/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js index 20354b5..5b55317 100644 --- a/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js +++ b/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js @@ -44,11 +44,12 @@ describe('Opportunity Status Processor', () => { // Mock fetch for robots.txt and HEAD requests global.fetch = sandbox.stub(); - global.fetch.resolves({ + global.fetch.callsFake((url) => Promise.resolve({ ok: true, status: 200, + url, // Include URL for resolveCanonicalUrl text: sandbox.stub().resolves('User-agent: *\nAllow: /'), - }); + })); // Mock context context = new MockContextBuilder() @@ -268,9 +269,9 @@ describe('Opportunity Status Processor', () => { mockSite.getOpportunities.resolves(mockOpportunities); // For this test, we'll just verify that the error is handled gracefully - // The actual resolveCanonicalUrl function will throw an error for invalid URLs + // resolveCanonicalUrl returns null for invalid URLs, triggering log.warn await runOpportunityStatusProcessor(message, context); - expect(context.log.warn.calledWith('Could not resolve canonical URL or parse siteUrl for data source checks: invalid-url', sinon.match.any)).to.be.true; + expect(context.log.warn.calledWith(sinon.match(/Could not resolve canonical URL for: invalid-url/))).to.be.true; expect(mockSite.getOpportunities.called).to.be.true; }); @@ -399,8 +400,8 @@ describe('Opportunity Status Processor', () => { await runOpportunityStatusProcessor(testMessage, testContext); - // Verify error handling for localhost URLs - expect(testContext.log.warn.calledWith(`Could not resolve canonical URL or parse siteUrl for data source checks: ${testCase.url}`, sinon.match.any)).to.be.true; + // Verify error handling for localhost URLs (now uses log.warn for null resolvedUrl) + expect(testContext.log.warn.calledWith(sinon.match(/Could not resolve canonical URL for.*Site may be unreachable/))).to.be.true; })); }); @@ -1028,7 +1029,7 @@ describe('Opportunity Status Processor', () => { await runOpportunityStatusProcessor(message, context); - expect(context.log.warn.calledWithMatch('Could not resolve canonical URL')).to.be.true; + expect(context.log.warn.calledWithMatch('Could not resolve canonical URL for: not-a-valid-url')).to.be.true; }); it('should handle opportunities with missing getData method', async () => { From 2a68751c2cc4cc14b912d51ccb43ef2ae749733d Mon Sep 17 00:00:00 2001 From: Tej Kotthakota Date: Fri, 5 Dec 2025 11:29:03 -0600 Subject: [PATCH 2/5] simiplify logic --- .../opportunity-status-processor/handler.js | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/tasks/opportunity-status-processor/handler.js b/src/tasks/opportunity-status-processor/handler.js index 25bcc50..726b686 100644 --- a/src/tasks/opportunity-status-processor/handler.js +++ b/src/tasks/opportunity-status-processor/handler.js @@ -469,46 +469,37 @@ export async function runOpportunityStatusProcessor(message, context) { const needsScraping = requiredDependencies.has('scraping'); const needsGSC = requiredDependencies.has('GSC'); - // Only check data sources that are needed - // Resolve canonical URL once (used by RUM, GSC, and scraping) - let resolvedUrl = null; - let domain = null; - - if (siteUrl && (needsRUM || needsGSC || needsScraping)) { - try { + // Only check data sources that are needed (all require siteUrl) + if (!siteUrl) { + log.warn('No siteUrl provided, skipping RUM, GSC, and scraping checks'); + } else { + // Resolve canonical URL (used by RUM and GSC - scraping uses siteUrl directly) + let resolvedUrl = null; + if (needsRUM || needsGSC) { resolvedUrl = await resolveCanonicalUrl(siteUrl); - log.info(`Resolved URL: ${resolvedUrl}`); + log.info(`Resolved URL: ${resolvedUrl || 'null'} for ${siteUrl}`); if (!resolvedUrl) { - log.warn(`Could not resolve canonical URL for: ${siteUrl}. Site may be unreachable. Skipping data source checks.`); + log.warn(`Could not resolve canonical URL for: ${siteUrl}. Site may be unreachable.`); if (slackContext) { - await say(env, log, slackContext, `:warning: Could not resolve canonical URL for \`${siteUrl}\`. Site may be unreachable. Skipping RUM, GSC, and scraping checks.`); + await say(env, log, slackContext, `:warning: Could not resolve canonical URL for \`${siteUrl}\`. Site may be unreachable.`); } - } else { - domain = new URL(resolvedUrl).hostname; - log.info(`Extracted domain: ${domain}`); - } - /* c8 ignore start */ - // URL parsing error is very rare (resolveCanonicalUrl returns null on failures) - } catch (error) { - log.error(`Failed to resolve URL or extract domain for ${siteUrl}:`, error); - if (slackContext) { - await say(env, log, slackContext, `:x: Failed to resolve URL for \`${siteUrl}\`: ${error.message}. Skipping RUM, GSC, and scraping checks.`); } } - /* c8 ignore stop */ - } - // Only proceed with data source checks if we have a valid resolved URL - if (resolvedUrl && domain) { + // Check RUM availability - use resolved URL if available, otherwise use base URL if (needsRUM) { + const urlToCheck = resolvedUrl || siteUrl; + const domain = new URL(urlToCheck).hostname; rumAvailable = await isRUMAvailable(domain, context); } - if (needsGSC) { + // Check GSC configuration - requires resolved URL + if (needsGSC && resolvedUrl) { gscConfigured = await isGSCConfigured(resolvedUrl, context); } + // Check scraping availability - uses siteUrl directly, independent of resolved URL if (needsScraping) { const scrapingCheck = await isScrapingAvailable(siteUrl, context); scrapingAvailable = scrapingCheck.available; From ee2eeeae2bf31fa9659042563091f0237d5f8aaf Mon Sep 17 00:00:00 2001 From: Tej Kotthakota Date: Fri, 5 Dec 2025 11:30:47 -0600 Subject: [PATCH 3/5] remove comments --- src/tasks/opportunity-status-processor/handler.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/tasks/opportunity-status-processor/handler.js b/src/tasks/opportunity-status-processor/handler.js index 726b686..6c43b3c 100644 --- a/src/tasks/opportunity-status-processor/handler.js +++ b/src/tasks/opportunity-status-processor/handler.js @@ -469,11 +469,9 @@ export async function runOpportunityStatusProcessor(message, context) { const needsScraping = requiredDependencies.has('scraping'); const needsGSC = requiredDependencies.has('GSC'); - // Only check data sources that are needed (all require siteUrl) if (!siteUrl) { log.warn('No siteUrl provided, skipping RUM, GSC, and scraping checks'); } else { - // Resolve canonical URL (used by RUM and GSC - scraping uses siteUrl directly) let resolvedUrl = null; if (needsRUM || needsGSC) { resolvedUrl = await resolveCanonicalUrl(siteUrl); @@ -487,19 +485,16 @@ export async function runOpportunityStatusProcessor(message, context) { } } - // Check RUM availability - use resolved URL if available, otherwise use base URL if (needsRUM) { const urlToCheck = resolvedUrl || siteUrl; const domain = new URL(urlToCheck).hostname; rumAvailable = await isRUMAvailable(domain, context); } - // Check GSC configuration - requires resolved URL if (needsGSC && resolvedUrl) { gscConfigured = await isGSCConfigured(resolvedUrl, context); } - // Check scraping availability - uses siteUrl directly, independent of resolved URL if (needsScraping) { const scrapingCheck = await isScrapingAvailable(siteUrl, context); scrapingAvailable = scrapingCheck.available; From 6cdc77d776a8d57f5a431bb9f06cb24eddf22258 Mon Sep 17 00:00:00 2001 From: Tej Kotthakota Date: Fri, 5 Dec 2025 18:46:50 -0600 Subject: [PATCH 4/5] Add toggle domain logic for RUM --- .../opportunity-status-processor/handler.js | 24 +++- .../opportunity-status-processor.test.js | 121 +++++++++++++++++- 2 files changed, 135 insertions(+), 10 deletions(-) diff --git a/src/tasks/opportunity-status-processor/handler.js b/src/tasks/opportunity-status-processor/handler.js index 6c43b3c..803f5c0 100644 --- a/src/tasks/opportunity-status-processor/handler.js +++ b/src/tasks/opportunity-status-processor/handler.js @@ -23,25 +23,41 @@ import { OPPORTUNITY_DEPENDENCY_MAP } from './opportunity-dependency-map.js'; const TASK_TYPE = 'opportunity-status-processor'; const AUDIT_WORKER_LOG_GROUP = '/aws/lambda/spacecat-services--audit-worker'; +/** + * Toggles the www subdomain in a hostname + * @param {string} hostname - The hostname to toggle + * @returns {string} The hostname with www toggled + */ +function toggleWWWHostname(hostname) { + return hostname.startsWith('www.') ? hostname.replace('www.', '') : `www.${hostname}`; +} + /** * Checks if RUM is available for a domain by attempting to get a domainkey + * Tries both with and without www prefix * @param {string} domain - The domain to check * @param {object} context - The context object with env and log * @returns {Promise} True if RUM is available, false otherwise */ async function isRUMAvailable(domain, context) { const { log } = context; + const rumClient = RUMAPIClient.createFrom(context); + const wwwToggledDomain = toggleWWWHostname(domain); try { - const rumClient = RUMAPIClient.createFrom(context); + await rumClient.retrieveDomainkey(wwwToggledDomain); + log.info(`RUM is available for domain: ${wwwToggledDomain}`); + return true; + } catch (error) { + log.warn(`RUM not available for ${wwwToggledDomain}: ${error.message}`); + } - // Attempt to get domainkey - if this succeeds, RUM is available + try { await rumClient.retrieveDomainkey(domain); - log.info(`RUM is available for domain: ${domain}`); return true; } catch (error) { - log.info(`RUM is not available for domain: ${domain}. Reason: ${error.message}`); + log.warn(`RUM is not available for domain: ${domain}. Reason: ${error.message}`); return false; } } diff --git a/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js b/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js index 5b55317..91c1931 100644 --- a/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js +++ b/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js @@ -349,6 +349,7 @@ describe('Opportunity Status Processor', () => { info: sinon.stub(), error: sinon.stub(), warn: sinon.stub(), + debug: sinon.stub(), }, env: { RUM_ADMIN_KEY: 'test-admin-key', @@ -406,7 +407,7 @@ describe('Opportunity Status Processor', () => { }); it('should handle RUM success scenarios', async () => { - // Test RUM available (success case) - use a simple URL that should resolve quickly + // Test RUM available (success case) - tries www-toggled variant first mockRUMClient.retrieveDomainkey.resolves('test-domain-key'); const RUMAPIClient = await import('@adobe/spacecat-shared-rum-api-client'); const createFromStub = sinon.stub(RUMAPIClient.default, 'createFrom').returns(mockRUMClient); @@ -421,30 +422,138 @@ describe('Opportunity Status Processor', () => { }, }; + const testMockSite = { + getOpportunities: sinon.stub().resolves([]), + }; + const testContext = { ...mockContext, dataAccess: { Site: { - findById: sinon.stub().resolves({ - getOpportunities: sinon.stub().resolves([]), - }), + findById: sinon.stub().resolves(testMockSite), }, SiteTopPage: { allBySiteIdAndSourceAndGeo: sinon.stub().resolves([]), }, + Configuration: { + findLatest: sinon.stub().resolves({}), + }, }, }; await runOpportunityStatusProcessor(testMessage, testContext); - // Verify RUM was checked successfully - this should cover lines 26-37 + // Verify RUM was checked successfully - now tries www-toggled variant first expect(createFromStub.calledWith(testContext)).to.be.true; - expect(mockRUMClient.retrieveDomainkey.calledWith('example.com')).to.be.true; + expect(mockRUMClient.retrieveDomainkey.callCount).to.be.at.least(1); + expect(mockRUMClient.retrieveDomainkey.getCall(0).args[0]).to.equal('www.example.com'); + expect(testContext.log.info.calledWith('RUM is available for domain: www.example.com')).to.be.true; + + createFromStub.restore(); + }); + + it('should try original domain when www-toggled variant fails', async () => { + // Test fallback to original domain when www-toggled fails + const mockRUMClientWithFallback = { + retrieveDomainkey: sinon.stub(), + }; + // First call (www.example.com) fails, second call (example.com) succeeds + mockRUMClientWithFallback.retrieveDomainkey + .onFirstCall().rejects(new Error('404 Not Found')) + .onSecondCall().resolves('test-domain-key'); + + const RUMAPIClient = await import('@adobe/spacecat-shared-rum-api-client'); + const createFromStub = sinon.stub(RUMAPIClient.default, 'createFrom').returns(mockRUMClientWithFallback); + + const testMessage = { + siteId: 'test-site-id', + siteUrl: 'https://example.com', + organizationId: 'test-org-id', + taskContext: { + auditTypes: ['cwv'], + slackContext: null, + }, + }; + + const testMockSite = { + getOpportunities: sinon.stub().resolves([]), + }; + + const testContext = { + ...mockContext, + dataAccess: { + Site: { + findById: sinon.stub().resolves(testMockSite), + }, + SiteTopPage: { + allBySiteIdAndSourceAndGeo: sinon.stub().resolves([]), + }, + Configuration: { + findLatest: sinon.stub().resolves({}), + }, + }, + }; + + await runOpportunityStatusProcessor(testMessage, testContext); + + // Verify both attempts were made and that lines 59-60 are covered + expect(mockRUMClientWithFallback.retrieveDomainkey.callCount).to.equal(2); + expect(mockRUMClientWithFallback.retrieveDomainkey.getCall(0).args[0]).to.equal('www.example.com'); + expect(mockRUMClientWithFallback.retrieveDomainkey.getCall(1).args[0]).to.equal('example.com'); expect(testContext.log.info.calledWith('RUM is available for domain: example.com')).to.be.true; createFromStub.restore(); }); + it('should handle both domain variations failing', async () => { + // Test when both www-toggled and original domain fail + const mockRUMClientFailBoth = { + retrieveDomainkey: sinon.stub().rejects(new Error('404 Not Found')), + }; + + const RUMAPIClient = await import('@adobe/spacecat-shared-rum-api-client'); + const createFromStub = sinon.stub(RUMAPIClient.default, 'createFrom').returns(mockRUMClientFailBoth); + + const testMessage = { + siteId: 'test-site-id', + siteUrl: 'https://example.com', + organizationId: 'test-org-id', + taskContext: { + auditTypes: ['cwv'], + slackContext: null, + }, + }; + + const testMockSite = { + getOpportunities: sinon.stub().resolves([]), + }; + + const testContext = { + ...mockContext, + dataAccess: { + Site: { + findById: sinon.stub().resolves(testMockSite), + }, + SiteTopPage: { + allBySiteIdAndSourceAndGeo: sinon.stub().resolves([]), + }, + Configuration: { + findLatest: sinon.stub().resolves({}), + }, + }, + }; + + await runOpportunityStatusProcessor(testMessage, testContext); + + // Verify both attempts were made + expect(mockRUMClientFailBoth.retrieveDomainkey.callCount).to.equal(2); + expect(mockRUMClientFailBoth.retrieveDomainkey.getCall(0).args[0]).to.equal('www.example.com'); + expect(mockRUMClientFailBoth.retrieveDomainkey.getCall(1).args[0]).to.equal('example.com'); + expect(testContext.log.warn.calledWith(sinon.match(/RUM is not available for domain: example.com/))).to.be.true; + + createFromStub.restore(); + }); + it('should handle opportunities with different types and localhost URLs', async () => { // Test opportunities with different types when using localhost URLs const testCases = [ From 827c08ce9cf492ed7b2027b0b0f8fc5df496ba8f Mon Sep 17 00:00:00 2001 From: Tej Kotthakota Date: Fri, 5 Dec 2025 18:59:01 -0600 Subject: [PATCH 5/5] first check with domain and then toggled domain --- .../opportunity-status-processor/handler.js | 17 +++++----- .../opportunity-status-processor.test.js | 32 +++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/tasks/opportunity-status-processor/handler.js b/src/tasks/opportunity-status-processor/handler.js index 803f5c0..3fee45c 100644 --- a/src/tasks/opportunity-status-processor/handler.js +++ b/src/tasks/opportunity-status-processor/handler.js @@ -43,23 +43,24 @@ async function isRUMAvailable(domain, context) { const { log } = context; const rumClient = RUMAPIClient.createFrom(context); - const wwwToggledDomain = toggleWWWHostname(domain); try { - await rumClient.retrieveDomainkey(wwwToggledDomain); - log.info(`RUM is available for domain: ${wwwToggledDomain}`); + await rumClient.retrieveDomainkey(domain); + log.info(`RUM is available for domain: ${domain}`); return true; } catch (error) { - log.warn(`RUM not available for ${wwwToggledDomain}: ${error.message}`); + log.warn(`RUM is not available for domain: ${domain}. Reason: ${error.message}`); } + // Try with www-toggled domain + const wwwToggledDomain = toggleWWWHostname(domain); try { - await rumClient.retrieveDomainkey(domain); - log.info(`RUM is available for domain: ${domain}`); + await rumClient.retrieveDomainkey(wwwToggledDomain); + log.info(`RUM is available for domain: ${wwwToggledDomain}`); return true; } catch (error) { - log.warn(`RUM is not available for domain: ${domain}. Reason: ${error.message}`); - return false; + log.warn(`RUM not available for ${wwwToggledDomain}: ${error.message}`); } + return false; } /** diff --git a/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js b/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js index 91c1931..706faa8 100644 --- a/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js +++ b/test/tasks/opportunity-status-processor/opportunity-status-processor.test.js @@ -407,7 +407,7 @@ describe('Opportunity Status Processor', () => { }); it('should handle RUM success scenarios', async () => { - // Test RUM available (success case) - tries www-toggled variant first + // Test RUM available (success case) - tries original domain first mockRUMClient.retrieveDomainkey.resolves('test-domain-key'); const RUMAPIClient = await import('@adobe/spacecat-shared-rum-api-client'); const createFromStub = sinon.stub(RUMAPIClient.default, 'createFrom').returns(mockRUMClient); @@ -443,21 +443,21 @@ describe('Opportunity Status Processor', () => { await runOpportunityStatusProcessor(testMessage, testContext); - // Verify RUM was checked successfully - now tries www-toggled variant first + // Verify RUM was checked successfully - now tries original domain first expect(createFromStub.calledWith(testContext)).to.be.true; expect(mockRUMClient.retrieveDomainkey.callCount).to.be.at.least(1); - expect(mockRUMClient.retrieveDomainkey.getCall(0).args[0]).to.equal('www.example.com'); - expect(testContext.log.info.calledWith('RUM is available for domain: www.example.com')).to.be.true; + expect(mockRUMClient.retrieveDomainkey.getCall(0).args[0]).to.equal('example.com'); + expect(testContext.log.info.calledWith('RUM is available for domain: example.com')).to.be.true; createFromStub.restore(); }); - it('should try original domain when www-toggled variant fails', async () => { - // Test fallback to original domain when www-toggled fails + it('should try toggled domain when original domain fails', async () => { + // Test fallback to toggled domain when original fails const mockRUMClientWithFallback = { retrieveDomainkey: sinon.stub(), }; - // First call (www.example.com) fails, second call (example.com) succeeds + // First call (example.com) fails, second call (www.example.com) succeeds mockRUMClientWithFallback.retrieveDomainkey .onFirstCall().rejects(new Error('404 Not Found')) .onSecondCall().resolves('test-domain-key'); @@ -496,17 +496,17 @@ describe('Opportunity Status Processor', () => { await runOpportunityStatusProcessor(testMessage, testContext); - // Verify both attempts were made and that lines 59-60 are covered + // Verify both attempts were made - original first, then toggled expect(mockRUMClientWithFallback.retrieveDomainkey.callCount).to.equal(2); - expect(mockRUMClientWithFallback.retrieveDomainkey.getCall(0).args[0]).to.equal('www.example.com'); - expect(mockRUMClientWithFallback.retrieveDomainkey.getCall(1).args[0]).to.equal('example.com'); - expect(testContext.log.info.calledWith('RUM is available for domain: example.com')).to.be.true; + expect(mockRUMClientWithFallback.retrieveDomainkey.getCall(0).args[0]).to.equal('example.com'); + expect(mockRUMClientWithFallback.retrieveDomainkey.getCall(1).args[0]).to.equal('www.example.com'); + expect(testContext.log.info.calledWith('RUM is available for domain: www.example.com')).to.be.true; createFromStub.restore(); }); it('should handle both domain variations failing', async () => { - // Test when both www-toggled and original domain fail + // Test when both original and toggled domain fail const mockRUMClientFailBoth = { retrieveDomainkey: sinon.stub().rejects(new Error('404 Not Found')), }; @@ -545,11 +545,11 @@ describe('Opportunity Status Processor', () => { await runOpportunityStatusProcessor(testMessage, testContext); - // Verify both attempts were made + // Verify both attempts were made - original first, then toggled expect(mockRUMClientFailBoth.retrieveDomainkey.callCount).to.equal(2); - expect(mockRUMClientFailBoth.retrieveDomainkey.getCall(0).args[0]).to.equal('www.example.com'); - expect(mockRUMClientFailBoth.retrieveDomainkey.getCall(1).args[0]).to.equal('example.com'); - expect(testContext.log.warn.calledWith(sinon.match(/RUM is not available for domain: example.com/))).to.be.true; + expect(mockRUMClientFailBoth.retrieveDomainkey.getCall(0).args[0]).to.equal('example.com'); + expect(mockRUMClientFailBoth.retrieveDomainkey.getCall(1).args[0]).to.equal('www.example.com'); + expect(testContext.log.warn.calledWith(sinon.match(/RUM not available for www.example.com/))).to.be.true; createFromStub.restore(); });