fed8b2eed7
Build and push multi-arch DocsGPT Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Has been cancelled
Backend release / release (push) Has been cancelled
Bandit Security Scan / bandit_scan (push) Has been cancelled
Build and push multi-arch DocsGPT Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Has been cancelled
Build and push multi-arch DocsGPT Docker image / manifest (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / build (linux/amd64, ubuntu-latest, amd64) (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Has been cancelled
Build and push DocsGPT FE Docker image for development / manifest (push) Has been cancelled
Python linting / ruff (push) Has been cancelled
Run python tests with pytest / Run tests and count coverage (3.12) (push) Has been cancelled
React Widget Build / build (push) Has been cancelled
97 lines
3.8 KiB
TypeScript
97 lines
3.8 KiB
TypeScript
/**
|
|
* P1-C · session_jwt auth flow.
|
|
*
|
|
* Covers e2e-plan.md §7.2: with `AUTH_TYPE=session_jwt` the backend issues a
|
|
* fresh token via `/api/generate_token` on first load, the frontend stashes
|
|
* it in localStorage, the main UI renders, and an authed call to a user
|
|
* route upserts a `users` row via `ensure_user_doc`.
|
|
*
|
|
* Single-boot strategy: this spec assumes the backend was launched with
|
|
* `AUTH_TYPE=session_jwt` (the default set by `scripts/e2e/env.sh`). It is
|
|
* tolerant of two startup paths because we pre-inject a JWT into
|
|
* `localStorage` via `newUserContext`:
|
|
* 1. The app sees the injected token on boot and skips /api/generate_token.
|
|
* 2. The app (for any reason) still calls /api/generate_token and overwrites.
|
|
* Either outcome is acceptable — we only assert the invariants that must hold
|
|
* once the shell is up: localStorage has a non-empty authToken AND the nav
|
|
* chrome is rendered.
|
|
*/
|
|
|
|
import * as playwright from '@playwright/test';
|
|
const { expect, test } = playwright;
|
|
|
|
import { authedRequest } from '../../helpers/api.js';
|
|
import { newUserContext } from '../../helpers/auth.js';
|
|
import { getUserRow } from '../../helpers/db.js';
|
|
import { resetDb } from '../../helpers/reset.js';
|
|
|
|
test.describe('auth · session_jwt', () => {
|
|
test.beforeEach(async () => {
|
|
await resetDb();
|
|
});
|
|
|
|
test('session_jwt: app bootstraps with a token and upserts users row on first authed call', async ({
|
|
browser,
|
|
}) => {
|
|
const sub = 'e2e-session-jwt-user';
|
|
const { context, token } = await newUserContext(browser, { sub });
|
|
|
|
try {
|
|
const page = await context.newPage();
|
|
|
|
// Race /api/generate_token vs the first paint. Either outcome is fine —
|
|
// the injected token makes the /generate_token call optional. We wait
|
|
// for whichever completes first, then proceed to the real assertions.
|
|
const generateTokenRace = page
|
|
.waitForResponse(
|
|
(r) => r.url().includes('/api/generate_token') && r.status() === 200,
|
|
{ timeout: 8_000 },
|
|
)
|
|
.catch(() => null);
|
|
|
|
await page.goto('/');
|
|
await generateTokenRace;
|
|
|
|
// Main UI rendered. `Spinner` is shown while `isAuthLoading` — once we
|
|
// see the nav chrome, the auth bootstrap has resolved. "New Chat" is
|
|
// the English-default label of the primary NavLink in Navigation.tsx
|
|
// and is visible on both desktop and mobile layouts.
|
|
await expect(
|
|
page.getByRole('link', { name: /new chat/i }).first(),
|
|
).toBeVisible();
|
|
|
|
// localStorage has a non-empty authToken — tolerates both the "reuse
|
|
// injected token" path and the "app issued a new token" path.
|
|
const storedToken = await page.evaluate(() =>
|
|
window.localStorage.getItem('authToken'),
|
|
);
|
|
expect(storedToken).toBeTruthy();
|
|
expect(typeof storedToken).toBe('string');
|
|
expect((storedToken ?? '').length).toBeGreaterThan(0);
|
|
|
|
// `ensure_user_doc` only fires on an authed user-route hit, not on the
|
|
// initial shell load. Hit the simplest user route and then assert the
|
|
// `users` row has been upserted for this sub.
|
|
//
|
|
// If the app re-issued a token via /generate_token, the stored token
|
|
// will have a different `sub` than the one we injected. In that case
|
|
// there's no way to predict the new `sub` — so we use OUR injected
|
|
// token directly against the backend to guarantee we're asserting
|
|
// against the JWT we control.
|
|
const api = await authedRequest(playwright, token);
|
|
try {
|
|
const res = await api.get('/api/get_prompts');
|
|
expect(res.status()).toBe(200);
|
|
|
|
const userRow = await getUserRow(sub);
|
|
expect(userRow).not.toBeNull();
|
|
expect(userRow?.user_id).toBe(sub);
|
|
} finally {
|
|
await api.dispose();
|
|
}
|
|
} finally {
|
|
await context.close();
|
|
}
|
|
});
|
|
});
|