# ============================================================ # 项目分析:绑定登录用户 + 写入租户知识库 # ============================================================ from __future__ import annotations from server.auth.context import IdentityContext, bind_identity, reset_identity from server.knowledge.assets import KnowledgeStore, get_knowledge from server.knowledge.ingest import bind_world_to_identity, ingest_project_analyze_report from server.knowledge.retrieval import hybrid_search from server.state.seed import empty_world def test_bind_world_to_current_user(): world = empty_world() identity = IdentityContext( user_id=42, username="ruiyang_admin", fullname="锐扬管理员", tenant_uuid="tenant-ruiyang-demo", roles=("admin",), ) token = bind_identity(identity) try: meta = bind_world_to_identity(world, sources=["湖南锐扬MOM主数据收集表.xlsx"]) assert world["meta"]["ownerUserId"] == 42 assert world["meta"]["ownerUsername"] == "ruiyang_admin" assert world["meta"]["tenantUuid"] == "tenant-ruiyang-demo" assert "湖南锐扬" in (meta.get("sources") or [])[0] finally: reset_identity(token) def test_ingest_analyze_report_into_tenant_kb(tmp_path, monkeypatch): identity = IdentityContext( user_id=7, username="planner_a", fullname="计划员甲", tenant_uuid="tenant-kb-demo", roles=("planner",), ) token = bind_identity(identity) try: # 隔离租户知识库到临时文件,避免污染本机 store kb_path = tmp_path / "kb.json" store = KnowledgeStore(path=str(kb_path)) store.assets = [] store._write() monkeypatch.setattr("server.knowledge.assets.get_knowledge", lambda: store) world = empty_world() deep = { "projectName": "湖南锐扬", "sources": ["MOM主数据收集表.xlsx"], "markdown": "# 湖南锐扬分析\n\n物料与设备已解析,可排产前需补工艺标准工时。", "summary": {"materials": 12, "orders": 3, "equipment": 5, "bom": 8}, "materials": [ {"code": "P-001", "name": "成品阀", "type": "FINISHED_PRODUCT"}, {"code": "M-010", "name": "阀体毛坯", "type": "RAW"}, ], "equipment": [ {"code": "EQ-01", "name": "数控车", "capabilities": "车削"}, ], "orders": [ {"orderNo": "SO-1", "productCode": "P-001", "quantity": 10, "dueDate": "2026-08-01"}, ], "plan": ["补标准工时", "核对 BOM 用量"], } world["flexMaterials"] = deep["materials"] + [ {"code": "M-LAST-999", "name": "完整集末尾物料", "type": "RAW_MATERIAL"}, ] world["flexEquipment"] = deep["equipment"] + [ {"code": "EQ-LAST-999", "name": "完整集末尾设备", "status": "RUNNING"}, ] world["flexOrders"] = deep["orders"] world["flexBom"] = [ {"productCode": "P-001", "materialCode": "M-LAST-999", "quantity": 2}, ] world["flexRoutings"] = [ {"productCode": "P-001", "seq": 99, "operationCode": "OP-LAST-999", "operationName": "完整集末尾工序", "stdTimePerUnit": 12}, ] applied = ingest_project_analyze_report(deep, world=world) assert applied.get("assetId") assert applied.get("chunkCount", 0) >= 1 assert applied.get("ownerUserId") == 7 assert applied.get("tenantUuid") == "tenant-kb-demo" assert world["meta"]["ownerUserId"] == 7 asset = store.get(applied["assetId"]) assert asset and "项目分析" in (asset.get("title") or "") tags = asset.get("tags") or [] assert any(str(t).startswith("user:planner_a") for t in tags) assert any(str(t).startswith("uid:7") for t in tags) chunk_text = "\n".join(str(c.get("text") or "") for c in (asset.get("chunks") or [])) assert "M-LAST-999" in chunk_text assert "EQ-LAST-999" in chunk_text assert "OP-LAST-999" in chunk_text assert "完整数据集" in tags units = store.iter_search_units() hits = hybrid_search(units, "阀体毛坯 数控车", top_k=5) assert hits assert any("锐扬" in (h.get("title") or "") or "阀" in (h.get("snippet") or h.get("text") or "") for h in hits) finally: reset_identity(token) # 清掉可能缓存的租户 store(若测试间复用 get_knowledge) try: from server.knowledge import assets as assets_mod assets_mod._stores.pop("tenant-kb-demo", None) except Exception: pass