Files
insights/playwright/e2e/shared-notebook-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

88 lines
3.4 KiB
TypeScript

/**
* Regression test: shared notebooks must render in a fully unauthenticated browser context.
* Sibling to shared-dashboard-unauthenticated.spec.ts — ensures the ExporterNotebookScene
* code path keeps working when the exporter bundle / shared-view skipping evolves.
*/
import { expect, Page } from '@playwright/test'
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 createSharedNotebook(
page: Page,
playwrightSetup: PlaywrightSetup,
orgName: string
): Promise<{ sharingData: SharingConfigurationType; notebookTitle: string; paragraphText: string }> {
const workspace = await playwrightSetup.createWorkspace(orgName)
const authHeaders = {
Authorization: `Bearer ${workspace.personal_api_key}`,
'Content-Type': 'application/json',
}
const notebookTitle = 'Logged-out notebook render'
const paragraphText = 'Anonymous notebook body content'
const notebookContent = {
type: 'doc',
content: [
{
type: 'heading',
attrs: { level: 1 },
content: [{ type: 'text', text: notebookTitle }],
},
{
type: 'paragraph',
content: [{ type: 'text', text: paragraphText }],
},
],
}
const notebookResponse = await page.request.post(`/v1/projects/${workspace.team_id}/notebooks/`, {
headers: authHeaders,
data: { title: notebookTitle, content: notebookContent },
})
expect(notebookResponse.ok()).toBe(true)
const notebookData = await notebookResponse.json()
expect(notebookData.short_id).toBeTruthy()
const sharingResponse = await page.request.patch(
`/v1/projects/${workspace.team_id}/notebooks/${notebookData.short_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, notebookTitle, paragraphText }
}
test.describe('Shared notebook (unauthenticated)', () => {
test('renders successfully in a logged-out browser context', async ({ browser, page, playwrightSetup }) => {
const { sharingData, notebookTitle, paragraphText } = await createSharedNotebook(
page,
playwrightSetup,
'Unauth Shared Notebook 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=${notebookTitle}`).first()).toBeVisible({ timeout: 30000 })
await expect(unauthPage.locator(`text=${paragraphText}`)).toBeVisible({ timeout: 30000 })
expectNoTeamScopedApiLeaks(failedApiResponses)
} finally {
await unauthContext.close()
}
})
})