package pluginhost import ( "context" "net/http" "net/http/httptest" "testing" "github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi" ) func TestRegisterManagementRoutesSkipsReservedAndUsesPriority(t *testing.T) { high := &managementPluginDouble{ routes: []pluginapi.ManagementRoute{ {Method: http.MethodGet, Path: "/config", Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) { return pluginapi.ManagementResponse{Body: []byte("reserved")}, nil })}, {Method: http.MethodGet, Path: "/plugins/shared/status", Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) { return pluginapi.ManagementResponse{Body: []byte("high")}, nil })}, }, } low := &managementPluginDouble{ routes: []pluginapi.ManagementRoute{ {Method: http.MethodGet, Path: "/plugins/shared/status", Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) { return pluginapi.ManagementResponse{Body: []byte("low")}, nil })}, {Method: http.MethodPost, Path: "plugins/low/run", Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) { return pluginapi.ManagementResponse{StatusCode: http.StatusAccepted, Body: []byte("low-only")}, nil })}, }, } host := newHostWithRecords( capabilityRecord{id: "low", priority: 1, plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{ManagementAPI: low}}}, capabilityRecord{id: "high", priority: 10, plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{ManagementAPI: high}}}, ) host.RegisterManagementRoutes(context.Background(), map[string]struct{}{ "GET /v0/management/config": {}, }) req := httptest.NewRequest(http.MethodGet, "/v0/management/plugins/shared/status", nil) rec := httptest.NewRecorder() if !host.ServeManagementHTTP(rec, req) { t.Fatal("ServeManagementHTTP() = false, want true") } if rec.Body.String() != "high" { t.Fatalf("Body = %q, want high", rec.Body.String()) } req = httptest.NewRequest(http.MethodPost, "/v0/management/plugins/low/run", nil) rec = httptest.NewRecorder() if !host.ServeManagementHTTP(rec, req) { t.Fatal("ServeManagementHTTP() for low route = false, want true") } if rec.Code != http.StatusAccepted || rec.Body.String() != "low-only" { t.Fatalf("response = %d %q, want 202 low-only", rec.Code, rec.Body.String()) } req = httptest.NewRequest(http.MethodGet, "/v0/management/config", nil) rec = httptest.NewRecorder() if host.ServeManagementHTTP(rec, req) { t.Fatal("reserved route was served by plugin") } } func TestManagementHandlerPanicFusesPlugin(t *testing.T) { host := newHostWithRecords(capabilityRecord{ id: "panic", plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{ ManagementAPI: &managementPluginDouble{routes: []pluginapi.ManagementRoute{{ Method: http.MethodGet, Path: "/plugins/panic", Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) { panic("boom") }), }}}, }}, }) host.RegisterManagementRoutes(context.Background(), nil) req := httptest.NewRequest(http.MethodGet, "/v0/management/plugins/panic", nil) rec := httptest.NewRecorder() if !host.ServeManagementHTTP(rec, req) { t.Fatal("ServeManagementHTTP() = false, want true") } if rec.Code != http.StatusBadGateway { t.Fatalf("status = %d, want 502", rec.Code) } if !host.isPluginFused("panic") { t.Fatal("plugin was not fused after panic") } } func TestServeResourceHTTPDispatchesPluginResource(t *testing.T) { host := newHostWithRecords(capabilityRecord{ id: "resource", plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{ ManagementAPI: &managementPluginDouble{resources: []pluginapi.ResourceRoute{{ Path: "/status", Menu: "Status", Description: "Shows plugin status.", Handler: managementHandlerFunc(func(_ context.Context, req pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) { if req.Path != "/v0/resource/plugins/resource/status" { t.Fatalf("resource request path = %q, want normalized resource path", req.Path) } return pluginapi.ManagementResponse{ Headers: http.Header{"Content-Type": []string{"text/html; charset=utf-8"}}, Body: []byte("