e00cab2f70
用户对 系统设置 → 数据源配置 的四条要求: ① 这里不要做文件格式筛选(格式归目录级标注决定); ② 列出我账号下能看到的知识库;③ 列出钉盘能看到哪些;④ 勾选那些作为来源。 ① 来源卡片不再渲染 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 已完成。
71 lines
2.6 KiB
Go
71 lines
2.6 KiB
Go
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)
|
||
}
|
||
}
|