Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:28:29 +08:00

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();
}
});
});