package transcode import ( "context" "encoding/base64" "errors" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry" "git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk" ) // stubTranscoder 是工具面测试用的最小 Transcoder(只为验证包装层,不碰真实音频)。 type stubTranscoder struct { supports bool res Result err error // lastReq 记录最后一次透传的请求(验证 source/target 归一化)。 lastReq Request } func (s *stubTranscoder) Transcode(_ context.Context, req Request) (Result, error) { s.lastReq = req if s.err != nil { return Result{}, s.err } return s.res, nil } func (s *stubTranscoder) Supports(_, _ Format) bool { return s.supports } func (s *stubTranscoder) Formats() []Format { return []Format{FormatAMR, FormatWAV} } // TestToolParameters 断言入参 schema 的形状(对象、必填 data_base64/target)。 func TestToolParameters(t *testing.T) { tool := NewTool(&stubTranscoder{}) if tool.Name() != ToolName { t.Fatalf("Name = %q, want %q", tool.Name(), ToolName) } if tool.Description() == "" { t.Fatalf("Description must not be empty") } schema, err := tool.Parameters() if err != nil { t.Fatalf("Parameters: %v", err) } if schema["type"] != "object" { t.Fatalf("schema type = %v, want object", schema["type"]) } required, ok := schema["required"].([]any) if !ok || len(required) != 2 { t.Fatalf("required = %v, want [data_base64 target]", schema["required"]) } props, ok := schema["properties"].(map[string]any) if !ok { t.Fatalf("properties missing") } for _, key := range []string{toolArgData, toolArgTarget, toolArgSource} { if _, ok := props[key]; !ok { t.Fatalf("properties missing %q", key) } } } // TestToolExecuteDecodesArgs 断言入参归一化与结果字段。 func TestToolExecuteDecodesArgs(t *testing.T) { stub := &stubTranscoder{supports: true, res: Result{ Data: []byte("wav-bytes"), Format: FormatWAV, Source: FormatAMR, SampleRate: amrSampleRate, Channels: amrChannels, DurationMs: 40, Frames: 2, }} out, err := NewTool(stub).Execute(context.Background(), map[string]any{ toolArgData: base64.StdEncoding.EncodeToString([]byte("amr-bytes")), toolArgTarget: ".WAV", }) if err != nil { t.Fatalf("Execute: %v", err) } if string(stub.lastReq.Data) != "amr-bytes" || stub.lastReq.Target != FormatWAV { t.Fatalf("request = %+v, want the decoded bytes and normalized target", stub.lastReq) } if stub.lastReq.Source != "" { t.Fatalf("source = %q, want empty(留空即嗅探)", stub.lastReq.Source) } res, ok := out.(map[string]any) if !ok { t.Fatalf("result type = %T, want map[string]any", out) } encoded, _ := res["data_base64"].(string) if decoded, err := base64.StdEncoding.DecodeString(encoded); err != nil || string(decoded) != "wav-bytes" { t.Fatalf("data_base64 round trip failed: %q (%v)", encoded, err) } if res["format"] != "wav" || res["source"] != "amr" || res["frames"] != 2 || res["duration_ms"] != int64(40) { t.Fatalf("result fields = %v", res) } } // TestToolRejectsBadArgs 断言入参不合法一律 ErrBadArgs,绝不猜测。 func TestToolRejectsBadArgs(t *testing.T) { tool := NewTool(&stubTranscoder{supports: true}) cases := []struct { name string args any }{ {"not an object", "data"}, {"nil args", nil}, {"missing data", map[string]any{toolArgTarget: "wav"}}, {"missing target", map[string]any{toolArgData: base64.StdEncoding.EncodeToString([]byte("x"))}}, {"bad base64", map[string]any{toolArgData: "!!!not base64!!!", toolArgTarget: "wav"}}, {"empty base64 payload", map[string]any{toolArgData: "", toolArgTarget: "wav"}}, {"wrong type", map[string]any{toolArgData: 42, toolArgTarget: "wav"}}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if _, err := tool.Execute(context.Background(), tc.args); !errors.Is(err, ErrBadArgs) { t.Fatalf("err = %v, want ErrBadArgs", err) } }) } } // TestToolPropagatesSentinels 断言工具面不吞哨兵(%w 链保持)。 func TestToolPropagatesSentinels(t *testing.T) { stub := &stubTranscoder{err: ErrUnsupportedFormat} _, err := NewTool(stub).Execute(context.Background(), map[string]any{ toolArgData: base64.StdEncoding.EncodeToString([]byte("x")), toolArgTarget: "wav", }) if !errors.Is(err, ErrUnsupportedFormat) { t.Fatalf("err = %v, want ErrUnsupportedFormat", err) } } // TestToolResultCap 断言工具结果体量有上限(产物是字节,不进上下文的红线: // 要转大文件请走进程内 DI,不要走模型工具面)。 func TestToolResultCap(t *testing.T) { big := make([]byte, MaxToolResultBytes+1) _, err := NewTool(&stubTranscoder{res: Result{Data: big}}).Execute(context.Background(), map[string]any{ toolArgData: base64.StdEncoding.EncodeToString([]byte("x")), toolArgTarget: "wav", }) if !errors.Is(err, ErrOutputTooLarge) { t.Fatalf("err = %v, want ErrOutputTooLarge", err) } } // TestToolNilTranscoderFailsLoudly 断言没接转码器时不假装成功。 func TestToolNilTranscoderFailsLoudly(t *testing.T) { _, err := NewTool(nil).Execute(context.Background(), map[string]any{ toolArgData: base64.StdEncoding.EncodeToString([]byte("x")), toolArgTarget: "wav", }) if err == nil { t.Fatalf("nil transcoder must fail") } } // TestRegisterToolIntoGlobalRegistry 是「两层注册」第二层的落点验收:走**既有** // 全局能力注册表,进去的是 KindTool、策略是零值(fail-closed = owner-only), // **不新增 Kind**(validKind 闭集不动)。 func TestRegisterToolIntoGlobalRegistry(t *testing.T) { caps := registry.New() if err := RegisterTool(caps, NewEngine(DefaultLimits(), WithCodecs(DefaultCodecs()...))); err != nil { t.Fatalf("RegisterTool: %v", err) } cap, err := caps.Get(ToolName) if err != nil { t.Fatalf("Get(%q): %v", ToolName, err) } if cap.Kind != registry.KindTool { t.Fatalf("kind = %q, want %q(不为转码新增 Kind)", cap.Kind, registry.KindTool) } if cap.Policy.AllowInGroup || cap.Policy.AllowInStranger { t.Fatalf("policy = %+v, want zero value (owner-only, fail-closed)", cap.Policy) } if _, ok := cap.Value.(*Tool); !ok { t.Fatalf("value type = %T, want *Tool", cap.Value) } if len(caps.List(registry.KindTool)) != 1 { t.Fatalf("tool listing did not include the transcode tool") } } // TestRegisterToolNilArgs 断言注册入口的 nil 纪律(与 vision.RegisterTools 同族)。 func TestRegisterToolNilArgs(t *testing.T) { if err := RegisterTool(nil, &stubTranscoder{}); !errors.Is(err, sdk.ErrNilRegistrar) { t.Fatalf("nil registrar err = %v, want sdk.ErrNilRegistrar", err) } caps := registry.New() if err := RegisterTool(caps, nil); err != nil { t.Fatalf("nil transcoder must be a no-op, got %v", err) } if len(caps.List(registry.KindTool)) != 0 { t.Fatalf("nil transcoder registered a capability") } } // TestStubTranscoderSatisfiesInterface 断言测试桩确实实现了接口(防止接口漂移 // 时测试静默失去意义)。 func TestStubTranscoderSatisfiesInterface(t *testing.T) { var _ Transcoder = (*stubTranscoder)(nil) var _ sdk.Tool = (*Tool)(nil) }