P23 — Skinny Packages: - Break conf→bib circular dependency with lazy guarded import - Extract aco.table.base → conf.table_base (shared foundation) - Add try/except ImportError guards on all cross-module imports - Define per-module optional-dependencies in pyproject.toml - Slim base deps to just pydantic; all heavy deps in optional groups - Add gen_config.py --check validation for module/dep sync - Add skinny-install CI matrix job (tests each extra in isolation) P24 — Tree Reorganization: - Move service configs to infra/ (traefik, coredns, grafana, etc.) - Consolidate Dockerfiles under infra/images/ - Move styles/ → assets/css/ + assets/icons/ + assets/nature.py - Update compose.yml, stack.toml, gen_config.py, all backend emitters - Update bootstrap scripts and install_certs.sh P25 — Cloud Provider Abstraction: - Add cloud/ directory with self-hosted, aws, gcp, azure stubs - Add cloud contexts (aws, gcp, azure) to stack.toml - Add conf/storage.py — get_filesystem() dispatches per context - Add aws, gcp, azure optional dependency groups - Document service mapping for each provider All 11955 tests pass. Zero functionality lost.
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")
|
|
}
|
|
})
|
|
}
|
|
}
|