From ee24ea05de8fcdbe6e7bdd37f098dc5af16ae9e1 Mon Sep 17 00:00:00 2001 From: MortenFriisSiteImprove Date: Tue, 15 Sep 2026 22:04:13 +0200 Subject: [PATCH] Match live report identity to the requested page URL --- docs/live-testing.md | 2 ++ tests/live/diagnostics.mjs | 2 +- tests/live/editor.mjs | 5 +++-- tests/live/settings.mjs | 5 +++-- tests/unit/live.test.cjs | 12 +++++++----- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/live-testing.md b/docs/live-testing.md index e7f7d31..97d3a7a 100644 --- a/docs/live-testing.md +++ b/docs/live-testing.md @@ -54,3 +54,5 @@ Unit tests verify configuration rejection, URL separation, report-readiness pred The outcome JSON contains an explicit `skipped` entry for missing-image-alternative result validation. Passing smoke checks establish authentication, report lookup, fresh draft handoff and loading-state exit; they do not establish correct prepublish results. This follows the [WordPress live runner](https://github.com/Siteimprove/CMS-plugin-Wordpress/blob/master/tests/live/run.js). After login, the test waits for authenticated report data for the mapped URL before opening the report panel, matching the WordPress flow. Diagnostic fields are independently filtered by the reporter; they contain no raw response fields, account text or URLs. + +Report identity is checked against the exact `url` parameter on the SDK polling request. Like WordPress, the response must be authenticated, error-free, include a nonnegative numeric issue count and a nonempty `mainUrl`; that response field is not assumed to equal the crawled page URL. diff --git a/tests/live/diagnostics.mjs b/tests/live/diagnostics.mjs index 323fe5f..300cfb4 100644 --- a/tests/live/diagnostics.mjs +++ b/tests/live/diagnostics.mjs @@ -1,5 +1,5 @@ const flags = ['entitlementReady', 'pollSeen', 'pollOk', 'pollAuthenticated', 'pollUrlMatches', - 'pollIssueCountValid', 'pollErrorNone', 'panelFramePresent', 'panelVisible', 'launcherVisible', + 'pollMainUrlPresent', 'pollIssueCountValid', 'pollErrorNone', 'panelFramePresent', 'panelVisible', 'launcherVisible', 'blockedExternalRequest']; // Account text and arbitrary response fields must never enter public artifacts. diff --git a/tests/live/editor.mjs b/tests/live/editor.mjs index 80c862e..8bd4ac4 100644 --- a/tests/live/editor.mjs +++ b/tests/live/editor.mjs @@ -34,10 +34,11 @@ export async function openLiveEditor(page, context) { diagnostic.pollOk = response.ok(); const body = await response.json(); diagnostic.pollAuthenticated = body?.authed === true; - diagnostic.pollUrlMatches = url.searchParams.get('url') === config.crawledUrl && body?.mainUrl === config.crawledUrl; + diagnostic.pollUrlMatches = url.searchParams.get('url') === config.crawledUrl; + diagnostic.pollMainUrlPresent = typeof body?.mainUrl === 'string' && body.mainUrl.trim().length > 0; diagnostic.pollIssueCountValid = Number.isFinite(body?.issues) && body.issues >= 0; diagnostic.pollErrorNone = body?.error === 'None'; - if (diagnostic.pollUrlMatches && response.ok() && isReport(body, config.crawledUrl)) reportReceived = true; + if (diagnostic.pollUrlMatches && response.ok() && isReport(body, url.searchParams.get('url'), config.crawledUrl)) reportReceived = true; } } catch { /* Invalid or failed responses cannot satisfy report readiness. */ } }); diff --git a/tests/live/settings.mjs b/tests/live/settings.mjs index 29ad7d2..3de8e57 100644 --- a/tests/live/settings.mjs +++ b/tests/live/settings.mjs @@ -22,7 +22,8 @@ export function allowedRequest(value) { && ['siteimprove.com', 'siteimprove.net'].some(host => url.hostname === host || url.hostname.endsWith('.' + host))); } -export function isReport(value, expectedUrl) { +export function isReport(value, requestedUrl, expectedUrl) { return value?.authed === true && value.error === 'None' && Number.isFinite(value.issues) - && value.issues >= 0 && value.mainUrl === expectedUrl; + && value.issues >= 0 && requestedUrl === expectedUrl + && typeof value.mainUrl === 'string' && value.mainUrl.trim().length > 0; } diff --git a/tests/unit/live.test.cjs b/tests/unit/live.test.cjs index 1c9c030..d57b1ea 100644 --- a/tests/unit/live.test.cjs +++ b/tests/unit/live.test.cjs @@ -29,15 +29,17 @@ test('missing credentials and unsupported or mismatched public paths fail closed } }); -test('only authenticated successful data for the expected page proves report readiness', async () => { +test('report readiness matches the requested page without treating mainUrl as its identity', async () => { const { isReport } = await import('../live/settings.mjs'); const expected = fixture().SITEIMPROVE_CRAWLED_URL; - const body = { authed: true, error: 'None', issues: 0, mainUrl: expected }; - assert.equal(isReport(body, expected), true); + const body = { authed: true, error: 'None', issues: 0, mainUrl: 'https://my2.siteimprove.com/report' }; + assert.equal(isReport(body, expected, expected), true); for (const patch of [{ authed: false }, { error: 'Failed' }, { issues: -1 }, - { issues: '0' }, { mainUrl: 'https://other.example.test/' }]) { - assert.equal(isReport({ ...body, ...patch }, expected), false); + { issues: '0' }, { mainUrl: '' }, { mainUrl: null }, { mainUrl: ' ' }]) { + assert.equal(isReport({ ...body, ...patch }, expected, expected), false); } + for (const requested of [null, 'https://other.example.test/', expected + '?preview=1']) + assert.equal(isReport(body, requested, expected), false); }); test('browser requests reject lookalike login domains and unrelated hosts', async () => {