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") }