返回 DeepSeek-Reasonix
errors_test.go
根目录 / internal / extension / protocol / errors_test.go
1 package protocol
2
3 import (
4 "encoding/json"
5 "testing"
6
7 "reasonix/internal/extension/rpcwire"
8 )
9
10 func TestErrorTableCoversRequiredReasons(t *testing.T) {
11 required := []ErrorReason{
12 ErrProtocolError, ErrUnknownMethod, ErrInvalidParams, ErrFrameTooLarge,
13 ErrContentRefExpired, ErrUnsupportedVersion, ErrCapabilityNotDeclared,
14 ErrShutdownTimeout, ErrStreamGap, ErrStreamCancelled,
15 ErrProviderFailed, ErrProviderInterrupted, ErrInterceptTimeout, ErrInternal,
16 }
17 if len(frozenErrorSpecs) != len(required) {
18 t.Fatalf("frozen error table has %d entries, want %d", len(frozenErrorSpecs), len(required))
19 }
20 for _, reason := range required {
21 if _, ok := frozenErrorSpecs[reason]; !ok {
22 t.Fatalf("frozen error table is missing %q", reason)
23 }
24 }
25 }
26
27 func TestErrorContractsAreUniqueSortedAndWellFormed(t *testing.T) {
28 contracts := ErrorContracts()
29 seen := map[ErrorReason]bool{}
30 for i, contract := range contracts {
31 if seen[contract.Reason] {
32 t.Fatalf("duplicate error reason %q", contract.Reason)
33 }
34 seen[contract.Reason] = true
35 if i > 0 && contracts[i-1].Reason >= contract.Reason {
36 t.Fatalf("error contracts not sorted at %q", contract.Reason)
37 }
38 if contract.Message == "" {
39 t.Fatalf("%q has an empty frozen message", contract.Reason)
40 }
41 switch contract.JSONRPCCode {
42 case DomainErrorCode, rpcwire.ErrInvalidRequest, rpcwire.ErrMethodNotFound,
43 rpcwire.ErrInvalidParams, rpcwire.ErrInternal:
44 default:
45 t.Fatalf("%q uses non-contract JSON-RPC code %d", contract.Reason, contract.JSONRPCCode)
46 }
47 spec := frozenErrorSpecs[contract.Reason]
48 if spec.Code != contract.JSONRPCCode || spec.Message != contract.Message || spec.Retryable != contract.Retryable {
49 t.Fatalf("%q contract does not match its frozen spec", contract.Reason)
50 }
51 }
52 }
53
54 func TestProtocolErrorWireShape(t *testing.T) {
55 err := MustProtocolError(ErrStreamGap)
56 if err.Error() == "" {
57 t.Fatal("ProtocolError.Error() is empty")
58 }
59 rpcErr := err.RPCError()
60 if rpcErr.Code != DomainErrorCode {
61 t.Fatalf("RPCError code = %d, want %d", rpcErr.Code, DomainErrorCode)
62 }
63 raw, marshalErr := json.Marshal(rpcErr.Data)
64 if marshalErr != nil {
65 t.Fatal(marshalErr)
66 }
67 var decoded ProtocolErrorData
68 if unmarshalErr := json.Unmarshal(raw, &decoded); unmarshalErr != nil {
69 t.Fatal(unmarshalErr)
70 }
71 if decoded.Reason != ErrStreamGap || !decoded.Retryable {
72 t.Fatalf("wire data = %+v, want stream_gap/retryable", decoded)
73 }
74 if validateErr := decoded.Validate(); validateErr != nil {
75 t.Fatalf("wire data failed validation: %v", validateErr)
76 }
77
78 // Standard JSON-RPC codes ride on the mapped reasons.
79 standard := map[ErrorReason]int{
80 ErrProtocolError: rpcwire.ErrInvalidRequest,
81 ErrUnknownMethod: rpcwire.ErrMethodNotFound,
82 ErrInvalidParams: rpcwire.ErrInvalidParams,
83 ErrInternal: rpcwire.ErrInternal,
84 }
85 for reason, code := range standard {
86 if got := MustProtocolError(reason).RPCError().Code; got != code {
87 t.Fatalf("%s RPCError code = %d, want %d", reason, got, code)
88 }
89 }
90
91 var nilErr *ProtocolError
92 if nilErr.Error() != "" || nilErr.RPCError().Code != rpcwire.ErrInternal {
93 t.Fatal("nil ProtocolError must degrade to a plain internal error")
94 }
95 }
96
97 func TestProtocolErrorDataValidate(t *testing.T) {
98 if err := (ProtocolErrorData{Reason: ErrShutdownTimeout, Retryable: true}).Validate(); err != nil {
99 t.Fatalf("valid data rejected: %v", err)
100 }
101 if err := (ProtocolErrorData{Reason: ErrShutdownTimeout, Retryable: false}).Validate(); err == nil {
102 t.Fatal("retryable mismatch accepted")
103 }
104 if err := (ProtocolErrorData{Reason: "bogus"}).Validate(); err == nil {
105 t.Fatal("unknown reason accepted")
106 }
107 if _, err := NewProtocolError("bogus"); err == nil {
108 t.Fatal("NewProtocolError accepted an unknown reason")
109 }
110 }
111
111 lines GO