71 lines
5.1 KiB
Bash
71 lines
5.1 KiB
Bash
set -euo pipefail
|
|
release=/home/ps/knowledge-v4-review-20260916
|
|
docker exec -i im-admin-api-1 python -u - <<'PY' | tee "$release/after.json"
|
|
from pathlib import Path
|
|
import sqlite3,json,time
|
|
import admin_backend
|
|
from knowledge_store import KnowledgeStore
|
|
c=sqlite3.connect('file:/data/backend.db?mode=ro',uri=True,timeout=30)
|
|
tables=['archive_message','archive_conversation','archive_person','archive_source_account','desktop_accounts','model_providers','users','knowledge_item','knowledge_job','knowledge_watch','knowledge_review_job','knowledge_review_target']
|
|
counts={t:c.execute('SELECT COUNT(*) FROM '+t).fetchone()[0] for t in tables}
|
|
check=c.execute('PRAGMA quick_check').fetchone()[0]
|
|
assert check=='ok',check
|
|
enabled=c.execute('SELECT COUNT(*) FROM knowledge_settings WHERE enabled!=0').fetchone()[0]
|
|
source=c.execute('SELECT tenant_id,source_account_id,COUNT(*) n FROM archive_message GROUP BY tenant_id,source_account_id ORDER BY n DESC LIMIT 1').fetchone()
|
|
result={}
|
|
if source:
|
|
started=time.monotonic()
|
|
preview=KnowledgeStore(admin_backend.Database(Path('/data/backend.db'))).preview_job(source[0],{'source_account_id':source[1],'max_messages':10000})
|
|
result={key:preview[key] for key in ('available_messages','eligible_text_messages','conversations','total_messages','limited')}
|
|
result['elapsed_seconds']=round(time.monotonic()-started,3)
|
|
print(json.dumps({'counts':counts,'enabled_tenants':enabled,'quick_check':check,'read_only_preview':result}),flush=True)
|
|
PY
|
|
python3 - <<'PY' | tee "$release/verification.json"
|
|
from pathlib import Path
|
|
import json,hashlib,subprocess,urllib.request,urllib.error
|
|
root=Path('/home/ps/knowledge-v4-review-20260916')
|
|
before=json.loads((root/'backup-verification.json').read_text())['counts']
|
|
after=json.loads((root/'after.json').read_text())
|
|
assert all(after['counts'][key]>=value for key,value in before.items()),'Existing data counts decreased'
|
|
assert after['counts']['knowledge_review_job']==0 and after['counts']['knowledge_review_target']==0, 'Deployment must not submit reviews'
|
|
assert after['enabled_tenants']==json.loads((root/'before.json').read_text())['enabled_tenants']
|
|
manifest=json.loads((root/'manifest.json').read_text(encoding='utf-8'))
|
|
for row in manifest:
|
|
assert hashlib.sha256((Path('/opt/im-admin')/row['path']).read_bytes()).hexdigest()==row['sha256'],row['path']
|
|
expected={row['path']:row['sha256'] for row in manifest if row['path'].endswith('.py') or '/dist/' in row['path']}
|
|
code='from pathlib import Path; import json,hashlib,sys; paths=json.load(sys.stdin); print(json.dumps({p:hashlib.sha256((Path("/app")/p).read_bytes()).hexdigest() for p in paths}))'
|
|
actual=json.loads(subprocess.check_output(['docker','exec','-i','im-admin-api-1','python','-c',code],input=json.dumps(list(expected)).encode()))
|
|
assert expected==actual,'Runtime files differ from release'
|
|
assets=[str(p.relative_to(root/'payload/admin-web/apps/web-antd/dist')).replace('\\','/') for pattern in ('center-*.js','job-wizard-*.js') for p in (root/'payload/admin-web/apps/web-antd/dist/js').glob(pattern)]
|
|
assert len(assets)>=2,'Missing new UI assets'
|
|
checks=[('/api/v2/health',200,'GET'),('/api/v2/knowledge/overview',401,'GET'),('/api/v2/knowledge/review-tasks',401,'GET'),('/api/v2/knowledge/review-tasks/preview',401,'POST'),('/api/v2/knowledge/models',401,'GET'),('/api/v2/knowledge/jobs/preview',401,'POST'),('/knowledge/center',200,'GET')]
|
|
checks += [('/'+path,200,'GET') for path in assets]
|
|
statuses={}
|
|
for path,wanted,method in checks:
|
|
body=b'{"source_account_id":"deployment-probe"}' if method=='POST' else None
|
|
request=urllib.request.Request('http://127.0.0.1:18766'+path,data=body,method=method,headers={'Content-Type':'application/json'})
|
|
try:
|
|
with urllib.request.urlopen(request,timeout=15) as response:
|
|
status=response.status
|
|
content=response.read()
|
|
if path.lstrip('/') in assets:
|
|
assert content==(root/'payload/admin-web/apps/web-antd/dist'/path.lstrip('/')).read_bytes()
|
|
except urllib.error.HTTPError as error:
|
|
status=error.code
|
|
assert status==wanted,(path,status,wanted)
|
|
statuses[method+' '+path]=status
|
|
names=['im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1']
|
|
expected_image=subprocess.check_output(['docker','image','inspect','zyt/wecom-admin:knowledge-v4-review-20260916','--format','{{.Id}}'],text=True).strip()
|
|
states={}
|
|
for container in json.loads(subprocess.check_output(['docker','inspect',*names])):
|
|
assert container['State']['Running'] and container['RestartCount']==0
|
|
assert container['Image']==expected_image
|
|
health=container['State'].get('Health',{}).get('Status','not_configured')
|
|
assert health in ('healthy','not_configured')
|
|
name=container['Name'].lstrip('/')
|
|
states[name]={'running':True,'restarts':0,'health':health}
|
|
logs=subprocess.check_output(['docker','logs','--tail','150',name],stderr=subprocess.STDOUT,text=True)
|
|
assert 'Traceback (most recent call last)' not in logs and 'ERROR:' not in logs,name
|
|
print(json.dumps({'source_files_verified':len(manifest),'runtime_files_verified':len(expected),'http':statuses,'containers':states,'image':expected_image,'archive_counts_preserved':True}))
|
|
PY
|