- Added `removeAccessAccount` function to handle account deletion in the database, ensuring no external references exist. - Created `deleteAccount` API function to facilitate account deletion requests from the client. - Developed `AccountDeleteDialog` component for user confirmation before account deletion, integrating with the delete API. - Introduced resource image handling with validation for PNG uploads, including UI components for image selection and preview. - Implemented resource icon template evolution logic to maintain icon bindings without overwriting existing fields. - Added scripts for auditing account deletion and usage checks, ensuring robust testing of the new features.
20 lines
3.0 KiB
Python
20 lines
3.0 KiB
Python
"""Comprueba el control PNG compartido mediante el navegador MCP de la auditoría aislada."""
|
||
import base64
|
||
import json
|
||
from pathlib import Path
|
||
|
||
|
||
def assert_list_icons(client, code, selector='[data-software-product] img', minimum=1):
|
||
code(client, "await page.locator(" + json.dumps(selector) + ").first().scrollIntoViewIfNeeded(); await page.waitForFunction(({selector,minimum})=>{const images=[...document.querySelectorAll(selector)].slice(0,minimum);return images.length===minimum&&images.every(image=>image.complete&&image.naturalWidth>0&&image.getAttribute('src').startsWith('data:image/png;base64,'));},{selector:" + json.dumps(selector) + ",minimum:" + str(minimum) + "}); if(await page.locator('body').innerText().then(text=>text.includes('data:image/png;base64,')))throw Error('Base64 visible como texto');")
|
||
|
||
|
||
def verify(client, code):
|
||
icon = Path(__file__).resolve().parent.parent / 'apps/server/data/resource-icons/windows-11.png'
|
||
encoded = base64.b64encode(icon.read_bytes()).decode('ascii')
|
||
valid = "{name:'icon.png',mimeType:'image/png',buffer:Buffer.from(" + json.dumps(encoded) + ",'base64')}"
|
||
code(client, "await page.getByLabel('Icono',{exact:true}).setInputFiles({name:'invalid.jpg',mimeType:'image/jpeg',buffer:Buffer.from('invalid')}); await page.getByText('Selecciona un PNG válido de hasta 512 × 512 píxeles y 2 MiB.',{exact:true}).waitFor(); if(await page.getByLabel('Icono',{exact:true}).evaluate(e=>e.checkValidity()))throw Error('Imagen inválida aceptada');")
|
||
code(client, "await page.getByLabel('Icono',{exact:true}).setInputFiles(" + valid + "); await page.getByAltText('Vista previa de Icono',{exact:true}).waitFor(); await page.getByAltText('Vista previa de Icono',{exact:true}).scrollIntoViewIfNeeded(); await page.waitForFunction(()=>[...document.querySelectorAll('dialog img,[role=dialog] img')].some(e=>e.complete&&e.naturalWidth>0));")
|
||
code(client, "await page.getByLabel('Icono',{exact:true}).setInputFiles({name:'corrupt.png',mimeType:'image/png',buffer:Buffer.from('invalid')}); await page.getByText('Selecciona un PNG válido de hasta 512 × 512 píxeles y 2 MiB.',{exact:true}).waitFor(); if(!await page.getByAltText('Vista previa de Icono',{exact:true}).count())throw Error('Se perdió el icono anterior'); await page.getByRole('button',{name:'Quitar imagen',exact:true}).focus(); await page.keyboard.press('Space'); await page.getByAltText('Vista previa de Icono',{exact:true}).waitFor({state:'detached'}); if(!await page.getByLabel('Icono',{exact:true}).evaluate(e=>e.checkValidity()))throw Error('Error no retirado');")
|
||
code(client, "await page.getByLabel('Icono',{exact:true}).setInputFiles(" + valid + "); await page.getByAltText('Vista previa de Icono',{exact:true}).waitFor(); if(!await page.getByLabel('Icono',{exact:true}).evaluate(e=>e.checkValidity()))throw Error('PNG válido bloqueado');")
|
||
return ['png-upload-preview', 'invalid-format-blocked', 'invalid-png-blocked', 'previous-image-preserved', 'remove-with-keyboard', 'replace-png']
|