from datetime import UTC, datetime from fastapi.testclient import TestClient from zbt.core.config import Settings from zbt.core.passwords import PasswordService from zbt.domains.identity.models import AdminUser from zbt.domains.identity.repository import InMemoryIdentityRepository from zbt.domains.knowledge.index import InMemoryKnowledgeIndex from zbt.domains.knowledge.repository import InMemoryKnowledgeRepository from zbt.main import create_app def test_admin_creates_draft_knowledge_document() -> None: settings = Settings( app_env="test", jwt_access_secret="a" * 32, jwt_refresh_secret="b" * 32, field_encryption_key="c" * 32, ) identities = InMemoryIdentityRepository() identities.save_admin_user( AdminUser( id="01ADMIN0000000000000000001", username="admin", password_hash=PasswordService().hash("zaq1XSW@"), display_name="知识管理员", status="ACTIVE", roles=("SUPER_ADMIN",), permissions=("knowledge:read", "knowledge:write"), data_scope="ALL", ) ) knowledge = InMemoryKnowledgeRepository() application = create_app( settings=settings, identity_repository=identities, knowledge_repository=knowledge, clock=lambda: datetime(2026, 7, 27, 2, 0, tzinfo=UTC), ) with TestClient(application) as client: login = client.post( "/api/v1/admin/auth/login", json={"username": "admin", "password": "zaq1XSW@"}, ) token = login.json()["data"]["tokens"]["access_token"] response = client.post( "/api/v1/admin/knowledge/documents", headers={"Authorization": f"Bearer {token}"}, json={ "title": "安心医疗险保险条款", "document_type": "INSURANCE_TERMS", "source_name": "安心医疗险条款.pdf", "product_code": "MED-BASIC", "content": "本产品等待期为30天,意外伤害导致的医疗费用不受等待期限制。", }, ) assert response.status_code == 201 document = response.json()["data"] assert document["title"] == "安心医疗险保险条款" assert document["document_type"] == "INSURANCE_TERMS" assert document["source_name"] == "安心医疗险条款.pdf" assert document["product_code"] == "MED-BASIC" assert document["status"] == "DRAFT" assert document["version_no"] == 1 assert document["chunk_count"] == 0 assert document["indexed_at"] is None assert document["published_at"] is None def test_admin_cannot_publish_document_before_indexing() -> None: settings = Settings( app_env="test", jwt_access_secret="a" * 32, jwt_refresh_secret="b" * 32, field_encryption_key="c" * 32, ) identities = InMemoryIdentityRepository() identities.save_admin_user( AdminUser( id="01ADMIN0000000000000000001", username="admin", password_hash=PasswordService().hash("zaq1XSW@"), display_name="知识管理员", status="ACTIVE", roles=("SUPER_ADMIN",), permissions=("knowledge:read", "knowledge:write", "knowledge:publish"), data_scope="ALL", ) ) application = create_app( settings=settings, identity_repository=identities, knowledge_repository=InMemoryKnowledgeRepository(), clock=lambda: datetime(2026, 7, 27, 2, 0, tzinfo=UTC), ) with TestClient(application) as client: login = client.post( "/api/v1/admin/auth/login", json={"username": "admin", "password": "zaq1XSW@"}, ) headers = { "Authorization": f"Bearer {login.json()['data']['tokens']['access_token']}" } created = client.post( "/api/v1/admin/knowledge/documents", headers=headers, json={ "title": "安心医疗险保险条款", "document_type": "INSURANCE_TERMS", "source_name": "安心医疗险条款.pdf", "content": "本产品等待期为30天,意外伤害导致的医疗费用不受等待期限制。", }, ) document_id = created.json()["data"]["document_id"] response = client.post( f"/api/v1/admin/knowledge/documents/{document_id}/publish", headers=headers, ) assert response.status_code == 409 assert response.json()["error"]["code"] == "KNOWLEDGE_DOCUMENT_NOT_INDEXED" def test_admin_cannot_publish_indexed_document_without_effective_search_test() -> None: settings = Settings( app_env="test", jwt_access_secret="a" * 32, jwt_refresh_secret="b" * 32, field_encryption_key="c" * 32, ) identities = InMemoryIdentityRepository() identities.save_admin_user( AdminUser( id="01ADMIN0000000000000000001", username="admin", password_hash=PasswordService().hash("zaq1XSW@"), display_name="知识管理员", status="ACTIVE", roles=("SUPER_ADMIN",), permissions=("knowledge:read", "knowledge:write", "knowledge:publish"), data_scope="ALL", ) ) application = create_app( settings=settings, identity_repository=identities, knowledge_repository=InMemoryKnowledgeRepository(), knowledge_index=InMemoryKnowledgeIndex(), clock=lambda: datetime(2026, 7, 27, 2, 0, tzinfo=UTC), ) with TestClient(application) as client: login = client.post( "/api/v1/admin/auth/login", json={"username": "admin", "password": "zaq1XSW@"}, ) headers = { "Authorization": f"Bearer {login.json()['data']['tokens']['access_token']}" } created = client.post( "/api/v1/admin/knowledge/documents", headers=headers, json={ "title": "安心医疗险保险条款", "document_type": "INSURANCE_TERMS", "source_name": "安心医疗险条款.pdf", "content": "本产品等待期为30天,意外伤害不受等待期限制。", }, ) document_id = created.json()["data"]["document_id"] client.post( f"/api/v1/admin/knowledge/documents/{document_id}/index", headers=headers, ) response = client.post( f"/api/v1/admin/knowledge/documents/{document_id}/publish", headers=headers, ) assert response.status_code == 409 assert response.json()["error"]["code"] == "KNOWLEDGE_SEARCH_TEST_REQUIRED" def test_admin_indexes_then_publishes_document() -> None: settings = Settings( app_env="test", jwt_access_secret="a" * 32, jwt_refresh_secret="b" * 32, field_encryption_key="c" * 32, ) identities = InMemoryIdentityRepository() identities.save_admin_user( AdminUser( id="01ADMIN0000000000000000001", username="admin", password_hash=PasswordService().hash("zaq1XSW@"), display_name="知识管理员", status="ACTIVE", roles=("SUPER_ADMIN",), permissions=("knowledge:read", "knowledge:write", "knowledge:publish"), data_scope="ALL", ) ) application = create_app( settings=settings, identity_repository=identities, knowledge_repository=InMemoryKnowledgeRepository(), knowledge_index=InMemoryKnowledgeIndex(), clock=lambda: datetime(2026, 7, 27, 2, 0, tzinfo=UTC), ) with TestClient(application) as client: login = client.post( "/api/v1/admin/auth/login", json={"username": "admin", "password": "zaq1XSW@"}, ) headers = { "Authorization": f"Bearer {login.json()['data']['tokens']['access_token']}" } created = client.post( "/api/v1/admin/knowledge/documents", headers=headers, json={ "title": "安心医疗险保险条款", "document_type": "INSURANCE_TERMS", "source_name": "安心医疗险条款.pdf", "content": ( "等待期规则\n本产品疾病医疗等待期为30天,意外伤害不受等待期限制。\n\n" "报销材料\n申请医疗费用报销协助时,需要提供发票、费用清单和诊断证明。" ), }, ) document_id = created.json()["data"]["document_id"] indexed = client.post( f"/api/v1/admin/knowledge/documents/{document_id}/index", headers=headers, ) search_test = client.post( f"/api/v1/admin/knowledge/documents/{document_id}/search-test", headers=headers, json={"query": "等待期是多少天?", "limit": 3}, ) published = client.post( f"/api/v1/admin/knowledge/documents/{document_id}/publish", headers=headers, ) search_tests = client.get( f"/api/v1/admin/knowledge/documents/{document_id}/search-tests", headers=headers, ) assert indexed.status_code == 200 assert indexed.json()["data"]["status"] == "INDEXED" assert indexed.json()["data"]["chunk_count"] == 2 assert indexed.json()["data"]["indexed_at"] is not None assert search_test.status_code == 200 assert search_test.json()["data"]["test_record"]["passed"] is True assert search_test.json()["data"]["test_record"]["hit_count"] >= 1 assert published.status_code == 200 assert published.json()["data"]["status"] == "PUBLISHED" assert published.json()["data"]["published_at"] is not None assert published.json()["data"]["search_test_count"] == 1 assert published.json()["data"]["last_search_test_passed"] is True assert search_tests.status_code == 200 assert search_tests.json()["data"]["total"] == 1 assert search_tests.json()["data"]["items"][0]["query"] == "等待期是多少天?" def test_admin_lists_and_disables_published_document() -> None: settings = Settings( app_env="test", jwt_access_secret="a" * 32, jwt_refresh_secret="b" * 32, field_encryption_key="c" * 32, ) identities = InMemoryIdentityRepository() identities.save_admin_user( AdminUser( id="01ADMIN0000000000000000001", username="admin", password_hash=PasswordService().hash("zaq1XSW@"), display_name="知识管理员", status="ACTIVE", roles=("SUPER_ADMIN",), permissions=("knowledge:read", "knowledge:write", "knowledge:publish"), data_scope="ALL", ) ) application = create_app( settings=settings, identity_repository=identities, knowledge_repository=InMemoryKnowledgeRepository(), knowledge_index=InMemoryKnowledgeIndex(), clock=lambda: datetime(2026, 7, 27, 2, 0, tzinfo=UTC), ) with TestClient(application) as client: login = client.post( "/api/v1/admin/auth/login", json={"username": "admin", "password": "zaq1XSW@"}, ) headers = { "Authorization": f"Bearer {login.json()['data']['tokens']['access_token']}" } created = client.post( "/api/v1/admin/knowledge/documents", headers=headers, json={ "title": "安心医疗险保险条款", "document_type": "INSURANCE_TERMS", "source_name": "安心医疗险条款.pdf", "content": "本产品疾病医疗等待期为30天,意外伤害不受等待期限制。", }, ) document_id = created.json()["data"]["document_id"] client.post( f"/api/v1/admin/knowledge/documents/{document_id}/index", headers=headers, ) client.post( f"/api/v1/admin/knowledge/documents/{document_id}/search-test", headers=headers, json={"query": "等待期是多少天?"}, ) client.post( f"/api/v1/admin/knowledge/documents/{document_id}/publish", headers=headers, ) disabled = client.post( f"/api/v1/admin/knowledge/documents/{document_id}/disable", headers=headers, ) listing = client.get("/api/v1/admin/knowledge/documents", headers=headers) assert disabled.status_code == 200 assert disabled.json()["data"]["status"] == "INACTIVE" assert listing.status_code == 200 assert listing.json()["data"]["total"] == 1 assert listing.json()["data"]["items"][0]["status"] == "INACTIVE" def test_admin_uploads_utf8_text_as_draft_document() -> None: settings = Settings( app_env="test", jwt_access_secret="a" * 32, jwt_refresh_secret="b" * 32, field_encryption_key="c" * 32, ) identities = InMemoryIdentityRepository() identities.save_admin_user( AdminUser( id="01ADMIN0000000000000000001", username="admin", password_hash=PasswordService().hash("zaq1XSW@"), display_name="知识管理员", status="ACTIVE", roles=("SUPER_ADMIN",), permissions=("knowledge:read", "knowledge:write"), data_scope="ALL", ) ) application = create_app( settings=settings, identity_repository=identities, knowledge_repository=InMemoryKnowledgeRepository(), knowledge_index=InMemoryKnowledgeIndex(), clock=lambda: datetime(2026, 7, 27, 2, 0, tzinfo=UTC), ) with TestClient(application) as client: login = client.post( "/api/v1/admin/auth/login", json={"username": "admin", "password": "zaq1XSW@"}, ) headers = { "Authorization": f"Bearer {login.json()['data']['tokens']['access_token']}" } response = client.post( "/api/v1/admin/knowledge/documents/upload", headers=headers, data={ "title": "医疗费用报销材料说明", "document_type": "SERVICE_RULES", "product_code": "MED-BASIC", }, files={ "file": ( "医疗费用报销材料说明.txt", "申请时需要提供发票、费用清单和诊断证明。".encode(), "text/plain", ) }, ) assert response.status_code == 201 document = response.json()["data"] assert document["title"] == "医疗费用报销材料说明" assert document["source_name"] == "医疗费用报销材料说明.txt" assert document["document_type"] == "SERVICE_RULES" assert document["status"] == "DRAFT" def test_admin_tests_indexed_document_before_publishing() -> None: settings = Settings( app_env="test", jwt_access_secret="a" * 32, jwt_refresh_secret="b" * 32, field_encryption_key="c" * 32, ) identities = InMemoryIdentityRepository() identities.save_admin_user( AdminUser( id="01ADMIN0000000000000000001", username="admin", password_hash=PasswordService().hash("zaq1XSW@"), display_name="知识管理员", status="ACTIVE", roles=("SUPER_ADMIN",), permissions=("knowledge:read", "knowledge:write"), data_scope="ALL", ) ) application = create_app( settings=settings, identity_repository=identities, knowledge_repository=InMemoryKnowledgeRepository(), knowledge_index=InMemoryKnowledgeIndex(), clock=lambda: datetime(2026, 7, 27, 2, 0, tzinfo=UTC), ) with TestClient(application) as client: login = client.post( "/api/v1/admin/auth/login", json={"username": "admin", "password": "zaq1XSW@"}, ) headers = { "Authorization": f"Bearer {login.json()['data']['tokens']['access_token']}" } created = client.post( "/api/v1/admin/knowledge/documents", headers=headers, json={ "title": "安心医疗险保险条款", "document_type": "INSURANCE_TERMS", "source_name": "安心医疗险条款.md", "content": "疾病医疗等待期为30天,意外伤害不受等待期限制。", }, ) document_id = created.json()["data"]["document_id"] client.post( f"/api/v1/admin/knowledge/documents/{document_id}/index", headers=headers, ) response = client.post( f"/api/v1/admin/knowledge/documents/{document_id}/search-test", headers=headers, json={"query": "等待期是多少天?", "limit": 3}, ) assert response.status_code == 200 result = response.json()["data"] assert result["document_id"] == document_id assert result["total"] == 1 assert "30天" in result["items"][0]["content"]