65 lines
5.7 KiB
JavaScript
65 lines
5.7 KiB
JavaScript
const assert=require('node:assert/strict');
|
|||
|
|
const fs=require('node:fs');
|
||
|
|
const {launchBrowser}=require('./support/browser.cjs');
|
||
|
|
const base=process.env.MEGURI_BROWSER_URL||'http://127.0.0.1:3011';
|
||
|
|
const username=process.env.MEGURI_BROWSER_SITE_USERNAME;
|
||
|
|
assert(username,'Set MEGURI_BROWSER_SITE_USERNAME to a dedicated test administrator');
|
||
|
|
assert(['localhost','127.0.0.1'].includes(new URL(base).hostname));
|
||
|
|
(async()=>{
|
||
|
|
const browser=await launchBrowser();
|
||
|
|
try{
|
||
|
|
const context=await browser.newContext({locale:'en-US',viewport:{width:1400,height:1050}});
|
||
|
|
const page=await context.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));page.on('dialog',d=>d.accept());
|
||
|
|
await page.goto(base+'/?view=site');
|
||
|
|
await page.getByLabel('Username',{exact:true}).fill(username);
|
||
|
|
await page.getByLabel('Password',{exact:true}).fill(process.env.MEGURI_BROWSER_SITE_PASSWORD||'local-browser-test-password');
|
||
|
|
await page.getByRole('button',{name:'Sign in',exact:true}).click();
|
||
|
|
await page.getByRole('link',{name:'Site administration',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>Alpine.$data(document.body).siteData);
|
||
|
|
const root=page.locator('.site-admin');
|
||
|
|
await root.getByLabel('Search',{exact:true}).fill(username);
|
||
|
|
await root.getByRole('button',{name:'Edit',exact:true}).click();
|
||
|
|
await root.getByLabel('Display name',{exact:true}).fill('Administration browser test');
|
||
|
|
await root.getByRole('button',{name:'Save changes',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>!Alpine.$data(document.body).busy&&!Alpine.$data(document.body).siteEdit);
|
||
|
|
await root.getByRole('button',{name:'Authentication',exact:true}).click();
|
||
|
|
assert(await root.getByText(/Configuration draft only/).isVisible());
|
||
|
|
await root.getByLabel('Enable OIDC single sign-on',{exact:true}).check();
|
||
|
|
await root.getByLabel('Allow password login',{exact:true}).uncheck();
|
||
|
|
await root.getByLabel('Issuer URL',{exact:true}).fill('https://identity.example.com/realms/company');
|
||
|
|
await root.getByLabel('Client ID',{exact:true}).fill('meguri-browser-test');
|
||
|
|
await root.getByLabel('Login button label',{exact:true}).fill('Company SSO');
|
||
|
|
await root.getByRole('button',{name:'Save configuration draft',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>!Alpine.$data(document.body).busy);
|
||
|
|
assert.equal(await page.locator('.error:visible').count(),0);
|
||
|
|
await page.reload();await page.waitForFunction(()=>Alpine.$data(document.body).siteData);
|
||
|
|
await root.getByRole('button',{name:'Authentication',exact:true}).click();
|
||
|
|
assert.equal(await root.getByLabel('Login button label',{exact:true}).inputValue(),'Company SSO');
|
||
|
|
assert.equal(await root.getByLabel('Allow password login',{exact:true}).isChecked(),false);
|
||
|
|
fs.mkdirSync('test-results',{recursive:true});await page.screenshot({path:'test-results/site-admin-desktop.png',fullPage:true});
|
||
|
|
await page.setViewportSize({width:390,height:844});await page.screenshot({path:'test-results/site-admin-mobile.png',fullPage:true});
|
||
|
|
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));
|
||
|
|
// Current password authentication stays active despite the saved draft.
|
||
|
|
const guest=await browser.newContext({locale:'en-US'});const other=await guest.newPage();await other.goto(base);
|
||
|
|
await other.getByLabel('Username',{exact:true}).fill(username);await other.getByLabel('Password',{exact:true}).fill(process.env.MEGURI_BROWSER_SITE_PASSWORD||'local-browser-test-password');await other.getByRole('button',{name:'Sign in',exact:true}).click();await other.waitForFunction(()=>Alpine.$data(document.body).user);
|
||
|
|
// Non-admins cannot enumerate the site, even with direct requests.
|
||
|
|
const ordinary=await browser.newContext();const userPage=await ordinary.newPage();await userPage.goto(base);
|
||
|
|
const denied=await userPage.evaluate(async()=>{const username='site-member-'+Date.now();const r=await fetch('/api/register',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username,email:username+'@example.com',display_name:'Ordinary user',password:'local-browser-test-password'})});if(!r.ok)throw new Error('signup');return (await fetch('/api/site')).status;});assert.equal(denied,403);
|
||
|
|
const created=await userPage.evaluate(async()=>{const session=await (await fetch('/api/session')).json();const result=await fetch('/api/workspaces',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':session.csrf},body:JSON.stringify({name:'Site UI workspace '+Date.now()})});if(!result.ok)throw new Error('workspace');return {workspace:await result.json(),user:session.user};});
|
||
|
|
await page.setViewportSize({width:1400,height:1050});
|
||
|
|
await root.getByRole('button',{name:'Refresh',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>!Alpine.$data(document.body).siteLoading);
|
||
|
|
await root.getByRole('button',{name:'Workspaces',exact:true}).click();
|
||
|
|
await root.getByLabel('Search',{exact:true}).fill('Site UI workspace');
|
||
|
|
await root.getByRole('button',{name:'Edit',exact:true}).last().click();
|
||
|
|
await root.getByLabel('Name',{exact:true}).fill('Site UI renamed workspace');
|
||
|
|
await root.getByLabel('Grant owner access to',{exact:true}).selectOption({label:username});
|
||
|
|
await root.getByRole('button',{name:'Save changes',exact:true}).click();
|
||
|
|
await page.waitForFunction(()=>!Alpine.$data(document.body).busy&&!Alpine.$data(document.body).siteEdit);
|
||
|
|
assert.equal(await page.locator('.error:visible').count(),0);
|
||
|
|
assert(await page.evaluate(()=>Alpine.$data(document.body).directoryState.workspaces.some(w=>w.name==='Site UI renamed workspace')));
|
||
|
|
assert.deepEqual(errors,[]);
|
||
|
|
await context.close();await guest.close();await ordinary.close();
|
||
|
|
console.log('PASS: site navigation, profile editing, persisted OIDC draft, unchanged password login, denied non-admin access, desktop/mobile');
|
||
|
|
}finally{await browser.close();}
|
||
|
|
})().catch(e=>{console.error(e);process.exitCode=1;});
|