fix(km): recover primary knowledge readback via direct fallback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 1m36s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 1m36s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
This commit is contained in:
@@ -3,9 +3,11 @@ import asyncio
|
||||
import pytest
|
||||
|
||||
from src.models.knowledge import (
|
||||
CategoryCount,
|
||||
EntrySource,
|
||||
EntryStatus,
|
||||
EntryType,
|
||||
KnowledgeAssetTaxonomyCount,
|
||||
KnowledgeEntry,
|
||||
KnowledgeListResponse,
|
||||
)
|
||||
@@ -148,6 +150,71 @@ async def test_knowledge_list_entries_bounds_primary_timeout_to_source_backed(mo
|
||||
assert response.total == 13
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_knowledge_list_entries_uses_direct_readback_after_session_pool_timeout(monkeypatch) -> None:
|
||||
service = KnowledgeService.__new__(KnowledgeService)
|
||||
primary_calls = 0
|
||||
direct_calls = 0
|
||||
primary_entry = KnowledgeEntry(
|
||||
id="km-primary-live-1",
|
||||
title="Primary KM live row",
|
||||
content="Persistent KM row recovered through direct readback",
|
||||
entry_type=EntryType.RUNBOOK,
|
||||
category="alert_handling",
|
||||
tags=["telegram", "ai_agent", "log"],
|
||||
source=EntrySource.AI_EXTRACTED,
|
||||
status=EntryStatus.APPROVED,
|
||||
)
|
||||
|
||||
async def pool_exhausted_primary(**_kwargs):
|
||||
nonlocal primary_calls
|
||||
primary_calls += 1
|
||||
raise RuntimeError("db pool exhausted")
|
||||
|
||||
async def direct_readback(**kwargs):
|
||||
nonlocal direct_calls
|
||||
direct_calls += 1
|
||||
assert kwargs["limit"] == 50
|
||||
return KnowledgeListResponse(
|
||||
items=[primary_entry],
|
||||
total=727,
|
||||
categories=[
|
||||
CategoryCount(category="alert_handling", count=113),
|
||||
CategoryCount(category="AI自動化/Ansible受控修復", count=87),
|
||||
],
|
||||
asset_taxonomy=[
|
||||
KnowledgeAssetTaxonomyCount(key="log", count=727),
|
||||
KnowledgeAssetTaxonomyCount(key="alert", count=443),
|
||||
KnowledgeAssetTaxonomyCount(key="mcp", count=36),
|
||||
],
|
||||
readback_status="ready_direct_connection_after_session_timeout",
|
||||
primary_readback_ready=True,
|
||||
operator_stage="knowledge_readback_direct_connection_recovered",
|
||||
next_step="repair_session_pool_readback_without_hiding_primary_km",
|
||||
writes_on_read=False,
|
||||
manual_review_required=False,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(service, "_list_entries_from_primary", pool_exhausted_primary)
|
||||
monkeypatch.setattr(service, "_list_entries_from_direct", direct_readback)
|
||||
monkeypatch.setattr(knowledge_service_module, "_PRIMARY_KM_RETRY_DELAY_SECONDS", 0)
|
||||
|
||||
response = await service.list_entries(limit=50)
|
||||
|
||||
assert primary_calls == 2
|
||||
assert direct_calls == 1
|
||||
assert response.total == 727
|
||||
assert response.items == [primary_entry]
|
||||
assert response.readback_status == "ready_direct_connection_after_session_timeout"
|
||||
assert response.primary_readback_ready is True
|
||||
assert response.operator_stage == "knowledge_readback_direct_connection_recovered"
|
||||
assert response.next_step == "repair_session_pool_readback_without_hiding_primary_km"
|
||||
assert response.categories[0].category == "alert_handling"
|
||||
assert response.asset_taxonomy[0].key == "log"
|
||||
assert response.writes_on_read is False
|
||||
assert response.manual_review_required is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_knowledge_list_entries_keeps_primary_items_when_taxonomy_degrades(monkeypatch) -> None:
|
||||
primary_entry = KnowledgeEntry(
|
||||
@@ -247,6 +314,30 @@ async def test_knowledge_categories_fails_soft_when_readback_breaks(monkeypatch)
|
||||
assert all(row.count == 1 for row in categories)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_knowledge_categories_use_direct_readback_when_pool_is_busy(monkeypatch) -> None:
|
||||
service = KnowledgeService.__new__(KnowledgeService)
|
||||
|
||||
async def pool_exhausted_categories():
|
||||
raise RuntimeError("db pool exhausted")
|
||||
|
||||
async def direct_categories():
|
||||
return [
|
||||
CategoryCount(category="alert_handling", count=113),
|
||||
CategoryCount(category="AI自動化/Ansible受控修復", count=87),
|
||||
]
|
||||
|
||||
monkeypatch.setattr(service, "_read_primary_categories", pool_exhausted_categories)
|
||||
monkeypatch.setattr(service, "_read_categories_direct", direct_categories)
|
||||
|
||||
categories = await service.get_categories()
|
||||
|
||||
assert [(row.category, row.count) for row in categories] == [
|
||||
("alert_handling", 113),
|
||||
("AI自動化/Ansible受控修復", 87),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_knowledge_asset_taxonomy_fails_soft_when_readback_breaks(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
@@ -275,6 +366,32 @@ async def test_knowledge_asset_taxonomy_fails_soft_when_readback_breaks(monkeypa
|
||||
assert all(row.count >= 1 for row in taxonomy)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_knowledge_asset_taxonomy_uses_direct_readback_when_pool_is_busy(monkeypatch) -> None:
|
||||
service = KnowledgeService.__new__(KnowledgeService)
|
||||
|
||||
async def pool_exhausted_taxonomy():
|
||||
raise RuntimeError("db pool exhausted")
|
||||
|
||||
async def direct_taxonomy():
|
||||
return [
|
||||
KnowledgeAssetTaxonomyCount(key="log", count=727),
|
||||
KnowledgeAssetTaxonomyCount(key="alert", count=443),
|
||||
KnowledgeAssetTaxonomyCount(key="mcp", count=36),
|
||||
]
|
||||
|
||||
monkeypatch.setattr(service, "_read_primary_asset_taxonomy", pool_exhausted_taxonomy)
|
||||
monkeypatch.setattr(service, "_read_asset_taxonomy_direct", direct_taxonomy)
|
||||
|
||||
taxonomy = await service.get_asset_taxonomy()
|
||||
|
||||
assert [(row.key, row.count) for row in taxonomy] == [
|
||||
("log", 727),
|
||||
("alert", 443),
|
||||
("mcp", 36),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_knowledge_side_readbacks_bound_timeouts_to_source_backed(monkeypatch) -> None:
|
||||
service = KnowledgeService.__new__(KnowledgeService)
|
||||
|
||||
Reference in New Issue
Block a user