package embedding // 本文件是 M52 的出口回归探针:embedding 客户端里「把 net/http 的 *url.Error 直接 // 打进错误串」的两处 —— `embedChunk` 的传输失败与 `normalizeEndpointURL` 的解析失败 —— // 一律先过 trace.URLCause。 // // 为什么这两处特别敏感:/embeddings 的端点常常配成带 `?api_key=` 的网关地址,而 // `client.Do` 失败时 net/http 会把**整条地址**渲染进错误串(密码被打码、用户名与 query // 不打码)。原文进日志就等于把网关凭据交出去。 // // 这一包与 model/vision 的差别:这里原本用的是 `%w`(不是 `%v`),因此除了「去地址」, // 还必须钉住 `%w` 的链没被削弱 —— 把 URLCause 换成「新建一个错误」会让 // errors.Is(err, context.DeadlineExceeded) 变红。 import ( "context" "errors" "fmt" "net/http" "strings" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/config" ) const ( urlCauseQuerySecret = "SECRET-2f8a1c" urlCauseUser = "svc-user" urlCauseBadHost = "bad host.example.com" ) func embedURLCauseGoodURL() string { return "https://" + urlCauseUser + ":svc-pass@embed.example.com/v1/embeddings?api_key=" + urlCauseQuerySecret } func embedURLCauseBadURL() string { return "https://" + urlCauseUser + ":svc-pass@" + urlCauseBadHost + "/v1/embeddings?api_key=" + urlCauseQuerySecret } // TestEmbedDropsURLFromTransportError 钉住 embedChunk 里 `e.client.Do` 失败那一处, // 并钉住 `%w` 的哨兵链仍然成立(context.DeadlineExceeded 可 errors.Is)。 func TestEmbedDropsURLFromTransportError(t *testing.T) { t.Parallel() emb := NewAPI( config.EmbeddingConfig{APIURL: embedURLCauseGoodURL()}, WithRoundTripper(roundTripFunc(func(*http.Request) (*http.Response, error) { return nil, fmt.Errorf("net/http: %w", context.DeadlineExceeded) })), ) _, err := emb.Embed(context.Background(), []string{"hi"}) if err == nil { t.Fatal("want error, got nil") } if strings.Contains(err.Error(), urlCauseQuerySecret) { t.Fatalf("错误串带出了 query 里的凭据:%v", err) } if strings.Contains(err.Error(), urlCauseUser) { t.Fatalf("错误串带出了 userinfo 里的用户名:%v", err) } if !strings.Contains(err.Error(), "deadline exceeded") { t.Fatalf("错误串丢了底层原因(只许少地址):%v", err) } // `%w` 链必须还在:URLCause 只剥 *url.Error 外壳,内层原因原样返回。 if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("err = %v, want errors.Is(context.DeadlineExceeded)(哨兵链不许被带偏)", err) } } // TestEmbedDropsURLFromParseError 钉住 normalizeEndpointURL 的解析失败: // url.Parse 的 *url.Error 里就是整条 api URL。 func TestEmbedDropsURLFromParseError(t *testing.T) { t.Parallel() emb := NewAPI(config.EmbeddingConfig{APIURL: embedURLCauseBadURL()}) _, err := emb.Embed(context.Background(), []string{"hi"}) if err == nil { t.Fatal("want error, got nil") } if strings.Contains(err.Error(), urlCauseQuerySecret) || strings.Contains(err.Error(), urlCauseUser) { t.Fatalf("解析失败的串带出了 api URL 里的凭据:%v", err) } if !strings.Contains(err.Error(), "invalid character") { t.Fatalf("错误串丢了底层解析原因(只许少地址):%v", err) } if !strings.Contains(err.Error(), "parse api url") { t.Fatalf("错误串丢了来源标记:%v", err) } }