dcdc8d1ddd
全栈知识治理平台首次入库(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 干净
142 lines
8.9 KiB
Python
142 lines
8.9 KiB
Python
"""Live browser acceptance for document workbench V2.
|
|
|
|
Writes one PP category rule and one file override through the UI, verifies each
|
|
read-back, then clears both. It never uploads, retries, reviews or publishes.
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
import time
|
|
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]
|
|
|
|
def request_json(path,method='GET',body=None):
|
|
data=None if body is None else json.dumps(body,ensure_ascii=False).encode()
|
|
request=urllib.request.Request(BASE+path,data=data,method=method,headers={'Accept':'application/json','Content-Type':'application/json'})
|
|
with urllib.request.urlopen(request,timeout=10) as response:return json.load(response)
|
|
|
|
def catalog():
|
|
items=[]
|
|
while True:
|
|
page=request_json(f'/api/v1/source-items?limit=100&offset={len(items)}')
|
|
items.extend(page['data'])
|
|
if len(items)>=page['summary']['total']:break
|
|
assert page['data']
|
|
assert len(items)==len({item['id'] for item in items})==page['summary']['total']
|
|
return items
|
|
|
|
def rules(connection):return request_json('/api/v1/scope-rules?connection_id='+connection)['data']
|
|
def eventually(check,seconds=25):
|
|
deadline=time.monotonic()+seconds
|
|
while time.monotonic()<deadline:
|
|
value=check()
|
|
if value:return value
|
|
time.sleep(.2)
|
|
raise AssertionError('condition did not become true')
|
|
|
|
before=catalog()
|
|
pp=[item for item in before if item['source_type']=='pp']
|
|
manual=[item for item in before if item['source_type']=='manual']
|
|
category=[item for item in pp if item['directory_path']=='产品介绍']
|
|
assert len(before)==618 and len(pp)==613 and len(manual)==5 and len(category)==5
|
|
connection=category[0]['connection_id']
|
|
assert not rules(connection),'acceptance requires no pre-existing PP scope rule'
|
|
selected_id=category[0]['id']
|
|
|
|
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'))
|
|
driver.set_window_size(1440,1100)
|
|
wait=WebDriverWait(driver,30,poll_frequency=.1)
|
|
write_actions=0
|
|
|
|
def body():return driver.find_element(By.TAG_NAME,'body').text
|
|
def root_fits():assert driver.execute_script('return document.documentElement.scrollWidth <= window.innerWidth'),'root overflow'
|
|
def fill_editor(editor,operator,reason,visibility):
|
|
Select(editor.find_element(By.TAG_NAME,'select')).select_by_value(visibility)
|
|
operator_input=editor.find_element(By.CSS_SELECTOR,'input[placeholder="姓名或工号"]');operator_input.clear();operator_input.send_keys(operator)
|
|
reason_input=editor.find_element(By.CSS_SELECTOR,'input[placeholder*="官网"]');reason_input.clear();reason_input.send_keys(reason)
|
|
|
|
def api_category(expected,origin):
|
|
current=[item for item in catalog() if item['connection_id']==connection and item['directory_path']=='产品介绍']
|
|
return len(current)==5 and all(item['visibility']==expected and item['visibility_origin']==origin for item in current)
|
|
|
|
try:
|
|
driver.get(BASE)
|
|
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='来源与目录')
|
|
wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.directory-panel .folder b').text=='613')
|
|
assert 'P3 持久队列' not in body()
|
|
assert '显示 5 份验收文档' in body()
|
|
assert driver.find_element(By.CSS_SELECTOR,'.document-counts span:first-child b').text=='613'
|
|
root_fits()
|
|
# Test data is deliberately opt-in and returns to hidden state.
|
|
toggle=driver.find_element(By.CSS_SELECTOR,'.test-document-toggle input');toggle.click()
|
|
wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.directory-panel .folder b').text=='618')
|
|
toggle.click();wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.directory-panel .folder b').text=='613')
|
|
# Select the real PP category.
|
|
driver.find_element(By.XPATH,"//aside//button[.//span[contains(.,'PP公开文档')]]").click()
|
|
wait.until(lambda d:d.find_elements(By.CSS_SELECTOR,'.scope-editor'))
|
|
driver.find_element(By.XPATH,"//aside//button[.//span[contains(.,'产品介绍')]]").click()
|
|
editor=wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.document-content .scope-editor'))
|
|
assert '此目录及子目录' in editor.text and '当前没有本级设置' in editor.text
|
|
fill_editor(editor,'browser-acceptance','验证目录继承,完成后恢复','internal')
|
|
editor.find_element(By.XPATH,".//button[.='保存范围']").click();write_actions+=1
|
|
eventually(lambda:api_category('internal','directory_rule'))
|
|
wait.until(lambda d:'对内' in d.find_element(By.CSS_SELECTOR,'.document-table tbody').text)
|
|
assert 'PP公开文档' in driver.find_element(By.CSS_SELECTOR,'.workspace-context-tabs').text
|
|
assert '产品介绍' in driver.find_element(By.CSS_SELECTOR,'.workspace-context-tabs').text
|
|
# Override one file, verify precedence, then restore inheritance.
|
|
first_link=driver.find_element(By.CSS_SELECTOR,'.file-link');file_title=first_link.text;first_link.click()
|
|
inspector=wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.document-inspector'))
|
|
for expected in ['取得文件','上传测试库','解析与索引','测试检索','原文与版本差异尚未接通']:
|
|
assert expected in inspector.text
|
|
assert file_title in driver.find_element(By.CSS_SELECTOR,'.workspace-context-tabs').text
|
|
file_editor=inspector.find_element(By.CSS_SELECTOR,'.scope-editor')
|
|
fill_editor(file_editor,'browser-acceptance','验证文件单独覆盖,完成后恢复','external')
|
|
file_editor.find_element(By.XPATH,".//button[.='保存范围']").click();write_actions+=1
|
|
eventually(lambda:next(item for item in catalog() if item['id']==selected_id)['visibility_origin']=='file_override')
|
|
wait.until(lambda d:'当前设置:对外' in d.find_element(By.CSS_SELECTOR,'.document-inspector').text)
|
|
file_editor=driver.find_element(By.CSS_SELECTOR,'.document-inspector .scope-editor')
|
|
fill_editor(file_editor,'browser-acceptance','恢复目录继承','external')
|
|
file_editor.find_element(By.XPATH,".//button[contains(.,'恢复继承')]").click();write_actions+=1
|
|
eventually(lambda:(lambda item:item['visibility']=='internal' and item['visibility_origin']=='directory_rule')(next(item for item in catalog() if item['id']==selected_id)))
|
|
# Clicking the source context closes directory/file context.
|
|
driver.find_element(By.XPATH,"//div[@aria-label='当前来源目录和文件']//button[.='PP公开文档']").click()
|
|
wait.until(lambda d:not d.find_elements(By.CSS_SELECTOR,'.document-inspector'))
|
|
assert '产品介绍' not in driver.find_element(By.CSS_SELECTOR,'.workspace-context-tabs').text
|
|
# Return to the category and clear its direct rule.
|
|
driver.find_element(By.XPATH,"//aside//button[.//span[contains(.,'产品介绍')]]").click()
|
|
editor=wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.document-content .scope-editor'))
|
|
fill_editor(editor,'browser-acceptance','恢复来源原始标记','internal')
|
|
editor.find_element(By.XPATH,".//button[contains(.,'恢复继承')]").click();write_actions+=1
|
|
eventually(lambda:not rules(connection))
|
|
eventually(lambda:api_category('external','pp_public_tag'))
|
|
assert write_actions==4
|
|
# Source-level truthful monitor reconciles every PP item.
|
|
driver.find_element(By.XPATH,"//nav//button[contains(.,'解析监控')]").click()
|
|
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='解析监控')
|
|
card=wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.source-progress-card'))
|
|
for expected in ['PP公开文档','已就绪 559 / 613','独立解析 530','处理中 0','需处理 54','内容复用 29','等待 0']:
|
|
assert expected in card.text
|
|
assert '100%' not in body()
|
|
root_fits()
|
|
driver.get_full_page_screenshot_as_file(str(ROOT/'tmp/DOCUMENT_MONITOR_V2_FIREFOX.png'))
|
|
driver.find_element(By.XPATH,"//nav//button[contains(.,'来源与目录')]").click()
|
|
wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='来源与目录')
|
|
for width in (1440,1024,768):
|
|
driver.set_window_size(width,1000);root_fits()
|
|
driver.set_window_size(1440,1100)
|
|
driver.get_full_page_screenshot_as_file(str(ROOT/'tmp/DOCUMENT_WORKBENCH_V2_FIREFOX.png'))
|
|
capabilities=request_json('/api/v1/release-capabilities')['data']
|
|
assert capabilities['production_release_requests_enabled'] is False
|
|
result={'catalog':618,'default_visible':613,'test_documents_hidden':5,'category':'产品介绍','category_items':5,'directory_inheritance':'pass','file_override':'pass','clear_restores_source':'pass','scope_writes':4,'active_rules_after_test':0,'source_monitor':{'completed':530,'total':613,'active':0,'attention':54,'reused':29,'waiting':0},'context_tabs':'pass','pipeline_detail':'pass','root_overflow':False,'production_release_requests_enabled':False}
|
|
(ROOT/'tmp/document-workbench-v2-acceptance.json').write_text(json.dumps(result,ensure_ascii=False,indent=2))
|
|
print(json.dumps(result,ensure_ascii=False))
|
|
finally:
|
|
driver.quit()
|