fix(km): read back mixed enum labels safely
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m6s
CD Pipeline / build-and-deploy (push) Failing after 10m25s
CD Pipeline / post-deploy-checks (push) Has been skipped
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 1s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 15s

This commit is contained in:
ogt
2026-07-14 15:46:56 +08:00
parent 938cb46cf2
commit f37bd611af
2 changed files with 80 additions and 9 deletions

View File

@@ -337,15 +337,18 @@ class KnowledgeDBRepository:
)
result = await self.db.execute(stmt)
entry_id: str = result.scalar_one()
# UPSERT 後用 id 重新 SELECT確保取到完整的 ORM 物件
# Read through the enum-safe projection because production keeps
# both legacy uppercase labels and the lowercase published label.
fetch_result = await self.db.execute(
select(KnowledgeEntryRecord).where(KnowledgeEntryRecord.id == entry_id)
select(*_knowledge_entry_columns()).where(
KnowledgeEntryRecord.id == entry_id
)
)
record = fetch_result.scalar_one()
record = fetch_result.mappings().one()
logger.info(
"knowledge_entry_upserted",
entry_id=record.id,
title=record.title,
entry_id=record["id"],
title=record["title"],
path_type=data.path_type,
incident_id=data.related_incident_id,
)
@@ -372,9 +375,19 @@ class KnowledgeDBRepository:
)
self.db.add(record)
await self.db.flush()
await self.db.refresh(record)
logger.info("knowledge_entry_created", entry_id=record.id, title=record.title)
return self._to_model(record)
entry_id = str(record.id)
fetch_result = await self.db.execute(
select(*_knowledge_entry_columns()).where(
KnowledgeEntryRecord.id == entry_id
)
)
readback = fetch_result.mappings().one()
logger.info(
"knowledge_entry_created",
entry_id=readback["id"],
title=readback["title"],
)
return self._to_model(readback)
async def get_by_id(self, entry_id: str) -> KnowledgeEntry | None:
"""根據 ID 取得知識條目(排除 archived"""

View File

@@ -7,7 +7,12 @@ import pytest
from sqlalchemy.dialects import postgresql
from src.db.models import KnowledgeEntryRecord
from src.models.knowledge import EntrySource, EntryStatus, EntryType
from src.models.knowledge import (
EntrySource,
EntryStatus,
EntryType,
KnowledgeEntryCreate,
)
from src.repositories.knowledge_repository import (
_ASSET_TAXONOMY_KEYS,
KnowledgeDBRepository,
@@ -277,3 +282,56 @@ def test_knowledge_enum_db_labels_match_deployed_postgresql_contract() -> None:
assert str(_knowledge_enum_db_value(EntryStatus.PUBLISHED)) == (
"'published'::entrystatus"
)
@pytest.mark.asyncio
async def test_idempotent_create_reads_back_published_with_enum_safe_projection(
) -> None:
class _CreatedId:
def scalar_one(self):
return "km-created"
class _CreateDb:
def __init__(self):
self.statements = []
self.results = [
_CreatedId(),
_MappingResult([
_record(
id="km-created",
entry_type="ANTI_PATTERN",
source="AI_EXTRACTED",
status="published",
related_incident_id="INC-TEST-ENUM",
path_type="ansible_no_write_replay:test",
).__dict__
]),
]
async def execute(self, statement):
self.statements.append(statement)
return self.results.pop(0)
db = _CreateDb()
entry = await KnowledgeDBRepository(db=db).create( # type: ignore[arg-type]
KnowledgeEntryCreate(
title="Enum-safe writeback",
content="Durable AI learning receipt",
entry_type=EntryType.ANTI_PATTERN,
category="AI automation",
source=EntrySource.AI_EXTRACTED,
status=EntryStatus.PUBLISHED,
related_incident_id="INC-TEST-ENUM",
path_type="ansible_no_write_replay:test",
)
)
assert entry.status is EntryStatus.PUBLISHED
readback_sql = str(
db.statements[1].compile(
dialect=postgresql.dialect(),
compile_kwargs={"literal_binds": True},
)
).lower()
assert "cast(knowledge_entries.status as varchar)" in readback_sql
assert "cast(knowledge_entries.entry_type as varchar)" in readback_sql