package storage import ( "net/http" "net/http/httptest" "net/url" "strconv" "strings" "testing" "time" ) func expiryOf(t *testing.T, raw string) int64 { t.Helper() u, err := url.Parse(raw) if err != nil { t.Fatalf("parse url: %v", err) } e, err := strconv.ParseInt(u.Query().Get("e"), 10, 64) if err != nil { t.Fatalf("parse expiry: %v", err) } return e } func TestSignerURLStableWithinWindow(t *testing.T) { s, err := NewSigner("secret", time.Hour, time.Hour) if err != nil { t.Fatalf("new signer: %v", err) } first := s.URL("objects/a/thumb.webp") second := s.URL("objects/a/thumb.webp") if first != second { t.Fatalf("URL changed within window:\n %q\n %q", first, second) } } func TestSignerURLChangesAcrossWindow(t *testing.T) { s, err := NewSigner("secret", time.Hour, 10*time.Minute) if err != nil { t.Fatalf("new signer: %v", err) } base := time.Now() s.now = func() time.Time { return base } first := expiryOf(t, s.URL("objects/a/thumb.webp")) s.now = func() time.Time { return base.Add(s.window) } second := expiryOf(t, s.URL("objects/a/thumb.webp")) if first == second { t.Fatalf("expiry did not advance across window: %d", first) } } func TestSignerURLVerifies(t *testing.T) { s, err := NewSigner("secret", time.Hour, 10*time.Minute) if err != nil { t.Fatalf("new signer: %v", err) } key := "objects/a/thumb.webp" raw := s.URL(key) req := httptest.NewRequest(http.MethodGet, raw, nil) if err := s.VerifyRequest(key, req); err != nil { t.Fatalf("verify issued URL: %v", err) } } func TestSignerURLIsOriginRelative(t *testing.T) { s, err := NewSigner("secret", time.Hour, 10*time.Minute) if err != nil { t.Fatalf("new signer: %v", err) } raw := s.URL("objects/a/thumb.webp") u, err := url.Parse(raw) if err != nil { t.Fatalf("parse url: %v", err) } if u.Scheme != "" || u.Host != "" || u.IsAbs() { t.Fatalf("url is not origin-relative: %q", raw) } if !strings.HasPrefix(raw, "/api/objects/") { t.Fatalf("url = %q, want /api/objects/ prefix", raw) } }