metis-desk/scripts/network-share-publisher-ui-audit.py

79 lines
10 KiB
Python

"""Alta SMB y publicación: regresión con HTTP real en el entorno visual desechable."""
import json
def verify(context, fixture, admin_password):
tool, browser, capture = context['tool'], context['browser'], context['capture']
origin, directory = context['origin'], context['directory']
company = fixture['company']
base = origin + '/companies/' + company + '/resources'
screenshots, checks = [], []
def code(client, source):
result = context['result_json'](tool(client, 'browser_run_code_unsafe', {
'code': 'async (page) => { page.setDefaultTimeout(15000); try { ' + source + ' return true; } catch(error) { return {failure: String(error.message)}; } }'}))
if isinstance(result, dict) and 'failure' in result:
raise RuntimeError(result['failure'])
return result
def create(client, name, active=True, smb=True):
print('Publicador SMB: ' + name, flush=True)
tool(client, 'browser_navigate', {'url': base})
code(client, "await page.getByRole('button',{name:'Nuevo recurso',exact:true}).click(); await page.getByRole('combobox',{name:'Plantilla de recurso',exact:true}).click(); await page.getByRole('option',{name:" + json.dumps('Carpeta compartida' if smb else 'Licencia de software') + ",exact:true}).click(); await page.getByRole('button',{name:'Siguiente',exact:true}).click();")
if smb:
unc = '\\\\alias-documental\\' + name.replace(' ', '-')
code(client, "await page.getByRole('textbox',{name:'Ruta UNC',exact:true}).fill(" + json.dumps(unc) + ");")
code(client, "await page.getByRole('button',{name:'Siguiente',exact:true}).click(); await page.getByRole('textbox',{name:'Nombre',exact:true}).fill(" + json.dumps(name) + ");")
if not active:
code(client, "await page.getByRole('combobox',{name:'Estado',exact:true}).click(); await page.getByRole('option',{name:'Inactivo',exact:true}).click();")
code(client, "await page.getByRole('button',{name:'Guardar',exact:true}).click(); await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Nuevo recurso',exact:true})}).waitFor({state:'hidden'});")
with browser(context['config']) as client:
try:
tool(client, 'browser_resize', {'width': 1440, 'height': 1000})
tool(client, 'browser_navigate', {'url': origin + '/login'})
code(client, "await page.getByRole('button',{name:'Entrar',exact:true}).waitFor();")
tool(client, 'browser_fill_form', {'fields': [
{'name': 'Correo electrónico', 'type': 'textbox', 'target': 'input[type=email]', 'value': fixture['email']},
{'name': 'Contraseña', 'type': 'textbox', 'target': 'input[type=password]', 'value': fixture['password']}]})
tool(client, 'browser_click', {'target': 'button[type=submit]'})
code(client, "await page.locator('[data-slot=workspace]').waitFor({timeout:30000});")
# Las comprobaciones y escrituras siguientes apuntan solo al contenedor sintético.
create(client, 'AUDIT-SMB Álvarez vinculado')
code(client, "await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Asignar publicación',exact:true})}).waitFor(); await page.getByRole('status').filter({hasText:'está guardada'}).waitFor(); if(!await page.getByRole('button',{name:'Confirmar',exact:true}).isDisabled())throw Error('Confirmación sin activo'); await page.getByRole('button',{name:'Seleccionar activo publicador',exact:true}).focus(); await page.keyboard.press('Enter'); await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Seleccionar activo publicador',exact:true})}).waitFor();")
checks.extend(['ordinary-user', 'create-smb-opens-publication', 'saved-resource-explained', 'no-default-publisher', 'keyboard-open-picker'])
code(client, "await page.getByRole('button',{name:'Siguiente',exact:true}).click(); await page.getByRole('button',{name:'Anterior',exact:true}).click(); await page.getByRole('textbox',{name:'Buscar',exact:true}).fill('NO-EXISTE-SMB'); await page.getByText('No hay elementos disponibles para esta búsqueda.',{exact:true}).waitFor(); await page.getByRole('textbox',{name:'Buscar',exact:true}).fill('AUDIT-R23-PORT-50'); await page.getByRole('radio',{name:'Seleccionar: AUDIT-R23-PORT-50',exact:true}).check(); await page.getByRole('button',{name:'Seleccionar',exact:true}).click(); await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Seleccionar activo publicador',exact:true})}).waitFor({state:'hidden'}); if(!await page.getByRole('button',{name:'Seleccionar activo publicador',exact:true}).evaluate(e=>e===document.activeElement))throw Error('Foco no restaurado');")
checks.extend(['asset-pagination', 'empty-search-recovery', 'asset-search', 'picker-focus-return'])
for width in [1440, 390, 320]:
tool(client, 'browser_resize', {'width': width, 'height': 900})
code(client, "if(await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Asignar publicación',exact:true})}).evaluate(e=>e.scrollWidth>e.clientWidth+1))throw Error('Desbordamiento horizontal');")
screenshots.append(capture(client, directory, 'publisher-create-' + str(width)))
tool(client, 'browser_resize', {'width': 1440, 'height': 1000})
pattern = '**/api/asset-network-share-publications/' + company
code(client, "await page.route(" + json.dumps(pattern) + ",route=>route.request().method()==='POST'?route.fulfill({status:409,contentType:'application/json',json:{code:'ASSET_INACTIVE'}}):route.continue()); await page.getByRole('button',{name:'Confirmar',exact:true}).click(); await page.getByRole('alert').waitFor(); if(await page.getByRole('button',{name:'Seleccionar activo publicador',exact:true}).textContent()!=='AUDIT-R23-PORT-50')throw Error('Selección perdida'); await page.unroute(" + json.dumps(pattern) + "); await page.getByRole('button',{name:'Confirmar',exact:true}).click(); await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Asignar publicación',exact:true})}).waitFor({state:'hidden'});")
outcome = code(client, "const resources=await (await page.request.get('" + origin + "/api/resources/" + company + "')).json(); const found=resources.resources.filter(r=>r.values.name==='AUDIT-SMB Álvarez vinculado'); if(found.length!==1)throw Error('Recurso duplicado'); const publications=await (await page.request.get('" + origin + "/api/asset-network-share-publications/" + company + "?resourceId='+found[0].id)).json(); if(publications.publications.length!==1||publications.publications[0].assetId!==" + json.dumps(fixture['softwareAssets'][-1]) + ")throw Error('Publicador incorrecto'); return {id:found[0].id};")
checks.extend(['publication-error-retains-selection', 'retry-no-duplicate-resource', 'real-publication-persisted', 'alias-does-not-infer-asset'])
tool(client, 'browser_navigate', {'url': base + '/' + outcome['id']})
code(client, "await page.getByRole('complementary').getByRole('button',{name:'Editar',exact:true}).click(); await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Editar recurso',exact:true})}).waitFor(); await page.getByRole('button',{name:'Guardar',exact:true}).click(); await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Editar recurso',exact:true})}).waitFor({state:'hidden'}); if(await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Asignar publicación',exact:true})}).count())throw Error('Edición repite alta');")
checks.append('editing-does-not-reopen-publication')
create(client, 'AUDIT-SMB pendiente')
code(client, "await page.getByRole('button',{name:'Vincular más tarde',exact:true}).click(); await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Asignar publicación',exact:true})}).waitFor({state:'hidden'}); const resources=await (await page.request.get('" + origin + "/api/resources/" + company + "')).json(); const r=resources.resources.find(r=>r.values.name==='AUDIT-SMB pendiente'); if(!r)throw Error('Recurso perdido'); const p=await (await page.request.get('" + origin + "/api/asset-network-share-publications/" + company + "?resourceId='+r.id)).json(); if(p.publications.length)throw Error('Publicación no confirmada');")
checks.append('link-later-keeps-unpublished-resource')
create(client, 'AUDIT-SMB inactivo', active=False)
code(client, "if(await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Asignar publicación',exact:true})}).count())throw Error('Alta inactiva ofrece publicación');")
create(client, 'AUDIT-LIC licencia', smb=False)
code(client, "if(await page.getByRole('dialog').filter({has:page.getByRole('heading',{name:'Asignar publicación',exact:true})}).count())throw Error('Recurso ordinario ofrece publicación');")
checks.extend(['inactive-share-skips-publication', 'ordinary-resource-skips-publication'])
code(client, "const response=await page.request.get('" + origin + "/api/asset-network-share-publications/" + fixture['otherCompanies'][0] + "'); if(!response.ok()||(await response.json()).publications.length)throw Error('Aislamiento de compañía');")
checks.append('other-company-has-no-publications')
except Exception:
capture(client, directory, 'publisher-failure')
snapshot = tool(client, 'browser_snapshot', {})
(directory / 'publisher-failure.json').write_text(json.dumps(snapshot, ensure_ascii=False))
raise
report = {'checks': checks, 'screenshots': screenshots, 'companies': 15,
'principal': {'people': 50, 'assets': 51, 'resources': 70, 'profiles': 50, 'workstations': 50},
'database': 'metis_desk_test', 'mcp': 'Docker stdio fijado'}
(directory / 'publisher-report.json').write_text(json.dumps(report, ensure_ascii=False, indent=2) + '\n')
print('Publicador SMB verificado: ' + str(len(checks)) + ' comprobaciones. Evidencias: ' + str(directory), flush=True)