mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: route Lark stream bot through international domain (#2148)
This commit is contained in:
@@ -32,6 +32,9 @@ import time
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FEISHU_DOMAIN = "https://open.feishu.cn"
|
||||||
|
LARK_DOMAIN = "https://open.larksuite.com"
|
||||||
|
|
||||||
# 尝试导入飞书 SDK
|
# 尝试导入飞书 SDK
|
||||||
try:
|
try:
|
||||||
import lark_oapi as lark
|
import lark_oapi as lark
|
||||||
@@ -43,7 +46,13 @@ try:
|
|||||||
CreateMessageRequest,
|
CreateMessageRequest,
|
||||||
CreateMessageRequestBody,
|
CreateMessageRequestBody,
|
||||||
)
|
)
|
||||||
|
from lark_oapi.core.const import (
|
||||||
|
FEISHU_DOMAIN as SDK_FEISHU_DOMAIN,
|
||||||
|
LARK_DOMAIN as SDK_LARK_DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
FEISHU_DOMAIN = SDK_FEISHU_DOMAIN
|
||||||
|
LARK_DOMAIN = SDK_LARK_DOMAIN
|
||||||
FEISHU_SDK_AVAILABLE = True
|
FEISHU_SDK_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
FEISHU_SDK_AVAILABLE = False
|
FEISHU_SDK_AVAILABLE = False
|
||||||
@@ -55,6 +64,21 @@ from src.formatters import format_feishu_markdown, chunk_content_by_max_bytes
|
|||||||
from src.config import get_config
|
from src.config import get_config
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_feishu_domain(domain: Optional[str]) -> str:
|
||||||
|
"""Resolve the configured Feishu/Lark region to an SDK API domain."""
|
||||||
|
raw_domain = str(domain or "feishu").strip().lower()
|
||||||
|
if raw_domain in ("feishu", FEISHU_DOMAIN.lower()):
|
||||||
|
return FEISHU_DOMAIN
|
||||||
|
if raw_domain in ("lark", LARK_DOMAIN.lower()):
|
||||||
|
return LARK_DOMAIN
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
"[Feishu Stream] 无效的 FEISHU_DOMAIN=%s,回退为 feishu",
|
||||||
|
raw_domain,
|
||||||
|
)
|
||||||
|
return FEISHU_DOMAIN
|
||||||
|
|
||||||
|
|
||||||
class FeishuReplyClient:
|
class FeishuReplyClient:
|
||||||
"""
|
"""
|
||||||
飞书消息回复客户端
|
飞书消息回复客户端
|
||||||
@@ -62,23 +86,37 @@ class FeishuReplyClient:
|
|||||||
使用飞书 API 发送回复消息。
|
使用飞书 API 发送回复消息。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app_id: str, app_secret: str):
|
def __init__(
|
||||||
|
self,
|
||||||
|
app_id: str,
|
||||||
|
app_secret: str,
|
||||||
|
domain: Optional[str] = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
app_id: 飞书应用 ID
|
app_id: 飞书应用 ID
|
||||||
app_secret: 飞书应用密钥
|
app_secret: 飞书应用密钥
|
||||||
|
domain: 飞书 API 区域(feishu/lark)或 SDK API 域名
|
||||||
"""
|
"""
|
||||||
if not FEISHU_SDK_AVAILABLE:
|
if not FEISHU_SDK_AVAILABLE:
|
||||||
raise ImportError("lark-oapi SDK 未安装")
|
raise ImportError("lark-oapi SDK 未安装")
|
||||||
|
|
||||||
|
config = get_config()
|
||||||
|
configured_domain = (
|
||||||
|
domain
|
||||||
|
if domain is not None
|
||||||
|
else getattr(config, 'feishu_domain', 'feishu')
|
||||||
|
)
|
||||||
|
self._domain = _resolve_feishu_domain(configured_domain)
|
||||||
|
|
||||||
self._client = lark.Client.builder() \
|
self._client = lark.Client.builder() \
|
||||||
.app_id(app_id) \
|
.app_id(app_id) \
|
||||||
.app_secret(app_secret) \
|
.app_secret(app_secret) \
|
||||||
|
.domain(self._domain) \
|
||||||
.log_level(lark.LogLevel.WARNING) \
|
.log_level(lark.LogLevel.WARNING) \
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
# 获取配置的最大字节数
|
# 获取配置的最大字节数
|
||||||
config = get_config()
|
|
||||||
self._max_bytes = getattr(config, 'feishu_max_bytes', 20000)
|
self._max_bytes = getattr(config, 'feishu_max_bytes', 20000)
|
||||||
|
|
||||||
def _send_interactive_card(self, content: str, message_id: Optional[str] = None,
|
def _send_interactive_card(self, content: str, message_id: Optional[str] = None,
|
||||||
@@ -538,12 +576,14 @@ class FeishuStreamClient:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
app_id: Optional[str] = None,
|
app_id: Optional[str] = None,
|
||||||
app_secret: Optional[str] = None
|
app_secret: Optional[str] = None,
|
||||||
|
domain: Optional[str] = None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
app_id: 应用 ID(不传则从配置读取)
|
app_id: 应用 ID(不传则从配置读取)
|
||||||
app_secret: 应用密钥(不传则从配置读取)
|
app_secret: 应用密钥(不传则从配置读取)
|
||||||
|
domain: 飞书 API 区域(feishu/lark)或 SDK API 域名
|
||||||
"""
|
"""
|
||||||
if not FEISHU_SDK_AVAILABLE:
|
if not FEISHU_SDK_AVAILABLE:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
@@ -556,6 +596,12 @@ class FeishuStreamClient:
|
|||||||
|
|
||||||
self._app_id = app_id or getattr(config, 'feishu_app_id', None)
|
self._app_id = app_id or getattr(config, 'feishu_app_id', None)
|
||||||
self._app_secret = app_secret or getattr(config, 'feishu_app_secret', None)
|
self._app_secret = app_secret or getattr(config, 'feishu_app_secret', None)
|
||||||
|
configured_domain = (
|
||||||
|
domain
|
||||||
|
if domain is not None
|
||||||
|
else getattr(config, 'feishu_domain', 'feishu')
|
||||||
|
)
|
||||||
|
self._domain = _resolve_feishu_domain(configured_domain)
|
||||||
|
|
||||||
if not self._app_id or not self._app_secret:
|
if not self._app_id or not self._app_secret:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -581,7 +627,11 @@ class FeishuStreamClient:
|
|||||||
def _create_event_handler(self) -> 'lark.EventDispatcherHandler':
|
def _create_event_handler(self) -> 'lark.EventDispatcherHandler':
|
||||||
"""创建事件分发处理器"""
|
"""创建事件分发处理器"""
|
||||||
# 创建回复客户端
|
# 创建回复客户端
|
||||||
self._reply_client = FeishuReplyClient(self._app_id, self._app_secret)
|
self._reply_client = FeishuReplyClient(
|
||||||
|
self._app_id,
|
||||||
|
self._app_secret,
|
||||||
|
domain=self._domain,
|
||||||
|
)
|
||||||
|
|
||||||
# 创建消息处理器
|
# 创建消息处理器
|
||||||
handler = FeishuStreamHandler(
|
handler = FeishuStreamHandler(
|
||||||
@@ -625,6 +675,7 @@ class FeishuStreamClient:
|
|||||||
app_id=self._app_id,
|
app_id=self._app_id,
|
||||||
app_secret=self._app_secret,
|
app_secret=self._app_secret,
|
||||||
event_handler=event_handler,
|
event_handler=event_handler,
|
||||||
|
domain=self._domain,
|
||||||
log_level=lark.LogLevel.WARNING,
|
log_level=lark.LogLevel.WARNING,
|
||||||
auto_reconnect=True
|
auto_reconnect=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- [修复] Web 分享图改为用户点击“分享”后才按需生成,不再在报告加载时自动请求
|
||||||
|
- [修复] 将 `SCREENING_ENABLED` 及 Web 选股功能开关归入“基础设置”,选股导航入口继续由该开关控制
|
||||||
|
- [修复] 飞书交互机器人在 `FEISHU_DOMAIN=lark` 时让 Stream 长连接与消息回复统一使用 Lark 国际版 API 域名,避免 SDK 默认连接飞书国内域名并返回 `Incorrect domain name`(fixes #937)。
|
||||||
|
- [修复] `scripts/ci_gate.sh` 的 `offline_test_suite` 给 `pytest -m "not network"` 加 `--timeout=120 -o timeout_method=thread` 与 `-o faulthandler_timeout=300`:单个测试(含其 teardown)超过 2 分钟直接 fail,单个测试(含其 teardown)超过 5 分钟时 dump 全部线程栈到 stderr。配合 `.github/requirements-ci.txt` 新增 `pytest-timeout>=2.3.0` 依赖。issue #2131 报告过 backend-gate 在 AlphaSift hotspot 用例附近间歇性无 traceback 卡住直到被 GitHub Actions 取消,此次修复让任何未来 CI hang 都会留下可定位的失败信息或 post-mortem 栈,而不是静默消亡。同步修正 `.github/workflows/docker-publish.yml` 的 `Install backend gate dependencies` 与 `setup-python cache-dependency-path` 对齐 `ci.yml` 的 backend-gate 依赖安装方式,避免发布流程跑同一个 `./scripts/ci_gate.sh` 时因缺少 `pytest-timeout` 而直接 fail。
|
||||||
|
|
||||||
|
- [修复] 选股策略栏稳定展示完整中文策略列表,并保留自定义策略 ID 入口
|
||||||
|
- [修复] 选股热点详情统一使用中文业务文案,不再显示内部类名、字段名和原始数据源错误
|
||||||
|
- [改进] 选中热点后先展示榜单已有摘要和核心股,后台再补充完整详情,并将单次热点源等待上限收紧为 8 秒
|
||||||
|
- [修复] 刷新热点榜单并保留当前题材时同步绕过详情缓存重拉该题材,避免新榜单继续搭配旧路线与成分股;详情质量可用且字段完整时不再展示底层数据源尝试失败
|
||||||
|
- [改进] 热点成分股并行获取东方财富与同花顺数据,并按固定数据源优先级合并;AkShare 调用复用 DSA 的可终止子进程 timeout,同花顺 HTTP 调用设置 connect/read timeout,并以进程级并发槽限制活跃任务、在返回前回收 worker;题材详情可按需复用 DSA 原生搜索服务的数据源优先级、时效过滤、缓存与同请求合并补充安全且带链接的近期消息,真实供应商调用使用限流且可终止回收的子进程,并在本地压缩摘要以避免额外 LLM 等待与降级提示;显式搜索增强不写入热点详情共享缓存或 Web 页面缓存
|
||||||
|
- [修复] 选股尾部轮换以分析器输入顺序为权威并保留并列分候选顺序;热点消息增强分别追加 `route` 与原始 `timeline`,同键搜索只允许缓存 owner 启动供应商链,子进程启动或清理失败也会释放全局容量;热点默认 provider 的正数超时配置覆盖板块、成分股及直接详情 fallback 并传递剩余硬截止,关闭外层预算仍保留单源安全上限;主动新闻搜索以同一绝对 deadline 覆盖缓存等待、重新竞争和 provider 执行,并区分有效空结果与运行失败
|
||||||
|
- [改进] 精简选股页面的重复说明,将任务标识、快照统计和排序诊断折叠到运行详情
|
||||||
|
- [新功能] SkillAggregator 基于独立满足 30 条 evaluated 门槛的真实 Skill Outcome bucket,使用 Beta 先验收缩、unable 惩罚和多周期证据加权生成有界运行时权重;缺失、低样本或异常统计保持中性。
|
||||||
|
- [改进] 将参考 AlphaSift 实现的选股核心与策略正式纳入 DSA,统一使用 `ScreeningService`、`SCREENING_ENABLED` 和 `/api/v1/screening`,并保留 Apache-2.0 归因与来源版本记录。
|
||||||
|
- [新功能] 选股结果按 `run_id` 持久化到 DSA 数据库,新增运行历史和数据源历史 API,接入公告事件上下文及其搜索缓存,并支持将候选连同筛选策略映射的 skill 交给单股深度分析。
|
||||||
|
- [修复] Outcome 候选按上次尝试时间公平调度,避免持续新增的缺失 key 使旧 `pending` outcome 永久得不到重试。
|
||||||
|
- [新功能] 新增按 skill、horizon 与 outcome engine version 独立聚合的只读 Skill Opinion 表现统计;少于 30 条 evaluated 样本时仅返回观察性计数,不输出表现指标或调整运行时权重。
|
||||||
|
- [修复] 选股主模型返回空内容、非 JSON 或低覆盖结构时继续尝试备用模型;全部失败时明确展示确定性因子排序状态。最终 JSON 必须在 `content` 或 `output` 块中;`reasoning_content`(链式思考)被视为内部辅助,不作为最终结果。
|
||||||
|
- [改进] Web 选股使用浏览器匿名种子与运行 ID 在最终评分后的有界近分池中生成每次运行的候选组合;Web Storage 不可用时在页面会话内存中复用同一临时种子;本地评分覆盖完整短名单,远程分析继续遵守数量上限,且只有完成相同后置分析的候选可参与轮换;原 Top-N 前半部分、明显领先候选、硬过滤、风险否决和得分保持不变。
|
||||||
|
- [改进] 热点榜单刷新与选股长流程解除双向串行等待,热点详情改为选中后按需加载;选股默认复用 5 分钟内且数据源优先级一致的成功全市场快照,同一来源链中的后备源结果也可复用,并在后台任务中展示快照、候选上下文、LLM 重排、最终评分和新闻事件增强阶段。
|
||||||
|
- [修复] 选股日线增强改用请求级 DSA-first fetcher 注入,不再临时替换进程级函数,避免重叠请求泄漏 wrapper 或重复执行 fallback;多个后置分析器按最新分数逐级重排,远程分析状态跟随实际提交候选,超限候选统一记录为 `skipped`,外部响应也不能改写未提交候选。
|
||||||
|
- [修复] 统一等价股票代码的本地日线候选与同源窗口解析;冲突沪深交易所代码不再降级匹配裸码,回测仅接受快照或交易日历确认的起点,并在同一起点中优先完整的单一代码窗口。
|
||||||
|
- [新功能] 新增按 individual SkillAgent 自身 signal、版本化 engine 与本地已存同源日线窗口计算并持久化 `skill_opinion_outcomes` 的核心服务。
|
||||||
|
- [修复] #1970 关闭认证属于高风险操作,即使携带有效 session cookie 也强制要求再次输入当前管理员密码二次确认;后端 `auth_update_settings` 的 disable 分支统一走 currentPassword 校验,命中 rate limit 时与 enable 路径一致返回 429,前端 `AuthSettingsCard` 在关闭认证时如有缺失当前密码将阻止提交并给出内联提示。
|
||||||
<!-- 新条目格式:- [类型] 描述(类型取值:新功能/改进/修复/文档/测试/chore)-->
|
<!-- 新条目格式:- [类型] 描述(类型取值:新功能/改进/修复/文档/测试/chore)-->
|
||||||
<!-- 每条独立一行追加到本段末尾,无需分类标题,合并时冲突最小 -->
|
<!-- 每条独立一行追加到本段末尾,无需分类标题,合并时冲突最小 -->
|
||||||
- [文档] FAQ 补充 macOS 桌面应用被 Gatekeeper quarantine 阻止启动时的受信任安装包临时放行步骤(refs #2113)。
|
- [文档] FAQ 补充 macOS 桌面应用被 Gatekeeper quarantine 阻止启动时的受信任安装包临时放行步骤(refs #2113)。
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ BOT_COMMAND_PREFIX=/
|
|||||||
# --- Feishu (Lark) bot ---
|
# --- Feishu (Lark) bot ---
|
||||||
FEISHU_APP_ID=
|
FEISHU_APP_ID=
|
||||||
FEISHU_APP_SECRET=
|
FEISHU_APP_SECRET=
|
||||||
|
FEISHU_DOMAIN=feishu # feishu (China) or lark (international/Lark)
|
||||||
FEISHU_VERIFICATION_TOKEN= # Event verification token
|
FEISHU_VERIFICATION_TOKEN= # Event verification token
|
||||||
FEISHU_ENCRYPT_KEY= # Encryption key (optional)
|
FEISHU_ENCRYPT_KEY= # Encryption key (optional)
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ FEISHU_CHAT_ID=oc_xxx
|
|||||||
FEISHU_RECEIVE_ID_TYPE=chat_id
|
FEISHU_RECEIVE_ID_TYPE=chat_id
|
||||||
# 事件订阅 / Stream Bot 时才开启
|
# 事件订阅 / Stream Bot 时才开启
|
||||||
FEISHU_STREAM_ENABLED=true
|
FEISHU_STREAM_ENABLED=true
|
||||||
|
# Lark 国际版必须设置为 lark;飞书国内版保持 feishu
|
||||||
|
FEISHU_DOMAIN=feishu
|
||||||
```
|
```
|
||||||
|
|
||||||
注意:
|
注意:
|
||||||
@@ -51,6 +53,7 @@ FEISHU_STREAM_ENABLED=true
|
|||||||
- 简单群通知优先配置 `FEISHU_WEBHOOK_URL`
|
- 简单群通知优先配置 `FEISHU_WEBHOOK_URL`
|
||||||
- 不用 Webhook 时,App Bot 主动推送必须同时配置 `FEISHU_APP_ID`、`FEISHU_APP_SECRET` 和 `FEISHU_CHAT_ID`
|
- 不用 Webhook 时,App Bot 主动推送必须同时配置 `FEISHU_APP_ID`、`FEISHU_APP_SECRET` 和 `FEISHU_CHAT_ID`
|
||||||
- `FEISHU_STREAM_ENABLED` 只代表事件订阅 / Stream Bot,不参与主动通知是否配置完成的判断
|
- `FEISHU_STREAM_ENABLED` 只代表事件订阅 / Stream Bot,不参与主动通知是否配置完成的判断
|
||||||
|
- `FEISHU_DOMAIN=lark` 会让 App Bot 主动发送、Stream 长连接和消息回复统一使用 `open.larksuite.com`;飞书国内版使用默认值 `feishu`
|
||||||
- 如果你做的是应用机器人 / Stream Bot,可直接看文末保留的原流程截图参考
|
- 如果你做的是应用机器人 / Stream Bot,可直接看文末保留的原流程截图参考
|
||||||
- App Bot 发送路径复用 `requirements.txt` 中已有的 `lark-oapi>=1.0.0`,标准安装使用 `pip install -r requirements.txt`;参考 [Feishu message create OpenAPI](https://open.feishu.cn/document/server-docs/im-v1/message/create)、[lark-oapi PyPI](https://pypi.org/project/lark-oapi/) 和 [SDK repo](https://github.com/larksuite/oapi-sdk-python)
|
- App Bot 发送路径复用 `requirements.txt` 中已有的 `lark-oapi>=1.0.0`,标准安装使用 `pip install -r requirements.txt`;参考 [Feishu message create OpenAPI](https://open.feishu.cn/document/server-docs/im-v1/message/create)、[lark-oapi PyPI](https://pypi.org/project/lark-oapi/) 和 [SDK repo](https://github.com/larksuite/oapi-sdk-python)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
from bot.platforms.feishu_stream import FeishuReplyClient
|
from unittest import mock
|
||||||
|
|
||||||
|
import bot.platforms.feishu_stream as feishu_stream
|
||||||
|
from bot.platforms.feishu_stream import (
|
||||||
|
FEISHU_DOMAIN,
|
||||||
|
LARK_DOMAIN,
|
||||||
|
FeishuReplyClient,
|
||||||
|
FeishuStreamClient,
|
||||||
|
)
|
||||||
from src.formatters import format_feishu_markdown
|
from src.formatters import format_feishu_markdown
|
||||||
|
|
||||||
|
|
||||||
@@ -30,6 +38,75 @@ class DummyFeishuReplyClient(FeishuReplyClient):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_reply_client_uses_lark_domain_from_config():
|
||||||
|
builder = mock.MagicMock()
|
||||||
|
builder.app_id.return_value = builder
|
||||||
|
builder.app_secret.return_value = builder
|
||||||
|
builder.domain.return_value = builder
|
||||||
|
builder.log_level.return_value = builder
|
||||||
|
|
||||||
|
config = mock.Mock(feishu_domain="lark", feishu_max_bytes=20000)
|
||||||
|
with mock.patch.object(feishu_stream, "FEISHU_SDK_AVAILABLE", True), \
|
||||||
|
mock.patch.object(feishu_stream, "get_config", return_value=config), \
|
||||||
|
mock.patch.object(feishu_stream, "lark", create=True) as lark:
|
||||||
|
lark.Client.builder.return_value = builder
|
||||||
|
FeishuReplyClient("cli_test", "secret")
|
||||||
|
|
||||||
|
builder.domain.assert_called_once_with(LARK_DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stream_and_reply_clients_share_lark_domain():
|
||||||
|
config = mock.Mock(
|
||||||
|
feishu_app_id="cli_test",
|
||||||
|
feishu_app_secret="secret",
|
||||||
|
feishu_domain="lark",
|
||||||
|
)
|
||||||
|
reply_client = mock.Mock()
|
||||||
|
ws_client = mock.Mock()
|
||||||
|
|
||||||
|
with mock.patch.object(feishu_stream, "FEISHU_SDK_AVAILABLE", True), \
|
||||||
|
mock.patch("src.config.get_config", return_value=config), \
|
||||||
|
mock.patch.object(
|
||||||
|
feishu_stream,
|
||||||
|
"FeishuReplyClient",
|
||||||
|
return_value=reply_client,
|
||||||
|
) as reply_client_class, \
|
||||||
|
mock.patch.object(feishu_stream, "ws", create=True) as ws, \
|
||||||
|
mock.patch.object(feishu_stream, "lark", create=True) as lark:
|
||||||
|
ws.Client.return_value = ws_client
|
||||||
|
client = FeishuStreamClient()
|
||||||
|
with mock.patch.object(client, "_create_event_handler", return_value=mock.Mock()):
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
ws.Client.assert_called_once_with(
|
||||||
|
app_id="cli_test",
|
||||||
|
app_secret="secret",
|
||||||
|
event_handler=mock.ANY,
|
||||||
|
domain=LARK_DOMAIN,
|
||||||
|
log_level=lark.LogLevel.WARNING,
|
||||||
|
auto_reconnect=True,
|
||||||
|
)
|
||||||
|
reply_client_class.assert_not_called()
|
||||||
|
ws_client.start.assert_called_once_with()
|
||||||
|
|
||||||
|
with mock.patch.object(feishu_stream, "FeishuReplyClient") as reply_client_class:
|
||||||
|
client._create_message_handler = mock.Mock(return_value=mock.Mock())
|
||||||
|
with mock.patch.object(feishu_stream, "FeishuStreamHandler"), \
|
||||||
|
mock.patch.object(feishu_stream, "lark", create=True):
|
||||||
|
client._create_event_handler()
|
||||||
|
|
||||||
|
reply_client_class.assert_called_once_with(
|
||||||
|
"cli_test",
|
||||||
|
"secret",
|
||||||
|
domain=LARK_DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_stream_domain_falls_back_to_feishu(caplog):
|
||||||
|
assert feishu_stream._resolve_feishu_domain("invalid") == FEISHU_DOMAIN
|
||||||
|
assert "回退为 feishu" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
def test_reply_text_chunked_keeps_reply_and_at_user(monkeypatch):
|
def test_reply_text_chunked_keeps_reply_and_at_user(monkeypatch):
|
||||||
client = DummyFeishuReplyClient(max_bytes=1000)
|
client = DummyFeishuReplyClient(max_bytes=1000)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user