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;});