Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

207 lines
7.1 KiB
Python

"""Tests for UnifiedContext, Attachment, StreamEvent, and trace helpers."""
from __future__ import annotations
from deeptutor.core.context import Attachment, UnifiedContext
from deeptutor.core.errors import (
ConfigurationError,
DeepTutorError,
EnvironmentConfigError,
LLMContextError,
LLMServiceError,
ServiceError,
ValidationError,
)
from deeptutor.core.stream import StreamEvent, StreamEventType
from deeptutor.core.trace import (
build_trace_metadata,
derive_trace_metadata,
merge_trace_metadata,
new_call_id,
)
# ---------------------------------------------------------------------------
# Attachment
# ---------------------------------------------------------------------------
class TestAttachment:
def test_default_fields(self) -> None:
att = Attachment(type="image")
assert att.type == "image"
assert att.url == ""
assert att.base64 == ""
assert att.filename == ""
assert att.mime_type == ""
def test_full_construction(self) -> None:
att = Attachment(
type="pdf",
url="https://example.com/doc.pdf",
filename="doc.pdf",
mime_type="application/pdf",
)
assert att.type == "pdf"
assert att.filename == "doc.pdf"
# ---------------------------------------------------------------------------
# UnifiedContext
# ---------------------------------------------------------------------------
class TestUnifiedContext:
def test_defaults(self) -> None:
ctx = UnifiedContext()
assert ctx.session_id == ""
assert ctx.user_message == ""
assert ctx.conversation_history == []
assert ctx.enabled_tools is None
assert ctx.active_capability is None
assert ctx.knowledge_bases == []
assert ctx.attachments == []
assert ctx.config_overrides == {}
assert ctx.language == "en"
assert ctx.memory_context == ""
assert ctx.source_manifest == ""
assert ctx.metadata == {}
def test_mutable_defaults_are_independent(self) -> None:
ctx_a = UnifiedContext()
ctx_b = UnifiedContext()
ctx_a.conversation_history.append({"role": "user", "content": "hi"})
assert ctx_b.conversation_history == []
def test_full_construction(self) -> None:
att = Attachment(type="image", url="https://img.png")
ctx = UnifiedContext(
session_id="s1",
user_message="hello",
conversation_history=[{"role": "user", "content": "hi"}],
enabled_tools=["rag", "web_search"],
active_capability="deep_solve",
knowledge_bases=["kb1"],
attachments=[att],
config_overrides={"temperature": 0.5},
language="zh",
memory_context="user preference",
source_manifest='id=nb-1 name="Lecture" type=notebook preview="..."',
metadata={"turn_id": "t1"},
)
assert ctx.session_id == "s1"
assert ctx.active_capability == "deep_solve"
assert len(ctx.attachments) == 1
assert ctx.attachments[0].type == "image"
assert ctx.language == "zh"
def test_enabled_tools_none_vs_empty(self) -> None:
"""None means 'not specified', [] means 'explicitly disable all'."""
ctx_none = UnifiedContext(enabled_tools=None)
ctx_empty = UnifiedContext(enabled_tools=[])
assert ctx_none.enabled_tools is None
assert ctx_empty.enabled_tools == []
# ---------------------------------------------------------------------------
# StreamEvent
# ---------------------------------------------------------------------------
class TestStreamEvent:
def test_defaults(self) -> None:
event = StreamEvent(type=StreamEventType.CONTENT)
assert event.source == ""
assert event.stage == ""
assert event.content == ""
assert event.metadata == {}
assert event.session_id == ""
assert event.turn_id == ""
assert event.seq == 0
assert isinstance(event.timestamp, float)
def test_to_dict(self) -> None:
event = StreamEvent(
type=StreamEventType.TOOL_CALL,
source="chat",
stage="responding",
content="web_search",
metadata={"args": {"q": "test"}},
)
d = event.to_dict()
assert d["type"] == "tool_call"
assert d["source"] == "chat"
assert d["metadata"]["args"]["q"] == "test"
def test_all_event_types_have_string_values(self) -> None:
for member in StreamEventType:
assert isinstance(member.value, str)
assert member.value == member.value.lower()
# ---------------------------------------------------------------------------
# Errors
# ---------------------------------------------------------------------------
class TestErrors:
def test_base_error_str_without_details(self) -> None:
err = DeepTutorError("boom")
assert str(err) == "boom"
assert err.details == {}
def test_base_error_str_with_details(self) -> None:
err = DeepTutorError("boom", details={"key": "val"})
assert "key" in str(err)
def test_hierarchy(self) -> None:
assert issubclass(ConfigurationError, DeepTutorError)
assert issubclass(ValidationError, DeepTutorError)
assert issubclass(ServiceError, DeepTutorError)
assert issubclass(LLMServiceError, ServiceError)
assert issubclass(LLMContextError, LLMServiceError)
assert issubclass(EnvironmentConfigError, ConfigurationError)
# ---------------------------------------------------------------------------
# Trace helpers
# ---------------------------------------------------------------------------
class TestTrace:
def test_new_call_id_prefix(self) -> None:
cid = new_call_id("solver")
assert cid.startswith("solver-")
assert len(cid) == len("solver-") + 10
def test_new_call_ids_are_unique(self) -> None:
ids = {new_call_id() for _ in range(100)}
assert len(ids) == 100
def test_build_trace_metadata(self) -> None:
meta = build_trace_metadata(
call_id="c1",
phase="plan",
label="Plan step",
call_kind="llm_reasoning",
trace_id="session-1",
)
assert meta["call_id"] == "c1"
assert meta["phase"] == "plan"
assert meta["trace_id"] == "session-1"
assert "trace_role" not in meta # omitted when None
def test_derive_trace_metadata_overrides(self) -> None:
base = {"call_id": "c1", "phase": "plan", "label": "X", "call_kind": "llm"}
derived = derive_trace_metadata(base, phase="solve", label="Y")
assert derived["phase"] == "solve"
assert derived["label"] == "Y"
assert derived["call_id"] == "c1" # unchanged
def test_merge_trace_metadata(self) -> None:
merged = merge_trace_metadata({"a": 1}, {"b": 2, "a": 3})
assert merged == {"a": 3, "b": 2}
def test_merge_trace_metadata_handles_none(self) -> None:
assert merge_trace_metadata(None, None) == {}
assert merge_trace_metadata({"x": 1}, None) == {"x": 1}