新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
634 lines
22 KiB
Python
634 lines
22 KiB
Python
"""Unit tests for Google Chat adapter modules.
|
|
|
|
Tests cover: formatter, normalizer, audit, streaming, cards,
|
|
mentions, threads, slash_commands, session, pubsub_ modules.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
from yuxi.channels.models import (
|
|
Attachment,
|
|
ChannelIdentity,
|
|
ChannelResponse,
|
|
ChannelType,
|
|
ChatType,
|
|
EventType,
|
|
MessageType,
|
|
)
|
|
|
|
|
|
# ============================================================
|
|
# formatter.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestFormatter:
|
|
def test_basic_text_format(self):
|
|
from yuxi.channels.adapters.googlechat.formatter import format_outbound
|
|
|
|
resp = ChannelResponse(
|
|
identity=ChannelIdentity(
|
|
channel_id="googlechat",
|
|
channel_type=ChannelType.GOOGLE_CHAT,
|
|
channel_user_id="user1",
|
|
channel_chat_id="spaces/ABC",
|
|
),
|
|
content="hello world",
|
|
)
|
|
result = format_outbound(resp)
|
|
assert result["text"] == "hello world"
|
|
|
|
def test_empty_content_defaults_to_space(self):
|
|
from yuxi.channels.adapters.googlechat.formatter import format_outbound
|
|
|
|
resp = ChannelResponse(
|
|
identity=ChannelIdentity(
|
|
channel_id="googlechat",
|
|
channel_type=ChannelType.GOOGLE_CHAT,
|
|
channel_user_id="user1",
|
|
channel_chat_id="spaces/ABC",
|
|
),
|
|
content="",
|
|
)
|
|
result = format_outbound(resp)
|
|
assert result["text"] == " "
|
|
|
|
def test_card_in_metadata(self):
|
|
from yuxi.channels.adapters.googlechat.formatter import format_outbound
|
|
|
|
resp = ChannelResponse(
|
|
identity=ChannelIdentity(
|
|
channel_id="googlechat",
|
|
channel_type=ChannelType.GOOGLE_CHAT,
|
|
channel_user_id="user1",
|
|
channel_chat_id="spaces/ABC",
|
|
),
|
|
content="check this card",
|
|
metadata={"card": {"cards_v2": [{"card": {"header": {"title": "Test"}}}]}},
|
|
)
|
|
result = format_outbound(resp)
|
|
assert "cards_v2" in result
|
|
assert result["cards_v2"][0]["card"]["header"]["title"] == "Test"
|
|
|
|
def test_image_attachment(self):
|
|
from yuxi.channels.adapters.googlechat.formatter import format_outbound
|
|
|
|
resp = ChannelResponse(
|
|
identity=ChannelIdentity(
|
|
channel_id="googlechat",
|
|
channel_type=ChannelType.GOOGLE_CHAT,
|
|
channel_user_id="user1",
|
|
channel_chat_id="spaces/ABC",
|
|
),
|
|
content="image",
|
|
attachments=[Attachment(type="image", url="https://example.com/img.png")],
|
|
)
|
|
result = format_outbound(resp)
|
|
assert "cards_v2" in result
|
|
|
|
def test_thread_reply(self):
|
|
from yuxi.channels.adapters.googlechat.formatter import format_outbound
|
|
|
|
resp = ChannelResponse(
|
|
identity=ChannelIdentity(
|
|
channel_id="googlechat",
|
|
channel_type=ChannelType.GOOGLE_CHAT,
|
|
channel_user_id="user1",
|
|
channel_chat_id="spaces/ABC",
|
|
),
|
|
content="reply",
|
|
metadata={"thread_key": "thread-123"},
|
|
)
|
|
result = format_outbound(resp)
|
|
assert result["messageReplyOption"] == "REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
|
|
assert result["thread"]["threadKey"] == "thread-123"
|
|
|
|
def test_truncate_long_text(self):
|
|
from yuxi.channels.adapters.googlechat.formatter import format_outbound
|
|
|
|
long_content = "A" * 40000
|
|
resp = ChannelResponse(
|
|
identity=ChannelIdentity(
|
|
channel_id="googlechat",
|
|
channel_type=ChannelType.GOOGLE_CHAT,
|
|
channel_user_id="user1",
|
|
channel_chat_id="spaces/ABC",
|
|
),
|
|
content=long_content,
|
|
)
|
|
result = format_outbound(resp)
|
|
assert len(result["text"].encode("utf-8")) <= 32000
|
|
assert result["text"].endswith("...")
|
|
|
|
def test_truncate_short_text_unchanged(self):
|
|
from yuxi.channels.adapters.googlechat.formatter import format_outbound
|
|
|
|
resp = ChannelResponse(
|
|
identity=ChannelIdentity(
|
|
channel_id="googlechat",
|
|
channel_type=ChannelType.GOOGLE_CHAT,
|
|
channel_user_id="user1",
|
|
channel_chat_id="spaces/ABC",
|
|
),
|
|
content="short",
|
|
)
|
|
result = format_outbound(resp)
|
|
assert result["text"] == "short"
|
|
|
|
|
|
# ============================================================
|
|
# normalizer.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestNormalizer:
|
|
def test_basic_text_message(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import normalize_inbound
|
|
|
|
raw = {
|
|
"event": {
|
|
"type": "MESSAGE",
|
|
"message": {"text": "hello", "name": "spaces/ABC/messages/xyz"},
|
|
"space": {"name": "spaces/ABC", "spaceType": "SPACE"},
|
|
"user": {"name": "users/123"},
|
|
}
|
|
}
|
|
result = normalize_inbound("googlechat", ChannelType.GOOGLE_CHAT, raw)
|
|
assert result.content == "hello"
|
|
assert result.event_type == EventType.MESSAGE_RECEIVED
|
|
assert result.message_type == MessageType.TEXT
|
|
|
|
def test_added_to_space_event(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import normalize_inbound
|
|
|
|
raw = {
|
|
"event": {
|
|
"type": "ADDED_TO_SPACE",
|
|
"space": {"name": "spaces/ABC", "spaceType": "SPACE"},
|
|
"user": {"name": "users/123"},
|
|
}
|
|
}
|
|
result = normalize_inbound("googlechat", ChannelType.GOOGLE_CHAT, raw)
|
|
assert result.event_type == EventType.BOT_ADDED
|
|
|
|
def test_card_clicked_event(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import normalize_inbound
|
|
|
|
raw = {
|
|
"event": {
|
|
"type": "CARD_CLICKED",
|
|
"space": {"name": "spaces/ABC", "spaceType": "SPACE"},
|
|
"user": {"name": "users/123"},
|
|
},
|
|
"action": {"function": "approve", "parameters": [{"key": "id", "value": "1"}]},
|
|
}
|
|
result = normalize_inbound("googlechat", ChannelType.GOOGLE_CHAT, raw)
|
|
assert result.event_type == EventType.CARD_ACTION
|
|
|
|
def test_thread_message(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import normalize_inbound
|
|
|
|
raw = {
|
|
"event": {
|
|
"type": "MESSAGE",
|
|
"message": {
|
|
"text": "reply",
|
|
"name": "spaces/ABC/messages/xyz",
|
|
"thread": {"threadKey": "thread-1"},
|
|
},
|
|
"space": {"name": "spaces/ABC", "spaceType": "SPACE"},
|
|
"user": {"name": "users/123"},
|
|
}
|
|
}
|
|
result = normalize_inbound("googlechat", ChannelType.GOOGLE_CHAT, raw)
|
|
assert result.chat_type == ChatType.THREAD
|
|
assert "threads/thread-1" in result.identity.channel_chat_id
|
|
|
|
def test_direct_message(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import normalize_inbound
|
|
|
|
raw = {
|
|
"event": {
|
|
"type": "MESSAGE",
|
|
"message": {"text": "hi"},
|
|
"space": {"name": "spaces/ABC", "spaceType": "DIRECT_MESSAGE"},
|
|
"user": {"name": "users/123"},
|
|
}
|
|
}
|
|
result = normalize_inbound("googlechat", ChannelType.GOOGLE_CHAT, raw)
|
|
assert result.chat_type == ChatType.DIRECT
|
|
|
|
def test_image_message_type(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import normalize_inbound
|
|
|
|
raw = {
|
|
"event": {
|
|
"type": "MESSAGE",
|
|
"message": {
|
|
"text": "",
|
|
"attachment": [{"contentType": "image/png", "name": "att/1"}],
|
|
},
|
|
"space": {"name": "spaces/ABC", "spaceType": "SPACE"},
|
|
"user": {"name": "users/123"},
|
|
}
|
|
}
|
|
result = normalize_inbound("googlechat", ChannelType.GOOGLE_CHAT, raw)
|
|
assert result.message_type == MessageType.IMAGE
|
|
|
|
def test_file_message_type(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import normalize_inbound
|
|
|
|
raw = {
|
|
"event": {
|
|
"type": "MESSAGE",
|
|
"message": {
|
|
"text": "",
|
|
"attachment": [{"contentType": "application/pdf", "name": "att/1"}],
|
|
},
|
|
"space": {"name": "spaces/ABC", "spaceType": "SPACE"},
|
|
"user": {"name": "users/123"},
|
|
}
|
|
}
|
|
result = normalize_inbound("googlechat", ChannelType.GOOGLE_CHAT, raw)
|
|
assert result.message_type == MessageType.FILE
|
|
|
|
def test_extract_attachments(self):
|
|
from yuxi.channels.adapters.googlechat.normalizer import extract_attachments
|
|
|
|
msg = {
|
|
"attachment": [
|
|
{"name": "att/1", "contentName": "photo.png", "contentType": "image/png"},
|
|
{"name": "att/2", "contentName": "doc.pdf", "contentType": "application/pdf"},
|
|
]
|
|
}
|
|
result = extract_attachments(msg)
|
|
assert len(result) == 2
|
|
assert result[0].type == "image"
|
|
assert result[0].filename == "photo.png"
|
|
assert result[1].type == "file"
|
|
|
|
|
|
# ============================================================
|
|
# audit.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestAudit:
|
|
def test_check_membership_true(self):
|
|
from yuxi.channels.adapters.googlechat.audit import check_space_membership
|
|
|
|
service = MagicMock()
|
|
service.spaces().members().get().execute.return_value = {"name": "member1"}
|
|
|
|
result = asyncio.run(check_space_membership(service, "spaces/ABC", "users/123"))
|
|
assert result is True
|
|
|
|
def test_check_membership_false_on_error(self):
|
|
from yuxi.channels.adapters.googlechat.audit import check_space_membership
|
|
|
|
service = MagicMock()
|
|
service.spaces().members().get().execute.side_effect = Exception("not found")
|
|
|
|
result = asyncio.run(check_space_membership(service, "spaces/ABC", "users/999"))
|
|
assert result is False
|
|
|
|
def test_list_members_success(self):
|
|
from yuxi.channels.adapters.googlechat.audit import list_space_members
|
|
|
|
service = MagicMock()
|
|
service.spaces().members().list().execute.return_value = {"memberships": [{"name": "m1"}, {"name": "m2"}]}
|
|
|
|
result = asyncio.run(list_space_members(service, "spaces/ABC"))
|
|
assert len(result) == 2
|
|
|
|
def test_get_member_role_success(self):
|
|
from yuxi.channels.adapters.googlechat.audit import get_member_role
|
|
|
|
service = MagicMock()
|
|
service.spaces().members().get().execute.return_value = {"role": "ROLE_MANAGER"}
|
|
|
|
result = asyncio.run(get_member_role(service, "spaces/ABC", "users/123"))
|
|
assert result == "ROLE_MANAGER"
|
|
|
|
def test_get_member_role_none_on_error(self):
|
|
from yuxi.channels.adapters.googlechat.audit import get_member_role
|
|
|
|
service = MagicMock()
|
|
service.spaces().members().get().execute.side_effect = Exception("error")
|
|
|
|
result = asyncio.run(get_member_role(service, "spaces/ABC", "users/999"))
|
|
assert result is None
|
|
|
|
|
|
# ============================================================
|
|
# streaming.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestStreaming:
|
|
def test_register_message(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
assert mgr.has_pending("chat1") is True
|
|
assert mgr.get_message_name("chat1") == "msg1"
|
|
assert mgr.get_accumulated_text("chat1") == "hello"
|
|
|
|
def test_append_text(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
mgr.append_text("chat1", " world")
|
|
assert mgr.get_accumulated_text("chat1") == "hello world"
|
|
|
|
def test_clear(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
mgr.register_message("chat2", "msg2", "hi")
|
|
mgr.clear()
|
|
assert mgr.pending_count == 0
|
|
|
|
def test_pending_count(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
assert mgr.pending_count == 0
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
assert mgr.pending_count == 1
|
|
mgr.register_message("chat2", "msg2", "hi")
|
|
assert mgr.pending_count == 2
|
|
|
|
def test_should_update_after_register(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
mgr._last_update["chat1"] = 0
|
|
assert mgr.should_update("chat1") is True
|
|
|
|
def test_should_not_update_immediately_after_mark(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
mgr.mark_update("chat1")
|
|
assert mgr.should_update("chat1") is False
|
|
|
|
def test_finalize_clears_state(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
service = MagicMock()
|
|
service.spaces().messages().update().execute.return_value = {"name": "msg1"}
|
|
|
|
result = asyncio.run(mgr.finalize(service, "chat1"))
|
|
assert result.success is True
|
|
assert mgr.pending_count == 0
|
|
|
|
def test_send_update_without_finished_appends_indicator(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
service = MagicMock()
|
|
service.spaces().messages().update().execute.return_value = {"name": "msg1"}
|
|
|
|
result = asyncio.run(mgr.send_update(service, "chat1", finished=False))
|
|
assert result.success is True
|
|
assert mgr.pending_count == 1
|
|
|
|
def test_send_update_finished_clears_state(self):
|
|
from yuxi.channels.adapters.googlechat.streaming import StreamManager
|
|
|
|
mgr = StreamManager()
|
|
mgr.register_message("chat1", "msg1", "hello")
|
|
service = MagicMock()
|
|
service.spaces().messages().update().execute.return_value = {"name": "msg1"}
|
|
|
|
result = asyncio.run(mgr.send_update(service, "chat1", finished=True))
|
|
assert result.success is True
|
|
assert mgr.pending_count == 0
|
|
|
|
|
|
# ============================================================
|
|
# cards.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestCards:
|
|
def test_build_approval_card(self):
|
|
from yuxi.channels.adapters.googlechat.cards import build_approval_card
|
|
|
|
card = build_approval_card("Approve?", "action_1")
|
|
assert "cards_v2" in card
|
|
assert card["cards_v2"][0]["card"]["header"]["title"] == "Approve?"
|
|
|
|
def test_build_poll_card(self):
|
|
from yuxi.channels.adapters.googlechat.cards import build_poll_card
|
|
|
|
card = build_poll_card("Favorite?", ["A", "B", "C"], "poll_1")
|
|
assert "cards_v2" in card
|
|
sections = card["cards_v2"][0]["card"]["sections"]
|
|
assert len(sections) >= 2
|
|
|
|
def test_build_info_card(self):
|
|
from yuxi.channels.adapters.googlechat.cards import build_info_card
|
|
|
|
card = build_info_card("Info", {"key1": "val1", "key2": "val2"})
|
|
assert "cards_v2" in card
|
|
|
|
def test_build_form_card(self):
|
|
from yuxi.channels.adapters.googlechat.cards import build_form_card
|
|
|
|
fields = [{"label": "Name", "name": "name", "input_type": "SINGLE_LINE"}]
|
|
card = build_form_card("Form", fields, "submit_action")
|
|
assert "cards_v2" in card
|
|
|
|
def test_build_exec_approval_card(self):
|
|
from yuxi.channels.adapters.googlechat.cards import build_exec_approval_card
|
|
|
|
card = build_exec_approval_card(
|
|
"Execute?",
|
|
"exec_1",
|
|
detail_fields={"Action": "Delete", "Target": "file.txt"},
|
|
)
|
|
assert "cards_v2" in card
|
|
|
|
|
|
# ============================================================
|
|
# mentions.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestMentions:
|
|
def test_parse_user_mentions(self):
|
|
from yuxi.channels.adapters.googlechat.mentions import parse_mentions
|
|
|
|
msg = {
|
|
"annotations": [
|
|
{"type": "USER_MENTION", "userMention": {"user": {"name": "users/123"}, "type": "MENTION"}},
|
|
{"type": "USER_MENTION", "userMention": {"user": {"name": "users/456"}, "type": "MENTION"}},
|
|
]
|
|
}
|
|
result = parse_mentions(msg)
|
|
assert len(result.mentioned_user_ids) == 2
|
|
assert "users/123" in result.mentioned_user_ids
|
|
assert result.is_bot_mentioned is False
|
|
|
|
def test_parse_bot_mention(self):
|
|
from yuxi.channels.adapters.googlechat.mentions import parse_mentions
|
|
|
|
msg = {
|
|
"annotations": [
|
|
{"type": "USER_MENTION", "userMention": {"user": {"name": "users/bot1"}, "type": "BOT"}},
|
|
]
|
|
}
|
|
result = parse_mentions(msg)
|
|
assert result.is_bot_mentioned is True
|
|
|
|
def test_no_annotations(self):
|
|
from yuxi.channels.adapters.googlechat.mentions import parse_mentions
|
|
|
|
result = parse_mentions({})
|
|
assert result.mentioned_user_ids == []
|
|
assert result.is_bot_mentioned is False
|
|
|
|
|
|
# ============================================================
|
|
# threads.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestThreads:
|
|
def test_parse_thread_key(self):
|
|
from yuxi.channels.adapters.googlechat.threads import parse_thread_key
|
|
|
|
msg = {"thread": {"threadKey": "key-123"}}
|
|
assert parse_thread_key(msg) == "key-123"
|
|
|
|
def test_parse_thread_key_none(self):
|
|
from yuxi.channels.adapters.googlechat.threads import parse_thread_key
|
|
|
|
assert parse_thread_key({}) is None
|
|
|
|
def test_is_thread_message(self):
|
|
from yuxi.channels.adapters.googlechat.threads import is_thread_message
|
|
|
|
assert is_thread_message({"thread": {"threadKey": "key-1"}}) is True
|
|
assert is_thread_message({}) is False
|
|
|
|
def test_extract_thread_metadata(self):
|
|
from yuxi.channels.adapters.googlechat.threads import extract_thread_metadata
|
|
|
|
meta = extract_thread_metadata({"thread": {"threadKey": "key-1", "name": "spaces/ABC/threads/key-1"}})
|
|
assert meta["thread_key"] == "key-1"
|
|
assert meta["is_thread_root"] is False
|
|
|
|
def test_extract_thread_metadata_root(self):
|
|
from yuxi.channels.adapters.googlechat.threads import extract_thread_metadata
|
|
|
|
meta = extract_thread_metadata({})
|
|
assert meta["is_thread_root"] is True
|
|
|
|
|
|
# ============================================================
|
|
# slash_commands.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestSlashCommands:
|
|
def test_extract_valid_command(self):
|
|
from yuxi.channels.adapters.googlechat.slash_commands import extract_command
|
|
|
|
cmd, args = extract_command("/help")
|
|
assert cmd == "/help"
|
|
assert args == ""
|
|
|
|
def test_extract_command_with_args(self):
|
|
from yuxi.channels.adapters.googlechat.slash_commands import extract_command
|
|
|
|
cmd, args = extract_command("/reset now")
|
|
assert cmd == "/reset"
|
|
assert args == "now"
|
|
|
|
def test_unknown_command(self):
|
|
from yuxi.channels.adapters.googlechat.slash_commands import extract_command
|
|
|
|
cmd, args = extract_command("/unknown")
|
|
assert cmd is None
|
|
assert args == "/unknown"
|
|
|
|
def test_non_command_text(self):
|
|
from yuxi.channels.adapters.googlechat.slash_commands import extract_command
|
|
|
|
cmd, args = extract_command("hello world")
|
|
assert cmd is None
|
|
assert args == "hello world"
|
|
|
|
def test_get_command_help(self):
|
|
from yuxi.channels.adapters.googlechat.slash_commands import get_command_help
|
|
|
|
help_text = get_command_help()
|
|
assert "/help" in help_text
|
|
assert "/reset" in help_text
|
|
|
|
|
|
# ============================================================
|
|
# session.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestSession:
|
|
def test_build_thread_id_for_space(self):
|
|
from yuxi.channels.adapters.googlechat.session import build_thread_id
|
|
|
|
tid = build_thread_id("agent1", "spaces/ABC", "user@example.com")
|
|
assert tid == "agent:agent1:googlechat:space:ABC"
|
|
|
|
def test_build_thread_id_for_dm(self):
|
|
from yuxi.channels.adapters.googlechat.session import build_thread_id
|
|
|
|
tid = build_thread_id("main", "non-space-id", "user@example.com")
|
|
assert "googlechat" in tid
|
|
assert "user@example.com" in tid
|
|
|
|
|
|
# ============================================================
|
|
# pubsub_.py tests
|
|
# ============================================================
|
|
|
|
|
|
class TestPubsub:
|
|
def test_decode_pubsub_push(self):
|
|
from yuxi.channels.adapters.googlechat.pubsub_ import decode_pubsub_push
|
|
|
|
import base64
|
|
|
|
payload = {"event": {"type": "MESSAGE"}}
|
|
encoded = base64.b64encode(json.dumps(payload).encode()).decode()
|
|
body = {"message": {"data": encoded}}
|
|
result = decode_pubsub_push(body)
|
|
assert result["event"]["type"] == "MESSAGE"
|
|
|
|
def test_decode_empty_data_returns_none(self):
|
|
from yuxi.channels.adapters.googlechat.pubsub_ import decode_pubsub_push
|
|
|
|
result = decode_pubsub_push({"message": {}})
|
|
assert result is None
|
|
|
|
def test_decode_invalid_base64_returns_none(self):
|
|
from yuxi.channels.adapters.googlechat.pubsub_ import decode_pubsub_push
|
|
|
|
result = decode_pubsub_push({"message": {"data": "!!!invalid!!!"}})
|
|
assert result is None
|