Move browser authentication behind a generated-contract BFF

This commit is contained in:
ms2k 2026-07-19 18:21:17 +09:00
commit fa7220ee33

View file

@ -17,23 +17,12 @@ const result: MockResult = {
}
test.beforeEach(async ({ page }) => {
await page.routeWebSocket(/\/v1\/events/, () => {})
await page.routeWebSocket(/\/bff\/events/, () => {})
})
test('completes credential, submission, result, history, and lifecycle workflows', async ({
page,
}) => {
test('creates, rotates, and revokes a credential', async ({ page }) => {
const state = await mockApi(page)
await authenticatedSession(page)
await expect
.poll(() =>
page.evaluate(() =>
sessionStorage.getItem(
`oidc.user:${window.location.origin}/oidc:remora-web`,
),
),
)
.not.toBeNull()
await expect(page.getByRole('heading', { name: 'Remora' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Sign out' })).toBeVisible()
await expect(page.getByLabel('Synchronization status')).toHaveText('live')
@ -55,7 +44,19 @@ test('completes credential, submission, result, history, and lifecycle workflows
await page.getByLabel('Replacement token').fill('replacement-token')
await page.getByRole('button', { name: 'Rotate credential' }).click()
await expect(page.getByLabel('Replacement token')).toHaveValue('')
await page.getByRole('button', { name: 'Revoke credential' }).click()
await expect(page.getByRole('alertdialog')).toContainText('future execution')
await page.getByRole('button', { name: 'Confirm' }).click()
await expect(
page.getByText('Credential revoked for future grants'),
).toBeVisible()
expect(state.mutations).toEqual([])
expect(new Set(state.idempotencyKeys).size).toBe(state.idempotencyKeys.length)
})
test('submits a job and browses its history and result', async ({ page }) => {
const state = await mockApi(page)
await authenticatedSession(page)
await page.getByRole('button', { name: 'Submit job' }).click()
await page.getByLabel('Label').fill('Imported archive')
await page
@ -75,7 +76,15 @@ test('completes credential, submission, result, history, and lifecycle workflows
await expect(page.getByText('AttemptCommitted')).toBeVisible()
await page.getByRole('button', { name: 'Browse result' }).click()
await expect(page.getByText('archive.bin')).toBeVisible()
expect(new Set(state.idempotencyKeys).size).toBe(state.idempotencyKeys.length)
})
test('confirms reversible and destructive job lifecycle actions', async ({
page,
}) => {
const state = await mockApi(page)
await authenticatedSession(page)
await page.getByRole('button', { name: /Existing archive/ }).click()
const softDelete = page.getByRole('button', { name: 'Soft delete' })
await softDelete.click()
await expect(page.getByRole('alertdialog')).toContainText(
@ -89,7 +98,7 @@ test('completes credential, submission, result, history, and lifecycle workflows
await page.getByRole('button', { name: 'Confirm' }).click()
await page.reload()
await expect(page.getByLabel('Synchronization status')).toHaveText('live')
await page.getByRole('button', { name: /Imported archive/ }).click()
await page.getByRole('button', { name: /Existing archive/ }).click()
expect(state.includeDeletedRequests).toBeGreaterThan(1)
await expect(page.getByRole('button', { name: 'Browse result' })).toBeHidden()
await page.getByRole('button', { name: 'Restore' }).click()
@ -103,15 +112,6 @@ test('completes credential, submission, result, history, and lifecycle workflows
page.getByRole('list', { name: 'Deletion operations' }).getByText('queued'),
).toBeVisible()
await page.getByRole('button', { name: 'Credentials' }).click()
await page.getByRole('button', { name: /Archive token/ }).click()
await page.getByRole('button', { name: 'Revoke credential' }).click()
await expect(page.getByRole('alertdialog')).toContainText('future execution')
await page.getByRole('button', { name: 'Confirm' }).click()
await expect(
page.getByText('Credential revoked for future grants'),
).toBeVisible()
expect(state.mutations).toEqual([
'soft-delete',
'restore',
@ -122,22 +122,14 @@ test('completes credential, submission, result, history, and lifecycle workflows
})
async function authenticatedSession(page: Page): Promise<void> {
await page.route('**/auth/session', (route) =>
json(route, {
kind: 'authenticated',
identity: 'issuer\u0000subject-0001',
csrfToken: 'csrf-value',
}),
)
await page.goto('/')
await page.evaluate(() => {
const authority = `${window.location.origin}/oidc`
sessionStorage.setItem(
`oidc.user:${authority}:remora-web`,
JSON.stringify({
id_token: 'id-token',
access_token: 'access-token',
token_type: 'Bearer',
scope: 'openid profile',
profile: { iss: authority, sub: 'subject-0001' },
expires_at: Math.floor(Date.now() / 1000) + 3600,
}),
)
})
await page.reload()
}
async function mockApi(page: Page): Promise<{
@ -162,10 +154,10 @@ async function mockApi(page: Page): Promise<{
}
let credential = sourceCredential('active')
let currentJob = job('Existing archive')
await page.route(/\/v1\//, async (route) => {
await page.route(/\/bff\/api\//, async (route) => {
const request = route.request()
const url = new URL(request.url())
const path = url.pathname.replace('/v1/', '')
const path = url.pathname.replace('/bff/api/', '')
state.paths.push(path)
if (
path === 'jobs' &&