Prove nested ML writes, unsafe markdown rejection, request-locale isolation, and generated FormView types. Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
4.4 KiB
Go
172 lines
4.4 KiB
Go
package surf
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/towel"
|
|
)
|
|
|
|
type stubLocaleResolver struct {
|
|
code string
|
|
rewriteTo string
|
|
}
|
|
|
|
func (s stubLocaleResolver) Resolve(http.ResponseWriter, *http.Request) string {
|
|
return s.code
|
|
}
|
|
|
|
func (s stubLocaleResolver) Rewrite(r *http.Request) *http.Request {
|
|
if s.rewriteTo == "" {
|
|
return r
|
|
}
|
|
clone := r.Clone(r.Context())
|
|
clone.URL.Path = s.rewriteTo
|
|
return clone
|
|
}
|
|
|
|
func TestLocaleResolverWritesValidatedCodeAndStripsPrefix(t *testing.T) {
|
|
cfg := writeHTTPConfig(t, "body_limits:\n default_bytes: 1024\n upload_bytes: 1024\n")
|
|
app := backpack.New(cfg)
|
|
if err := app.Publish[LocaleResolver](stubLocaleResolver{code: "pl", rewriteTo: "/posts"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := New(nil)
|
|
r.app = app
|
|
r.defaultBytes = 1024
|
|
r.BindPlugin("acme.blog")
|
|
var gotPath, gotLoc string
|
|
var ok bool
|
|
r.Get("/posts", func(w http.ResponseWriter, req *http.Request) {
|
|
gotPath = req.URL.Path
|
|
gotLoc, ok = towel.Locale(req.Context())
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
h, err := r.compile()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req := httptest.NewRequest(http.MethodGet, "/pl/posts", nil)
|
|
req.Header.Set("Accept-Language", "de")
|
|
h.ServeHTTP(httptest.NewRecorder(), req)
|
|
if gotPath != "/posts" {
|
|
t.Fatalf("path = %q, want /posts", gotPath)
|
|
}
|
|
if !ok || gotLoc != "pl" {
|
|
t.Fatalf("locale = %q ok=%t, want pl from resolver", gotLoc, ok)
|
|
}
|
|
}
|
|
|
|
func TestLocaleResolver(t *testing.T) {
|
|
t.Run("present writes validated code", TestLocaleResolverWritesValidatedCodeAndStripsPrefix)
|
|
t.Run("absent keeps Accept-Language", TestLocaleAbsentResolverKeepsAcceptLanguage)
|
|
t.Run("conflicting requests stay isolated", TestLocaleResolverRequestIsolation)
|
|
}
|
|
|
|
func TestLocaleResolverRequestIsolation(t *testing.T) {
|
|
cfg := writeHTTPConfig(t, "body_limits:\n default_bytes: 1024\n upload_bytes: 1024\n")
|
|
app := backpack.New(cfg)
|
|
if err := app.Publish[LocaleResolver](pathLocaleResolver{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := New(nil)
|
|
r.app = app
|
|
r.defaultBytes = 1024
|
|
r.BindPlugin("acme.blog")
|
|
var mu sync.Mutex
|
|
got := map[string]string{}
|
|
r.Get("/posts", func(w http.ResponseWriter, req *http.Request) {
|
|
code, _ := towel.Locale(req.Context())
|
|
mu.Lock()
|
|
got[req.Header.Get("X-Req")] = code
|
|
mu.Unlock()
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
h, err := r.compile()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var wg sync.WaitGroup
|
|
for i, code := range []string{"pl", "en", "pl", "en"} {
|
|
wg.Add(1)
|
|
go func(i int, code string) {
|
|
defer wg.Done()
|
|
id := fmt.Sprintf("%s-%d", code, i)
|
|
req := httptest.NewRequest(http.MethodGet, "/"+code+"/posts", nil)
|
|
req.Header.Set("X-Req", id)
|
|
req.Header.Set("Accept-Language", "de")
|
|
h.ServeHTTP(httptest.NewRecorder(), req)
|
|
mu.Lock()
|
|
if got[id] != code {
|
|
t.Errorf("request %s locale = %q", id, got[id])
|
|
}
|
|
mu.Unlock()
|
|
}(i, code)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
type pathLocaleResolver struct{}
|
|
|
|
func (pathLocaleResolver) Resolve(_ http.ResponseWriter, r *http.Request) string {
|
|
if r == nil {
|
|
return "en"
|
|
}
|
|
if v := r.Context().Value(stubPrefixKey{}); v != nil {
|
|
if code, ok := v.(string); ok && code != "" {
|
|
return code
|
|
}
|
|
}
|
|
path := r.URL.Path
|
|
if len(path) >= 3 && path[0] == '/' && (path[1:3] == "pl" || path[1:3] == "en") {
|
|
return path[1:3]
|
|
}
|
|
return "en"
|
|
}
|
|
|
|
func (pathLocaleResolver) Rewrite(r *http.Request) *http.Request {
|
|
if r == nil {
|
|
return r
|
|
}
|
|
path := r.URL.Path
|
|
if len(path) >= 3 && path[0] == '/' && (path[1:3] == "pl" || path[1:3] == "en") && (len(path) == 3 || path[3] == '/') {
|
|
code := path[1:3]
|
|
clone := r.Clone(context.WithValue(r.Context(), stubPrefixKey{}, code))
|
|
if len(path) == 3 {
|
|
clone.URL.Path = "/"
|
|
} else {
|
|
clone.URL.Path = path[3:]
|
|
}
|
|
return clone
|
|
}
|
|
return r
|
|
}
|
|
|
|
type stubPrefixKey struct{}
|
|
|
|
func TestLocaleAbsentResolverKeepsAcceptLanguage(t *testing.T) {
|
|
r := New(nil)
|
|
r.BindPlugin("acme.blog")
|
|
var gotLoc string
|
|
var ok bool
|
|
r.Get("/posts", func(w http.ResponseWriter, req *http.Request) {
|
|
gotLoc, ok = towel.Locale(req.Context())
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
h, err := r.compile()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req := httptest.NewRequest(http.MethodGet, "/posts", nil)
|
|
req.Header.Set("Accept-Language", "pl")
|
|
h.ServeHTTP(httptest.NewRecorder(), req)
|
|
if !ok || gotLoc != "pl" {
|
|
t.Fatalf("locale = %q ok=%t, want pl from Accept-Language", gotLoc, ok)
|
|
}
|
|
}
|