From 06975f95a92112b50c4e647fe38c4070442286d6 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Sun, 4 Oct 2026 19:09:59 +0000 Subject: [PATCH] Test that a URL made on the generator page with a ttl expires (closes #199) A new handler test makes a URL on the generator page with a ttl of one second, checks that /v1/e/ serves it at once, waits two seconds and checks that it then answers 410. The expiry is kept in whole seconds, so two seconds is the longest a one-second ttl can take to pass. Test only. Model: opus-5-5 --- TODO.md | 7 +++ .../handlers/auth_session_internal_test.go | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/TODO.md b/TODO.md index 18d33b4..89006d5 100644 --- a/TODO.md +++ b/TODO.md @@ -31,6 +31,13 @@ P2: security: referer blacklist # Completed Steps +- 2026-10-04 a URL made on the generator page with a `ttl` is tested to + expire (closes #199): a new test in `internal/handlers` makes a URL on the + generator page with a `ttl` of one second, checks that `/v1/e/` serves it at + once, waits two seconds and checks that it then answers 410. The test waits + for real, as pixa reads the clock directly when it makes and checks a URL; it + waits two seconds because the time a URL expires is kept in whole seconds. + Test only. - 2026-10-04 fewer files in the repository root (closes #97): `config.example.yml` moved unchanged to `configs/config.example.yml`, and `README.md`, the comments in `internal/config/config.go` and the startup error diff --git a/internal/handlers/auth_session_internal_test.go b/internal/handlers/auth_session_internal_test.go index 3bc57f8..883e434 100644 --- a/internal/handlers/auth_session_internal_test.go +++ b/internal/handlers/auth_session_internal_test.go @@ -8,6 +8,7 @@ import ( "regexp" "strings" "testing" + "time" "sneak.berlin/go/pixa/internal/imgcache" "sneak.berlin/go/pixa/internal/session" @@ -241,3 +242,51 @@ func TestGeneratePost_URLServesImage(t *testing.T) { requireServedPhoto(t, imageRec) } + +// TestGeneratePost_URLWithTTLExpires verifies that a URL the generator page +// makes with a ttl of one second is served by /v1/e/ at once and answers 410 +// once the ttl has passed. +func TestGeneratePost_URLWithTTLExpires(t *testing.T) { + t.Parallel() + + _, imageSrv := newSignedHostServer(t, slog.New(slog.DiscardHandler)) + + rec := generatePost(t, url.Values{ + sourceURLField: {"https://" + signedHost + photoPath}, + widthField: {"50"}, + heightField: {"50"}, + formatField: {string(imgcache.FormatJPEG)}, + ttlField: {"1"}, + }) + + if rec.Code != http.StatusOK { + t.Fatalf("POST /generate status = %d, want %d", rec.Code, http.StatusOK) + } + + match := generatedURLPattern.FindStringSubmatch(rec.Body.String()) + if match == nil { + t.Fatalf("generator page shows no URL: %s", rec.Body.String()) + } + + imageRec := httptest.NewRecorder() + imageSrv.ServeHTTP(imageRec, httptest.NewRequestWithContext( + t.Context(), http.MethodGet, match[1], nil)) + + requireServedPhoto(t, imageRec) + + // The URL keeps the time it expires in whole seconds and is served + // through the whole of that second, so a ttl of one second has passed + // for certain two seconds after the URL was made. + time.Sleep(2 * time.Second) + + imageRec = httptest.NewRecorder() + imageSrv.ServeHTTP(imageRec, httptest.NewRequestWithContext( + t.Context(), http.MethodGet, match[1], nil)) + + t.Logf("GET %s after the ttl: %d %q", match[1], imageRec.Code, imageRec.Body) + + if imageRec.Code != http.StatusGone { + t.Errorf("status after the ttl = %d, want %d", + imageRec.Code, http.StatusGone) + } +}