Files
pixa/internal/handlers/request_id_internal_test.go
T
sneak 8a03d0dbe7
check / check (push) Canceled after 0s
Take the signature, the expiry and the URL scheme off the image request (closes #314)
The image request now holds only its source and its transform, exactly what
the variant key covers. The /v1/image/ handler checks the allowlist and
verifies the signature itself with Signer.Verify(req, sig, expires). Both
image handlers keep the URL's expiry as a local value for Cache-Control. The
image service holds no signer, allowlist or AllowHTTP, and ValidateRequest is
gone. The fetcher takes the source and builds its URL with Source.URL from its
own AllowHTTP, so no URL string is built and parsed again. chooseAutoFormat
returns the format it chooses instead of writing into the request. The signed
URL format and every route's answers are unchanged.

Model: opus-5-5
2026-10-26 12:18:23 +00:00

135 lines
3.3 KiB
Go

package handlers
import (
"bytes"
"context"
"encoding/json"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/go-chi/chi/v5/middleware"
"sneak.berlin/go/pixa/internal/imagerequest"
)
// signedPhotoURL returns a signed /v1/image/ URL, valid for a minute, for the
// JPEG at photoPath on signedHost at 50x50. It is signed with testSigningKey,
// the key of the image service newSignedHostServer builds. It takes a Handlers
// it does not use, so that it has the type of encPhotoURL.
func signedPhotoURL(t *testing.T, _ *Handlers) string {
t.Helper()
return generateSignedURL(testSigningKey, &imagerequest.Request{
Source: imagerequest.Source{
Host: signedHost,
Path: photoPath,
},
Transform: imagerequest.Transform{
Size: imagerequest.Size{Width: 50, Height: 50},
Format: imagerequest.FormatJPEG,
},
}, time.Minute)
}
// encPhotoURL returns an encrypted /v1/e/ URL, which never expires, for the
// JPEG at photoPath on signedHost at 50x50, made with h's generator.
func encPhotoURL(t *testing.T, h *Handlers) string {
t.Helper()
token, err := h.encGen.Generate(&imagerequest.Request{
Source: imagerequest.Source{
Host: signedHost,
Path: photoPath,
},
Transform: imagerequest.Transform{
Size: imagerequest.Size{Width: 50, Height: 50},
Format: imagerequest.FormatJPEG,
},
}, time.Time{})
if err != nil {
t.Fatalf("Generate() error = %v", err)
}
return "/v1/e/" + token + "/img.jpg"
}
// requestIDByMessage reads the JSON log lines in logs and returns the
// request_id of each line, by its message.
func requestIDByMessage(t *testing.T, logs io.Reader) map[string]string {
t.Helper()
logged := make(map[string]string)
dec := json.NewDecoder(logs)
for dec.More() {
var line map[string]any
err := dec.Decode(&line)
if err != nil {
t.Fatalf("decoding log line: %v", err)
}
msg, _ := line["msg"].(string)
requestID, _ := line["request_id"].(string)
logged[msg] = requestID
}
return logged
}
// TestImageLogLinesCarryRequestID verifies that the lines logged when an image
// is fetched, converted and served through either image route carry the
// request's ID as request_id, as the request log line does, so they can be
// found from it.
func TestImageLogLinesCarryRequestID(t *testing.T) {
t.Parallel()
const requestID = "test-request-id"
imageURLs := map[string]func(*testing.T, *Handlers) string{
"/v1/image/": signedPhotoURL,
"/v1/e/": encPhotoURL,
}
for route, imageURL := range imageURLs {
t.Run(route, func(t *testing.T) {
t.Parallel()
var logs bytes.Buffer
h, srv := newSignedHostServer(t,
slog.New(slog.NewJSONHandler(&logs, nil)))
ctx := context.WithValue(t.Context(),
middleware.RequestIDKey, requestID)
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, httptest.NewRequestWithContext(
ctx, http.MethodGet, imageURL(t, h), nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
t.Logf("logged:\n%s", logs.String())
logged := requestIDByMessage(t, &logs)
for _, msg := range []string{
"upstream fetched", "image converted", "image served",
} {
got, ok := logged[msg]
if !ok {
t.Errorf("no %q line logged", msg)
} else if got != requestID {
t.Errorf("%q line has request_id %q, want %q",
msg, got, requestID)
}
}
})
}
}