76 lines
3.2 KiB
TypeScript
76 lines
3.2 KiB
TypeScript
/**
|
|
* End-to-end: a local-org member sees 'org'-scoped resources; a non-member
|
|
* does not. Validates the whole chain — local org membership → resolveOrgIds
|
|
* → session.orgIds → the provider-agnostic buildVisibilityWhere.
|
|
*
|
|
* See docs/superpowers/plans/2026-06-09-local-orgs.md.
|
|
*/
|
|
import { afterEach, beforeEach, describe, it, expect } from 'vitest';
|
|
import { mkdtempSync, rmSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { tmpdir } from 'os';
|
|
import { Repository } from '../db/repository.js';
|
|
import { runMigrations } from '../db/migrate.js';
|
|
import { resolveOrgIds } from './auth.js';
|
|
|
|
function viewer(id: string, orgIds: string[]): Express.User {
|
|
return {
|
|
id, email: `${id}@x.com`, name: id, avatarUrl: null,
|
|
role: 'user', status: 'active', orgIds,
|
|
defaultVisibility: 'private', defaultVisibilityOrgId: null,
|
|
};
|
|
}
|
|
|
|
describe('local orgs — org visibility E2E', () => {
|
|
let tempDir = '';
|
|
let repo: Repository;
|
|
|
|
beforeEach(() => {
|
|
tempDir = mkdtempSync(join(tmpdir(), 'maestro-orgvis-'));
|
|
repo = new Repository(join(tempDir, 'orchestrator.db'));
|
|
runMigrations(repo.getDb());
|
|
});
|
|
afterEach(() => {
|
|
repo.close();
|
|
if (tempDir) { rmSync(tempDir, { recursive: true, force: true }); tempDir = ''; }
|
|
});
|
|
|
|
it('resolveOrgIds includes the local orgs a user belongs to', () => {
|
|
const alice = repo.createUser({ email: 'a@x.com', name: 'A', role: 'user', status: 'active' }).id;
|
|
const org = repo.createLocalOrg('Team', alice);
|
|
repo.addOrgMember(org.id, alice);
|
|
expect(resolveOrgIds(repo, alice)).toContain(org.id);
|
|
});
|
|
|
|
it('an org member sees an org-scoped task; a non-member does not', async () => {
|
|
const carol = repo.createUser({ email: 'carol@x.com', name: 'Carol', role: 'user', status: 'active' }).id;
|
|
const alice = repo.createUser({ email: 'alice@x.com', name: 'Alice', role: 'user', status: 'active' }).id;
|
|
const bob = repo.createUser({ email: 'bob@x.com', name: 'Bob', role: 'user', status: 'active' }).id;
|
|
const org = repo.createLocalOrg('Team', carol);
|
|
repo.addOrgMember(org.id, carol);
|
|
repo.addOrgMember(org.id, alice); // alice is a member, bob is not
|
|
|
|
await repo.createLocalTask({
|
|
title: 'org task', body: 'b', ownerId: carol,
|
|
visibility: 'org', visibilityScopeOrgId: org.id,
|
|
});
|
|
|
|
const aliceTasks = await repo.listLocalTasks({ viewer: viewer(alice, resolveOrgIds(repo, alice)) });
|
|
const bobTasks = await repo.listLocalTasks({ viewer: viewer(bob, resolveOrgIds(repo, bob)) });
|
|
|
|
expect(aliceTasks.some(t => t.title === 'org task')).toBe(true); // member sees it
|
|
expect(bobTasks.some(t => t.title === 'org task')).toBe(false); // non-member does not
|
|
});
|
|
|
|
it('the org name resolves on display after the COALESCE extension', async () => {
|
|
const carol = repo.createUser({ email: 'c@x.com', name: 'C', role: 'user', status: 'active' }).id;
|
|
const org = repo.createLocalOrg('Engineering', carol);
|
|
repo.addOrgMember(org.id, carol);
|
|
const t = await repo.createLocalTask({
|
|
title: 'x', body: 'b', ownerId: carol, visibility: 'org', visibilityScopeOrgId: org.id,
|
|
});
|
|
const got = await repo.getLocalTask(t.id, { viewer: viewer(carol, resolveOrgIds(repo, carol)) });
|
|
expect(got?.visibilityScopeOrgName).toBe('Engineering');
|
|
});
|
|
});
|