WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify category migration through authenticated, read-only acceptance APIs."""
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from weknora_category_etl import TENANT, save_json
|
||||
|
||||
|
||||
BASE = "http://127.0.0.1:18080"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--manifest", type=Path, required=True)
|
||||
parser.add_argument("--credentials", type=Path, required=True)
|
||||
parser.add_argument("--out", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
manifest = json.loads(args.manifest.read_text())
|
||||
credentials = json.loads(args.credentials.read_text())
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
|
||||
def request(path, token=None, payload=None):
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if token:
|
||||
headers["Authorization"] = "Bearer " + token
|
||||
req = urllib.request.Request(BASE + path, data=json.dumps(payload).encode() if payload else None, headers=headers)
|
||||
try:
|
||||
with opener.open(req, timeout=20) as response:
|
||||
return response.status, json.load(response)
|
||||
except urllib.error.HTTPError as error:
|
||||
return error.code, None
|
||||
|
||||
status, auth = request("/api/v1/auth/login", payload={k: credentials[k] for k in ("email", "password")})
|
||||
if status != 200 or auth.get("active_tenant", {}).get("id") != TENANT:
|
||||
raise RuntimeError("Verification login failed or outside migration tenant")
|
||||
token = auth["token"]
|
||||
report = {"checked_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), "tenant_id": TENANT, "checks": []}
|
||||
|
||||
def check(name, passed, **counts):
|
||||
report["checks"].append({"check": name, "passed": bool(passed), **counts})
|
||||
|
||||
for kb in manifest["knowledge_bases"]:
|
||||
files = [f for f in manifest["files"] if f["knowledge_base_id"] == kb]
|
||||
tags = [t for t in manifest["tags"] if t["knowledge_base_id"] == kb]
|
||||
status, response = request(f"/api/v1/knowledge-bases/{kb}/knowledge/folders", token)
|
||||
tree = response.get("data", {}) if response else {}
|
||||
nodes = {}
|
||||
|
||||
def walk(items):
|
||||
for node in items:
|
||||
nodes[node["path"]] = node
|
||||
walk(node.get("children") or [])
|
||||
|
||||
walk(tree.get("folders") or [])
|
||||
counts_match = all(t["folder_path"] in nodes and nodes[t["folder_path"]]["total_count"] == sum(
|
||||
f["folder_path"] == t["folder_path"] or f["folder_path"].startswith(t["folder_path"] + "/") for f in files) for t in tags)
|
||||
check("folder_tree", status == 200 and tree.get("total_document_count") == len(files) and counts_match,
|
||||
knowledge_base_id=kb, folders=len(nodes), documents=tree.get("total_document_count"))
|
||||
status, response = request(f"/api/v1/knowledge-bases/{kb}/tags?page=1&page_size=100", token)
|
||||
data = response.get("data", {}) if response else {}
|
||||
items = data.get("data", [])
|
||||
check("tag_list", status == 200 and {t["id"] for t in items} == {t["id"] for t in tags},
|
||||
knowledge_base_id=kb, tags=len(items))
|
||||
for tag in tags:
|
||||
params = urllib.parse.urlencode({"page": 1, "page_size": 100, "tag_ids": tag["id"]})
|
||||
status, response = request(f"/api/v1/knowledge-bases/{kb}/knowledge?{params}", token)
|
||||
data = response.get("data", []) if response else []
|
||||
if isinstance(data, dict):
|
||||
data = data.get("items", [])
|
||||
expected = {r["knowledge_id"] for r in manifest["relations"] if r["tag_id"] == tag["id"]}
|
||||
actual = {f["id"] for f in data}
|
||||
check("tag_document_scope", status == 200 and actual == expected,
|
||||
legacy_category_id=tag["legacy_id"], expected=len(expected), returned=len(actual))
|
||||
status, _ = request(f"/api/v1/knowledge-bases/{kb}/knowledge/folders")
|
||||
check("anonymous_denied", status in (401, 403), knowledge_base_id=kb)
|
||||
report["passed"] = all(c["passed"] for c in report["checks"])
|
||||
save_json(args.out, report)
|
||||
print(json.dumps({"passed": report["passed"], "checks": len(report["checks"]),
|
||||
"failed": [c for c in report["checks"] if not c["passed"]], "out": str(args.out)}))
|
||||
return 0 if report["passed"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user