251 lines
7.4 KiB
Python
251 lines
7.4 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from sqlalchemy import JSON, select
|
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
|
|
from app.models.base import Base
|
|
from app.models.ingest_event import IngestEvent
|
|
from app.models.knowledge_base_doc import KnowledgeBaseDoc
|
|
from app.models.project import Project
|
|
from app.models.signal import Signal
|
|
from app.models.task import Task
|
|
from app.models.truth_node import TruthNode
|
|
from app.models.user import User
|
|
from app.services.semantic_fact_service.hooks import register_semantic_fact_hooks
|
|
|
|
|
|
def _use_sqlite_json_types(*tables) -> None:
|
|
for table in tables:
|
|
for column in table.columns:
|
|
if column.type.__class__.__name__ in {"JSONB", "ARRAY", "Vector"}:
|
|
column.type = JSON()
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def db_session():
|
|
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
tables = [
|
|
User.__table__,
|
|
Project.__table__,
|
|
Signal.__table__,
|
|
TruthNode.__table__,
|
|
Task.__table__,
|
|
KnowledgeBaseDoc.__table__,
|
|
IngestEvent.__table__,
|
|
]
|
|
_use_sqlite_json_types(*tables)
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(lambda sync_conn: Base.metadata.create_all(sync_conn, tables=tables))
|
|
maker = async_sessionmaker(engine, expire_on_commit=False)
|
|
async with maker() as session:
|
|
yield session
|
|
await engine.dispose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_semantic_fact_hooks_enqueue_extract_events_for_supported_entities(db_session) -> None:
|
|
register_semantic_fact_hooks()
|
|
|
|
user = User(
|
|
tenant_id=1,
|
|
username="semantic_admin",
|
|
email="semantic_admin@example.com",
|
|
password_hash="hash",
|
|
name="管理员",
|
|
role="REVIEWER",
|
|
status="active",
|
|
is_active=True,
|
|
)
|
|
db_session.add(user)
|
|
await db_session.flush()
|
|
|
|
project = Project(
|
|
tenant_id=1,
|
|
name="alpha",
|
|
title="Alpha 项目",
|
|
description="项目描述",
|
|
owner_id=int(user.id),
|
|
status="active",
|
|
assigned_role="backend",
|
|
)
|
|
signal = Signal(
|
|
tenant_id=1,
|
|
title="信号标题",
|
|
content="信号内容",
|
|
status="待处理",
|
|
processed=False,
|
|
priority="high",
|
|
)
|
|
doc = KnowledgeBaseDoc(
|
|
tenant_id=1,
|
|
title="知识文档",
|
|
file_type="md",
|
|
source_type="file",
|
|
status="complete",
|
|
uploaded_by=int(user.id),
|
|
)
|
|
db_session.add_all([project, signal, doc])
|
|
await db_session.flush()
|
|
|
|
charter = TruthNode(
|
|
tenant_id=1,
|
|
node_type="charter",
|
|
parent_id=None,
|
|
project_id=int(project.id),
|
|
signal_id=None,
|
|
created_by=int(user.id),
|
|
title="开票系统需求",
|
|
content={"summary": "建设开票系统"},
|
|
version=1,
|
|
status="draft",
|
|
)
|
|
release = TruthNode(
|
|
tenant_id=1,
|
|
node_type="release",
|
|
parent_id=None,
|
|
project_id=int(project.id),
|
|
signal_id=None,
|
|
created_by=int(user.id),
|
|
title="发布记录",
|
|
content={"summary": "上线版本"},
|
|
version=1,
|
|
status="pending",
|
|
)
|
|
review = TruthNode(
|
|
tenant_id=1,
|
|
node_type="review",
|
|
parent_id=None,
|
|
project_id=int(project.id),
|
|
signal_id=None,
|
|
created_by=int(user.id),
|
|
title="阶段复盘",
|
|
content={"summary": "复盘内容"},
|
|
version=1,
|
|
status="draft",
|
|
)
|
|
db_session.add_all([charter, release, review])
|
|
await db_session.flush()
|
|
|
|
task = Task(
|
|
tenant_id=1,
|
|
truth_node_id=int(charter.id),
|
|
project_id=int(project.id),
|
|
assignee_id=int(user.id),
|
|
ai_recommended_assignees=None,
|
|
ai_breakdown_payload=None,
|
|
manual_overrides=None,
|
|
title="实现开票接口",
|
|
description="任务描述",
|
|
required_skill_tag_ids=[],
|
|
status="todo",
|
|
priority="high",
|
|
estimated_hours=None,
|
|
actual_hours=None,
|
|
quality_score=None,
|
|
due_date=None,
|
|
completed_at=None,
|
|
)
|
|
db_session.add(task)
|
|
await db_session.commit()
|
|
|
|
rows = (
|
|
await db_session.execute(
|
|
select(IngestEvent).where(
|
|
IngestEvent.tenant_id == 1,
|
|
IngestEvent.channel == "semantic_fact_extract",
|
|
).order_by(IngestEvent.event_key.asc())
|
|
)
|
|
).scalars().all()
|
|
payload_by_key = {str(row.event_key): row.payload for row in rows}
|
|
|
|
assert payload_by_key["document:%s" % int(doc.id)]["operation"] == "extract"
|
|
assert payload_by_key["project:%s" % int(project.id)]["operation"] == "extract"
|
|
assert payload_by_key["release:%s" % int(release.id)]["entity_type"] == "release"
|
|
assert payload_by_key["requirement:%s" % int(charter.id)]["entity_type"] == "requirement"
|
|
assert payload_by_key["review:%s" % int(review.id)]["entity_type"] == "review"
|
|
assert payload_by_key["signal:%s" % int(signal.id)]["entity_type"] == "signal"
|
|
assert payload_by_key["task:%s" % int(task.id)]["entity_type"] == "task"
|
|
assert payload_by_key["user:%s" % int(user.id)]["entity_type"] == "user"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_semantic_fact_hooks_switch_to_deactivate_for_archive_disable_and_delete(db_session) -> None:
|
|
register_semantic_fact_hooks()
|
|
|
|
user = User(
|
|
tenant_id=1,
|
|
username="semantic_admin_2",
|
|
email="semantic_admin_2@example.com",
|
|
password_hash="hash",
|
|
name="管理员2",
|
|
role="REVIEWER",
|
|
status="active",
|
|
is_active=True,
|
|
)
|
|
project = Project(
|
|
tenant_id=1,
|
|
name="alpha",
|
|
title="Alpha 项目",
|
|
description="项目描述",
|
|
owner_id=None,
|
|
status="active",
|
|
assigned_role="backend",
|
|
)
|
|
signal = Signal(
|
|
tenant_id=1,
|
|
title="信号标题",
|
|
content="信号内容",
|
|
status="待处理",
|
|
processed=False,
|
|
priority="high",
|
|
)
|
|
doc = KnowledgeBaseDoc(
|
|
tenant_id=1,
|
|
title="知识文档",
|
|
file_type="md",
|
|
source_type="file",
|
|
status="complete",
|
|
uploaded_by=None,
|
|
)
|
|
db_session.add_all([user, project, signal, doc])
|
|
await db_session.flush()
|
|
|
|
review = TruthNode(
|
|
tenant_id=1,
|
|
node_type="review",
|
|
parent_id=None,
|
|
project_id=int(project.id),
|
|
signal_id=None,
|
|
created_by=int(user.id),
|
|
title="阶段复盘",
|
|
content={"summary": "复盘内容"},
|
|
version=1,
|
|
status="draft",
|
|
)
|
|
db_session.add(review)
|
|
await db_session.commit()
|
|
|
|
user.is_active = False
|
|
user.status = "offline"
|
|
signal.status = "已关闭"
|
|
review.status = "archived"
|
|
await db_session.delete(doc)
|
|
await db_session.commit()
|
|
|
|
rows = (
|
|
await db_session.execute(
|
|
select(IngestEvent).where(
|
|
IngestEvent.tenant_id == 1,
|
|
IngestEvent.channel == "semantic_fact_extract",
|
|
)
|
|
)
|
|
).scalars().all()
|
|
payload_by_key = {str(row.event_key): row.payload for row in rows}
|
|
|
|
assert payload_by_key["document:%s" % int(doc.id)]["operation"] == "deactivate"
|
|
assert payload_by_key["review:%s" % int(review.id)]["operation"] == "deactivate"
|
|
assert payload_by_key["signal:%s" % int(signal.id)]["operation"] == "deactivate"
|
|
assert payload_by_key["user:%s" % int(user.id)]["operation"] == "deactivate"
|