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>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"error-code-system/domains"
|
||||
"error-code-system/errcode"
|
||||
"error-code-system/httpx"
|
||||
)
|
||||
|
||||
func testServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
reg := errcode.NewRegistry()
|
||||
domains.RegisterAll(reg)
|
||||
return httptest.NewServer(New(reg).Handler())
|
||||
}
|
||||
|
||||
func TestHealthListGetValidate(t *testing.T) {
|
||||
srv := testServer(t)
|
||||
defer srv.Close()
|
||||
|
||||
res, err := http.Get(srv.URL + "/v1/health")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK || res.Header.Get(httpx.TraceHeader) == "" {
|
||||
t.Fatalf("health %d trace %q", res.StatusCode, res.Header.Get(httpx.TraceHeader))
|
||||
}
|
||||
|
||||
res, err = http.Get(srv.URL + "/v1/codes?domain=agent&q=provider")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var defs []errcode.Definition
|
||||
if err := json.NewDecoder(res.Body).Decode(&defs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(defs) == 0 {
|
||||
t.Fatal("expected agent provider codes")
|
||||
}
|
||||
for _, def := range defs {
|
||||
if def.Code.Domain() != "agent" || !strings.Contains(string(def.Code), "provider") && !strings.Contains(def.Summary, "提供") {
|
||||
t.Fatalf("unexpected hit %+v", def.Code)
|
||||
}
|
||||
}
|
||||
|
||||
res, err = http.Get(srv.URL + "/v1/codes/provider_unavailable")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var def errcode.Definition
|
||||
if err := json.NewDecoder(res.Body).Decode(&def); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if def.Code != errcode.AgentProviderUnavailable {
|
||||
t.Fatalf("alias get resolved to %s", def.Code)
|
||||
}
|
||||
|
||||
res, err = http.Get(srv.URL + "/v1/codes/missing.code")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNotFound {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
t.Fatalf("missing status %d body %s", res.StatusCode, body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, srv.URL+"/v1/validate", strings.NewReader(`{"codes":["TIMEOUT","nope"]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set(httpx.TraceHeader, "trace-server-1")
|
||||
res, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var payload struct {
|
||||
Results []struct {
|
||||
Input string `json:"input"`
|
||||
Known bool `json:"known"`
|
||||
Resolved string `json:"resolved"`
|
||||
} `json:"results"`
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(payload.Results) != 2 || !payload.Results[0].Known || payload.Results[0].Resolved != string(errcode.AgentTimeout) {
|
||||
t.Fatalf("validate %+v", payload.Results)
|
||||
}
|
||||
if payload.Results[1].Known || payload.Results[1].Resolved != string(errcode.SysUnknownCode) {
|
||||
t.Fatalf("unknown %+v", payload.Results[1])
|
||||
}
|
||||
if res.Header.Get(httpx.TraceHeader) != "trace-server-1" {
|
||||
t.Fatal("trace not echoed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user