Files
insights/playwright/e2e/shared-dashboard-unauthenticated.spec.ts
zeekay 40d120dbf7 refactor(api): say /v1/ in the Python callers
Every first-party Python caller and test now names the mount the API actually
answers on. Mechanical: URL literals only, and only where the /api/ was ours --
a preceding word character means it was a module path like insights/api/ or a
third-party host like slack.com/api/, and those are untouched.

Left saying /api/, each for a reason rather than for safety:
- products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints
- services/llm-gateway deploys separately and is under a code freeze, so moving
  its two calls would break it during the skew
- the mcp_store and Slack/Teams redirect_uris are registered with third parties
  and must byte-match what was registered
- the Wizard and Raycast client-metadata paths are OAuth client_ids
- /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the
  ingress routes to the Rust fleets, held by released SDK versions

Test URLconfs moved with the requests that call them, and the Datastore
user-selection fixtures now carry the leading slash that request.path has.
2026-08-08 18:38:44 -07:00

94 lines
3.9 KiB
TypeScript

/**
* Regression test: shared dashboards must render in a fully unauthenticated browser context.
* Added after a fix that skipped "always-401" API calls in shared/exported views had to be
* reverted — the skip broke the shared dashboard render path. A logged-out goto of
* /shared/{token} reliably reproduces that class of regression.
*/
import { expect, Page } from '@playwright/test'
import { InsightVizNode, NodeKind, TrendsQuery } from '../../frontend/src/queries/schema/schema-general'
import { SharingConfigurationType } from '../../frontend/src/types'
import { PlaywrightSetup } from '../utils/playwright-setup'
import { expectNoTeamScopedApiLeaks, openUnauthenticatedSharedPage } from '../utils/sharedViewExpectations'
import { test } from '../utils/workspace-test-base'
async function createSharedDashboard(
page: Page,
playwrightSetup: PlaywrightSetup,
orgName: string
): Promise<{ sharingData: SharingConfigurationType; dashboardName: string; insightName: string }> {
const workspace = await playwrightSetup.createWorkspace(orgName)
const authHeaders = {
Authorization: `Bearer ${workspace.personal_api_key}`,
'Content-Type': 'application/json',
}
const dashboardName = 'Logged-out dashboard render'
const dashboardResponse = await page.request.post(`/v1/projects/${workspace.team_id}/dashboards/`, {
headers: authHeaders,
data: { name: dashboardName },
})
expect(dashboardResponse.ok()).toBe(true)
const dashboardData = await dashboardResponse.json()
const insightName = 'Trends pageview tile'
const insightPayload: { name: string; query: InsightVizNode<TrendsQuery>; dashboards: number[] } = {
name: insightName,
query: {
kind: NodeKind.InsightVizNode,
source: {
kind: NodeKind.TrendsQuery,
series: [{ kind: NodeKind.EventsNode, event: '$pageview' }],
dateRange: { date_from: '2024-10-04', date_to: '2024-11-03', explicitDate: true },
},
},
dashboards: [dashboardData.id],
}
const insightResponse = await page.request.post(`/v1/projects/${workspace.team_id}/insights/`, {
headers: authHeaders,
data: insightPayload,
})
expect(insightResponse.ok()).toBe(true)
const sharingResponse = await page.request.patch(
`/v1/projects/${workspace.team_id}/dashboards/${dashboardData.id}/sharing`,
{
headers: authHeaders,
data: { enabled: true },
}
)
expect(sharingResponse.ok()).toBe(true)
const sharingData: SharingConfigurationType = await sharingResponse.json()
expect(sharingData.access_token).toBeTruthy()
expect(sharingData.enabled).toBe(true)
return { sharingData, dashboardName, insightName }
}
test.describe('Shared dashboard (unauthenticated)', () => {
test('renders successfully in a logged-out browser context', async ({ browser, page, playwrightSetup }) => {
const { sharingData, dashboardName, insightName } = await createSharedDashboard(
page,
playwrightSetup,
'Unauth Shared Dashboard Test Org'
)
const unauthContext = await browser.newContext({ storageState: { cookies: [], origins: [] } })
const { unauthPage, failedApiResponses } = await openUnauthenticatedSharedPage(unauthContext)
try {
await unauthPage.goto(`/shared/${sharingData.access_token}`)
await expect(unauthPage.locator('body.ExporterBody')).toBeVisible()
await expect(unauthPage.locator(`text=${dashboardName}`)).toBeVisible({ timeout: 30000 })
await expect(unauthPage.locator(`text=${insightName}`)).toBeVisible({ timeout: 30000 })
await expect(unauthPage.locator('[data-attr="insights-graph"]').first()).toBeVisible({ timeout: 30000 })
expectNoTeamScopedApiLeaks(failedApiResponses)
} finally {
await unauthContext.close()
}
})
})