- AliasHandler: normalise model-generated flag casing (--userId → --user-id) - StickyHandler: split glued flag values (--limit100 → --limit 100) - ParamNameHandler: fix near-miss flag typos (--limt → --limit) - ParamValueHandler: normalise structured parameter values after parsing
301 lines
7.9 KiB
Go
301 lines
7.9 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pipeline
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// --- test helpers ---
|
|
|
|
type stubHandler struct {
|
|
name string
|
|
phase Phase
|
|
fn func(*Context) error
|
|
called bool
|
|
callSeq *[]string
|
|
}
|
|
|
|
func (h *stubHandler) Name() string { return h.name }
|
|
func (h *stubHandler) Phase() Phase { return h.phase }
|
|
func (h *stubHandler) Handle(ctx *Context) error {
|
|
h.called = true
|
|
if h.callSeq != nil {
|
|
*h.callSeq = append(*h.callSeq, h.name)
|
|
}
|
|
if h.fn != nil {
|
|
return h.fn(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newStub(name string, phase Phase, fn func(*Context) error) *stubHandler {
|
|
return &stubHandler{name: name, phase: phase, fn: fn}
|
|
}
|
|
|
|
func newStubWithSeq(name string, phase Phase, seq *[]string) *stubHandler {
|
|
return &stubHandler{name: name, phase: phase, callSeq: seq}
|
|
}
|
|
|
|
// --- tests ---
|
|
|
|
func TestNewEngine(t *testing.T) {
|
|
e := NewEngine()
|
|
if e == nil {
|
|
t.Fatal("NewEngine returned nil")
|
|
}
|
|
if got := e.HandlerCount(); got != 0 {
|
|
t.Errorf("HandlerCount = %d, want 0", got)
|
|
}
|
|
}
|
|
|
|
func TestRegisterAndHandlerCount(t *testing.T) {
|
|
e := NewEngine()
|
|
e.Register(newStub("a", PreParse, nil))
|
|
e.Register(newStub("b", PreParse, nil))
|
|
e.Register(newStub("c", PostParse, nil))
|
|
|
|
if got := e.HandlerCount(); got != 3 {
|
|
t.Errorf("HandlerCount = %d, want 3", got)
|
|
}
|
|
if got := len(e.Handlers(PreParse)); got != 2 {
|
|
t.Errorf("PreParse handlers = %d, want 2", got)
|
|
}
|
|
if got := len(e.Handlers(PostParse)); got != 1 {
|
|
t.Errorf("PostParse handlers = %d, want 1", got)
|
|
}
|
|
if got := len(e.Handlers(PreRequest)); got != 0 {
|
|
t.Errorf("PreRequest handlers = %d, want 0", got)
|
|
}
|
|
}
|
|
|
|
func TestRegisterAll(t *testing.T) {
|
|
e := NewEngine()
|
|
e.RegisterAll(
|
|
newStub("a", PreParse, nil),
|
|
newStub("b", PostParse, nil),
|
|
newStub("c", PreRequest, nil),
|
|
)
|
|
if got := e.HandlerCount(); got != 3 {
|
|
t.Errorf("HandlerCount = %d, want 3", got)
|
|
}
|
|
}
|
|
|
|
func TestHasHandlers(t *testing.T) {
|
|
e := NewEngine()
|
|
if e.HasHandlers(PreParse) {
|
|
t.Error("HasHandlers(PreParse) = true on empty engine")
|
|
}
|
|
e.Register(newStub("a", PreParse, nil))
|
|
if !e.HasHandlers(PreParse) {
|
|
t.Error("HasHandlers(PreParse) = false after registration")
|
|
}
|
|
if e.HasHandlers(PostResponse) {
|
|
t.Error("HasHandlers(PostResponse) = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestRunPhaseChainOrder(t *testing.T) {
|
|
var seq []string
|
|
e := NewEngine()
|
|
e.Register(newStubWithSeq("first", PreParse, &seq))
|
|
e.Register(newStubWithSeq("second", PreParse, &seq))
|
|
e.Register(newStubWithSeq("third", PreParse, &seq))
|
|
|
|
ctx := &Context{Args: []string{"test"}}
|
|
if err := e.RunPhase(PreParse, ctx); err != nil {
|
|
t.Fatalf("RunPhase returned error: %v", err)
|
|
}
|
|
want := "first,second,third"
|
|
got := strings.Join(seq, ",")
|
|
if got != want {
|
|
t.Errorf("execution order = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRunPhaseContextMutationPropagates(t *testing.T) {
|
|
e := NewEngine()
|
|
e.Register(newStub("append-foo", PreParse, func(ctx *Context) error {
|
|
ctx.Args = append(ctx.Args, "--foo")
|
|
return nil
|
|
}))
|
|
e.Register(newStub("append-bar", PreParse, func(ctx *Context) error {
|
|
ctx.Args = append(ctx.Args, "--bar")
|
|
return nil
|
|
}))
|
|
|
|
ctx := &Context{Args: []string{"cmd"}}
|
|
if err := e.RunPhase(PreParse, ctx); err != nil {
|
|
t.Fatalf("RunPhase error: %v", err)
|
|
}
|
|
want := "cmd,--foo,--bar"
|
|
got := strings.Join(ctx.Args, ",")
|
|
if got != want {
|
|
t.Errorf("Args = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRunPhaseErrorAbortsChain(t *testing.T) {
|
|
boom := errors.New("boom")
|
|
h1 := newStub("ok", PreParse, nil)
|
|
h2 := newStub("fail", PreParse, func(*Context) error { return boom })
|
|
h3 := newStub("never", PreParse, nil)
|
|
|
|
e := NewEngine()
|
|
e.RegisterAll(h1, h2, h3)
|
|
|
|
ctx := &Context{}
|
|
err := e.RunPhase(PreParse, ctx)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !errors.Is(err, boom) {
|
|
t.Errorf("error = %v, want wrapped boom", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "fail") {
|
|
t.Errorf("error should contain handler name, got %q", err.Error())
|
|
}
|
|
if !h1.called {
|
|
t.Error("h1 should have been called")
|
|
}
|
|
if !h2.called {
|
|
t.Error("h2 should have been called")
|
|
}
|
|
if h3.called {
|
|
t.Error("h3 should NOT have been called after error")
|
|
}
|
|
}
|
|
|
|
func TestRunPhaseEmptyPhaseNoError(t *testing.T) {
|
|
e := NewEngine()
|
|
ctx := &Context{}
|
|
if err := e.RunPhase(PreParse, ctx); err != nil {
|
|
t.Errorf("RunPhase on empty phase returned error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRunAllPhasesOrder(t *testing.T) {
|
|
var seq []string
|
|
e := NewEngine()
|
|
e.Register(newStubWithSeq("post-resp", PostResponse, &seq))
|
|
e.Register(newStubWithSeq("pre-parse", PreParse, &seq))
|
|
e.Register(newStubWithSeq("register", Register, &seq))
|
|
e.Register(newStubWithSeq("post-parse", PostParse, &seq))
|
|
e.Register(newStubWithSeq("pre-req", PreRequest, &seq))
|
|
|
|
ctx := &Context{}
|
|
if err := e.Run(ctx); err != nil {
|
|
t.Fatalf("Run returned error: %v", err)
|
|
}
|
|
want := "register,pre-parse,post-parse,pre-req,post-resp"
|
|
got := strings.Join(seq, ",")
|
|
if got != want {
|
|
t.Errorf("phase execution order = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRunAbortsOnPhaseError(t *testing.T) {
|
|
var seq []string
|
|
e := NewEngine()
|
|
e.Register(newStubWithSeq("pre-parse", PreParse, &seq))
|
|
e.Register(newStub("post-parse-fail", PostParse, func(*Context) error {
|
|
return errors.New("fail")
|
|
}))
|
|
e.Register(newStubWithSeq("pre-req", PreRequest, &seq))
|
|
|
|
ctx := &Context{}
|
|
err := e.Run(ctx)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if len(seq) != 1 || seq[0] != "pre-parse" {
|
|
t.Errorf("seq = %v, want [pre-parse]", seq)
|
|
}
|
|
}
|
|
|
|
func TestContextAddCorrection(t *testing.T) {
|
|
ctx := &Context{}
|
|
ctx.AddCorrection("alias", PreParse, "--userId", "--userId", "--user-id", "alias")
|
|
ctx.AddCorrection("sticky", PreParse, "--limit", "--limit100", "--limit 100", "sticky")
|
|
|
|
if got := len(ctx.Corrections); got != 2 {
|
|
t.Fatalf("Corrections count = %d, want 2", got)
|
|
}
|
|
c := ctx.Corrections[0]
|
|
if c.Handler != "alias" || c.Original != "--userId" || c.Corrected != "--user-id" {
|
|
t.Errorf("first correction = %+v", c)
|
|
}
|
|
}
|
|
|
|
func TestPhaseString(t *testing.T) {
|
|
tests := []struct {
|
|
phase Phase
|
|
want string
|
|
}{
|
|
{Register, "register"},
|
|
{PreParse, "pre-parse"},
|
|
{PostParse, "post-parse"},
|
|
{PreRequest, "pre-request"},
|
|
{PostResponse, "post-response"},
|
|
{Phase(99), "unknown"},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := tt.phase.String(); got != tt.want {
|
|
t.Errorf("Phase(%d).String() = %q, want %q", tt.phase, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunPhaseErrorContainsPhaseAndHandler(t *testing.T) {
|
|
e := NewEngine()
|
|
e.Register(newStub("my-handler", PostParse, func(*Context) error {
|
|
return errors.New("bad value")
|
|
}))
|
|
|
|
err := e.RunPhase(PostParse, &Context{})
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
msg := err.Error()
|
|
if !strings.Contains(msg, "post-parse") {
|
|
t.Errorf("error should contain phase name, got %q", msg)
|
|
}
|
|
if !strings.Contains(msg, "my-handler") {
|
|
t.Errorf("error should contain handler name, got %q", msg)
|
|
}
|
|
}
|
|
|
|
func TestMultipleHandlersSameAndDifferentPhases(t *testing.T) {
|
|
var seq []string
|
|
e := NewEngine()
|
|
e.Register(newStubWithSeq("pp1", PreParse, &seq))
|
|
e.Register(newStubWithSeq("pp2", PreParse, &seq))
|
|
e.Register(newStubWithSeq("op1", PostParse, &seq))
|
|
e.Register(newStubWithSeq("rq1", PreRequest, &seq))
|
|
e.Register(newStubWithSeq("rq2", PreRequest, &seq))
|
|
e.Register(newStubWithSeq("rs1", PostResponse, &seq))
|
|
|
|
ctx := &Context{}
|
|
if err := e.Run(ctx); err != nil {
|
|
t.Fatalf("Run error: %v", err)
|
|
}
|
|
want := "pp1,pp2,op1,rq1,rq2,rs1"
|
|
got := strings.Join(seq, ",")
|
|
if got != want {
|
|
t.Errorf("order = %q, want %q", got, want)
|
|
}
|
|
}
|