mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-23 04:30:21 +08:00
- Added `rpcPluginError` to encapsulate plugin errors with HTTP status codes. - Enhanced `decodeEnvelopeResult` to preserve and return detailed plugin errors with status codes. - Introduced `isPluginErrorEnvelope` to identify plugin error envelopes. - Updated plugin call logic in Unix and Windows loaders to differentiate plugin errors from system errors. - Added unit tests to verify error handling and status code preservation.
83 lines
2.4 KiB
Go
83 lines
2.4 KiB
Go
package pluginhost
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginabi"
|
|
)
|
|
|
|
type staticEnvelopePluginClient struct {
|
|
raw []byte
|
|
}
|
|
|
|
func (c staticEnvelopePluginClient) Call(context.Context, string, []byte) ([]byte, error) {
|
|
return c.raw, nil
|
|
}
|
|
|
|
func (c staticEnvelopePluginClient) Shutdown() {}
|
|
|
|
func TestDecodeEnvelopeResultPreservesPluginHTTPStatus(t *testing.T) {
|
|
_, errDecode := decodeEnvelopeResult[rpcEmptyResponse](pluginabi.Envelope{
|
|
OK: false,
|
|
Error: &pluginabi.Error{
|
|
Code: "plugin_error",
|
|
Message: "license required",
|
|
HTTPStatus: http.StatusForbidden,
|
|
},
|
|
})
|
|
if errDecode == nil {
|
|
t.Fatal("decodeEnvelopeResult returned nil error")
|
|
}
|
|
if got := errDecode.Error(); got != "license required" {
|
|
t.Fatalf("error = %q, want license required", got)
|
|
}
|
|
statusProvider, ok := errDecode.(interface{ StatusCode() int })
|
|
if !ok {
|
|
t.Fatalf("error %T does not expose StatusCode", errDecode)
|
|
}
|
|
if got := statusProvider.StatusCode(); got != http.StatusForbidden {
|
|
t.Fatalf("status = %d, want %d", got, http.StatusForbidden)
|
|
}
|
|
}
|
|
|
|
func TestCallPluginReturnsPluginErrorWithoutMethodWrapper(t *testing.T) {
|
|
raw, errMarshal := json.Marshal(pluginabi.Envelope{
|
|
OK: false,
|
|
Error: &pluginabi.Error{
|
|
Code: "plugin_error",
|
|
Message: "license required",
|
|
HTTPStatus: http.StatusForbidden,
|
|
},
|
|
})
|
|
if errMarshal != nil {
|
|
t.Fatalf("marshal envelope: %v", errMarshal)
|
|
}
|
|
_, errCall := callPlugin[rpcEmptyResponse](context.Background(), staticEnvelopePluginClient{raw: raw}, pluginabi.MethodExecutorExecuteStream, rpcEmptyResponse{})
|
|
if errCall == nil {
|
|
t.Fatal("callPlugin returned nil error")
|
|
}
|
|
if got := errCall.Error(); got != "license required" {
|
|
t.Fatalf("error = %q, want license required", got)
|
|
}
|
|
statusProvider, ok := errCall.(interface{ StatusCode() int })
|
|
if !ok {
|
|
t.Fatalf("error %T does not expose StatusCode", errCall)
|
|
}
|
|
if got := statusProvider.StatusCode(); got != http.StatusForbidden {
|
|
t.Fatalf("status = %d, want %d", got, http.StatusForbidden)
|
|
}
|
|
}
|
|
|
|
func TestIsPluginErrorEnvelopeAcceptsNonzeroReturnEnvelope(t *testing.T) {
|
|
raw := marshalRPCError("plugin_error", "upstream failed")
|
|
if !isPluginErrorEnvelope(raw) {
|
|
t.Fatalf("isPluginErrorEnvelope(%s) = false, want true", raw)
|
|
}
|
|
if isPluginErrorEnvelope([]byte(`not json`)) {
|
|
t.Fatal("isPluginErrorEnvelope accepted invalid JSON")
|
|
}
|
|
}
|