Files
knowledge-governance/internal/dingtalkdiscovery/exec_runner_test.go
T
姚章浩 e00cab2f70 feat(settings): 数据源配置改成「列出来、勾选、变成来源」,并去掉文件格式筛选
用户对 系统设置 → 数据源配置 的四条要求:
① 这里不要做文件格式筛选(格式归目录级标注决定);
② 列出我账号下能看到的知识库;③ 列出钉盘能看到哪些;④ 勾选那些作为来源。

① 来源卡片不再渲染 FilenameFilterEditor(目录级筛选仍在「来源与目录」里,按目录就近覆盖)。
②③ 新端点 GET /api/v1/dingtalk-spaces/available(管理员,只读):用 dws 列举
   - 知识库:wiki +space-list --type orgWikiSpace/myWikiSpace(实测 17 + 1 个)
   - 钉盘:wiki space list --type mySpace/orgSpace(实测 我的文件 + 6 个组织空间)
   实测约束写进命令形态:--limit 上限是 50;翻页必须 --page-all(--max-items 兜底),
   否则超过一页的知识库会被静默截断(被截断时页面显示"可能不全"而不是假装列全)。
   列不出来 ≠ 空列表:dws 未登录/权限不足 → 503 + 原因;某一类失败只丢那一类。
④ 面板勾选后走既有登记路径(/api/v1/dingtalk-spaces),首次目录扫描自动排队;
   已登记的按空间名标「已是来源」并禁用勾选;扫码会话失效时直接弹二维码,扫完自动重试。

基础设施:api 与 worker 共享同一份 dws 状态(bind mount 同一份,refresh token 是轮换制,
不能各留一份)。子进程 HOME 必须指向 DWS_HOME —— 这条是实测踩出来的:漏了会报"未登录",
已有回归测试钉住。dws 子进程沿用白名单环境,库口令等变量不进子进程。

测试:新包 internal/dingtalkdiscovery(fixture 用真实 dws 输出形状):paging 约束、
截断提示、一类失败不连带、全部失败→ErrUnavailable、子进程环境;httpapi 侧 401/501/503/500
与不泄漏内部错误;OpenAPI 契约(400/503 组件)通过。前端 189 条 + tsc 干净。

面板实测:知识库(18)/钉盘(7)都列出来,已有三条钉钉来源自动标「已是来源」;
勾选「视觉文档」→ 加入为来源 → 新来源 sc-8a4be5b8293d1c9a048c3c7e9b194155(dingtalk,enabled)
+ source.catalog.refresh 已完成。
2026-10-10 16:46:39 +08:00

71 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dingtalkdiscovery
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// dws 的文件型 keychain 固定在 $HOME/.local/share/dws-cli,而 keychain 挂在 DWS_HOME 下面;
// 子进程若沿用容器自身的 HOME 就会报"未登录"。这条断言把该行为钉住(实测踩过)。
func TestDWSEnvPutsTheKeychainHomeOnTheDWSHome(t *testing.T) {
t.Setenv("DWS_HOME", "/data/home")
t.Setenv("HOME", "/home/app")
t.Setenv("DWS_CONFIG_DIR", "/data/dws")
t.Setenv("DWS_DISABLE_KEYCHAIN", "1")
t.Setenv("DWS_BIN", "/usr/local/bin/dws")
t.Setenv("DATABASE_URL", "postgres://governance:secret@postgres/governance")
t.Setenv("TEST_WEKNORA_API_KEY", "sk-should-not-leak")
env := dwsEnv()
joined := strings.Join(env, "\n")
if !strings.Contains(joined, "HOME=/data/home") {
t.Fatalf("dws child HOME should follow DWS_HOME, env=%v", env)
}
if strings.Contains(joined, "HOME=/home/app") {
t.Fatalf("container HOME leaked into dws child: %v", env)
}
for _, leaked := range []string{"DATABASE_URL", "TEST_WEKNORA_API_KEY"} {
if strings.Contains(joined, leaked) {
t.Fatalf("%s leaked into the dws child: %v", leaked, env)
}
}
}
// 没有 DWS_HOME 时退回进程自己的 HOME。
func TestDWSEnvFallsBackToProcessHome(t *testing.T) {
t.Setenv("DWS_HOME", "")
t.Setenv("HOME", "/home/app")
if joined := strings.Join(dwsEnv(), "\n"); !strings.Contains(joined, "HOME=/home/app") {
t.Fatalf("expected fallback HOME, got %v", dwsEnv())
}
}
// 真跑一个假的 dws:确认参数被原样传给二进制,且非零退出码时仍把 stdout 交回上层
// (dws 把错误 JSON 写在 stdout 上,丢了它就没法分辨"未登录"与"权限不足")。
func TestExecRunnerRunsTheBinaryAndKeepsStdoutOnFailure(t *testing.T) {
dir := t.TempDir()
binary := filepath.Join(dir, "dws")
script := "#!/bin/sh\necho \"args:$@\"\necho '{\"ok\":false,\"error\":{\"message\":\"dws: 未登录\"}}'\nexit 4\n"
if err := os.WriteFile(binary, []byte(script), 0o755); err != nil {
t.Fatal(err)
}
runner := ExecRunner{Binary: binary, Timeout: 5 * time.Second}
out, err := runner.RunDWS(context.Background(), "wiki", "+space-list", "--limit", "200")
if err == nil {
t.Fatal("expected a non-zero exit to be reported")
}
if !strings.Contains(string(out), "未登录") {
t.Fatalf("stdout should be preserved on failure, got %q", string(out))
}
if !strings.Contains(string(out), "args:wiki +space-list --limit 200") {
t.Fatalf("args not passed through: %q", string(out))
}
if !strings.Contains(err.Error(), "wiki +space-list") {
t.Fatalf("error should name the command: %v", err)
}
}