From 4b3453e496abdbb5c937d75172222e2d1722bd62 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Fri, 5 Jun 2026 18:01:35 +0300 Subject: [PATCH] add compression tests Signed-off-by: Mohammed Al Sahaf --- modules/caddyhttp/encode/gzip/gzip_test.go | 157 +++++++++++++++++ modules/caddyhttp/encode/zstd/zstd_test.go | 193 +++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 modules/caddyhttp/encode/gzip/gzip_test.go create mode 100644 modules/caddyhttp/encode/zstd/zstd_test.go diff --git a/modules/caddyhttp/encode/gzip/gzip_test.go b/modules/caddyhttp/encode/gzip/gzip_test.go new file mode 100644 index 000000000..c3fe1c86f --- /dev/null +++ b/modules/caddyhttp/encode/gzip/gzip_test.go @@ -0,0 +1,157 @@ +package caddygzip + +import ( + "bytes" + "io" + "strconv" + "testing" + + "github.com/klauspost/compress/gzip" + + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func TestGzipCaddyModule(t *testing.T) { + g := Gzip{} + info := g.CaddyModule() + if info.ID != "http.encoders.gzip" { + t.Errorf("CaddyModule().ID = %v, want 'http.encoders.gzip'", info.ID) + } + if info.New == nil { + t.Fatal("CaddyModule().New is nil") + } + mod := info.New() + if _, ok := mod.(*Gzip); !ok { + t.Errorf("CaddyModule().New() returned %T, want *Gzip", mod) + } +} + +func TestGzipAcceptEncoding(t *testing.T) { + g := Gzip{} + if got := g.AcceptEncoding(); got != "gzip" { + t.Errorf("AcceptEncoding() = %q, want %q", got, "gzip") + } +} + +func TestGzipValidate(t *testing.T) { + tests := []struct { + name string + level int + wantErr bool + }{ + {name: "default level 5", level: 5}, + {name: "best speed", level: gzip.BestSpeed}, + {name: "best compression", level: gzip.BestCompression}, + {name: "no compression", level: gzip.NoCompression}, + {name: "stateless compression", level: gzip.StatelessCompression}, + {name: "too low", level: gzip.StatelessCompression - 1, wantErr: true}, + {name: "too high", level: 10, wantErr: true}, + {name: "way too high", level: 100, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := Gzip{Level: tt.level} + err := g.Validate() + if tt.wantErr && err == nil { + t.Errorf("Validate() with level %d should return error", tt.level) + } + if !tt.wantErr && err != nil { + t.Errorf("Validate() with level %d unexpected error: %v", tt.level, err) + } + }) + } +} + +func TestGzipNewEncoder(t *testing.T) { + g := Gzip{Level: gzip.BestSpeed} + enc := g.NewEncoder() + if enc == nil { + t.Fatal("NewEncoder() returned nil") + } + + // Verify the encoder can actually compress data + var buf bytes.Buffer + enc.Reset(&buf) + data := []byte("Hello, Gzip compression test!") + _, err := enc.Write(data) + if err != nil { + t.Fatalf("encoder.Write() error: %v", err) + } + err = enc.Close() + if err != nil { + t.Fatalf("encoder.Close() error: %v", err) + } + + // Verify we can decompress the output + reader, err := gzip.NewReader(&buf) + if err != nil { + t.Fatalf("gzip.NewReader() error: %v", err) + } + defer reader.Close() + decoded, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("io.ReadAll() error: %v", err) + } + if string(decoded) != string(data) { + t.Errorf("round-trip mismatch: got %q, want %q", decoded, data) + } +} + +func TestGzipUnmarshalCaddyfile(t *testing.T) { + tests := []struct { + name string + input string + wantLevel int + wantErr bool + }{ + { + name: "with level", + input: "gzip 6", + wantLevel: 6, + }, + { + name: "no level keeps zero", + input: "gzip", + wantLevel: 0, + }, + { + name: "invalid level", + input: "gzip notanumber", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := caddyfile.NewTestDispenser(tt.input) + g := &Gzip{} + err := g.UnmarshalCaddyfile(d) + if tt.wantErr { + if err == nil { + t.Error("UnmarshalCaddyfile() should return error") + } + return + } + if err != nil { + t.Fatalf("UnmarshalCaddyfile() error: %v", err) + } + if g.Level != tt.wantLevel { + t.Errorf("Level = %d, want %d", g.Level, tt.wantLevel) + } + }) + } +} + +func TestGzipNewEncoderAllLevels(t *testing.T) { + // Verify NewEncoder works at all valid compression levels + for level := gzip.StatelessCompression; level <= gzip.BestCompression; level++ { + t.Run("level_"+strconv.Itoa(level), func(t *testing.T) { + g := Gzip{Level: level} + enc := g.NewEncoder() + if enc == nil { + t.Fatalf("NewEncoder() at level %d returned nil", level) + } + }) + } +} diff --git a/modules/caddyhttp/encode/zstd/zstd_test.go b/modules/caddyhttp/encode/zstd/zstd_test.go new file mode 100644 index 000000000..3a6b4ec3d --- /dev/null +++ b/modules/caddyhttp/encode/zstd/zstd_test.go @@ -0,0 +1,193 @@ +package caddyzstd + +import ( + "bytes" + "io" + "testing" + + "github.com/klauspost/compress/zstd" + + caddy "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" +) + +func TestZstdCaddyModule(t *testing.T) { + z := Zstd{} + info := z.CaddyModule() + if info.ID != "http.encoders.zstd" { + t.Errorf("CaddyModule().ID = %v, want 'http.encoders.zstd'", info.ID) + } + if info.New == nil { + t.Fatal("CaddyModule().New is nil") + } + mod := info.New() + if _, ok := mod.(*Zstd); !ok { + t.Errorf("CaddyModule().New() returned %T, want *Zstd", mod) + } +} + +func TestZstdAcceptEncoding(t *testing.T) { + z := Zstd{} + if got := z.AcceptEncoding(); got != "zstd" { + t.Errorf("AcceptEncoding() = %q, want %q", got, "zstd") + } +} + +func TestZstdProvision(t *testing.T) { + tests := []struct { + name string + level string + wantErr bool + wantLevel zstd.EncoderLevel + }{ + { + name: "empty defaults to SpeedDefault", + level: "", + wantLevel: zstd.SpeedDefault, + }, + { + name: "fastest", + level: zstd.SpeedFastest.String(), + wantLevel: zstd.SpeedFastest, + }, + { + name: "default", + level: zstd.SpeedDefault.String(), + wantLevel: zstd.SpeedDefault, + }, + { + name: "better", + level: zstd.SpeedBetterCompression.String(), + wantLevel: zstd.SpeedBetterCompression, + }, + { + name: "best", + level: zstd.SpeedBestCompression.String(), + wantLevel: zstd.SpeedBestCompression, + }, + { + name: "invalid level", + level: "superfast", + wantErr: true, + }, + { + name: "numeric level string", + level: "5", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := &Zstd{Level: tt.level} + err := z.Provision(caddy.Context{}) + if tt.wantErr { + if err == nil { + t.Error("Provision() should return error") + } + return + } + if err != nil { + t.Fatalf("Provision() error: %v", err) + } + if z.level != tt.wantLevel { + t.Errorf("level = %v, want %v", z.level, tt.wantLevel) + } + }) + } +} + +func TestZstdNewEncoder(t *testing.T) { + z := Zstd{level: zstd.SpeedFastest} + enc := z.NewEncoder() + if enc == nil { + t.Fatal("NewEncoder() returned nil") + } + + // Verify the encoder can actually compress data + var buf bytes.Buffer + enc.Reset(&buf) + data := []byte("Hello, Zstandard compression test! This is some test data to compress.") + _, err := enc.Write(data) + if err != nil { + t.Fatalf("encoder.Write() error: %v", err) + } + err = enc.Close() + if err != nil { + t.Fatalf("encoder.Close() error: %v", err) + } + + // Verify we can decompress the output + reader, err := zstd.NewReader(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatalf("zstd.NewReader() error: %v", err) + } + defer reader.Close() + decoded, err := io.ReadAll(reader) + if err != nil { + t.Fatalf("io.ReadAll() error: %v", err) + } + if string(decoded) != string(data) { + t.Errorf("round-trip mismatch: got %q, want %q", decoded, data) + } +} + +func TestZstdUnmarshalCaddyfile(t *testing.T) { + tests := []struct { + name string + input string + wantLevel string + wantErr bool + }{ + { + name: "with fastest level", + input: "zstd fastest", + wantLevel: "fastest", + }, + { + name: "with default level", + input: "zstd default", + wantLevel: "default", + }, + { + name: "with better level", + input: "zstd better", + wantLevel: "better", + }, + { + name: "with best level", + input: "zstd best", + wantLevel: "best", + }, + { + name: "no level keeps empty", + input: "zstd", + wantLevel: "", + }, + { + name: "invalid level", + input: "zstd invalid_level", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := caddyfile.NewTestDispenser(tt.input) + z := &Zstd{} + err := z.UnmarshalCaddyfile(d) + if tt.wantErr { + if err == nil { + t.Error("UnmarshalCaddyfile() should return error") + } + return + } + if err != nil { + t.Fatalf("UnmarshalCaddyfile() error: %v", err) + } + if z.Level != tt.wantLevel { + t.Errorf("Level = %q, want %q", z.Level, tt.wantLevel) + } + }) + } +}