87 lines
3.4 KiB
TypeScript
87 lines
3.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildVisibilityWhere, canUserSeeTask } from './visibility.js';
|
|
|
|
function makeUser(overrides: Partial<Express.User> = {}): Express.User {
|
|
return {
|
|
id: 'user-1', email: 'u@x.com', name: 'u', avatarUrl: null,
|
|
role: 'user', status: 'active',
|
|
orgIds: [], defaultVisibility: 'private', defaultVisibilityOrgId: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('buildVisibilityWhere', () => {
|
|
it('admin sees everything (1=1)', () => {
|
|
const w = buildVisibilityWhere(makeUser({ role: 'admin' }), 'lt');
|
|
expect(w.clause).toBe('1=1');
|
|
expect(w.params).toEqual([]);
|
|
});
|
|
|
|
it('user with no orgs: owner or public only', () => {
|
|
const w = buildVisibilityWhere(makeUser(), 'lt');
|
|
expect(w.clause).toContain('lt.owner_id = ?');
|
|
expect(w.clause).toContain("lt.visibility = 'public'");
|
|
expect(w.clause).toContain('IN (NULL)'); // empty orgs → never matches
|
|
expect(w.params).toEqual(['user-1']);
|
|
});
|
|
|
|
it('user with orgs: owner or public or same-org', () => {
|
|
const w = buildVisibilityWhere(makeUser({ orgIds: ['10', '20'] }), 'lt');
|
|
expect(w.clause).toMatch(/lt\.visibility_scope_org_id IN \(\?,\?\)/);
|
|
expect(w.params).toEqual(['user-1', '10', '20']);
|
|
});
|
|
|
|
it('respects custom table alias', () => {
|
|
const w = buildVisibilityWhere(makeUser(), 'j');
|
|
expect(w.clause).toContain('j.owner_id');
|
|
expect(w.clause).not.toContain('lt.');
|
|
});
|
|
});
|
|
|
|
describe('canUserSeeTask', () => {
|
|
const adminUser = makeUser({ role: 'admin' });
|
|
const aliceNoOrg = makeUser({ id: 'alice' });
|
|
const bobOrg10 = makeUser({ id: 'bob', orgIds: ['10'] });
|
|
|
|
it('admin sees private tasks of others', () => {
|
|
const t = { ownerId: 'someone-else', visibility: 'private' as const, visibilityScopeOrgId: null };
|
|
expect(canUserSeeTask(adminUser, t)).toBe(true);
|
|
});
|
|
|
|
it('owner sees own private task', () => {
|
|
const t = { ownerId: 'alice', visibility: 'private' as const, visibilityScopeOrgId: null };
|
|
expect(canUserSeeTask(aliceNoOrg, t)).toBe(true);
|
|
});
|
|
|
|
it('non-owner cannot see another user\'s private task', () => {
|
|
const t = { ownerId: 'someone-else', visibility: 'private' as const, visibilityScopeOrgId: null };
|
|
expect(canUserSeeTask(aliceNoOrg, t)).toBe(false);
|
|
});
|
|
|
|
it('public task is visible to anyone', () => {
|
|
const t = { ownerId: 'someone-else', visibility: 'public' as const, visibilityScopeOrgId: null };
|
|
expect(canUserSeeTask(aliceNoOrg, t)).toBe(true);
|
|
});
|
|
|
|
it('org task: same org member can see', () => {
|
|
const t = { ownerId: 'someone-else', visibility: 'org' as const, visibilityScopeOrgId: '10' };
|
|
expect(canUserSeeTask(bobOrg10, t)).toBe(true);
|
|
});
|
|
|
|
it('org task: different org member cannot see', () => {
|
|
const t = { ownerId: 'someone-else', visibility: 'org' as const, visibilityScopeOrgId: '99' };
|
|
expect(canUserSeeTask(bobOrg10, t)).toBe(false);
|
|
});
|
|
|
|
it('org task with null scope: only owner can see', () => {
|
|
const t = { ownerId: 'alice', visibility: 'org' as const, visibilityScopeOrgId: null };
|
|
expect(canUserSeeTask(aliceNoOrg, t)).toBe(true);
|
|
expect(canUserSeeTask(bobOrg10, t)).toBe(false);
|
|
});
|
|
|
|
it('owner with null ownerId: not matched (null !== null check skipped)', () => {
|
|
const t = { ownerId: null, visibility: 'private' as const, visibilityScopeOrgId: null };
|
|
expect(canUserSeeTask(aliceNoOrg, t)).toBe(false);
|
|
});
|
|
});
|