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") }} func TestCallPluginPreservesStatusFromNewErrorEnvelope(t *testing.T) { raw, errMarshal := pluginabi.NewErrorEnvelope("insufficient_quota", "plan limit reached", http.StatusForbidden) if errMarshal != nil { t.Fatalf("NewErrorEnvelope() error = %v", errMarshal) } _, errCall := callPlugin[rpcEmptyResponse](context.Background(), staticEnvelopePluginClient{raw: raw}, pluginabi.MethodExecutorExecute, rpcEmptyResponse{}) if errCall == nil { t.Fatal("callPlugin returned nil error") } if got := errCall.Error(); got != "plan limit reached" { t.Fatalf("error = %q, want plan limit reached", 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) }}
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")
}
}
func TestCallPluginPreservesStatusFromNewErrorEnvelope(t *testing.T) {
raw, errMarshal := pluginabi.NewErrorEnvelope("insufficient_quota", "plan limit reached", http.StatusForbidden)
if errMarshal != nil {
t.Fatalf("NewErrorEnvelope() error = %v", errMarshal)
}
_, errCall := callPlugin[rpcEmptyResponse](context.Background(), staticEnvelopePluginClient{raw: raw}, pluginabi.MethodExecutorExecute, rpcEmptyResponse{})
if errCall == nil {
t.Fatal("callPlugin returned nil error")
}
if got := errCall.Error(); got != "plan limit reached" {
t.Fatalf("error = %q, want plan limit reached", 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)
}
}