mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-18 15:23:51 +01:00
* 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>`
297 lines
6.1 KiB
Go
297 lines
6.1 KiB
Go
package rules
|
|
|
|
import (
|
|
"testing"
|
|
|
|
gperr "github.com/yusing/goutils/errs"
|
|
expect "github.com/yusing/goutils/testing"
|
|
)
|
|
|
|
func TestSplitPipe(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
input: "",
|
|
want: []string{},
|
|
},
|
|
{
|
|
name: "single",
|
|
input: "rule",
|
|
want: []string{"rule"},
|
|
},
|
|
{
|
|
name: "simple_pipe",
|
|
input: "rule1 | rule2",
|
|
want: []string{"rule1", "rule2"},
|
|
},
|
|
{
|
|
name: "multiple_pipes",
|
|
input: "rule1 | rule2 | rule3",
|
|
want: []string{"rule1", "rule2", "rule3"},
|
|
},
|
|
{
|
|
name: "pipe_in_quotes",
|
|
input: `path regex("^(_next/static|_next/image|favicon.ico).*$")`,
|
|
want: []string{`path regex("^(_next/static|_next/image|favicon.ico).*$")`},
|
|
},
|
|
{
|
|
name: "pipe_in_single_quotes",
|
|
input: `path regex('^(_next/static|_next/image|favicon.ico).*$')`,
|
|
want: []string{`path regex('^(_next/static|_next/image|favicon.ico).*$')`},
|
|
},
|
|
{
|
|
name: "pipe_in_backticks",
|
|
input: "path regex(`^(_next/static|_next/image|favicon.ico).*$`)",
|
|
want: []string{"path regex(`^(_next/static|_next/image|favicon.ico).*$`)"},
|
|
},
|
|
{
|
|
name: "pipe_in_brackets",
|
|
input: "path regex(^(_next/static|_next/image|favicon.ico).*$)",
|
|
want: []string{"path regex(^(_next/static|_next/image|favicon.ico).*$)"},
|
|
},
|
|
{
|
|
name: "escaped_pipe",
|
|
input: `path regex("^(_next/static\|_next/image\|favicon.ico).*$")`,
|
|
want: []string{`path regex("^(_next/static\|_next/image\|favicon.ico).*$")`},
|
|
},
|
|
{
|
|
name: "mixed_quotes_and_pipes",
|
|
input: `rule1 | path regex("^(_next/static|_next/image|favicon.ico).*$") | rule3`,
|
|
want: []string{"rule1", `path regex("^(_next/static|_next/image|favicon.ico).*$")`, "rule3"},
|
|
},
|
|
{
|
|
name: "nested_brackets",
|
|
input: "path regex(^(foo|bar(baz|qux)).*$)",
|
|
want: []string{"path regex(^(foo|bar(baz|qux)).*$)"},
|
|
},
|
|
{
|
|
name: "spaces_around",
|
|
input: " rule1 | rule2 | rule3 ",
|
|
want: []string{"rule1", "rule2", "rule3"},
|
|
},
|
|
{
|
|
name: "empty_segments",
|
|
input: "rule1 || rule2 | | rule3",
|
|
want: []string{"rule1", "", "rule2", "", "rule3"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := splitPipe(tt.input)
|
|
expect.Equal(t, got, tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSplitAnd(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "empty",
|
|
input: "",
|
|
want: []string{},
|
|
},
|
|
{
|
|
name: "single",
|
|
input: "rule",
|
|
want: []string{"rule"},
|
|
},
|
|
{
|
|
name: "multiple",
|
|
input: "rule1 & rule2",
|
|
want: []string{"rule1", "rule2"},
|
|
},
|
|
{
|
|
name: "multiple_newline",
|
|
input: "rule1\n\nrule2",
|
|
want: []string{"rule1", "rule2"},
|
|
},
|
|
{
|
|
name: "multiple_newline_and",
|
|
input: "rule1\nrule2 & rule3",
|
|
want: []string{"rule1", "rule2", "rule3"},
|
|
},
|
|
{
|
|
name: "empty segment",
|
|
input: "rule1\n& &rule2& rule3",
|
|
want: []string{"rule1", "rule2", "rule3"},
|
|
},
|
|
{
|
|
name: "double_and",
|
|
input: "rule1\nrule2 && rule3",
|
|
want: []string{"rule1", "rule2", "rule3"},
|
|
},
|
|
{
|
|
name: "spaces_around",
|
|
input: " rule1\nrule2 & rule3 ",
|
|
want: []string{"rule1", "rule2", "rule3"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := splitAnd(tt.input)
|
|
expect.Equal(t, got, tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseOn(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr gperr.Error
|
|
}{
|
|
// header
|
|
{
|
|
name: "header_valid_kv",
|
|
input: "header Connection Upgrade",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "header_valid_k",
|
|
input: "header Connection",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "header_missing_arg",
|
|
input: "header",
|
|
wantErr: ErrExpectKVOptionalV,
|
|
},
|
|
// query
|
|
{
|
|
name: "query_valid_kv",
|
|
input: "query key value",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "query_valid_k",
|
|
input: "query key",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "query_missing_arg",
|
|
input: "query",
|
|
wantErr: ErrExpectKVOptionalV,
|
|
},
|
|
{
|
|
name: "cookie_valid_kv",
|
|
input: "cookie key value",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "cookie_valid_k",
|
|
input: "cookie key",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "cookie_missing_arg",
|
|
input: "cookie",
|
|
wantErr: ErrExpectKVOptionalV,
|
|
},
|
|
// method
|
|
{
|
|
name: "method_valid",
|
|
input: "method GET",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "method_invalid",
|
|
input: "method invalid",
|
|
wantErr: ErrInvalidArguments,
|
|
},
|
|
{
|
|
name: "method_missing_arg",
|
|
input: "method",
|
|
wantErr: ErrExpectOneArg,
|
|
},
|
|
// path
|
|
{
|
|
name: "path_valid",
|
|
input: "path /home",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "path_missing_arg",
|
|
input: "path",
|
|
wantErr: ErrExpectOneArg,
|
|
},
|
|
// remote
|
|
{
|
|
name: "remote_valid",
|
|
input: "remote 127.0.0.1",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "remote_invalid",
|
|
input: "remote abcd",
|
|
wantErr: ErrInvalidArguments,
|
|
},
|
|
{
|
|
name: "remote_missing_arg",
|
|
input: "remote",
|
|
wantErr: ErrExpectOneArg,
|
|
},
|
|
{
|
|
name: "unknown_target",
|
|
input: "unknown",
|
|
wantErr: ErrInvalidOnTarget,
|
|
},
|
|
// route
|
|
{
|
|
name: "route_valid",
|
|
input: "route example",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "route_missing_arg",
|
|
input: "route",
|
|
wantErr: ErrExpectOneArg,
|
|
},
|
|
{
|
|
name: "route_extra_arg",
|
|
input: "route example1 example2",
|
|
wantErr: ErrExpectOneArg,
|
|
},
|
|
// pipe splitting tests
|
|
{
|
|
name: "pipe_simple",
|
|
input: "method GET | method POST",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "pipe_in_quotes",
|
|
input: `path regex("^(_next/static|_next/image|favicon.ico).*$")`,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "pipe_in_brackets",
|
|
input: "path regex(^(_next/static|_next/image|favicon.ico).*$)",
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "pipe_mixed",
|
|
input: `method GET | path regex("^(_next/static|_next/image|favicon.ico).*$") | header Authorization`,
|
|
wantErr: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
on := &RuleOn{}
|
|
err := on.Parse(tt.input)
|
|
if tt.wantErr != nil {
|
|
expect.HasError(t, tt.wantErr, err)
|
|
} else {
|
|
expect.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|