package main import ( "os" "reflect" "strings" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/transcode" "git.yeij.top/AskaEth/Cyrene_v2/internal/services/channelbridge" ) // TestWireChannels_InjectsVoiceTranscoder 是一道「接线存在性」防回归检查 // (裁决 11;与同目录 `TestWireChannels_WiresInboundDocumentStore` 同一手法)。 // // 钉住的事实:组合根把 `wireTranscode` 装配的本地转码引擎交给渠道 bridge,于是**不可播 // 格式的入站语音(微信 / QQ 的 AMR)在落副本之前先被转成可播副本** // (design/25 §7.1 第 ④ 步)。少了这一行,bridge 按 nil 走它的诚实路径 —— 那句 // `category=transcode_not_wired` 的 WARN 照记、消息一个字不丢,**但那条语音不落副本** // ⇒ 客户端拿不到可回放的字节,而**没有任何既有用例会变红**。所以必须显式钉住。 // // 为什么是源码级检查而不是端到端:组合根本身无法在单元测试里实例化(开库、迁移、绑端口, // 而后端还在运行);退化的行为侧(「转码不可用 ⇒ 不落副本」)已由 // `internal/services/channelbridge/inbound_voice_copy_test.go` 覆盖,这里要钉的只是 // **那一行注入**(以及它的**时机**,见下)。 func TestWireChannels_InjectsVoiceTranscoder(t *testing.T) { t.Parallel() src := readSource(t, "wire_channels.go") // 断言的是**一整行字段赋值**(行首到行尾),因此被注释掉的那一行 // (`\t\t// VoiceTranscoder: …`)或写在别处的同名字符串都不会让它通过。 const assignment = "\n\t\tVoiceTranscoder: a.transcoder," if !strings.Contains(src, assignment) { t.Fatalf("wire_channels.go 里找不到 %q —— 入站语音的 AMR 不会被转成可播副本,"+ "微信语音因此不落副本(design/25 §7.1 第 ④ 步 / 裁决 11)", strings.TrimSpace(assignment)) } // 注入面必须真装得下我们交进去的那个值:字段类型与 `a.transcoder` 的静态类型同源, // 否则「接线成功」这句话就只是字面巧合(例如字段被改成一条自定义窄接口)。 field, ok := reflect.TypeOf(channelbridge.Options{}).FieldByName("VoiceTranscoder") if !ok { t.Fatal("channelbridge.Options 没有 VoiceTranscoder 字段:注入面无处可放(设计改了?)") } if want := reflect.TypeOf((*transcode.Transcoder)(nil)).Elem(); field.Type != want { t.Fatalf("channelbridge.Options.VoiceTranscoder 的静态类型 = %v,want %v", field.Type, want) } // 注入的**时机**同样要钉住:`a.transcoder` 由 `wireTranscode()`(在 `wireRuntime` 里) // 赋值,所以 `wireRuntime` 必须先于 `wireChannels` 执行。反过来的话上面那行注入的是 // **nil** —— 一切照旧、一条日志都不差,只有「AMR 语音落副本」这条能力静默消失。 app := readSource(t, "wire.go") idxRuntime, idxChannels := strings.Index(app, "a.wireRuntime()"), strings.Index(app, "a.wireChannels()") if idxRuntime < 0 || idxChannels < 0 || idxRuntime > idxChannels { t.Fatalf("wire.go 里 wireRuntime / wireChannels 的先后不对(偏移 %d vs %d):转码引擎必须"+ "在渠道桥被构造**之前**装配好,否则 VoiceTranscoder 注入的是 nil", idxRuntime, idxChannels) } } // readSource 读一个组合根源文件并把行尾归一成 LF(断言里的 `\n` 因此与平台无关)。 func readSource(t *testing.T, name string) string { t.Helper() raw, err := os.ReadFile(name) if err != nil { t.Fatalf("read %s: %v", name, err) } return strings.ReplaceAll(string(raw), "\r\n", "\n") }