const fs=require('node:fs'); const vm=require('node:vm'); const test=require('node:test'); const assert=require('node:assert/strict'); const auth=fs.readFileSync('web/static/js/auth.js','utf8'); const settings=fs.readFileSync('web/static/js/settings.js','utf8'); function input(id,value='') { const attrs={}; return {id,value,type:'password',style:{},classList:{toggle(){},remove(){}},focus(){this.focused=true;},setAttribute(k,v){attrs[k]=v;},getAttribute:k => attrs[k]}; } function harness(values={}) { const fields={}; for (const id of ['auth-current-password','auth-new-password','auth-confirm-password','login-username','login-password']) fields[id]=input(id,values[id]||''); for (const id of ['auth-current-password-error','auth-new-password-error','auth-confirm-password-error','password-form-error','login-error','login-password-toggle']) fields[id]=input(id); const button={disabled:false};let calls=0; const ctx={document:{getElementById:id => fields[id]||null,querySelector:() => button},window:{t:x => x},alert(){},console:{error(){}}, fetch:async () => {calls++;throw Error('unexpected login request');}, apiFetch:async () => {calls++;return {ok:false,json:async () => ({error:'test-only incorrect current password'})};}}; vm.createContext(ctx); let start=auth.indexOf('function setLoginPasswordVisible('); vm.runInContext(auth.slice(start,auth.indexOf('function showLoginOverlay(',start)),ctx); start=auth.indexOf('function clearLoginFormError('); vm.runInContext(auth.slice(start,auth.indexOf('\nfunction ',start)),ctx); start=settings.indexOf('function setPasswordFieldError('); vm.runInContext(settings.slice(start,settings.indexOf('// ==================== 外部MCP管理',start)),ctx); return {ctx,fields,button,calls:() => calls}; } test('empty login password explains the error and never calls the server',async () => { const h=harness({'login-username':'admin'}); await h.ctx.submitLogin({preventDefault(){}}); assert.equal(h.calls(),0); assert.equal(h.fields['login-error'].textContent,'auth.enterPassword'); assert.equal(h.fields['login-password'].getAttribute('aria-invalid'),'true'); assert.equal(h.fields['login-password'].focused,true); h.ctx.clearLoginFormError({target:h.fields['login-password']}); assert.equal(h.fields['login-error'].style.display,'none'); assert.equal(h.fields['login-password'].getAttribute('aria-invalid'),'false'); assert.match(fs.readFileSync('web/templates/index.html','utf8'),/