""" Knowledge Repository 整合測試 ============================== 使用真實 awoooi_dev PostgreSQL,禁止 Mock 建立時間: 2026-04-04 (台北時區) 建立者: Claude Code (整合測試 Phase 1) 測試覆蓋: - create: 建立知識條目 - get_by_id: 根據 ID 查詢 - list_entries: 列表 + 分類/關鍵字篩選 - update: 更新欄位 - delete: 軟刪除 (status → archived) - search: 關鍵字搜尋 - get_categories: 分類統計 - increment_view_count: 瀏覽數遞增 """ import pytest from src.models.knowledge import EntrySource, EntryStatus, EntryType, KnowledgeEntryCreate from src.repositories.knowledge_repository import KnowledgeDBRepository # ============================================================================= # Helpers # ============================================================================= def make_entry(**kwargs) -> KnowledgeEntryCreate: defaults = { "title": "測試 Runbook", "content": "## 測試內容\n這是整合測試建立的條目。", "entry_type": EntryType.RUNBOOK, "category": "infrastructure", "tags": ["test", "integration"], "source": EntrySource.HUMAN, } defaults.update(kwargs) return KnowledgeEntryCreate(**defaults) # ============================================================================= # Tests # ============================================================================= class TestKnowledgeRepositoryCreate: @pytest.mark.asyncio async def test_create_returns_entry_with_id(self, db_session): repo = KnowledgeDBRepository(db_session) entry = await repo.create(make_entry()) assert entry.id is not None assert entry.title == "測試 Runbook" assert entry.status == EntryStatus.DRAFT assert entry.view_count == 0 assert entry.source == EntrySource.HUMAN @pytest.mark.asyncio async def test_create_stores_tags(self, db_session): repo = KnowledgeDBRepository(db_session) entry = await repo.create(make_entry(tags=["k8s", "alert", "infra"])) assert set(entry.tags) == {"k8s", "alert", "infra"} @pytest.mark.asyncio async def test_create_optional_fields_none(self, db_session): repo = KnowledgeDBRepository(db_session) entry = await repo.create(make_entry(related_incident_id=None, created_by=None)) assert entry.related_incident_id is None assert entry.created_by is None class TestKnowledgeRepositoryGetById: @pytest.mark.asyncio async def test_get_by_id_returns_entry(self, db_session): repo = KnowledgeDBRepository(db_session) created = await repo.create(make_entry(title="可查詢條目")) fetched = await repo.get_by_id(created.id) assert fetched is not None assert fetched.id == created.id assert fetched.title == "可查詢條目" @pytest.mark.asyncio async def test_get_by_id_nonexistent_returns_none(self, db_session): repo = KnowledgeDBRepository(db_session) result = await repo.get_by_id("00000000-0000-0000-0000-000000000000") assert result is None @pytest.mark.asyncio async def test_get_by_id_archived_returns_none(self, db_session): repo = KnowledgeDBRepository(db_session) created = await repo.create(make_entry(title="即將封存")) await repo.delete(created.id) result = await repo.get_by_id(created.id) assert result is None class TestKnowledgeRepositoryList: @pytest.mark.asyncio async def test_list_returns_entries(self, db_session): repo = KnowledgeDBRepository(db_session) await repo.create(make_entry(title="條目 A", category="infrastructure")) await repo.create(make_entry(title="條目 B", category="infrastructure")) entries, total = await repo.list_entries() titles = [e.title for e in entries] assert "條目 A" in titles assert "條目 B" in titles assert total >= 2 @pytest.mark.asyncio async def test_list_filter_by_category(self, db_session): repo = KnowledgeDBRepository(db_session) await repo.create(make_entry(title="基礎設施條目", category="infrastructure")) await repo.create(make_entry(title="安全條目", category="security")) entries, total = await repo.list_entries(category="security") assert all(e.category == "security" for e in entries) assert any(e.title == "安全條目" for e in entries) @pytest.mark.asyncio async def test_list_filter_by_entry_type(self, db_session): repo = KnowledgeDBRepository(db_session) await repo.create(make_entry(title="Runbook 條目", entry_type=EntryType.RUNBOOK)) await repo.create(make_entry(title="最佳實踐條目", entry_type=EntryType.BEST_PRACTICE)) entries, _ = await repo.list_entries(entry_type=EntryType.BEST_PRACTICE) assert all(e.entry_type == EntryType.BEST_PRACTICE for e in entries) assert any(e.title == "最佳實踐條目" for e in entries) @pytest.mark.asyncio async def test_list_keyword_search(self, db_session): repo = KnowledgeDBRepository(db_session) await repo.create(make_entry(title="Kubernetes 告警處理", content="K8s pod crash 處理步驟")) await repo.create(make_entry(title="資料庫備份", content="PostgreSQL 備份策略")) entries, total = await repo.list_entries(q="Kubernetes") assert total >= 1 assert any("Kubernetes" in e.title for e in entries) @pytest.mark.asyncio async def test_list_excludes_archived(self, db_session): repo = KnowledgeDBRepository(db_session) created = await repo.create(make_entry(title="封存後不顯示")) await repo.delete(created.id) entries, _ = await repo.list_entries() assert all(e.id != created.id for e in entries) @pytest.mark.asyncio async def test_list_limit(self, db_session): repo = KnowledgeDBRepository(db_session) for i in range(5): await repo.create(make_entry(title=f"分頁測試 {i}")) entries, _ = await repo.list_entries(limit=3) assert len(entries) <= 3 class TestKnowledgeRepositoryUpdate: @pytest.mark.asyncio async def test_update_title(self, db_session): repo = KnowledgeDBRepository(db_session) created = await repo.create(make_entry(title="原始標題")) updated = await repo.update(created.id, {"title": "更新後標題"}) assert updated is not None assert updated.title == "更新後標題" @pytest.mark.asyncio async def test_update_status_to_approved(self, db_session): repo = KnowledgeDBRepository(db_session) created = await repo.create(make_entry()) updated = await repo.update(created.id, {"status": EntryStatus.APPROVED}) assert updated is not None assert updated.status == EntryStatus.APPROVED @pytest.mark.asyncio async def test_update_nonexistent_returns_none(self, db_session): repo = KnowledgeDBRepository(db_session) result = await repo.update("00000000-0000-0000-0000-000000000000", {"title": "X"}) assert result is None class TestKnowledgeRepositoryDelete: @pytest.mark.asyncio async def test_delete_soft_deletes(self, db_session): repo = KnowledgeDBRepository(db_session) created = await repo.create(make_entry()) success = await repo.delete(created.id) assert success is True # 軟刪除後 get_by_id 應返回 None assert await repo.get_by_id(created.id) is None @pytest.mark.asyncio async def test_delete_nonexistent_returns_false(self, db_session): repo = KnowledgeDBRepository(db_session) result = await repo.delete("00000000-0000-0000-0000-000000000000") assert result is False class TestKnowledgeRepositorySearch: @pytest.mark.asyncio async def test_search_by_title(self, db_session): repo = KnowledgeDBRepository(db_session) await repo.create(make_entry(title="Redis 連線問題排查")) await repo.create(make_entry(title="PostgreSQL 效能調優")) results = await repo.search("Redis") assert any("Redis" in e.title for e in results) assert all("Redis" in e.title or "Redis" in e.content for e in results) @pytest.mark.asyncio async def test_search_by_content(self, db_session): repo = KnowledgeDBRepository(db_session) await repo.create(make_entry( title="告警處理", content="當 Prometheus 發出 AlertFiring 時的處理步驟" )) results = await repo.search("AlertFiring") assert len(results) >= 1 assert any("AlertFiring" in e.content for e in results) @pytest.mark.asyncio async def test_search_no_results(self, db_session): repo = KnowledgeDBRepository(db_session) results = await repo.search("XYZABCNOTEXIST12345") assert results == [] class TestKnowledgeRepositoryCategories: @pytest.mark.asyncio async def test_get_categories_returns_counts(self, db_session): repo = KnowledgeDBRepository(db_session) await repo.create(make_entry(category="infrastructure")) await repo.create(make_entry(category="infrastructure")) await repo.create(make_entry(category="security")) categories = await repo.get_categories() cat_dict = dict(categories) assert "infrastructure" in cat_dict assert "security" in cat_dict assert cat_dict["infrastructure"] >= 2 assert cat_dict["security"] >= 1 class TestKnowledgeRepositoryViewCount: @pytest.mark.asyncio async def test_increment_view_count(self, db_session): repo = KnowledgeDBRepository(db_session) created = await repo.create(make_entry()) assert created.view_count == 0 await repo.increment_view_count(created.id) await repo.increment_view_count(created.id) await db_session.flush() fetched = await repo.get_by_id(created.id) assert fetched is not None assert fetched.view_count == 2 @pytest.mark.asyncio async def test_increment_view_count_nonexistent_returns_false(self, db_session): repo = KnowledgeDBRepository(db_session) result = await repo.increment_view_count("00000000-0000-0000-0000-000000000000") assert result is False