Files
weichenandCursor bd53e9ba79
ci / vet-and-test (push) Successful in 3s
ci / docker-checkout-hostgateway (push) Successful in 3s
Add a shared error-code catalog so failures can be traced by stable codes.
Services can resolve legacy aliases and expose the catalog over CLI and HTTP instead of ad-hoc error strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-29 13:43:12 +00:00

178 lines
4.7 KiB
Go

package errcode
import (
"encoding/json"
"errors"
"fmt"
"testing"
)
func sampleDef(code Code) Definition {
return Definition{
Code: code,
HTTP: 503,
Severity: SeverityError,
Retry: RetryTransient,
SideEffects: SideEffectNone,
Summary: "提供方不可用",
Hint: "退避后重试",
Aliases: []string{"provider_unavailable"},
}
}
func TestCodeFormat(t *testing.T) {
ok := []Code{SysInternal, AgentProviderUnavailable, SquadRoleNotFound, "custom-domain.snake_name"}
for _, c := range ok {
if !c.Valid() {
t.Fatalf("%s should be valid", c)
}
if c.Domain() == "" || c.Mnemonic() == "" {
t.Fatalf("%s domain/mnemonic empty", c)
}
}
bad := []Code{"", "NoDot", "A.b", "sys.", ".internal", "sys.Internal", "1sys.internal", "sys.a"}
for _, c := range bad {
if c.Valid() {
t.Fatalf("%s should be invalid", c)
}
}
}
func TestRegisterLookupAliasResolve(t *testing.T) {
r := NewRegistry()
def := sampleDef(AgentProviderUnavailable)
if err := r.Register(def); err != nil {
t.Fatal(err)
}
if err := r.Register(def); err == nil {
t.Fatal("duplicate register should fail")
}
got, ok := r.Lookup("provider_unavailable")
if !ok || got.Code != AgentProviderUnavailable {
t.Fatalf("alias lookup: %+v ok=%v", got, ok)
}
if err := r.Alias("TRANSIENT", AgentProviderUnavailable); err != nil {
t.Fatal(err)
}
if _, ok := r.Lookup("TRANSIENT"); !ok {
t.Fatal("added alias missing")
}
unknown := r.Resolve("not.a.real")
if unknown.Code != SysUnknownCode {
t.Fatalf("resolve fallback = %s", unknown.Code)
}
if r.Known("not.a.real") {
t.Fatal("unknown should not be known")
}
if !r.Known(string(AgentProviderUnavailable)) {
t.Fatal("canonical should be known")
}
}
func TestRegisterRejectsBadAliasAndRollsBack(t *testing.T) {
r := NewRegistry()
def := sampleDef(AgentTimeout)
def.Aliases = []string{"ok_alias", "bad alias"}
if err := r.Register(def); err == nil {
t.Fatal("expected invalid alias")
}
if r.Known(string(AgentTimeout)) || r.Known("ok_alias") {
t.Fatal("failed register must not leave partial state")
}
}
func TestLoadJSONRollback(t *testing.T) {
r := NewRegistry()
raw := []byte(`[
{"code":"demo.one","http":400,"severity":"warn","retry":"never","side_effects":"none","summary":"一","hint":"检查"},
{"code":"demo.one","http":400,"severity":"warn","retry":"never","side_effects":"none","summary":"重复","hint":"检查"}
]`)
if err := r.LoadJSON(raw); err == nil {
t.Fatal("expected duplicate failure")
}
if len(r.List("")) != 0 {
t.Fatal("batch should roll back")
}
wrapped := []byte(`{"codes":[{"code":"demo.two","http":404,"severity":"info","retry":"never","side_effects":"none","summary":"没有","hint":"确认标识"}]}`)
if err := r.LoadJSON(wrapped); err != nil {
t.Fatal(err)
}
if !r.Known("demo.two") {
t.Fatal("wrapped catalog not loaded")
}
}
func TestSearchAndExport(t *testing.T) {
r := NewRegistry()
if err := r.Register(sampleDef(AgentProviderUnavailable)); err != nil {
t.Fatal(err)
}
found := r.Search("不可用")
if len(found) != 1 {
t.Fatalf("search %d", len(found))
}
data, err := r.ExportJSON()
if err != nil {
t.Fatal(err)
}
var defs []Definition
if err := json.Unmarshal(data, &defs); err != nil {
t.Fatal(err)
}
if len(defs) != 1 || defs[0].Code != AgentProviderUnavailable {
t.Fatalf("export %+v", defs)
}
}
func TestErrorWrapIsAsJSON(t *testing.T) {
root := fmt.Errorf("connection reset")
err := Wrap(root, AgentProviderUnavailable, "模型不可达", "model", "glm-4")
if !errors.Is(err, New(AgentProviderUnavailable, "")) {
t.Fatal("errors.Is should match code")
}
if !errors.Is(err, root) {
t.Fatal("errors.Is should see cause")
}
var got *E
if !errors.As(err, &got) {
t.Fatal("errors.As")
}
if From(fmt.Errorf("wrap: %w", err)).Code != AgentProviderUnavailable {
t.Fatal("From should walk the chain")
}
if err.ErrorCode() != string(AgentProviderUnavailable) {
t.Fatal(err.ErrorCode())
}
fields := err.ErrorFields()
if fields["model"] != "glm-4" || fields["reason"] != "模型不可达" {
t.Fatalf("fields %+v", fields)
}
fields["model"] = "mutated"
if err.ErrorFields()["model"] != "glm-4" {
t.Fatal("ErrorFields must return a copy")
}
if Wrap(nil, SysInternal, "x") != nil {
t.Fatal("Wrap(nil) must be nil")
}
data, errJSON := json.Marshal(err)
if errJSON != nil {
t.Fatal(errJSON)
}
var back E
if err := json.Unmarshal(data, &back); err != nil {
t.Fatal(err)
}
if back.Code != AgentProviderUnavailable || back.Unwrap() == nil || back.Unwrap().Error() != root.Error() {
t.Fatalf("roundtrip %+v cause=%v", back, back.Unwrap())
}
}
func TestPanicOnOddFields(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected panic")
}
}()
New(SysInternal, "x", "only-key")
}