mirror of
https://github.com/arc53/DocsGPT.git
synced 2026-05-07 06:30:03 +00:00
280 lines
9.3 KiB
Python
280 lines
9.3 KiB
Python
from datetime import datetime, timezone
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from application.templates.namespaces import (
|
|
NamespaceBuilder,
|
|
NamespaceManager,
|
|
PassthroughNamespace,
|
|
SourceNamespace,
|
|
SystemNamespace,
|
|
ToolsNamespace,
|
|
)
|
|
|
|
|
|
# ── SystemNamespace ────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestSystemNamespace:
|
|
def test_namespace_name(self):
|
|
ns = SystemNamespace()
|
|
assert ns.namespace_name == "system"
|
|
|
|
def test_build_returns_expected_keys(self):
|
|
ns = SystemNamespace()
|
|
result = ns.build()
|
|
assert "date" in result
|
|
assert "time" in result
|
|
assert "timestamp" in result
|
|
assert "request_id" in result
|
|
assert "user_id" in result
|
|
|
|
def test_build_with_request_id(self):
|
|
ns = SystemNamespace()
|
|
result = ns.build(request_id="req-123")
|
|
assert result["request_id"] == "req-123"
|
|
|
|
def test_build_with_user_id(self):
|
|
ns = SystemNamespace()
|
|
result = ns.build(user_id="user-456")
|
|
assert result["user_id"] == "user-456"
|
|
|
|
def test_build_generates_uuid_when_no_request_id(self):
|
|
ns = SystemNamespace()
|
|
result = ns.build()
|
|
assert len(result["request_id"]) == 36 # UUID format
|
|
|
|
def test_user_id_defaults_to_none(self):
|
|
ns = SystemNamespace()
|
|
result = ns.build()
|
|
assert result["user_id"] is None
|
|
|
|
def test_date_format(self):
|
|
ns = SystemNamespace()
|
|
fixed = datetime(2026, 1, 15, 10, 30, 45, tzinfo=timezone.utc)
|
|
with patch("application.templates.namespaces.datetime") as mock_dt:
|
|
mock_dt.now.return_value = fixed
|
|
mock_dt.side_effect = lambda *a, **kw: datetime(*a, **kw)
|
|
result = ns.build()
|
|
assert result["date"] == "2026-01-15"
|
|
assert result["time"] == "10:30:45"
|
|
|
|
def test_extra_kwargs_ignored(self):
|
|
ns = SystemNamespace()
|
|
result = ns.build(unknown_param="whatever")
|
|
assert "date" in result
|
|
|
|
|
|
# ── PassthroughNamespace ───────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPassthroughNamespace:
|
|
def test_namespace_name(self):
|
|
ns = PassthroughNamespace()
|
|
assert ns.namespace_name == "passthrough"
|
|
|
|
def test_none_data_returns_empty(self):
|
|
ns = PassthroughNamespace()
|
|
assert ns.build(passthrough_data=None) == {}
|
|
|
|
def test_no_data_returns_empty(self):
|
|
ns = PassthroughNamespace()
|
|
assert ns.build() == {}
|
|
|
|
def test_safe_types_pass_through(self):
|
|
ns = PassthroughNamespace()
|
|
data = {"s": "string", "i": 42, "f": 3.14, "b": True, "n": None}
|
|
result = ns.build(passthrough_data=data)
|
|
assert result == data
|
|
|
|
def test_non_serializable_types_filtered(self):
|
|
ns = PassthroughNamespace()
|
|
data = {"good": "ok", "bad": object()}
|
|
result = ns.build(passthrough_data=data)
|
|
assert result == {"good": "ok"}
|
|
|
|
def test_list_value_filtered(self):
|
|
ns = PassthroughNamespace()
|
|
data = {"list_val": [1, 2, 3]}
|
|
result = ns.build(passthrough_data=data)
|
|
assert result == {}
|
|
|
|
def test_dict_value_filtered(self):
|
|
ns = PassthroughNamespace()
|
|
data = {"nested": {"key": "val"}}
|
|
result = ns.build(passthrough_data=data)
|
|
assert result == {}
|
|
|
|
|
|
# ── SourceNamespace ────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestSourceNamespace:
|
|
def test_namespace_name(self):
|
|
ns = SourceNamespace()
|
|
assert ns.namespace_name == "source"
|
|
|
|
def test_no_docs_returns_empty(self):
|
|
ns = SourceNamespace()
|
|
assert ns.build() == {}
|
|
|
|
def test_with_docs(self):
|
|
ns = SourceNamespace()
|
|
docs = [{"text": "doc1"}, {"text": "doc2"}]
|
|
result = ns.build(docs=docs)
|
|
assert result["documents"] == docs
|
|
assert result["count"] == 2
|
|
|
|
def test_with_docs_together(self):
|
|
ns = SourceNamespace()
|
|
result = ns.build(docs_together="all content together")
|
|
assert result["content"] == "all content together"
|
|
assert result["docs_together"] == "all content together"
|
|
assert result["summaries"] == "all content together"
|
|
|
|
def test_with_both_docs_and_docs_together(self):
|
|
ns = SourceNamespace()
|
|
docs = [{"text": "doc1"}]
|
|
result = ns.build(docs=docs, docs_together="combined")
|
|
assert result["documents"] == docs
|
|
assert result["count"] == 1
|
|
assert result["content"] == "combined"
|
|
|
|
def test_empty_docs_list_returns_empty(self):
|
|
ns = SourceNamespace()
|
|
result = ns.build(docs=[])
|
|
assert "documents" not in result
|
|
|
|
|
|
# ── ToolsNamespace ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestToolsNamespace:
|
|
def test_namespace_name(self):
|
|
ns = ToolsNamespace()
|
|
assert ns.namespace_name == "tools"
|
|
|
|
def test_none_data_returns_empty(self):
|
|
ns = ToolsNamespace()
|
|
assert ns.build(tools_data=None) == {}
|
|
|
|
def test_no_data_returns_empty(self):
|
|
ns = ToolsNamespace()
|
|
assert ns.build() == {}
|
|
|
|
def test_safe_types_pass_through(self):
|
|
ns = ToolsNamespace()
|
|
data = {
|
|
"str_tool": "result",
|
|
"dict_tool": {"key": "val"},
|
|
"list_tool": [1, 2],
|
|
"int_tool": 42,
|
|
"float_tool": 3.14,
|
|
"bool_tool": True,
|
|
"none_tool": None,
|
|
}
|
|
result = ns.build(tools_data=data)
|
|
assert result == data
|
|
|
|
def test_non_serializable_filtered(self):
|
|
ns = ToolsNamespace()
|
|
data = {"good": "ok", "bad": object()}
|
|
result = ns.build(tools_data=data)
|
|
assert result == {"good": "ok"}
|
|
|
|
|
|
# ── NamespaceBuilder ABC ──────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestNamespaceBuilderABC:
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
NamespaceBuilder()
|
|
|
|
def test_subclass_must_implement_both(self):
|
|
class Incomplete(NamespaceBuilder):
|
|
pass
|
|
|
|
with pytest.raises(TypeError):
|
|
Incomplete()
|
|
|
|
def test_concrete_subclass_works(self):
|
|
class Complete(NamespaceBuilder):
|
|
@property
|
|
def namespace_name(self):
|
|
return "test"
|
|
|
|
def build(self, **kwargs):
|
|
return {"ok": True}
|
|
|
|
inst = Complete()
|
|
assert inst.namespace_name == "test"
|
|
assert inst.build() == {"ok": True}
|
|
|
|
|
|
# ── NamespaceManager ──────────────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestNamespaceManager:
|
|
def test_build_context_contains_all_namespaces(self):
|
|
mgr = NamespaceManager()
|
|
ctx = mgr.build_context()
|
|
assert "system" in ctx
|
|
assert "passthrough" in ctx
|
|
assert "source" in ctx
|
|
assert "tools" in ctx
|
|
|
|
def test_system_namespace_populated(self):
|
|
mgr = NamespaceManager()
|
|
ctx = mgr.build_context(request_id="r1", user_id="u1")
|
|
assert ctx["system"]["request_id"] == "r1"
|
|
assert ctx["system"]["user_id"] == "u1"
|
|
|
|
def test_passthrough_namespace_populated(self):
|
|
mgr = NamespaceManager()
|
|
ctx = mgr.build_context(passthrough_data={"key": "val"})
|
|
assert ctx["passthrough"] == {"key": "val"}
|
|
|
|
def test_source_namespace_populated(self):
|
|
mgr = NamespaceManager()
|
|
docs = [{"text": "doc"}]
|
|
ctx = mgr.build_context(docs=docs)
|
|
assert ctx["source"]["count"] == 1
|
|
|
|
def test_tools_namespace_populated(self):
|
|
mgr = NamespaceManager()
|
|
ctx = mgr.build_context(tools_data={"search": "results"})
|
|
assert ctx["tools"] == {"search": "results"}
|
|
|
|
def test_empty_kwargs_all_namespaces_present(self):
|
|
mgr = NamespaceManager()
|
|
ctx = mgr.build_context()
|
|
for ns in ["system", "passthrough", "source", "tools"]:
|
|
assert ns in ctx
|
|
assert isinstance(ctx[ns], dict)
|
|
|
|
def test_builder_exception_returns_empty_namespace(self):
|
|
mgr = NamespaceManager()
|
|
with patch.object(
|
|
mgr._builders["system"], "build", side_effect=RuntimeError("boom")
|
|
):
|
|
ctx = mgr.build_context()
|
|
assert ctx["system"] == {}
|
|
assert "passthrough" in ctx
|
|
|
|
def test_get_builder_existing(self):
|
|
mgr = NamespaceManager()
|
|
builder = mgr.get_builder("system")
|
|
assert isinstance(builder, SystemNamespace)
|
|
|
|
def test_get_builder_nonexistent(self):
|
|
mgr = NamespaceManager()
|
|
assert mgr.get_builder("nonexistent") is None
|