package encurl_test import ( "errors" "testing" "time" "sneak.berlin/go/pixa/internal/encurl" "sneak.berlin/go/pixa/internal/imagerequest" ) // Shared test fixture strings. const ( testSourceHost = "cdn.example.com" testSourcePath = "/images/photo.jpg" testSourceQuery = "v=2" ) func TestGenerator_GenerateAndParse(t *testing.T) { t.Parallel() gen, err := encurl.NewGenerator("test-signing-key-12345") if err != nil { t.Fatalf("NewGenerator() error = %v", err) } req := &imagerequest.Request{ Source: imagerequest.Source{ Host: testSourceHost, Path: testSourcePath, Query: testSourceQuery, }, Transform: imagerequest.Transform{ Size: imagerequest.Size{Width: 800, Height: 600}, Format: imagerequest.FormatWebP, Quality: 90, FitMode: imagerequest.FitContain, }, Expires: time.Now().Add(time.Hour), } token, err := gen.Generate(req) if err != nil { t.Fatalf("Generate() error = %v", err) } if token == "" { t.Fatal("Generate() returned empty token") } parsed, err := gen.Parse(token) if err != nil { t.Fatalf("Parse() error = %v", err) } // Verify all fields if parsed.Source.Host != req.Source.Host { t.Errorf("SourceHost = %q, want %q", parsed.Source.Host, req.Source.Host) } if parsed.Source.Path != req.Source.Path { t.Errorf("SourcePath = %q, want %q", parsed.Source.Path, req.Source.Path) } if parsed.Source.Query != req.Source.Query { t.Errorf("SourceQuery = %q, want %q", parsed.Source.Query, req.Source.Query) } if parsed.Transform.Size.Width != req.Transform.Size.Width { t.Errorf("Width = %d, want %d", parsed.Transform.Size.Width, req.Transform.Size.Width) } if parsed.Transform.Size.Height != req.Transform.Size.Height { t.Errorf("Height = %d, want %d", parsed.Transform.Size.Height, req.Transform.Size.Height) } if parsed.Transform.Format != req.Transform.Format { t.Errorf("Format = %q, want %q", parsed.Transform.Format, req.Transform.Format) } if parsed.Transform.Quality != req.Transform.Quality { t.Errorf("Quality = %d, want %d", parsed.Transform.Quality, req.Transform.Quality) } if parsed.Transform.FitMode != req.Transform.FitMode { t.Errorf("FitMode = %q, want %q", parsed.Transform.FitMode, req.Transform.FitMode) } if parsed.Expires.Unix() != req.Expires.Unix() { t.Errorf("ExpiresAt = %d, want %d", parsed.Expires.Unix(), req.Expires.Unix()) } } func TestGenerator_Parse_Expired(t *testing.T) { t.Parallel() gen, _ := encurl.NewGenerator("test-signing-key-12345") req := &imagerequest.Request{ Source: imagerequest.Source{ Host: testSourceHost, Path: testSourcePath, }, Expires: time.Now().Add(-time.Hour), // Already expired } token, err := gen.Generate(req) if err != nil { t.Fatalf("Generate() error = %v", err) } _, err = gen.Parse(token) if err == nil { t.Error("Parse() should fail for expired token") } if !errors.Is(err, encurl.ErrExpired) { t.Errorf("Parse() error = %v, want %v", err, encurl.ErrExpired) } } func TestGenerator_Parse_InvalidToken(t *testing.T) { t.Parallel() gen, _ := encurl.NewGenerator("test-signing-key-12345") _, err := gen.Parse("not-a-valid-token") if err == nil { t.Error("Parse() should fail for invalid token") } } func TestGenerator_Parse_TamperedToken(t *testing.T) { t.Parallel() gen, _ := encurl.NewGenerator("test-signing-key-12345") req := &imagerequest.Request{ Source: imagerequest.Source{ Host: testSourceHost, Path: testSourcePath, }, Expires: time.Now().Add(time.Hour), } token, _ := gen.Generate(req) // Tamper with the token tampered := []byte(token) if len(tampered) > 10 { tampered[10] ^= 0x01 } _, err := gen.Parse(string(tampered)) if err == nil { t.Error("Parse() should fail for tampered token") } } func TestGenerator_Parse_WrongKey(t *testing.T) { t.Parallel() gen1, _ := encurl.NewGenerator("signing-key-1") gen2, _ := encurl.NewGenerator("signing-key-2") req := &imagerequest.Request{ Source: imagerequest.Source{ Host: testSourceHost, Path: testSourcePath, }, Expires: time.Now().Add(time.Hour), } token, _ := gen1.Generate(req) _, err := gen2.Parse(token) if err == nil { t.Error("Parse() should fail with different signing key") } } func TestGenerator_Parse_Defaults(t *testing.T) { t.Parallel() gen, _ := encurl.NewGenerator("test-signing-key-12345") // Request with only required fields - should get defaults token, err := gen.Generate(&imagerequest.Request{ Source: imagerequest.Source{ Host: testSourceHost, Path: testSourcePath, }, Expires: time.Now().Add(time.Hour), }) if err != nil { t.Fatalf("Generate() error = %v", err) } req, err := gen.Parse(token) if err != nil { t.Fatalf("Parse() error = %v", err) } if req.Transform.Format != encurl.DefaultFormat { t.Errorf("Format = %q, want default %q", req.Transform.Format, encurl.DefaultFormat) } if req.Transform.Quality != encurl.DefaultQuality { t.Errorf("Quality = %d, want default %d", req.Transform.Quality, encurl.DefaultQuality) } if req.Transform.FitMode != encurl.DefaultFitMode { t.Errorf("FitMode = %q, want default %q", req.Transform.FitMode, encurl.DefaultFitMode) } } func TestGenerator_TokenIsURLSafe(t *testing.T) { t.Parallel() gen, _ := encurl.NewGenerator("test-signing-key-12345") req := &imagerequest.Request{ Source: imagerequest.Source{ Host: testSourceHost, Path: testSourcePath, }, Expires: time.Now().Add(time.Hour), } token, _ := gen.Generate(req) // Check that token only contains URL-safe characters for _, c := range token { isURLSafe := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' if !isURLSafe { t.Errorf("Token contains non-URL-safe character: %q", c) } } }