Files
Cyrene_v2/cmd/cyrene/wire_channels_test.go
AskaEth ddd3d00d09 fix: 修「假话但绿」的用例 + 补 VoiceTranscoder 注入(W4)
- history_toolcalls_gap_test.go 改成正向断言:单路回合经 StartRoundWithTurnID 带权威 turnId
  ⇒ 落库行按该标识查得到 ⇒ HTTP 出口 toolCalls 在场;并保留「无权威标识 ⇒ 整体缺席」边界用例
- 组合根补 VoiceTranscoder: a.transcoder ⇒ 微信/QQ 的 AMR 语音在落盘前先转 wav 再落副本
- 4 个变异自检(删注入/摘 ctx 标识/清 turn_id/调换 wiring 顺序,均红)
2026-10-06 15:59:14 +08:00

69 lines
3.6 KiB
Go
Raw Permalink 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 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")
}