Single registry drives Traefik routing, Prometheus scraping, and
the dashboard. FQDN pattern: {service}.{DOMAIN}. Adding a service
= one line in the reef dict + a compose block.
- Parameterize .env (DOMAIN, HOST_IP, S3_*, etc.) and compose.yml
- Zero-trust: remove host port exposure from postgres, rustfs, jaeger
- Traefik Go template replaces 193-line hand-written routing config
- Fold middlewares.yml into the template, delete separate file
- Prometheus switches to file_sd_configs with targets registry
- Dashboard uses JS domain resolution (zero hardcoded FQDNs)
- Grafana provisioning: datasources for Prometheus, Loki, Jaeger
- Vendor rewrite-body plugin (remove nested .git, track as files)
- Trino catalog uses ${ENV:...} for S3 settings
- CI: split traefik validation (yamllint static + template render)
- Add .env.example documenting all deployment knobs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
257 lines
6.1 KiB
Go
257 lines
6.1 KiB
Go
package rewrite_body
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/packruler/rewrite-body/compressutil"
|
|
"github.com/packruler/rewrite-body/handler"
|
|
"github.com/packruler/rewrite-body/httputil"
|
|
)
|
|
|
|
func TestServeHTTP(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
contentEncoding string
|
|
contentType string `default:"text/html"`
|
|
rewrites []handler.Rewrite
|
|
lastModified bool
|
|
resBody string
|
|
expResBody string
|
|
expLastModified bool
|
|
}{
|
|
{
|
|
desc: "should replace foo by bar",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentType: "text/html",
|
|
resBody: "foo is the new bar",
|
|
expResBody: "bar is the new bar",
|
|
},
|
|
{
|
|
desc: "should replace foo by bar, then by foo",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
{
|
|
Regex: "bar",
|
|
Replacement: "foo",
|
|
},
|
|
},
|
|
contentType: "text/html",
|
|
resBody: "foo is the new bar",
|
|
expResBody: "foo is the new foo",
|
|
},
|
|
{
|
|
desc: "should not replace anything if content encoding is not identity or empty",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentEncoding: "other",
|
|
contentType: "text/html",
|
|
resBody: "foo is the new bar",
|
|
expResBody: "foo is the new bar",
|
|
},
|
|
{
|
|
desc: "should not replace anything if content type does not contain text or is not empty",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentType: "image",
|
|
resBody: "foo is the new bar",
|
|
expResBody: "foo is the new bar",
|
|
},
|
|
{
|
|
desc: "should replace foo by bar if content encoding is identity",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentEncoding: "identity",
|
|
contentType: "text/html",
|
|
resBody: "foo is the new bar",
|
|
expResBody: "bar is the new bar",
|
|
},
|
|
{
|
|
desc: "should not remove the last modified header",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentEncoding: "identity",
|
|
contentType: "text/html",
|
|
lastModified: true,
|
|
resBody: "foo is the new bar",
|
|
expResBody: "bar is the new bar",
|
|
expLastModified: true,
|
|
},
|
|
{
|
|
desc: "should support gzip encoding",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentEncoding: "gzip",
|
|
contentType: "text/html",
|
|
lastModified: true,
|
|
resBody: compressString("foo is the new bar", "gzip"),
|
|
expResBody: compressString("bar is the new bar", "gzip"),
|
|
expLastModified: true,
|
|
},
|
|
{
|
|
desc: "should support deflate encoding",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentEncoding: "deflate",
|
|
contentType: "text/html",
|
|
lastModified: true,
|
|
resBody: compressString("foo is the new bar", "deflate"),
|
|
expResBody: compressString("bar is the new bar", "deflate"),
|
|
expLastModified: true,
|
|
},
|
|
{
|
|
desc: "should ignore unsupported encoding",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
contentEncoding: "br",
|
|
contentType: "text/html",
|
|
lastModified: true,
|
|
resBody: "foo is the new bar",
|
|
expResBody: "foo is the new bar",
|
|
expLastModified: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
config := &handler.Config{
|
|
LastModified: test.lastModified,
|
|
Rewrites: test.rewrites,
|
|
LogLevel: -1,
|
|
}
|
|
|
|
next := func(responseWriter http.ResponseWriter, req *http.Request) {
|
|
responseWriter.Header().Set("Content-Encoding", test.contentEncoding)
|
|
responseWriter.Header().Set("Content-Type", test.contentType)
|
|
responseWriter.Header().Set("Last-Modified", "Thu, 02 Jun 2016 06:01:08 GMT")
|
|
responseWriter.Header().Set("Content-Length", strconv.Itoa(len(test.resBody)))
|
|
responseWriter.WriteHeader(http.StatusOK)
|
|
|
|
_, _ = fmt.Fprintf(responseWriter, test.resBody)
|
|
}
|
|
|
|
rewriteBody, err := New(context.Background(), http.HandlerFunc(next), config, "rewriteBody")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
recorder := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.Header.Set("Accept", "text/html")
|
|
|
|
rewriteBody.ServeHTTP(recorder, req)
|
|
|
|
if _, exists := recorder.Result().Header["Last-Modified"]; exists != test.expLastModified {
|
|
t.Errorf("got last-modified header %v, want %v", exists, test.expLastModified)
|
|
}
|
|
|
|
if _, exists := recorder.Result().Header["Content-Length"]; exists {
|
|
t.Error("The Content-Length Header must be deleted")
|
|
}
|
|
|
|
if !bytes.Equal([]byte(test.expResBody), recorder.Body.Bytes()) {
|
|
t.Errorf("got body: %v\n wanted: %v", recorder.Body.Bytes(), []byte(test.expResBody))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func compressString(value string, encoding string) string {
|
|
compressed, _ := compressutil.Encode([]byte(value), encoding)
|
|
|
|
return string(compressed)
|
|
}
|
|
|
|
func TestNew(t *testing.T) {
|
|
tests := []struct {
|
|
desc string
|
|
rewrites []handler.Rewrite
|
|
expErr bool
|
|
}{
|
|
{
|
|
desc: "should return no error",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "foo",
|
|
Replacement: "bar",
|
|
},
|
|
{
|
|
Regex: "bar",
|
|
Replacement: "foo",
|
|
},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
desc: "should return an error",
|
|
rewrites: []handler.Rewrite{
|
|
{
|
|
Regex: "*",
|
|
Replacement: "bar",
|
|
},
|
|
},
|
|
expErr: true,
|
|
},
|
|
}
|
|
|
|
defaultMonitoring := httputil.MonitoringConfig{
|
|
Types: []string{"text/html"},
|
|
Methods: []string{http.MethodGet},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
config := &handler.Config{
|
|
Rewrites: test.rewrites,
|
|
Monitoring: defaultMonitoring,
|
|
}
|
|
|
|
_, err := New(context.Background(), nil, config, "rewriteBody")
|
|
if test.expErr && err == nil {
|
|
t.Fatal("expected error on bad regexp format")
|
|
}
|
|
})
|
|
}
|
|
}
|