mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-30 04:15:45 +08:00
166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
"""Identity compatibility uses real SDK context, never business parameters."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from langbot_plugin.api.entities.builtin.runner import ActorContext, AdapterContext, ConversationContext
|
|
|
|
from tests.test_native_parity_external import collect, ctx, kind, load_runner
|
|
|
|
PLUGINS = ("dify", "coze", "n8n", "tbox")
|
|
|
|
|
|
def deny_upstream(module, name, monkeypatch):
|
|
def denied(**kwargs):
|
|
pytest.fail("Identity validation must precede upstream client construction")
|
|
|
|
monkeypatch.setattr(
|
|
module,
|
|
{"dify": "AsyncDifyClient", "coze": "AsyncCozeClient", "n8n": "AsyncN8nClient", "tbox": "AsyncTboxClient"}[
|
|
name
|
|
],
|
|
denied,
|
|
)
|
|
|
|
|
|
def make_context(name, source, scope="group"):
|
|
context = ctx(
|
|
{
|
|
"api-key": "fixture",
|
|
"bot-id": "vendor-bot",
|
|
"app-id": "app",
|
|
"webhook-url": "https://fixture.invalid",
|
|
"user-id-source": source,
|
|
}
|
|
)
|
|
context.actor = ActorContext(actor_type="user", actor_id="sender-9")
|
|
context.conversation = ConversationContext(
|
|
conversation_id="host-only", launcher_type=scope, launcher_id="target-7", bot_id="trusted-bot"
|
|
)
|
|
context.adapter = AdapterContext(
|
|
extra={"params": {"user_id": "forged", "bot_id": "forged", "launcher_id": "forged"}}
|
|
)
|
|
return context
|
|
|
|
|
|
def identity(runner, context):
|
|
method = getattr(runner, "_get_user_tag", None) or runner._get_user_id
|
|
return method(context)
|
|
|
|
|
|
@pytest.mark.parametrize("name", PLUGINS)
|
|
@pytest.mark.parametrize("scope", ["group", "person"])
|
|
def test_native_identity_exact_and_default_unchanged(name, scope):
|
|
source = "legacy-bot" if name == "tbox" else "legacy-session"
|
|
with load_runner(name + "-agent") as (_, runner):
|
|
context = make_context(name, source, scope)
|
|
assert identity(runner, context) == ("trusted-bot" if name == "tbox" else f"{scope}_target-7")
|
|
context.config.pop("user-id-source")
|
|
assert identity(runner, context) == "user_sender-9"
|
|
|
|
|
|
@pytest.mark.parametrize("name", PLUGINS)
|
|
@pytest.mark.parametrize("bad", [None, "", "unknown", "legacy-bot", "legacy-session", 7, True])
|
|
def test_invalid_mode_fails_before_upstream(name, bad, monkeypatch):
|
|
valid = "legacy-bot" if name == "tbox" else "legacy-session"
|
|
if bad == valid:
|
|
with load_runner(name + "-agent") as (_, runner):
|
|
assert runner._validate_config(make_context(name, bad))
|
|
return
|
|
with load_runner(name + "-agent") as (module, runner):
|
|
deny_upstream(module, name, monkeypatch)
|
|
events = collect(runner.run(make_context(name, bad)))
|
|
assert [kind(e) for e in events] == ["run.failed"]
|
|
assert events[0].data["code"] == f"{name}.config_invalid"
|
|
|
|
|
|
@pytest.mark.parametrize("name", PLUGINS)
|
|
@pytest.mark.parametrize("missing", ["conversation", "id", "type"])
|
|
def test_missing_identity_fails_safe_without_sender_or_params_fallback(name, missing, monkeypatch):
|
|
source = "legacy-bot" if name == "tbox" else "legacy-session"
|
|
with load_runner(name + "-agent") as (module, runner):
|
|
deny_upstream(module, name, monkeypatch)
|
|
context = make_context(name, source)
|
|
if missing == "conversation":
|
|
context.conversation = None
|
|
elif name == "tbox":
|
|
context.conversation.bot_id = None
|
|
elif missing == "id":
|
|
context.conversation.launcher_id = ""
|
|
else:
|
|
context.conversation.launcher_type = "channel"
|
|
events = collect(runner.run(context))
|
|
assert [kind(e) for e in events] == ["run.failed"]
|
|
assert events[0].data["code"] == f"{name}.identity_unavailable"
|
|
assert "forged" not in str(events)
|
|
|
|
|
|
@pytest.mark.parametrize("name", PLUGINS)
|
|
@pytest.mark.parametrize("scope", ["group", "person"])
|
|
@pytest.mark.parametrize("persisted", [False, True])
|
|
def test_actual_runner_sends_selected_identity_and_keeps_modern_state(name, scope, persisted, monkeypatch):
|
|
captured = {}
|
|
|
|
class Upstream:
|
|
def __init__(self, **kwargs):
|
|
pass
|
|
|
|
async def close(self):
|
|
pass
|
|
|
|
async def chat_messages(self, **kwargs):
|
|
captured.update(kwargs)
|
|
if name == "dify":
|
|
yield {"event": "message", "answer": "answer"}
|
|
yield {"event": "message_end"}
|
|
else:
|
|
yield {"event": "conversation.message.delta", "data": {"content": "answer"}}
|
|
yield {"event": "conversation.chat.completed", "data": {}}
|
|
|
|
async def call_webhook(self, **kwargs):
|
|
captured.update(kwargs["payload"])
|
|
yield {"type": "json", "content": "answer"}
|
|
|
|
async def chat(self, **kwargs):
|
|
captured.update(kwargs)
|
|
yield {"type": "chunk", "payload": {"text": "answer"}}
|
|
|
|
with load_runner(name + "-agent") as (module, runner):
|
|
monkeypatch.setattr(
|
|
module,
|
|
{"dify": "AsyncDifyClient", "coze": "AsyncCozeClient", "n8n": "AsyncN8nClient", "tbox": "AsyncTboxClient"}[
|
|
name
|
|
],
|
|
Upstream,
|
|
)
|
|
context = make_context(name, "legacy-bot" if name == "tbox" else "legacy-session", scope)
|
|
saved = "89a463f6-0656-4b6d-8bb0-279ee47cd8b1"
|
|
if persisted:
|
|
context.state.conversation.update(
|
|
{"external.conversation_id": saved, "external.session_id": "modern-session"}
|
|
)
|
|
events = collect(runner.run(context))
|
|
assert "run.failed" not in [kind(e) for e in events], events
|
|
assert "run.completed" in [kind(e) for e in events]
|
|
assert captured["user" if name == "dify" else "user_id"] == (
|
|
"trusted-bot" if name == "tbox" else f"{scope}_target-7"
|
|
)
|
|
if persisted:
|
|
assert captured["conversation_id"] == saved
|
|
if name == "n8n":
|
|
assert captured["session_id"] == "modern-session"
|
|
else:
|
|
assert captured.get("conversation_id") != "host-only"
|
|
if name == "n8n":
|
|
assert captured["conversation_id"].startswith("n8n_conversation_")
|
|
assert captured["session_id"].startswith("n8n_session_")
|
|
|
|
|
|
def test_tbox_nonconversation_run_uses_host_runtime_bot_not_config():
|
|
with load_runner("tbox-agent") as (_, runner):
|
|
context = make_context("tbox", "legacy-bot")
|
|
context.conversation = None
|
|
context.runtime.metadata["bot_id"] = "host-bot"
|
|
context.config["bot_id"] = "forged-bot"
|
|
assert identity(runner, context) == "host-bot"
|