Files
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

172 lines
6.0 KiB
TypeScript

import { PgTestClient } from 'insforge-test';
import { getConnections } from './utils';
let db: PgTestClient;
let teardown: () => Promise<void>;
const USER_A = '550e8400-e29b-41d4-a716-446655440001';
const USER_B = '550e8400-e29b-41d4-a716-446655440002';
const USER_C = '550e8400-e29b-41d4-a716-446655440003';
const asUser = (userId: string) => ({
role: 'authenticated',
'request.jwt.claims': JSON.stringify({ sub: userId }),
});
beforeAll(async () => {
({ db, teardown } = await getConnections());
await db.query(`
CREATE TABLE todos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL DEFAULT auth.uid(),
title TEXT NOT NULL,
done BOOLEAN DEFAULT false
);
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
CREATE POLICY "owner_select" ON todos FOR SELECT
TO authenticated USING (auth.uid() = user_id);
CREATE POLICY "owner_insert" ON todos FOR INSERT
TO authenticated WITH CHECK (auth.uid() = user_id);
CREATE POLICY "owner_update" ON todos FOR UPDATE
TO authenticated USING (auth.uid() = user_id);
CREATE POLICY "owner_delete" ON todos FOR DELETE
TO authenticated USING (auth.uid() = user_id);
GRANT ALL ON todos TO authenticated;
`);
});
afterAll(() => teardown());
beforeEach(() => db.beforeEach());
afterEach(() => db.afterEach());
describe('owner crud', () => {
it('should allow user to create their own todo', async () => {
db.setContext(asUser(USER_A));
const todo = await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING *`, ['Buy milk']);
expect(todo.title).toBe('Buy milk');
expect(todo.user_id).toBe(USER_A);
});
it('should allow user to read only their own todos', async () => {
db.setContext(asUser(USER_A));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Task A']);
db.setContext(asUser(USER_B));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Task B']);
db.setContext(asUser(USER_A));
const rows = await db.many(`SELECT * FROM todos`);
expect(rows).toHaveLength(1);
expect(rows[0].title).toBe('Task A');
});
it('should allow user to update their own todo', async () => {
db.setContext(asUser(USER_A));
const todo = await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Draft']);
const updated = await db.one(`UPDATE todos SET title = $1 WHERE id = $2 RETURNING *`, [
'Final',
todo.id,
]);
expect(updated.title).toBe('Final');
});
it('should allow user to delete their own todo', async () => {
db.setContext(asUser(USER_A));
const todo = await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Remove me']);
const result = await db.query(`DELETE FROM todos WHERE id = $1`, [todo.id]);
expect(result.rowCount).toBe(1);
});
});
describe('cross-user isolation', () => {
it('should prevent user from seeing another users todos', async () => {
db.setContext(asUser(USER_A));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Secret']);
db.setContext(asUser(USER_B));
const { rows } = await db.query(`SELECT * FROM todos`);
expect(rows).toHaveLength(0);
});
it('should prevent user from updating another users todo', async () => {
db.setContext(asUser(USER_A));
const todo = await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Private']);
db.setContext(asUser(USER_B));
const result = await db.query(`UPDATE todos SET title = 'hacked' WHERE id = $1`, [todo.id]);
expect(result.rowCount).toBe(0);
});
it('should prevent user from deleting another users todo', async () => {
db.setContext(asUser(USER_A));
const todo = await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Keep me']);
db.setContext(asUser(USER_B));
const result = await db.query(`DELETE FROM todos WHERE id = $1`, [todo.id]);
expect(result.rowCount).toBe(0);
});
it('should isolate three users from each other', async () => {
db.setContext(asUser(USER_A));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['A task']);
db.setContext(asUser(USER_B));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['B task']);
db.setContext(asUser(USER_C));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['C task']);
// Each user sees only their own
db.setContext(asUser(USER_A));
const aRows = await db.many(`SELECT * FROM todos`);
expect(aRows).toHaveLength(1);
expect(aRows[0].title).toBe('A task');
db.setContext(asUser(USER_B));
const bRows = await db.many(`SELECT * FROM todos`);
expect(bRows).toHaveLength(1);
expect(bRows[0].title).toBe('B task');
db.setContext(asUser(USER_C));
const cRows = await db.many(`SELECT * FROM todos`);
expect(cRows).toHaveLength(1);
expect(cRows[0].title).toBe('C task');
});
});
describe('role access control', () => {
it('should block anon from reading any data', async () => {
db.setContext(asUser(USER_A));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Exists']);
db.setContext({ role: 'anon' });
const { rows } = await db.query(`SELECT * FROM todos`);
expect(rows).toHaveLength(0);
});
it('should allow project_admin to bypass RLS via BYPASSRLS role attribute', async () => {
db.setContext(asUser(USER_A));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Admin can see me']);
db.setContext(asUser(USER_B));
await db.one(`INSERT INTO todos (title) VALUES ($1) RETURNING id`, ['Admin sees this too']);
// project_admin has BYPASSRLS — no per-table policy needed
db.setContext({ role: 'project_admin' });
const rows = await db.many(`SELECT * FROM todos`);
expect(rows.length).toBeGreaterThanOrEqual(2);
});
it('should confirm no project_admin_policy exists on the table', async () => {
const { rows } = await db.query(
`SELECT policyname FROM pg_policies WHERE tablename = 'todos' AND policyname = 'project_admin_policy'`
);
expect(rows).toHaveLength(0);
});
});