#!/usr/bin/env python3
"""Изолированные искусственные отказы. Не настоящий браузерный/сетевой допуск."""
import asyncio,json,sys
from pathlib import Path
from playwright.async_api import async_playwright
from офлайн_стенд import ROOT,open_offline,BROWSER
from испытать_офлайн_помощник import fill_form,blob,wait_download
OUT=ROOT/'доказательства/редакция-4.13.0';rows=[]
SCOPE='Изолированный DOM; fetch, digest, хранилище и скачивание заменены стендом. Политики браузера не менялись. Нет настоящей сетевой, печатной или владельческой проверки.'
def record(name,risk,inp,ok,result):
 rows.append({'проверка':name,'риск':risk,'вход':inp,'пройдено':bool(ok),'фактический_результат':result,'предел_вывода':SCOPE,'команда':'python инструменты/испытать-отказы-интерфейса-повтор-4-13.py'});print(name,bool(ok),flush=True)
async def main():
 async with async_playwright() as p:
  b=await p.chromium.launch(executable_path=BROWSER,headless=True,args=['--no-sandbox'])
  for name in ['переносимый-аудит.html','проверка-симметрии.html','проверка-отказов.html','пакет-обжалования.html','реестр-требований.html','жизненный-цикл-данных.html','сущности.html','атлас.html','происхождение.html','учения.html']:
   if not (ROOT/name).exists():continue
   page,errors,req=await open_offline(b,name,1280,"window.fetch=async()=>{throw Error('Искусственный отказ сети 4.13.0')}")
   await page.wait_for_timeout(400);txt=await page.locator('body').inner_text()
   if name=='происхождение.html':
    original=await page.locator('[data-provenance-card]:visible').count();await page.locator('#provenance-search').fill('НЕСУЩЕСТВУЮЩАЯСТРОКА440');empty=await page.locator('[data-provenance-card]:visible').count();await page.locator('#provenance-search').fill('');restored=await page.locator('[data-provenance-card]:visible').count()
    record('Происхождение: встроенная карта при отказе сети','Зависимость локального поиска от сети','fetch отклонён; поиск, отсутствие совпадений, очистка',original>0 and empty==0 and original==restored and not errors,{'карточек':original,'после_поиска':empty,'после_очистки':restored,'сеть':'Для этого инструмента не используется; не выдаём фиктивную сетевую ошибку.'});await page.close();continue
   indications=[t for t in ['не запущен','не загружен','не загружена','недоступ','Не удалось','Искусственный отказ','ошибка','Ошибка'] if t in txt]
   record('Отказ чтения данных: '+name,'Пустой или ложный успешный инструмент при недоступных данных','fetch отклоняет каждый запрос',bool(indications) and not errors,{'сообщения':indications,'исключения':errors,'примечание':'Проверено наличие сообщения, не полнота всех состояний инструмента.'})
   await page.close()
  page,errors,_=await open_offline(b,'реестр-требований.html',1280);await page.wait_for_timeout(300)
  await page.evaluate("() => {localStorage.setItem=()=>{throw Error('Искусственная квота')}}")
  await fill_form(page,'#форма-требования');await page.wait_for_timeout(350)
  t=await page.locator('#статус-требований').inner_text();n=await page.locator('#журнал-требований article').count()
  record('Реестр: недоступная запись','Ложное долговременное сохранение','setItem отклонён',n==1 and 'оперативной памяти' in t,{'записей':n,'сообщение':t})
  await page.locator('[data-action="сброс-требований"]').click();t=await page.locator('#статус-требований').inner_text()
  record('Реестр: неудачная очистка','Потеря несохранённой работы','setItem отклонён при очистке',await page.locator('#журнал-требований article').count()==1 and 'не выполнена' in t,t)
  await page.locator('#импорт-требований').set_input_files({'name':'подмена.json','mimeType':'application/json','buffer':b'{"a":1,"a":2}'})
  await page.wait_for_timeout(300);t=await page.locator('#статус-требований').inner_text()
  record('Реестр: отклонённый импорт сохраняет запись','Повреждение рабочего реестра','Повторный ключ в JSON',await page.locator('#журнал-требований article').count()==1 and 'Импорт отклонён' in t,t);await page.close()
  page,errors,_=await open_offline(b,'пакет-обжалования.html',1280);await page.wait_for_timeout(300)
  await fill_form(page,'#форма-процессуального-комплекта');await page.wait_for_timeout(200)
  ident=await page.locator('#дело-код').inner_text()
  await page.locator('#импорт-приложений').set_input_files({'name':'опасное.html','mimeType':'text/html','buffer':b'<script>alert(1)</script>'});await page.wait_for_timeout(200)
  t=await page.locator('#статус-дела').inner_text();attachments=await page.locator('#приложения-дела').inner_text()
  record('Дело: опасное вложение','Активный код в переносимом деле','HTML со script', 'опасное.html' not in attachments and bool(t),t)
  await page.locator('#импорт-дела').set_input_files({'name':'bad.json','mimeType':'application/json','buffer':b'{}'});await page.wait_for_timeout(300)
  t=await page.locator('#статус-дела').inner_text()
  record('Дело: восстановление после неудачного импорта','Замена рабочей папки неверным входом','Пустой объект',await page.locator('#дело-код').inner_text()==ident and await page.locator('#документы-дела option').count()==5 and 'Импорт отклонён' in t,t)
  # Native download -> import cannot be certified in detached DOM after Worker integration.
  await page.evaluate("Object.defineProperty(navigator,'clipboard',{configurable:true,value:{writeText:async()=>{throw Error('Искусственный запрет')}}})")
  await page.locator('[data-action="копировать-документ"]').click();t=await page.locator('#статус-дела').inner_text()
  record('Дело: буфер обмена недоступен','Ложное сообщение о копировании','writeText отклонён','Копирование недоступно' in t,t);await page.close()
  page,errors,_=await open_offline(b,'журнал-дрейфа.html',1280)
  await page.locator('#drift-project').fill('Искусственный сохранённый во вкладке проект')
  await page.evaluate("() => {localStorage.setItem=()=>{throw Error('Квота')};localStorage.removeItem=()=>{throw Error('Отказ')}}")
  await page.locator('#drift-save-project').click();t=await page.locator('#drift-status').inner_text()
  record('Лаборатория: отказ сохранения','Ложная долговременная запись','setItem отклонён','Сохранение не выполнено' in t,t)
  await page.locator('#drift-reset').click();t=await page.locator('#drift-status').inner_text()
  record('Лаборатория: отказ очистки','Потеря данных после отказа хранилища','removeItem отклонён','Очистка не выполнена' in t and 'Искусственный' in await page.locator('#drift-project').input_value(),t)
  await page.locator('#drift-export').click();await wait_download(page,1);d=await blob(page)
  await page.locator('#drift-import').set_input_files({'name':'good.json','mimeType':'application/json','buffer':d['text'].encode()});await page.wait_for_timeout(200)
  await page.locator('#drift-export').click();await wait_download(page,2);d2=await blob(page)
  original=json.loads(d['text']);restored=json.loads(d2['text']);origin=restored.pop('исходник_импорта',None)
  import base64,hashlib
  exact=bool(origin) and base64.b64decode(origin['база64'])==d['text'].encode() and origin['ша256']==hashlib.sha256(d['text'].encode()).hexdigest()
  record('Лаборатория: экспорт и обратный импорт','Отсутствующий общий parser или потеря состояния','JSON самого инструмента; новые сведения о происхождении сравниваются отдельно',original==restored and exact and not errors,{'данные_тождественны':original==restored,'исходные_байты_сохранены':exact,'ошибки':errors})
  await page.locator('#drift-import').set_input_files({'name':'bad.json','mimeType':'application/json','buffer':b'{"a":1,"a":2}'});await page.wait_for_timeout(200);t=await page.locator('#drift-status').inner_text()
  record('Лаборатория: повторные ключи','Неоднозначный JSON','Повторный ключ','Импорт журнала отклонён' in t,t);await page.close()
  # Actual DOM anchors are inspected in a fresh isolated document, not claimed as URL navigation.
  page,errors,_=await open_offline(b,'сущности.html',1280);await page.wait_for_timeout(350)
  broken=await page.locator('main a[href^="#"]').evaluate_all("es=>es.filter(e=>!document.getElementById(decodeURIComponent(e.hash.slice(1)))).map(e=>e.hash)")
  record('Граф: локальные якоря отрисованного документа','Ссылка без цели в интерфейсе','Свежий изолированный документ',not broken and not errors,{'отсутствующие_якоря':broken,'ошибки':errors,'настоящая_навигация':'не выполнена'});await page.close()
  # Find a genuine existing general clipboard control, rather than inventing a target.
  from bs4 import BeautifulSoup
  for path in sorted(ROOT.glob('*.html')):
   if not BeautifulSoup(path.read_text(),'html.parser').select('[data-copy-target]'):continue
   page,errors,_=await open_offline(b,path.name,1280)
   await page.evaluate("Object.defineProperty(navigator,'clipboard',{configurable:true,value:{writeText:async()=>{throw Error('Запрет')}}});document.execCommand=()=>false")
   button=page.locator('[data-copy-target]').first;await button.click();await page.wait_for_timeout(150);txt=await page.locator('body').inner_text()
   record('Общий буфер обмена: оба пути отказали','Ложный успех fallback','writeText отклонён, execCommand false','Копирование не выполнено' in txt,{'страница':path.name,'сообщение_об_отказе':'Копирование не выполнено' in txt,'фокус':await page.evaluate('document.activeElement?.id')});await page.close();break
  await b.close()
 OUT.mkdir(parents=True,exist_ok=True)
 (OUT/'отказы-интерфейса.json').write_text(json.dumps({'редакция':'4.13.0-wip','искусственные_входы':True,'проверок':len(rows),'пройдено':sum(r['пройдено'] for r in rows),'предел':SCOPE,'не_выполнено':['Настоящий цикл экспорта/импорта дела в браузере; локальный Worker недоступен в этом стенде'],'проверки':rows},ensure_ascii=False,indent=2)+'\n')
 return all(r['пройдено'] for r in rows)
if __name__=='__main__':sys.exit(0 if asyncio.run(main()) else 1)
