Files
meguri/tests/browser-contact-approval.cjs
T
Aleksandr BogomiakovandClaude 38105c65c2 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>
2026-10-07 16:06:10 +01:00

51 lines
3.0 KiB
JavaScript

const assert=require('node:assert/strict');
const fs=require('node:fs');
const {launchBrowser,isolatedUser,signIn}=require('./support/browser.cjs');
const base=process.env.MEGURI_BROWSER_URL||'http://127.0.0.1:3000';
assert(['localhost','127.0.0.1'].includes(new URL(base).hostname));
(async()=>{
const browser=await launchBrowser();
try {
const page=await browser.newPage({locale:'en-US',viewport:{width:1280,height:1000}});
const errors=[];page.on('pageerror',e=>errors.push(e.message));
// Sandbox 9 belongs to this suite; workspaces cannot be created, so the fixture configures it.
const tester=await isolatedUser({sandbox:9,prefix:'contact-approval',display:'Contact planner'});
await signIn(page,tester.username,tester.password);
await page.getByLabel('Workspace',{exact:true}).waitFor();
const fixture=await page.evaluate(async()=>{
const a=Alpine.$data(document.body);
const r=(await a.api('/api/workspaces/'+a.workspace.id)).workspace;
const start=a.addDays(a.today(),2),contact=crypto.randomUUID();
const doc=r.document;
doc.people.push({id:contact,user_id:null,display_name:'Email colleague',email:'colleague@example.com',country:'CY'});
doc.schedules=[{id:crypto.randomUUID(),name:'Contact duty',type:'rotation',participants:[a.user.id],anchor:start,shift_days:7,handover:'11:00:00'}];
await a.api('/api/workspaces/'+r.id+'/config','PUT',{revision:r.revision,document:doc});
return {workspace:r.id,start,contact};
});
await page.goto(base+'/?workspace='+fixture.workspace);
await page.locator('[data-duty-date="'+fixture.start+'"]').click();
const dialog=page.getByRole('dialog');
await dialog.getByLabel('Search colleagues').fill('colleague@example.com');
await dialog.getByRole('button',{name:'Email colleague',exact:true}).click();
const apply=dialog.getByRole('button',{name:'Apply now',exact:true});
await apply.waitFor();
await page.waitForFunction(()=>Alpine.$data(document.body).canSendRequest);
assert.equal(await apply.isEnabled(),true);
await dialog.getByText('Applies immediately. No approval needed.',{exact:true}).waitFor();
fs.mkdirSync('test-results',{recursive:true});
await page.screenshot({path:'test-results/contact-approval.png'});
await apply.click();
await dialog.waitFor({state:'hidden'});
const request=await page.evaluate(()=>Alpine.$data(document.body).doc.requests.at(-1));
assert.equal(request.status,'applied');
assert.equal(request.forced_by,null);
assert.deepEqual(request.required_approvers,[]);
await page.reload();
await page.waitForFunction(()=>Alpine.$data(document.body).doc?.requests.length);
assert.match(await page.locator('[data-duty-date="'+fixture.start+'"]').innerText(),/Email/);
assert.equal(await page.evaluate(id=>Alpine.$data(document.body).doc.requests.at(-1).legs[0].to_user,fixture.contact),fixture.contact);
assert.deepEqual(errors,[]);
console.log('PASS: email contact preview, immediate unforced save, calendar persistence, no JavaScript errors');
}finally{await browser.close();}
})().catch(e=>{console.error(e);process.exitCode=1;});