package core import ( "context" "errors" "fmt" "sync" "sync/atomic" "testing" "ragflow/internal/harness/core/schema" ) // ============================================================ // Middleware chain error recovery — failure in one layer // ============================================================ func TestMiddleware_ChainErrorRecovery(t *testing.T) { var callOrder []string var mu sync.Mutex record := func(s string) { mu.Lock(); callOrder = append(callOrder, s); mu.Unlock() } failingMW := &testMiddleware{ beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("failing_beforeModel") return ctx, state, fmt.Errorf("middleware failure") }, afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("failing_afterModel") return ctx, state, nil }, beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) { record("failing_beforeAgent") return ctx, rc, nil }, afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) { record("failing_afterAgent") return ctx, nil }, } normalMW := &testMiddleware{ beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("normal_beforeModel") return ctx, state, nil }, afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("normal_afterModel") return ctx, state, nil }, beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) { record("normal_beforeAgent") return ctx, rc, nil }, afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) { record("normal_afterAgent") return ctx, nil }, } model := &mockModel{} model.addResp("response") agent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: model, Middlewares: []ReActMiddleware{failingMW, normalMW}, }).WithName("mw_chain") agent.name = "mw_chain" ctx := context.Background() iter := agent.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("test")}}) gotError := false for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { gotError = true t.Logf("middleware chain error: %v", ev.Err) break } } mu.Lock() order := make([]string, len(callOrder)) copy(order, callOrder) mu.Unlock() t.Logf("middleware call order: %v", order) if !gotError { t.Error("expected error from failing middleware, got none") } found := false for _, s := range order { if s == "failing_beforeAgent" { found = true } } if !found { t.Error("failingMW.BeforeAgent should have been called") } } // ============================================================ // Interrupt signal tree serialization — deep nesting // ============================================================ func TestInterrupt_TreeSerialization(t *testing.T) { t.Run("deeply_nested_tree", func(t *testing.T) { var root *InterruptSignal current := &InterruptSignal{ID: "root", Info: "root", State: "root-state"} root = current for i := 0; i < 10; i++ { child := &InterruptSignal{ ID: fmt.Sprintf("level_%d", i), Info: fmt.Sprintf("info_%d", i), State: fmt.Sprintf("state_%d", i), } current.Children = []*InterruptSignal{child} current = child } id2addr, id2state := signalToMaps(root) if len(id2addr) != 11 { t.Errorf("expected 11 entries in id2addr (root + 10 levels), got %d", len(id2addr)) } if len(id2state) != 11 { t.Errorf("expected 11 entries in id2state, got %d", len(id2state)) } t.Logf("deep tree serialization: %d addresses, %d states", len(id2addr), len(id2state)) }) t.Run("nil_signal", func(t *testing.T) { id2addr, id2state := signalToMaps(nil) if len(id2addr) != 0 || len(id2state) != 0 { t.Error("expected empty maps for nil signal") } }) t.Run("empty_signal", func(t *testing.T) { id2addr, id2state := signalToMaps(&InterruptSignal{}) if len(id2addr) != 0 || len(id2state) != 0 { t.Error("expected empty maps for empty signal") } }) t.Run("from_interrupt_contexts_empty", func(t *testing.T) { sig := FromInterruptContexts(nil) if sig != nil { t.Error("expected nil for empty input") } }) t.Run("from_interrupt_contexts", func(t *testing.T) { ctxs := []*InterruptCtx{ {ID: "a", Info: "info-a"}, {ID: "b", Info: "info-b"}, } sig := FromInterruptContexts(ctxs) if len(sig.Children) != 2 { t.Errorf("expected 2 children, got %d", len(sig.Children)) } }) } // ============================================================ // Session values concurrent safety — SetRunLocalValue from goroutines // ============================================================ func TestSession_ConcurrentValueAccess(t *testing.T) { model := &mockModel{} model.addResp("done") agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}).WithName("session_conc") agent.name = "session_conc" ctx := context.Background() iter := agent.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("test")}}) for { ev, ok := iter.Next() if !ok { break } _ = ev } var wg sync.WaitGroup errs := make(chan error, 20) for i := 0; i < 20; i++ { wg.Add(1) go func(id int) { defer wg.Done() err := SetRunLocalValue(context.Background(), fmt.Sprintf("key_%d", id), fmt.Sprintf("val_%d", id)) if err != nil && !errors.Is(err, errNotInAgentExec) { errs <- fmt.Errorf("SetRunLocalValue: %w", err) } _, _, err = GetRunLocalValue(context.Background(), "test") if err != nil && !errors.Is(err, errNotInAgentExec) { errs <- fmt.Errorf("GetRunLocalValue: %w", err) } }(i) } wg.Wait() close(errs) for err := range errs { t.Error(err) } } // ============================================================ // Callback system — 100K events under streaming // ============================================================ func TestCallback_HighVolumeEvents(t *testing.T) { const numEvents = 1000 var callbackCount int32 model := &mockModel{} model.addResp("response") agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}).WithName("cb_volume") agent.name = "cb_volume" runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent}) ctx := context.Background() msgs := make([]Message, numEvents) for i := 0; i < numEvents; i++ { msgs[i] = schema.UserMessage(fmt.Sprintf("msg %d", i)) } iter := runner.Run(ctx, msgs) for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Logf("callback volume error: %v", ev.Err) break } } t.Logf("callback count: %d", atomic.LoadInt32(&callbackCount)) } // ============================================================ // Multiple middleware interaction // ============================================================ func TestMiddleware_MultipleMiddlewareInteraction(t *testing.T) { var callOrder []string var mu sync.Mutex record := func(s string) { mu.Lock(); callOrder = append(callOrder, s); mu.Unlock() } mw1 := &testMiddleware{ beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("mw1_beforeModel") return ctx, state, nil }, afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("mw1_afterModel") return ctx, state, nil }, beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) { record("mw1_beforeAgent") return ctx, rc, nil }, afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) { record("mw1_afterAgent") return ctx, nil }, } mw2 := &testMiddleware{ beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("mw2_beforeModel") return ctx, state, nil }, afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { record("mw2_afterModel") return ctx, state, nil }, beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) { record("mw2_beforeAgent") return ctx, rc, nil }, afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) { record("mw2_afterAgent") return ctx, nil }, } model := &mockModel{} model.addResp("response") agent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: model, Middlewares: []ReActMiddleware{mw1, mw2}, }).WithName("multi_mw") agent.name = "multi_mw" ctx := context.Background() iter := agent.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("test")}}) for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } } mu.Lock() order := make([]string, len(callOrder)) copy(order, callOrder) mu.Unlock() if len(order) < 8 { t.Errorf("expected at least 8 middleware hooks, got %d: %v", len(order), order) } else { t.Logf("multi-middleware call order: %v", order) } }