61 lines
4.0 KiB
Bash
61 lines
4.0 KiB
Bash
set -euo pipefail
|
|
release=/home/ps/token-usage-20260917
|
|
docker exec -i im-admin-api-1 python -u - <<'PY' > "$release/after.json"
|
|
from pathlib import Path
|
|
import json,sqlite3,time
|
|
import admin_backend
|
|
c=sqlite3.connect('file:/data/backend.db?mode=ro',uri=True,timeout=5)
|
|
counts={t:c.execute('SELECT COUNT(*) FROM '+t).fetchone()[0] for t in ['archive_message','knowledge_item','desktop_accounts','model_providers','users']}
|
|
assert c.execute("SELECT name FROM sqlite_master WHERE name='model_usage_events'").fetchone()
|
|
# Query only. Do not manufacture billable calls or production usage records.
|
|
db=admin_backend.Database(Path('/data/backend.db'))
|
|
started=time.monotonic()
|
|
stats=db.model_usage_stats(7,None)
|
|
page=db.list_model_usage(7,limit=1,tenant_id=None)
|
|
assert stats['summary']['request_count']==stats['summary']['known_count']+stats['summary']['missing_count']
|
|
assert stats['timezone']=='UTC' and len(stats['daily'])==7
|
|
assert page['total']>=stats['summary']['request_count']
|
|
print(json.dumps({'counts':counts,'usage_summary':stats['summary'],'legacy_unmetered_calls':stats['legacy_unmetered_calls'],'query_seconds':round(time.monotonic()-started,3)}))
|
|
PY
|
|
python3 - <<'PY' | tee "$release/verification.json"
|
|
from pathlib import Path
|
|
import json,hashlib,subprocess,urllib.request,urllib.error
|
|
root=Path('/home/ps/token-usage-20260917');target=Path('/opt/im-admin')
|
|
manifest=json.loads((root/'manifest.json').read_text())
|
|
for row in manifest:
|
|
assert hashlib.sha256((target/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'
|
|
dist=root/'payload/admin-web/apps/web-antd/dist'
|
|
assets=[str(p.relative_to(dist)) for p in (dist/'js').glob('ModelUsageStats-*.js')]
|
|
assert len(assets)==1,assets
|
|
content=(dist/assets[0]).read_text()
|
|
assert 'Token' in content and '未返回' in content
|
|
checks=[('/',200),('/api/v2/health',200),('/api/v2/stats/model-usage',401),('/api/v2/stats/model-usage/log',401)]+[('/'+p,200) for p in assets]
|
|
statuses={}
|
|
for path,wanted in checks:
|
|
try:
|
|
with urllib.request.urlopen('http://127.0.0.1:18766'+path,timeout=15) as response:
|
|
status=response.status;body=response.read()
|
|
if path=='/': assert body==(dist/'index.html').read_bytes()
|
|
if path.lstrip('/') in assets: assert body==(dist/path.lstrip('/')).read_bytes()
|
|
except urllib.error.HTTPError as error: status=error.code
|
|
assert status==wanted,(path,status,wanted)
|
|
statuses[path]=status
|
|
wanted_image=subprocess.check_output(['docker','image','inspect','zyt/wecom-admin:token-usage-20260917','--format','{{.Id}}'],text=True).strip()
|
|
states={}
|
|
for container in json.loads(subprocess.check_output(['docker','inspect','im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1'])):
|
|
assert container['State']['Running'] and container['RestartCount']==0 and container['Image']==wanted_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','100',name],stderr=subprocess.STDOUT,text=True)
|
|
assert 'Traceback (most recent call last)' not in logs and 'ERROR:' not in logs,name
|
|
before=json.loads((root/'before.json').read_text());after=json.loads((root/'after.json').read_text())
|
|
assert after['counts']['model_providers']>=before['counts']['model_providers']
|
|
print(json.dumps({'source_files_verified':len(manifest),'runtime_files_verified':len(expected),'http':statuses,'containers':states,'image':wanted_image,**after}))
|
|
PY
|