Make web tests database-free and finish review hardening.

Introduce a Store interface with Postgres and in-memory backends, cover mutations/CSRF/session rotation without Postgres, bound avatar decode dimensions, add truncate/prepareAvatar unit tests, and run go test -race in CI.
This commit is contained in:
2026-08-22 07:36:13 -07:00
parent afd2476f3c
commit f4cec32afb
12 changed files with 945 additions and 223 deletions
+23 -7
View File
@@ -99,7 +99,7 @@ func (s *Server) handleProfile(w http.ResponseWriter, r *http.Request) {
if avatarURL != "" {
u.AvatarURL = avatarURL
}
if err := u.SaveProfile(r.Context()); err != nil {
if err := s.store.SaveUserProfile(r.Context(), u); err != nil {
http.Error(w, "could not save profile", http.StatusInternalServerError)
return
}
@@ -131,13 +131,29 @@ func prepareAvatar(r io.Reader, maxBytes int64) (body []byte, ext, contentType s
return nil, "", "", fmt.Errorf("unsupported type %s", sniff)
}
img, format, err := image.Decode(bytes.NewReader(raw))
cfg, format, err := image.DecodeConfig(bytes.NewReader(raw))
if err != nil {
return nil, "", "", err
}
const maxDim = 4096
const maxPixels = 4096 * 4096
if cfg.Width <= 0 || cfg.Height <= 0 || cfg.Width > maxDim || cfg.Height > maxDim {
return nil, "", "", fmt.Errorf("image dimensions out of range")
}
if int64(cfg.Width)*int64(cfg.Height) > maxPixels {
return nil, "", "", fmt.Errorf("image too many pixels")
}
img, decodedFormat, err := image.Decode(bytes.NewReader(raw))
if err != nil {
return nil, "", "", err
}
if format != "" {
decodedFormat = format
}
var out bytes.Buffer
switch format {
switch decodedFormat {
case "jpeg":
if err := jpeg.Encode(&out, img, &jpeg.Options{Quality: 90}); err != nil {
return nil, "", "", err
@@ -149,7 +165,7 @@ func prepareAvatar(r io.Reader, maxBytes int64) (body []byte, ext, contentType s
}
return out.Bytes(), ".png", "image/png", nil
default:
return nil, "", "", fmt.Errorf("unsupported format %s", format)
return nil, "", "", fmt.Errorf("unsupported format %s", decodedFormat)
}
}
@@ -161,16 +177,16 @@ func (s *Server) renderProfile(w http.ResponseWriter, r *http.Request, u *store.
)
if u.Admin() {
label = "Questions you answered"
questions, err = store.ListQuestionsAnsweredBy(r.Context(), s.db, u.ID)
questions, err = s.store.ListQuestionsAnsweredBy(r.Context(), u.ID)
} else {
label = "Your questions"
questions, err = store.ListQuestionsByAuthor(r.Context(), s.db, u.ID)
questions, err = s.store.ListQuestionsByAuthor(r.Context(), u.ID)
}
if err != nil {
http.Error(w, "could not load questions", http.StatusInternalServerError)
return
}
if fresh, e := store.UserByID(r.Context(), s.db, u.ID); e == nil {
if fresh, e := s.store.UserByID(r.Context(), u.ID); e == nil {
u = fresh
}
p := s.basePage(r, "Profile")