package surf import ( "net/http" "reflect" "slices" "strings" "testing" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/party" ) // TestPierMount (SC-1, D-30, D-33) runs in both builds. Without the // summercms_mcp tag: mcp off leaves the route table exactly as without the // key, and mcp.enabled true fails boot naming the tag. With the tag (the // gate's tagged run): mcp.enabled true mounts POST, GET and DELETE at the // endpoint as stream routes and the two protected resource metadata // documents, and a plugin route already at the endpoint path fails boot // naming its plugin. func TestPierMount(t *testing.T) { plugins := []party.Plugin{dispatchPlugin{}} files := func(mcp string) map[string]string { f := map[string]string{"http.yaml": transportBodyLimits, "app.yaml": "name: mount-test\nurl: http://acme.test\n"} if mcp != "" { f["mcp.yaml"] = mcp } return f } t.Run("off leaves the route table unchanged", func(t *testing.T) { base, err := BuildRouter(transportApp(t, "development", files("")), plugins) if err != nil { t.Fatal(err) } for _, mcp := range []string{"enabled: false\n", "path: /agents\nallow_personal_tokens: true\n"} { router, err := BuildRouter(transportApp(t, "development", files(mcp)), plugins) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(router.Routes(), base.Routes()) { t.Fatalf("%q changes the route table:\n%+v\n%+v", mcp, router.Routes(), base.Routes()) } } }) if mountPier == nil { t.Run("enabled without the tag fails boot", func(t *testing.T) { _, err := BuildRouter(transportApp(t, "development", files("enabled: true\n")), plugins) if err == nil || !strings.Contains(err.Error(), MCPBuildTag) { t.Fatalf("err = %v, want one naming %s", err, MCPBuildTag) } }) return } t.Run("enabled mounts the endpoint as stream routes", func(t *testing.T) { router, err := BuildRouter(transportApp(t, "development", files("enabled: true\n")), plugins) if err != nil { t.Fatal(err) } var endpoint []string for _, rt := range router.routes { if rt.path == "/mcp" { if !rt.stream || rt.pluginID != "summercms.pier" || !slices.Contains(rt.middleware, "throttle:120,1") { t.Fatalf("%s /mcp = stream %v plugin %q middleware %v", rt.method, rt.stream, rt.pluginID, rt.middleware) } endpoint = append(endpoint, rt.method) } } slices.Sort(endpoint) if !slices.Equal(endpoint, []string{http.MethodDelete, http.MethodGet, http.MethodPost}) { t.Fatalf("endpoint methods = %v", endpoint) } for _, path := range []string{"/.well-known/oauth-protected-resource", "/.well-known/oauth-protected-resource/mcp"} { found := false for _, ri := range router.Routes() { if ri.Method == http.MethodGet && ri.Pattern == path && ri.PluginID == "summercms.pier" { found = true } } if !found { t.Fatalf("GET %s is not mounted", path) } } if _, ok := router.Handler(http.MethodPost, "/mcp"); !ok { t.Fatal("no compiled handler for POST /mcp") } }) t.Run("a plugin route at the endpoint fails boot", func(t *testing.T) { squatter := routesPluginFunc{id: "acme.squatter", routes: func(r pact.Router) { r.Get("/mcp", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) }} _, err := BuildRouter(transportApp(t, "development", files("enabled: true\n")), append(plugins, squatter)) if err == nil || !strings.Contains(err.Error(), "acme.squatter") { t.Fatalf("err = %v, want one naming acme.squatter", err) } }) }