refactor: extend command execution pipeline from 3 to 5 stages
This commit is contained in:
1 parent
70e21b58b4
commit
16202c83a3
9 files changed
+626
-1
No files matched your search
@@ -244,6 +244,194 @@ func TestFullPipelineEndToEnd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFullFivePhasePipeline exercises all five phases in order:
|
||||
// Register → PreParse → PostParse → PreRequest → PostResponse,
|
||||
// simulating a complete command lifecycle from registration through
|
||||
// response output.
|
||||
func TestFullFivePhasePipeline(t *testing.T) {
|
||||
engine := pipeline.NewEngine()
|
||||
engine.RegisterAll(
|
||||
RegisterHandler{},
|
||||
AliasHandler{},
|
||||
StickyHandler{},
|
||||
ParamNameHandler{},
|
||||
ParamValueHandler{},
|
||||
PreRequestHandler{},
|
||||
PostResponseHandler{},
|
||||
)
|
||||
|
||||
// Verify all five phases have handlers.
|
||||
for _, phase := range []pipeline.Phase{
|
||||
pipeline.Register,
|
||||
pipeline.PreParse,
|
||||
pipeline.PostParse,
|
||||
pipeline.PreRequest,
|
||||
pipeline.PostResponse,
|
||||
} {
|
||||
if !engine.HasHandlers(phase) {
|
||||
t.Fatalf("engine missing handlers for phase %v", phase)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1: Register — command tree being built.
|
||||
ctx := &pipeline.Context{
|
||||
Command: "aitable",
|
||||
}
|
||||
if err := engine.RunPhase(pipeline.Register, ctx); err != nil {
|
||||
t.Fatalf("Register error: %v", err)
|
||||
}
|
||||
|
||||
// Phase 2: PreParse — fix raw argv.
|
||||
ctx.Args = []string{
|
||||
"--userId", "u001",
|
||||
"--pageSize50",
|
||||
"--verbosetrue",
|
||||
}
|
||||
ctx.FlagSpecs = flagSpecs("user-id", "page-size", "verbose")
|
||||
|
||||
if err := engine.RunPhase(pipeline.PreParse, ctx); err != nil {
|
||||
t.Fatalf("PreParse error: %v", err)
|
||||
}
|
||||
|
||||
want := "--user-id u001 --page-size 50 --verbose true"
|
||||
got := strings.Join(ctx.Args, " ")
|
||||
if got != want {
|
||||
t.Errorf("after PreParse: Args = %q, want %q", got, want)
|
||||
}
|
||||
preParseCorrections := len(ctx.Corrections)
|
||||
|
||||
// Phase 3: PostParse — simulate Cobra having parsed the corrected
|
||||
// args into structured params, then normalise values.
|
||||
ctx.Command = "aitable.query_records"
|
||||
ctx.Params = map[string]any{
|
||||
"user_id": "u001",
|
||||
"page_size": "1,000",
|
||||
"verbose": "yes",
|
||||
}
|
||||
ctx.Schema = map[string]any{
|
||||
"properties": map[string]any{
|
||||
"user_id": map[string]any{"type": "string"},
|
||||
"page_size": map[string]any{"type": "integer"},
|
||||
"verbose": map[string]any{"type": "boolean"},
|
||||
},
|
||||
}
|
||||
|
||||
if err := engine.RunPhase(pipeline.PostParse, ctx); err != nil {
|
||||
t.Fatalf("PostParse error: %v", err)
|
||||
}
|
||||
|
||||
if got := ctx.Params["verbose"]; got != true {
|
||||
t.Errorf("verbose = %v (%T), want true (bool)", got, got)
|
||||
}
|
||||
if got := ctx.Params["page_size"]; got != int64(1000) {
|
||||
t.Errorf("page_size = %v, want 1000", got)
|
||||
}
|
||||
postParseCorrections := len(ctx.Corrections) - preParseCorrections
|
||||
if postParseCorrections != 2 {
|
||||
t.Errorf("PostParse corrections = %d, want 2", postParseCorrections)
|
||||
}
|
||||
|
||||
// Phase 4: PreRequest — inspect final payload before dispatch.
|
||||
ctx.Payload = ctx.Params
|
||||
if err := engine.RunPhase(pipeline.PreRequest, ctx); err != nil {
|
||||
t.Fatalf("PreRequest error: %v", err)
|
||||
}
|
||||
// Verify payload was not corrupted.
|
||||
if ctx.Payload["user_id"] != "u001" {
|
||||
t.Error("PreRequest corrupted Payload")
|
||||
}
|
||||
|
||||
// Phase 5: PostResponse — process response before output.
|
||||
ctx.Response = map[string]any{
|
||||
"records": []any{
|
||||
map[string]any{"id": "rec001", "fields": map[string]any{"name": "test"}},
|
||||
},
|
||||
"total": 1,
|
||||
}
|
||||
if err := engine.RunPhase(pipeline.PostResponse, ctx); err != nil {
|
||||
t.Fatalf("PostResponse error: %v", err)
|
||||
}
|
||||
// Verify response was not corrupted.
|
||||
if ctx.Response["total"] != 1 {
|
||||
t.Error("PostResponse corrupted Response")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFullFivePhasePipelineWithEngineRun exercises all five phases
|
||||
// using Engine.Run (single shot) to verify the ordering is correct
|
||||
// end-to-end.
|
||||
func TestFullFivePhasePipelineWithEngineRun(t *testing.T) {
|
||||
var seq []string
|
||||
|
||||
engine := pipeline.NewEngine()
|
||||
engine.RegisterAll(
|
||||
&phaseTracker{name: "reg", phase: pipeline.Register, seq: &seq},
|
||||
&phaseTracker{name: "pre-parse", phase: pipeline.PreParse, seq: &seq},
|
||||
&phaseTracker{name: "post-parse", phase: pipeline.PostParse, seq: &seq},
|
||||
&phaseTracker{name: "pre-req", phase: pipeline.PreRequest, seq: &seq},
|
||||
&phaseTracker{name: "post-resp", phase: pipeline.PostResponse, seq: &seq},
|
||||
)
|
||||
|
||||
ctx := &pipeline.Context{Command: "test.tool"}
|
||||
if err := engine.Run(ctx); err != nil {
|
||||
t.Fatalf("Engine.Run error: %v", err)
|
||||
}
|
||||
|
||||
want := "reg,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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFivePhasePipelineCorrectHandlerCounts verifies that the
|
||||
// production-equivalent engine has the expected handler distribution.
|
||||
func TestFivePhasePipelineCorrectHandlerCounts(t *testing.T) {
|
||||
engine := pipeline.NewEngine()
|
||||
engine.RegisterAll(
|
||||
RegisterHandler{},
|
||||
AliasHandler{},
|
||||
StickyHandler{},
|
||||
ParamNameHandler{},
|
||||
ParamValueHandler{},
|
||||
PreRequestHandler{},
|
||||
PostResponseHandler{},
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
phase pipeline.Phase
|
||||
want int
|
||||
}{
|
||||
{pipeline.Register, 1},
|
||||
{pipeline.PreParse, 3},
|
||||
{pipeline.PostParse, 1},
|
||||
{pipeline.PreRequest, 1},
|
||||
{pipeline.PostResponse, 1},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := len(engine.Handlers(tt.phase)); got != tt.want {
|
||||
t.Errorf("Handlers(%v) = %d, want %d", tt.phase, got, tt.want)
|
||||
}
|
||||
}
|
||||
if got := engine.HandlerCount(); got != 7 {
|
||||
t.Errorf("HandlerCount = %d, want 7", got)
|
||||
}
|
||||
}
|
||||
|
||||
// phaseTracker is a test helper that records its name when Handle is called.
|
||||
type phaseTracker struct {
|
||||
name string
|
||||
phase pipeline.Phase
|
||||
seq *[]string
|
||||
}
|
||||
|
||||
func (h *phaseTracker) Name() string { return h.name }
|
||||
func (h *phaseTracker) Phase() pipeline.Phase { return h.phase }
|
||||
func (h *phaseTracker) Handle(_ *pipeline.Context) error {
|
||||
*h.seq = append(*h.seq, h.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestPreParseDoesNotBreakValidArgs verifies that valid, correctly
|
||||
// formatted args pass through the pipeline without modification.
|
||||
func TestPreParseDoesNotBreakValidArgs(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
||||
)
|
||||
|
||||
// PostResponseHandler runs in the PostResponse phase — after the
|
||||
// transport returns a result and before the output is written to
|
||||
// stdout. It receives the raw response and can mutate it.
|
||||
//
|
||||
// Default behaviour: no-op pass-through. This establishes the
|
||||
// extension point for:
|
||||
// - Output format transformation (e.g. table, CSV, YAML renderers)
|
||||
// - Response field filtering or redaction
|
||||
// - Pagination metadata injection
|
||||
// - Response caching or analytics collection
|
||||
//
|
||||
// Logging is handled at the integration point in canonical.go,
|
||||
// consistent with how other phases log at their call sites.
|
||||
type PostResponseHandler struct{}
|
||||
|
||||
func (PostResponseHandler) Name() string { return "postresponse" }
|
||||
func (PostResponseHandler) Phase() pipeline.Phase { return pipeline.PostResponse }
|
||||
|
||||
func (PostResponseHandler) Handle(ctx *pipeline.Context) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
||||
)
|
||||
|
||||
func TestPostResponseHandlerMeta(t *testing.T) {
|
||||
h := PostResponseHandler{}
|
||||
if got := h.Name(); got != "postresponse" {
|
||||
t.Errorf("Name() = %q, want %q", got, "postresponse")
|
||||
}
|
||||
if got := h.Phase(); got != pipeline.PostResponse {
|
||||
t.Errorf("Phase() = %v, want %v", got, pipeline.PostResponse)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostResponseHandlerEmptyContext(t *testing.T) {
|
||||
h := PostResponseHandler{}
|
||||
ctx := &pipeline.Context{}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostResponseHandlerNoSideEffects(t *testing.T) {
|
||||
h := PostResponseHandler{}
|
||||
ctx := &pipeline.Context{
|
||||
Command: "aitable.query_records",
|
||||
Response: map[string]any{
|
||||
"records": []any{
|
||||
map[string]any{"id": "rec001"},
|
||||
},
|
||||
"total": 1,
|
||||
},
|
||||
}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
if ctx.Response["total"] != 1 {
|
||||
t.Error("PostResponseHandler should not mutate Response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostResponseHandlerNilResponse(t *testing.T) {
|
||||
h := PostResponseHandler{}
|
||||
ctx := &pipeline.Context{
|
||||
Command: "todo.list",
|
||||
Response: nil,
|
||||
}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostResponseHandlerInEngine(t *testing.T) {
|
||||
engine := pipeline.NewEngine()
|
||||
engine.Register(PostResponseHandler{})
|
||||
|
||||
if !engine.HasHandlers(pipeline.PostResponse) {
|
||||
t.Fatal("engine should have PostResponse handler")
|
||||
}
|
||||
|
||||
ctx := &pipeline.Context{
|
||||
Command: "calendar.list_events",
|
||||
Response: map[string]any{"events": []any{}},
|
||||
}
|
||||
if err := engine.RunPhase(pipeline.PostResponse, ctx); err != nil {
|
||||
t.Fatalf("RunPhase(PostResponse) returned error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
||||
)
|
||||
|
||||
// PreRequestHandler runs in the PreRequest phase — after parameter
|
||||
// validation succeeds and just before the JSON-RPC call is dispatched.
|
||||
// It receives the final payload and can inspect or mutate it.
|
||||
//
|
||||
// Default behaviour: no-op pass-through. This establishes the
|
||||
// extension point for:
|
||||
// - Raw API fallback routing (detecting unsupported tools and
|
||||
// rewriting the payload to a raw HTTP endpoint)
|
||||
// - Request signing or header injection
|
||||
// - Dry-run payload capture
|
||||
// - Rate-limit pre-checks
|
||||
//
|
||||
// Logging is handled at the integration point in canonical.go,
|
||||
// consistent with how other phases log at their call sites.
|
||||
type PreRequestHandler struct{}
|
||||
|
||||
func (PreRequestHandler) Name() string { return "prerequest" }
|
||||
func (PreRequestHandler) Phase() pipeline.Phase { return pipeline.PreRequest }
|
||||
|
||||
func (PreRequestHandler) Handle(ctx *pipeline.Context) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
||||
)
|
||||
|
||||
func TestPreRequestHandlerMeta(t *testing.T) {
|
||||
h := PreRequestHandler{}
|
||||
if got := h.Name(); got != "prerequest" {
|
||||
t.Errorf("Name() = %q, want %q", got, "prerequest")
|
||||
}
|
||||
if got := h.Phase(); got != pipeline.PreRequest {
|
||||
t.Errorf("Phase() = %v, want %v", got, pipeline.PreRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreRequestHandlerEmptyContext(t *testing.T) {
|
||||
h := PreRequestHandler{}
|
||||
ctx := &pipeline.Context{}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreRequestHandlerNoSideEffects(t *testing.T) {
|
||||
h := PreRequestHandler{}
|
||||
ctx := &pipeline.Context{
|
||||
Command: "aitable.query_records",
|
||||
Params: map[string]any{
|
||||
"spaceId": "sp001",
|
||||
"datasheetId": "ds001",
|
||||
},
|
||||
Payload: map[string]any{
|
||||
"spaceId": "sp001",
|
||||
"datasheetId": "ds001",
|
||||
},
|
||||
}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
if ctx.Params["spaceId"] != "sp001" {
|
||||
t.Error("PreRequestHandler should not mutate Params")
|
||||
}
|
||||
if ctx.Payload["spaceId"] != "sp001" {
|
||||
t.Error("PreRequestHandler should not mutate Payload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreRequestHandlerNilPayload(t *testing.T) {
|
||||
h := PreRequestHandler{}
|
||||
ctx := &pipeline.Context{
|
||||
Command: "chat.send_message",
|
||||
Params: map[string]any{"userId": "u001"},
|
||||
}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreRequestHandlerInEngine(t *testing.T) {
|
||||
engine := pipeline.NewEngine()
|
||||
engine.Register(PreRequestHandler{})
|
||||
|
||||
if !engine.HasHandlers(pipeline.PreRequest) {
|
||||
t.Fatal("engine should have PreRequest handler")
|
||||
}
|
||||
|
||||
ctx := &pipeline.Context{
|
||||
Command: "todo.create",
|
||||
Params: map[string]any{"subject": "test"},
|
||||
Payload: map[string]any{"subject": "test"},
|
||||
}
|
||||
if err := engine.RunPhase(pipeline.PreRequest, ctx); err != nil {
|
||||
t.Fatalf("RunPhase(PreRequest) returned error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
||||
)
|
||||
|
||||
// RegisterHandler runs during the Register phase — the first stage
|
||||
// in the pipeline, executed while the Cobra command tree is being
|
||||
// built. It validates that the registration context carries a
|
||||
// non-empty command identifier.
|
||||
//
|
||||
// The handler is intentionally lightweight and side-effect free.
|
||||
// This provides the structural hook for future extensions (e.g.
|
||||
// dynamic command injection, feature gating, or Raw API fallback
|
||||
// command registration) without adding any runtime overhead to
|
||||
// the default path. Logging is handled at the call site in
|
||||
// canonical.go, consistent with how PreParse logging is done
|
||||
// in cobra.go.
|
||||
type RegisterHandler struct{}
|
||||
|
||||
func (RegisterHandler) Name() string { return "register" }
|
||||
func (RegisterHandler) Phase() pipeline.Phase { return pipeline.Register }
|
||||
|
||||
func (RegisterHandler) Handle(ctx *pipeline.Context) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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 handlers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
||||
)
|
||||
|
||||
func TestRegisterHandlerMeta(t *testing.T) {
|
||||
h := RegisterHandler{}
|
||||
if got := h.Name(); got != "register" {
|
||||
t.Errorf("Name() = %q, want %q", got, "register")
|
||||
}
|
||||
if got := h.Phase(); got != pipeline.Register {
|
||||
t.Errorf("Phase() = %v, want %v", got, pipeline.Register)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterHandlerEmptyContext(t *testing.T) {
|
||||
h := RegisterHandler{}
|
||||
ctx := &pipeline.Context{}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterHandlerWithCommand(t *testing.T) {
|
||||
h := RegisterHandler{}
|
||||
ctx := &pipeline.Context{
|
||||
Command: "aitable",
|
||||
Schema: map[string]any{
|
||||
"properties": map[string]any{
|
||||
"spaceId": map[string]any{"type": "string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterHandlerNoSideEffects(t *testing.T) {
|
||||
h := RegisterHandler{}
|
||||
ctx := &pipeline.Context{
|
||||
Command: "todo",
|
||||
Params: map[string]any{"key": "value"},
|
||||
}
|
||||
if err := h.Handle(ctx); err != nil {
|
||||
t.Fatalf("Handle returned error: %v", err)
|
||||
}
|
||||
if ctx.Params["key"] != "value" {
|
||||
t.Error("RegisterHandler should not mutate Params")
|
||||
}
|
||||
if ctx.Command != "todo" {
|
||||
t.Error("RegisterHandler should not mutate Command")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterHandlerInEngine(t *testing.T) {
|
||||
engine := pipeline.NewEngine()
|
||||
engine.Register(RegisterHandler{})
|
||||
|
||||
if !engine.HasHandlers(pipeline.Register) {
|
||||
t.Fatal("engine should have Register handler")
|
||||
}
|
||||
|
||||
ctx := &pipeline.Context{Command: "calendar"}
|
||||
if err := engine.RunPhase(pipeline.Register, ctx); err != nil {
|
||||
t.Fatalf("RunPhase(Register) returned error: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user