chi matches routes against the raw (escaped) request path, so chi.URLParam returns parameters still percent-encoded when clients escape reserved characters. The web UI sends marker provider IDs via encodeURIComponent, so plugin-based providers like "plugin:6:introdb" arrived as "plugin%3A6%3Aintrodb", breaking validate (400) and update (404) for any provider ID containing a colon. Add a shared decodedURLParam helper and use it in the marker provider, subtitle provider, and watch provider handlers, returning 400 on malformed escape sequences. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
102 lines
3.5 KiB
Go
102 lines
3.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/markers"
|
|
)
|
|
|
|
type fakeMarkerStatsSubmitter struct{}
|
|
|
|
func (fakeMarkerStatsSubmitter) ID() string { return "introdb" }
|
|
func (fakeMarkerStatsSubmitter) FetchMarkers(context.Context, markers.Request) (markers.Result, error) {
|
|
return markers.Result{}, nil
|
|
}
|
|
func (fakeMarkerStatsSubmitter) SubmitMarker(context.Context, markers.SubmissionRequest) (markers.SubmissionResult, error) {
|
|
return markers.SubmissionResult{}, nil
|
|
}
|
|
func (fakeMarkerStatsSubmitter) FetchUserStats(context.Context) (markers.UserStats, error) {
|
|
return markers.UserStats{Total: 10, Accepted: 7, Pending: 2, Rejected: 1, AcceptanceRate: 0.7, CurrentStreak: 3, BestStreak: 5}, nil
|
|
}
|
|
|
|
func TestValidateMarkerProviderUsesSnakeCaseStats(t *testing.T) {
|
|
reg := markers.NewRegistry(nil)
|
|
if err := reg.Register(fakeMarkerStatsSubmitter{}); err != nil {
|
|
t.Fatalf("register provider: %v", err)
|
|
}
|
|
h := NewAdminMarkerProvidersHandler(reg, nil, nil, nil)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/markers/providers/introdb/validate", nil)
|
|
rctx := chi.NewRouteContext()
|
|
rctx.URLParams.Add("provider", "introdb")
|
|
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
|
|
|
|
rec := httptest.NewRecorder()
|
|
h.HandleValidateProvider(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
body := rec.Body.String()
|
|
if !strings.Contains(body, `"acceptance_rate":0.7`) || strings.Contains(body, "AcceptanceRate") {
|
|
t.Fatalf("unexpected stats response shape: %s", body)
|
|
}
|
|
}
|
|
|
|
type fakePluginMarkerSubmitter struct{ fakeMarkerStatsSubmitter }
|
|
|
|
func (fakePluginMarkerSubmitter) ID() string { return "plugin:6:introdb" }
|
|
|
|
// Browsers send encodeURIComponent-escaped provider IDs (plugin%3A6%3Aintrodb),
|
|
// and chi matches the raw path, so the handler must decode the route param.
|
|
func TestValidateMarkerProviderDecodesEncodedID(t *testing.T) {
|
|
reg := markers.NewRegistry(nil)
|
|
if err := reg.Register(fakePluginMarkerSubmitter{}); err != nil {
|
|
t.Fatalf("register provider: %v", err)
|
|
}
|
|
h := NewAdminMarkerProvidersHandler(reg, nil, nil, nil)
|
|
|
|
router := chi.NewRouter()
|
|
router.Post("/admin/markers/providers/{provider}/validate", h.HandleValidateProvider)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/markers/providers/plugin%3A6%3Aintrodb/validate", nil)
|
|
rec := httptest.NewRecorder()
|
|
router.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if !strings.Contains(rec.Body.String(), `"valid":true`) {
|
|
t.Fatalf("unexpected response: %s", rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestMarkerProviderResponseIncludesPluginMetadata(t *testing.T) {
|
|
resp := toProviderConfigResponse(
|
|
markers.ProviderConfig{Provider: "plugin:4:markers", FetchEnabled: true, FetchPriority: 25},
|
|
true,
|
|
markers.ProviderDescriptor{
|
|
DisplayName: "Plugin Markers",
|
|
SourceType: markers.ProviderSourcePlugin,
|
|
PluginID: "silo.plugin.markers",
|
|
PluginInstallationID: 4,
|
|
CapabilityID: "markers",
|
|
},
|
|
)
|
|
if resp.DisplayName != "Plugin Markers" || resp.SourceType != markers.ProviderSourcePlugin {
|
|
t.Fatalf("plugin metadata fields missing: %+v", resp)
|
|
}
|
|
if resp.PluginID != "silo.plugin.markers" ||
|
|
resp.PluginInstallationID != 4 ||
|
|
resp.CapabilityID != "markers" ||
|
|
!resp.IsSubmitter {
|
|
t.Fatalf("plugin identity fields missing: %+v", resp)
|
|
}
|
|
}
|