fix(admin): refresh verified connection state without losing drafts
deploy-preland / deploy zoomstage.online (push) Successful in 56s

This commit is contained in:
Panfilov Konstantin committed 2026-10-25 19:27:09 +03:00
1 parent 522aaf8554
commit 6ec813bc99
2 files changed
+29 -1

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
/* preland#22: real verified status refresh, isolated to disposable LOCAL fixtures. */
const {chromium}=require('playwright-core');const assert=require('node:assert/strict');const fs=require('node:fs');
(async()=>{
const browser=await chromium.launch({executablePath:process.env.CHROMIUM_PATH||'/usr/bin/chromium',args:['--no-sandbox']});const page=await browser.newPage({baseURL:process.env.ADMIN_URL||'http://admin',viewport:{width:1440,height:1000}});
page.on('dialog',d=>d.accept());const errors=[];page.on('pageerror',e=>errors.push(e.message));let original=null,csrf='',primaryError=null;
const api=async(route,data)=>{const r=await page.request.post('/api/'+route,{data,headers:{'X-CSRF-Token':csrf}}).then(r=>r.json());assert.ok(r.ok,r.error);return r;};
const state=async()=>{const r=await page.request.get('/api/state').then(r=>r.json());assert.ok(r.ok,r.error);return r;};
const read=async id=>{const r=await page.request.get('/api/config?target='+id).then(r=>r.json());assert.ok(r.ok,r.error);return r;};
try{
await page.goto(process.env.ADMIN_URL||'http://admin');await page.getByLabel('Логин',{exact:true}).fill(process.env.ADMIN_USER||'admin');await page.getByLabel('Пароль',{exact:true}).fill(process.env.ADMIN_PASSWORD||'test-password');await page.getByRole('button',{name:'Войти',exact:true}).click();await page.locator('#workspace').waitFor({state:'visible'});
let current=await state();csrf=current.csrf;original=current.targets.find(t=>t.id===(process.env.TARGET_ID||'a'));assert.ok(original);assert.equal(original.type,'local','this test MUST NOT modify SSH targets');assert.ok(current.targets.some(t=>t.id==='b'&&t.enabled));
// A local adapter ignores port. Identity change invalidates verification without touching host/secret/root/config.
const changed={...original,port:original.port===22?23:22};const saved=await api('targets',{target:changed});assert.equal(saved.target.verified,false,'real server flag invalidated');
await page.reload();await page.locator('#workspace').waitFor({state:'visible'});current=await state();csrf=current.csrf;
const row=page.locator('#connection-list .connection').filter({hasText:original.domain}),header=page.locator('#overview-table th[data-overview-target="'+original.id+'"]');await header.waitFor();assert.match(await row.innerText(),/требуется проверка/);assert.match(await header.innerText(),/соединение не проверено/);
const before=await read(original.id);
await page.locator('#source-target').selectOption(original.id);await page.getByLabel('Основной сайт',{exact:true}).waitFor();await page.getByLabel('Основной сайт',{exact:true}).fill('unsaved-verification.example.org');await page.locator('#targets input[value=b]').check();await page.waitForFunction(()=>!document.querySelector('#targets').textContent.includes('чтение версии'));
const versionText=await page.locator('#read-state').innerText(),selected=await page.locator('#targets input:checked').evaluateAll(inputs=>inputs.map(i=>i.value));assert.equal(selected.length,2);
await page.locator('#builder-target').selectOption(original.id);const theme=page.locator('#builder-fields select[data-marker=theme]');await theme.waitFor();await theme.selectOption((await theme.inputValue())==='rnd'?'1':'rnd');const constructorDraft=await page.locator('#builder-fields select').evaluateAll(inputs=>inputs.map(i=>[i.dataset.marker,i.value]));
await row.getByRole('button',{name:'Проверить соединение',exact:true}).click();await page.locator('#indpic-result-modal').waitFor({state:'visible'});assert.match(await page.locator('#result-body').innerText(),/проверен/i);assert.equal((await state()).targets.find(t=>t.id===original.id).verified,true,'real backend verified flag true');
assert.match(await row.innerText(),/соединение проверено/,'connection row refreshed without reload');assert.doesNotMatch(await row.innerText(),/требуется проверка/);assert.doesNotMatch(await header.innerText(),/соединение не проверено/,'overview header refreshed without reload');
assert.equal(await page.getByLabel('Основной сайт',{exact:true}).inputValue(),'unsaved-verification.example.org');assert.equal(await page.locator('#read-state').innerText(),versionText);assert.deepEqual(await page.locator('#targets input:checked').evaluateAll(inputs=>inputs.map(i=>i.value)),selected);assert.deepEqual(await page.locator('#builder-fields select').evaluateAll(inputs=>inputs.map(i=>[i.dataset.marker,i.value])),constructorDraft);assert.match(await page.locator('#change-summary').innerText(),/host/);
const after=await read(original.id);assert.equal(after.version,before.version);assert.deepEqual(after.config,before.config);assert.deepEqual(errors,[]);
await page.screenshot({path:(process.env.UI_OUTPUT||'/tmp')+'/verified-refresh.png',fullPage:true});fs.writeFileSync((process.env.UI_OUTPUT||'/tmp')+'/verified-refresh-evidence.json',JSON.stringify({target:original.id,serverVerified:true,configVersion:after.version,selected,constructorDraftPreserved:true,pageErrors:errors},null,2));console.log('PASS real targets/test modal + connection/overview verified refresh + editor/selected2/CAS/constructor preserved/config unchanged');
}catch(error){primaryError=error;}finally{
const cleanup=[];if(original)try{await api('targets',{target:original});if(original.verified)await api('targets/test',{id:original.id});}catch(error){cleanup.push(error);}try{await browser.close();}catch(error){cleanup.push(error);}if(primaryError&&cleanup.length)throw new AggregateError([primaryError,...cleanup],'verification UI failure with separate fixture cleanup errors',{cause:primaryError});if(primaryError)throw primaryError;if(cleanup.length)throw new AggregateError(cleanup,'fixture cleanup failed');
}
})().catch(error=>{console.error(error);process.exit(1);});