Merge NEXT: planning, generation map and portable configuration on top of the OIDC access model
NEXT predates the access work, so the identity layer follows master: access comes from configured groups, instance administrators replace the site administration, and accounts are created by SSO or by an administrator. Kept from NEXT: manual publication with the Review changes panel and shared generator drafts, portable import/export (meguri/v2), the layered generation map with binding provenance, JSM provider changes, contact/approval semantics and the account-owned Person. Dropped: site administration, local sign-up, and the mandatory unique email with its contact/account migrations. The manual-publication migration is renumbered m_0010 after m_0009_access. Accounts without an email still get a Person in every workspace they reach, a changed sign-in email updates the account's person, and an account that loses access keeps its linked person so schedules stay valid. Instance administrators may use Sync and shared drafts. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
38105c65c2
57 files changed
+6890
-431
No files matched your search
@@ -15,12 +15,26 @@ const baseUrl = () => process.env.MEGURI_BROWSER_URL || 'http://127.0.0.1:3000';
|
||||
exports.baseUrl = baseUrl;
|
||||
|
||||
// The demo generates one password per server start and publishes it to the login screen.
|
||||
// Generator drafts are shared and persisted per workspace, so a suite that edited a demo
|
||||
// workspace without applying leaves them behind; discard them when a suite starts.
|
||||
let password;
|
||||
exports.demoPassword = () => password ||= fetch(baseUrl() + '/api/session').then(r => r.json()).then(session => {
|
||||
exports.demoPassword = () => password ||= fetch(baseUrl() + '/api/session').then(r => r.json()).then(async session => {
|
||||
if (!session.demo_lifecycle?.password) throw new Error('Browser tests require a disposable demo instance');
|
||||
await discardDemoDrafts(session.demo_lifecycle.password);
|
||||
return session.demo_lifecycle.password;
|
||||
});
|
||||
|
||||
async function discardDemoDrafts(secret) {
|
||||
const lead = await exports.apiLogin('alice', secret);
|
||||
for (const { id } of (await lead.get('/api/workspaces')).workspaces) {
|
||||
const detail = await lead.get('/api/workspaces/' + id);
|
||||
const draft = detail.generation_draft;
|
||||
if (!draft?.payload) continue;
|
||||
const saved = detail.workspace.document.generation || {};
|
||||
await lead.put(`/api/workspaces/${id}/generation-draft`, { revision: draft.revision, payload: { revision: detail.workspace.revision, variables: saved.variables || [], rules: saved.rules || [], route_position: saved.route_position || 0 } });
|
||||
}
|
||||
}
|
||||
|
||||
exports.signIn = async (page, username, secret) => {
|
||||
await page.goto(baseUrl());
|
||||
await page.getByLabel('Username', { exact: true }).fill(username);
|
||||
@@ -54,21 +68,21 @@ exports.adminApi = () => exports.apiLogin('admin');
|
||||
// Mutating suites each own one empty sandbox workspace ("Sandbox N", demo started with
|
||||
// MEGURI_DEMO_SANDBOXES) whose groups are sandbox-N-leads (administrators) and sandbox-N-staff (employees).
|
||||
// Nobody is in a sandbox except accounts created here, so suites do not depend on each other or on order.
|
||||
exports.createUser = async (admin, { sandbox, role = 'admin', prefix, display }) => {
|
||||
exports.createUser = async (admin, { sandbox, role = 'admin', prefix, display, email }) => {
|
||||
const overview = await admin.get('/api/admin/overview');
|
||||
const target = overview.workspaces.find(w => w.name === `Sandbox ${sandbox}` && !w.disabled_at);
|
||||
if (!target) throw new Error(`Start the demo with MEGURI_DEMO_SANDBOXES>=${sandbox}: Sandbox ${sandbox} not found`);
|
||||
const groups = role === 'admin' ? target.admin_groups : target.member_groups;
|
||||
const username = `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.toLowerCase();
|
||||
const secret = 'local-browser-test-password';
|
||||
await admin.post('/api/admin/users', { username, display_name: display, password: secret, groups });
|
||||
return { username, password: secret, display, workspace: target };
|
||||
await admin.post('/api/admin/users', { username, display_name: display, password: secret, groups, ...(email ? { email } : {}) });
|
||||
return { username, password: secret, display, email, workspace: target };
|
||||
};
|
||||
|
||||
// An administrator of exactly one sandbox, plus any employees (display names) created beside them.
|
||||
exports.isolatedUser = async ({ sandbox, prefix, display, employees = [] }) => {
|
||||
exports.isolatedUser = async ({ sandbox, prefix, display, email, employees = [] }) => {
|
||||
const admin = await exports.adminApi();
|
||||
const user = await exports.createUser(admin, { sandbox, role: 'admin', prefix, display });
|
||||
const user = await exports.createUser(admin, { sandbox, role: 'admin', prefix, display, email });
|
||||
const staff = [];
|
||||
for (const name of employees) staff.push(await exports.createUser(admin, { sandbox, role: 'member', prefix: name.split(' ')[0], display: name }));
|
||||
return { admin, ...user, staff };
|
||||
|
||||
Reference in new issue
Block a user