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

94 lines
3.6 KiB
TypeScript

/**
* P1-C · cross-user isolation.
*
* Covers the cross-user isolation acceptance criterion: two
* freshly-minted users (A and B) must not see each other's writes.
* User A creates a prompt; user B's
* `/api/get_prompts` must not include it, and the DB must report zero
* `prompts` rows for user B.
*
* This spec is mode-agnostic — it drives the API directly with signed
* tokens and doesn't touch the JWT modal — so it runs under both
* `AUTH_TYPE=session_jwt` and `AUTH_TYPE=simple_jwt` (though simple_jwt
* would in practice reject distinct subs: the server still accepts any
* HS256-signed token with the shared secret, per application/auth.py).
*/
import * as playwright from '@playwright/test';
const { expect, test } = playwright;
import { authedRequest } from '../../helpers/api.js';
import { signJwt } from '../../helpers/auth.js';
import { countRows } from '../../helpers/db.js';
import { resetDb } from '../../helpers/reset.js';
const USER_A_SUB = 'e2e-isolation-a';
const USER_B_SUB = 'e2e-isolation-b';
type Prompt = { id: string; name: string; type: 'public' | 'private' };
test.describe('auth · cross-user isolation', () => {
test.beforeEach(async () => {
await resetDb();
});
test("isolation: user A's prompt is invisible to user B across API and DB", async () => {
const tokenA = signJwt(USER_A_SUB);
const tokenB = signJwt(USER_B_SUB);
const apiA = await authedRequest(playwright, tokenA);
const apiB = await authedRequest(playwright, tokenB);
try {
// User A creates a prompt. Contract per
// application/api/user/prompts/routes.py:19-50 — POST JSON body with
// `name` + `content`, returns `{id}` on 200.
const promptName = 'e2e-isolation-prompt';
const promptContent = 'Isolation check — only user A should see me.';
const createRes = await apiA.post('/api/create_prompt', {
data: { name: promptName, content: promptContent },
});
expect(createRes.status()).toBe(200);
const createBody = (await createRes.json()) as { id: string };
expect(createBody.id).toBeTruthy();
// User A can see the prompt in their list.
const listARes = await apiA.get('/api/get_prompts');
expect(listARes.status()).toBe(200);
const listA = (await listARes.json()) as Prompt[];
const privateAPrompts = listA.filter((p) => p.type === 'private');
expect(privateAPrompts).toHaveLength(1);
expect(privateAPrompts[0].name).toBe(promptName);
expect(privateAPrompts[0].id).toBe(createBody.id);
// User B cannot see it. The three built-in public prompts
// (default/creative/strict) are always returned by /get_prompts —
// the isolation assertion is on the `private` slice.
const listBRes = await apiB.get('/api/get_prompts');
expect(listBRes.status()).toBe(200);
const listB = (await listBRes.json()) as Prompt[];
const privateBPrompts = listB.filter((p) => p.type === 'private');
expect(privateBPrompts).toHaveLength(0);
expect(listB.find((p) => p.id === createBody.id)).toBeUndefined();
// DB-level assertion — UI/API view can lie (bugs like missing
// WHERE user_id clauses) but the underlying row count cannot.
const aRowCount = await countRows('prompts', {
sql: 'user_id = $1',
params: [USER_A_SUB],
});
expect(aRowCount).toBe(1);
const bRowCount = await countRows('prompts', {
sql: 'user_id = $1',
params: [USER_B_SUB],
});
expect(bRowCount).toBe(0);
} finally {
await apiA.dispose();
await apiB.dispose();
}
});
});