mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-25 10:18:59 +02:00
feat(rules): add post-request rules system with response manipulation (#160)
* Add comprehensive post-request rules support for response phase * Enable response body, status, and header manipulation via set commands * Refactor command handlers to support both request and response phases * Implement response modifier system for post-request template execution * Support response-based rule matching with status and header checks * Add comprehensive benchmarks for matcher performance * Refactor authentication and proxying commands for unified error handling * Support negated conditions with ! * Enhance error handling, error formatting and validation * Routes: add `rule_file` field with rule preset support * Environment variable substitution: now supports variables without `GODOXY_` prefix * new conditions: * `on resp_header <key> [<value>]` * `on status <status>` * new commands: * `require_auth` * `set resp_header <key> <template>` * `set resp_body <template>` * `set status <code>` * `log <level> <path> <template>` * `notify <level> <provider> <title_template> <body_template>`
This commit is contained in:
169
internal/route/rules/matcher_test.go
Normal file
169
internal/route/rules/matcher_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
)
|
||||
|
||||
func TestExtractExpr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
wantT MatcherType
|
||||
wantExpr string
|
||||
}{
|
||||
{
|
||||
name: "string implicit",
|
||||
in: "foo",
|
||||
wantT: MatcherTypeString,
|
||||
wantExpr: "foo",
|
||||
},
|
||||
{
|
||||
name: "string explicit",
|
||||
in: "string(`foo`)",
|
||||
wantT: MatcherTypeString,
|
||||
wantExpr: "foo",
|
||||
},
|
||||
{
|
||||
name: "glob",
|
||||
in: "glob(foo)",
|
||||
wantT: MatcherTypeGlob,
|
||||
wantExpr: "foo",
|
||||
},
|
||||
{
|
||||
name: "glob quoted",
|
||||
in: "glob(`foo`)",
|
||||
wantT: MatcherTypeGlob,
|
||||
wantExpr: "foo",
|
||||
},
|
||||
{
|
||||
name: "regex",
|
||||
in: "regex(^[A-Z]+$)",
|
||||
wantT: MatcherTypeRegex,
|
||||
wantExpr: "^[A-Z]+$",
|
||||
},
|
||||
{
|
||||
name: "regex quoted",
|
||||
in: "regex(`^[A-Z]+$`)",
|
||||
wantT: MatcherTypeRegex,
|
||||
wantExpr: "^[A-Z]+$",
|
||||
},
|
||||
{
|
||||
name: "regex with parentheses",
|
||||
in: "regex(test(group))",
|
||||
wantT: MatcherTypeRegex,
|
||||
wantExpr: "test(group)",
|
||||
},
|
||||
{
|
||||
name: "regex complex",
|
||||
in: `regex("^(_next/static|_next/image|favicon.ico).*$")`,
|
||||
wantT: MatcherTypeRegex,
|
||||
wantExpr: "^(_next/static|_next/image|favicon.ico).*$",
|
||||
},
|
||||
{
|
||||
name: "quoted expr",
|
||||
in: "glob(`'foo'`)",
|
||||
wantT: MatcherTypeGlob,
|
||||
wantExpr: "'foo'",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
typ, expr, err := ExtractExpr(tt.in)
|
||||
expect.NoError(t, err)
|
||||
expect.Equal(t, tt.wantT, typ)
|
||||
expect.Equal(t, tt.wantExpr, expr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractExprInvalid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "missing closing quote",
|
||||
in: "glob(`foo)",
|
||||
wantErr: "unterminated quotes",
|
||||
},
|
||||
{
|
||||
name: "missing closing bracket",
|
||||
in: "glob(`foo",
|
||||
wantErr: "unterminated brackets",
|
||||
},
|
||||
{
|
||||
name: "invalid matcher type",
|
||||
in: "invalid(`foo`)",
|
||||
wantErr: "invalid matcher type: invalid",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, _, err := ExtractExpr(tt.in)
|
||||
expect.HasError(t, err)
|
||||
expect.ErrorContains(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegated(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expr string
|
||||
in string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "negated_string_match",
|
||||
expr: "!string(`foo`)",
|
||||
in: "foo",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "negated_string_no_match",
|
||||
expr: "!string(`foo`)",
|
||||
in: "bar",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "negated_glob_match",
|
||||
expr: "!glob(`foo`)",
|
||||
in: "foo",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "negated_glob_no_match",
|
||||
expr: "!glob(`foo`)",
|
||||
in: "bar",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "negated_regex_match",
|
||||
expr: "!regex(`^(_next/static|_next/image|favicon.ico).*$`)",
|
||||
in: "favicon.ico",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "negated_regex_no_match",
|
||||
expr: "!regex(`^(_next/static|_next/image|favicon.ico).*$`)",
|
||||
in: "bar",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "negated_regex_no_match2",
|
||||
expr: "!regex(`^(_next/static|_next/image|favicon.ico).*$`)",
|
||||
in: "/",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher, err := ParseMatcher(tt.expr)
|
||||
expect.NoError(t, err)
|
||||
expect.Equal(t, tt.want, matcher(tt.in))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user