feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环): - 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口) - 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入 parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截 - 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过): * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id) * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配 * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数 * 回滚 job payload 支持 prior_execution_id(strict 解码) - 三通道发布+回滚演练实测:publish 4/4 verified,检索命中, rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false - 前端 A·Data-console 风格全站统一,120 用例通过 - 门禁:go vet / go test 全包通过,gofmt 干净
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""Read-only UI acceptance; no review, upload, retry or production writes."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import urllib.request
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.firefox.service import Service
|
||||
from selenium.webdriver.support.ui import WebDriverWait, Select
|
||||
|
||||
BASE = 'http://127.0.0.1:8088'
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
items = []
|
||||
while True:
|
||||
with urllib.request.urlopen(f'{BASE}/api/v1/source-items?limit=100&offset={len(items)}', timeout=10) as response:
|
||||
page = json.load(response)
|
||||
items.extend(page['data'])
|
||||
if len(items) >= page['summary']['total']:
|
||||
break
|
||||
assert page['data']
|
||||
assert len(items) == len({x['id'] for x in items}) == page['summary']['total']
|
||||
assert all(x['title'] for x in items)
|
||||
assert not any(x['directory_path'].startswith('/') or '://' in x['directory_path'] for x in items)
|
||||
(ROOT/'tmp/document-ui-catalog.json').write_text(json.dumps(items,ensure_ascii=False))
|
||||
options = Options()
|
||||
options.add_argument('--headless')
|
||||
options.binary_location = '/snap/firefox/current/usr/lib/firefox/firefox'
|
||||
driver = webdriver.Firefox(options=options, service=Service('/snap/bin/geckodriver'))
|
||||
wait = WebDriverWait(driver,30,poll_frequency=.15)
|
||||
driver.set_window_size(1440,1100)
|
||||
|
||||
def text():
|
||||
return driver.find_element(By.TAG_NAME,'body').text
|
||||
|
||||
def nav(label):
|
||||
driver.find_element(By.XPATH,f"//nav//button[contains(.,'{label}')]").click()
|
||||
|
||||
def loaded():
|
||||
wait.until(lambda d: not d.find_element(By.XPATH,"//header//button[contains(.,'刷新')]").get_attribute('disabled'))
|
||||
|
||||
def root_fits():
|
||||
assert driver.execute_script('return document.documentElement.scrollWidth <= window.innerWidth'), 'root overflow'
|
||||
|
||||
try:
|
||||
driver.get(BASE)
|
||||
wait.until(lambda d: d.find_element(By.TAG_NAME,'h1').text == '来源与目录')
|
||||
loaded()
|
||||
assert not driver.find_elements(By.CSS_SELECTOR,'[role="alert"]')
|
||||
assert '未接通目录' in text()
|
||||
assert '只走这 8 步' not in text()
|
||||
expected_external = sum(x['visibility']=='external' for x in items)
|
||||
counts = driver.find_element(By.CSS_SELECTOR,'.document-counts').text
|
||||
assert str(len(items)) in counts and str(expected_external) in counts
|
||||
first = next(x for x in items if x['directory_path'] and x['source_type']=='pp')
|
||||
folder = next(e for e in driver.find_elements(By.CSS_SELECTOR,'.folder.child') if first['directory_path'] in e.text)
|
||||
folder.click()
|
||||
wait.until(lambda d: first['directory_path'] in d.find_element(By.CSS_SELECTOR,'.document-location').text)
|
||||
rows = driver.find_elements(By.CSS_SELECTOR,'.document-table tbody tr')
|
||||
assert rows and all(first['directory_path'] in r.text for r in rows)
|
||||
driver.find_element(By.CSS_SELECTOR,'.folder').click()
|
||||
search = driver.find_element(By.CSS_SELECTOR,'input[type="search"]')
|
||||
search.send_keys(first['title'])
|
||||
wait.until(lambda d: first['title'] in d.find_element(By.CSS_SELECTOR,'.document-table').text)
|
||||
driver.find_element(By.CSS_SELECTOR,'.file-link').click()
|
||||
detail = wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.document-inspector'))
|
||||
assert detail.is_displayed() and first['title'] in detail.text
|
||||
assert '原文预览尚未接通' in detail.text
|
||||
driver.find_element(By.XPATH,"//button[.='关闭详情']").click()
|
||||
search.clear()
|
||||
search.send_keys(' ')
|
||||
selects = driver.find_elements(By.CSS_SELECTOR,'.document-filters select')
|
||||
Select(selects[0]).select_by_value('internal')
|
||||
wait.until(lambda d: '筛选结果 5 份' in d.find_element(By.CSS_SELECTOR,'.document-location').text)
|
||||
assert len(driver.find_elements(By.CSS_SELECTOR,'.document-table tbody tr')) == 5
|
||||
Select(selects[0]).select_by_value('')
|
||||
root_fits()
|
||||
assert driver.execute_script('return document.documentElement.scrollHeight < window.innerHeight + 300'), 'document table should scroll inside its panel'
|
||||
driver.get_full_page_screenshot_as_file(str(ROOT/'tmp/DOCUMENT_WORKBENCH_FIREFOX.png'))
|
||||
nav('解析监控')
|
||||
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='解析监控')
|
||||
loaded()
|
||||
before = driver.execute_script("return performance.getEntriesByType('resource').filter(x=>x.name.includes('/source-items?')).length")
|
||||
WebDriverWait(driver,25).until(lambda d:d.execute_script("return performance.getEntriesByType('resource').filter(x=>x.name.includes('/source-items?')).length") > before)
|
||||
loaded()
|
||||
assert '解析完成不代表已上线' in text()
|
||||
nav('待处理文件')
|
||||
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='待处理文件')
|
||||
expected_attention = sum(x['content_state']=='over_limit' or x['processing_state'] in ('failed','stalled') for x in items)
|
||||
assert f'筛选结果 {expected_attention} 份' in text()
|
||||
nav('来源连接')
|
||||
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='来源连接')
|
||||
assert not driver.find_element(By.CSS_SELECTOR,'.scan-audit-panel').get_attribute('open')
|
||||
nav('发布管理')
|
||||
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='从审核到安全发布')
|
||||
assert not driver.find_element(By.CSS_SELECTOR,'.release-help').get_attribute('open')
|
||||
assert '只走这 8 步' not in text()
|
||||
assert not driver.find_elements(By.XPATH,"//button[.='授权生产发布']")
|
||||
nav('来源与目录')
|
||||
loaded()
|
||||
for width in (1440,1024,768):
|
||||
driver.set_window_size(width,1000)
|
||||
root_fits()
|
||||
driver.refresh()
|
||||
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='来源与目录')
|
||||
assert driver.find_element(By.CSS_SELECTOR,'.tab-list').text.count('解析监控')==1
|
||||
result = {'catalog':len(items),'titles':sum(bool(x['title']) for x in items),'categories':sum(bool(x['directory_path']) for x in items),'external':expected_external,'attention':expected_attention,'search':'pass','directory_filter':'pass','scope_filter':'pass','file_drawer':'pass','automatic_refresh':'pass','tabs_restore':'pass','root_overflow':False,'write_requests':0}
|
||||
(ROOT/'tmp/document-ui-acceptance.json').write_text(json.dumps(result,ensure_ascii=False,indent=2))
|
||||
print(json.dumps(result,ensure_ascii=False))
|
||||
finally:
|
||||
driver.quit()
|
||||
Reference in New Issue
Block a user