package channelbridge // 本文件承载 IM 入站附件摄取的**诚实性与边界**用例(design/24 §4.4;切片 M1), // 与 inbound_documents_test.go 是同一批夹具的搬家(夹具留在那边,同包可见): // // - 拒绝原因必须**分类正确**:超限说超限,绝不笼统说成「格式不支持」; // - 渠道只给远程引用(目前适配器的现实形态)时不代取、不静默; // - 图片类媒体保持现状(不走文档摄取); // - 组合根未接线时照旧跑回合,但模型知道附件读不到。 import ( "context" "strings" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/documents" ) // TestInboundOversizeFileIsHonestAboutSize 钉住诚实口径的**分类**:体积超限必须 // 说「超过体积上限」,绝不能笼统说成「格式不支持」——把原因说错也是不诚实。 func TestInboundOversizeFileIsHonestAboutSize(t *testing.T) { t.Parallel() env := newIngestEnv(t, documents.StoreConfig{MaxBytes: 8}, true) msg := fileMessage("这个大吗", "big.txt", []byte("这是明显超过八个字节的正文\n"), "text/plain") env.bridge.process(context.Background(), msg) if names := docDirNames(t, env.uploads); len(names) != 0 { t.Fatalf("超限附件被落库了:%v", names) } visible := env.model.visibleText() if !strings.Contains(visible, "体积上限") { t.Fatalf("超限附件的提示没有点明体积上限:%q", visible) } if strings.Contains(visible, "格式不在可读白名单内") { t.Fatalf("超限附件被说成了「格式不支持」(原因说错):%q", visible) } if log := env.logs.String(); !strings.Contains(log, "inbound attachment rejected") { t.Fatalf("缺少带来源的拒绝日志:%q", log) } } // TestInboundRemoteOnlyMediaIsHonestWithoutFetch 钉住「字节为空」这一形态:桥接侧 // **不代取**(受支持格式的代取由适配器在入站时按平台资产白名单完成,切片 IM-1), // 字节空 ⇒ 一行诚实提示 + 一条带来源的日志,绝不静默。 func TestInboundRemoteOnlyMediaIsHonestWithoutFetch(t *testing.T) { t.Parallel() env := newIngestEnv(t, documents.StoreConfig{}, true) msg := directMessage() msg.Content = "这个文件里是什么" msg.Media = []adapter.Media{{ Kind: adapter.MediaFile, URL: "https://cdn.example.com/secret-token=abc/report.pdf", Name: "report.pdf", Size: 1024, MIMEType: "application/pdf", }} env.bridge.process(context.Background(), msg) if names := docDirNames(t, env.uploads); len(names) != 0 { t.Fatalf("远程引用的附件被落库了:%v", names) } visible := env.model.visibleText() if !strings.Contains(visible, "report.pdf") || !strings.Contains(visible, "远程引用") { t.Fatalf("模型可见视图里没有诚实提示行:%q", visible) } log := env.logs.String() if !strings.Contains(log, "no inlined bytes") { t.Fatalf("缺少「无内联字节」的带来源日志:%q", log) } // 远程引用可能带凭据,绝不进日志。 if strings.Contains(log, "secret-token=abc") { t.Fatalf("日志里出现了远程引用原串(可能含凭据):%q", log) } } // TestInboundImageMediaKeepsExistingPath 钉住边界:图片类媒体**不**走文档摄取 // (design/24 §4.2 末:图片走既有视觉链路),因此不落库、不挂文档部件、不产生文档提示行。 // 它的视觉链路出口是 inbound_images.go 的 data: URI 图片部件(与本文的文档路互不影响)。 func TestInboundImageMediaKeepsExistingPath(t *testing.T) { t.Parallel() env := newIngestEnv(t, documents.StoreConfig{}, true) msg := directMessage() msg.Content = "看这张图" msg.Media = []adapter.Media{{ Kind: adapter.MediaImage, Name: "photo.png", MIMEType: "image/png", Data: []byte("\x89PNG\r\n\x1a\n"), Size: 8, }} env.bridge.process(context.Background(), msg) if names := docDirNames(t, env.uploads); len(names) != 0 { t.Fatalf("图片类媒体被文档摄取落库了:%v", names) } visible := env.model.visibleText() if strings.Contains(visible, "[附件 ") || strings.Contains(visible, "无法读取") { t.Fatalf("图片类媒体产生了文档清单/提示行:%q", visible) } if !strings.Contains(visible, "看这张图") { t.Fatalf("用户文本丢了:%q", visible) } if log := env.logs.String(); strings.Contains(log, "attachment") { t.Fatalf("图片类媒体产生了附件日志:%q", log) } } // TestInboundFileMediaWithoutStoreIsHonest 钉住「未接线」路径:组合根没给落盘配置时, // 带文件的消息照旧能跑(模型收到诚实提示),只是不落库 —— 绝不假装读到了。 func TestInboundFileMediaWithoutStoreIsHonest(t *testing.T) { t.Parallel() env := newIngestEnv(t, documents.StoreConfig{}, false) msg := fileMessage("这个文件呢", "notes.txt", []byte("正文\n"), "text/plain") env.bridge.process(context.Background(), msg) if env.model.calls() == 0 { t.Fatal("未接线时回合没有跑到模型") } if !strings.Contains(env.model.visibleText(), "未接线文档存储") { t.Fatalf("未接线时没有诚实提示:%q", env.model.visibleText()) } if log := env.logs.String(); !strings.Contains(log, "document store not wired") { t.Fatalf("未接线时没有带来源的日志:%q", log) } }