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) } }