diff --git a/internal/context/builder_test.go b/internal/context/builder_test.go index 41f53806..d40c0eac 100644 --- a/internal/context/builder_test.go +++ b/internal/context/builder_test.go @@ -220,6 +220,29 @@ func TestDefaultBuilderBuildIncludesTodosBeforeSystemState(t *testing.T) { } } +func TestNewBuilderWithMemoAndSummarizersIncludesMemoSection(t *testing.T) { + t.Parallel() + + builder := NewBuilderWithMemoAndSummarizers(nil, nil, stubPromptSectionSource{ + sections: []promptSection{ + NewPromptSection("memo", "remember this"), + }, + }) + + got, err := builder.Build(stdcontext.Background(), BuildInput{ + Messages: []providertypes.Message{ + {Role: "user", Parts: []providertypes.ContentPart{providertypes.NewTextPart("hello")}}, + }, + Metadata: testMetadata(t.TempDir()), + }) + if err != nil { + t.Fatalf("Build() error = %v", err) + } + if !strings.Contains(got.SystemPrompt, "## memo") { + t.Fatalf("expected memo section in prompt, got %q", got.SystemPrompt) + } +} + func TestDefaultBuilderBuildUsesSpanTrimPolicyWhenTrimPolicyIsUnset(t *testing.T) { t.Parallel() diff --git a/internal/runtime/planning_test.go b/internal/runtime/planning_test.go index e18ad7ae..5bcd873e 100644 --- a/internal/runtime/planning_test.go +++ b/internal/runtime/planning_test.go @@ -460,3 +460,94 @@ func TestRememberFullPlanRevisionClearsAlignmentFlags(t *testing.T) { t.Fatalf("expected one-shot alignment flags to be cleared, got %+v", session) } } + +func TestMarkCurrentPlanRestorePendingAndContextDirty(t *testing.T) { + t.Parallel() + + session := agentsession.New("mark restore/context dirty") + if markCurrentPlanRestorePending(&session) { + t.Fatal("expected false when current plan is missing") + } + if markCurrentPlanContextDirty(&session) { + t.Fatal("expected false when current plan is missing") + } + + session.CurrentPlan = &agentsession.PlanArtifact{ + ID: "plan-restore", + Revision: 1, + Status: agentsession.PlanStatusApproved, + Spec: agentsession.PlanSpec{ + Goal: "restore full plan", + Steps: []string{"step one"}, + Verify: []string{"verify one"}, + }, + } + if !markCurrentPlanRestorePending(&session) { + t.Fatal("expected first restore mark to succeed") + } + if markCurrentPlanRestorePending(&session) { + t.Fatal("expected duplicated restore mark to be ignored") + } + if !markCurrentPlanContextDirty(&session) { + t.Fatal("expected first context dirty mark to succeed") + } + if markCurrentPlanContextDirty(&session) { + t.Fatal("expected duplicated context dirty mark to be ignored") + } + + session.CurrentPlan.Status = agentsession.PlanStatusCompleted + session.PlanCompletionPendingFullReview = false + session.PlanRestorePendingAlign = false + session.PlanContextDirty = false + if markCurrentPlanRestorePending(&session) { + t.Fatal("expected completed plan without full review pending not to mark restore align") + } + if markCurrentPlanContextDirty(&session) { + t.Fatal("expected completed plan without full review pending not to mark context dirty") + } +} + +func TestApplyCurrentPlanRevisionNilGuards(t *testing.T) { + t.Parallel() + + session := agentsession.New("apply plan revision nil guards") + plan := &agentsession.PlanArtifact{ID: "plan-1", Revision: 1} + if applyCurrentPlanRevision(nil, plan) { + t.Fatal("expected nil session to return false") + } + if applyCurrentPlanRevision(&session, nil) { + t.Fatal("expected nil plan to return false") + } +} + +func TestApproveCurrentPlanValidationErrors(t *testing.T) { + t.Parallel() + + session := agentsession.New("approve validation") + if err := approveCurrentPlan(&session, "plan-1", 1); err == nil { + t.Fatal("expected error when current plan does not exist") + } + + session.CurrentPlan = &agentsession.PlanArtifact{ + ID: "plan-1", + Revision: 2, + Status: agentsession.PlanStatusDraft, + Spec: agentsession.PlanSpec{ + Goal: "审批校验", + Steps: []string{"步骤一"}, + Verify: []string{"验证一"}, + }, + } + + if err := approveCurrentPlan(&session, "plan-2", 2); err == nil { + t.Fatal("expected id mismatch error") + } + if err := approveCurrentPlan(&session, "plan-1", 1); err == nil { + t.Fatal("expected revision mismatch error") + } + + session.CurrentPlan.Status = agentsession.PlanStatusApproved + if err := approveCurrentPlan(&session, "plan-1", 2); err == nil { + t.Fatal("expected status mismatch error") + } +} diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index 02d5c4df..99ae1206 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -4186,6 +4186,63 @@ func TestServiceApproveCurrentPlanNilService(t *testing.T) { } } +func TestServiceApproveCurrentPlanCanceledContext(t *testing.T) { + t.Parallel() + + manager := newRuntimeConfigManager(t) + store := newMemoryStore() + service := NewWithFactory(manager, tools.NewRegistry(), store, &scriptedProviderFactory{provider: &scriptedProvider{}}, &stubContextBuilder{}) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + err := service.ApproveCurrentPlan(ctx, ApproveCurrentPlanInput{ + SessionID: "session-1", + PlanID: "plan-1", + Revision: 1, + }) + if !errors.Is(err, context.Canceled) { + t.Fatalf("expected context canceled, got %v", err) + } +} + +func TestServiceApproveCurrentPlanTrimsSessionID(t *testing.T) { + t.Parallel() + + manager := newRuntimeConfigManager(t) + store := newMemoryStore() + seed := agentsession.New("approve current plan with trimmed session id") + seed.CurrentPlan = &agentsession.PlanArtifact{ + ID: "plan-trim", + Revision: 1, + Status: agentsession.PlanStatusDraft, + Spec: agentsession.PlanSpec{ + Goal: "trim session id before load", + Steps: []string{"step one"}, + Verify: []string{"verify one"}, + }, + } + if _, err := store.CreateSession(context.Background(), createSessionInputFromSession(seed)); err != nil { + t.Fatalf("CreateSession() error = %v", err) + } + + service := NewWithFactory(manager, tools.NewRegistry(), store, &scriptedProviderFactory{provider: &scriptedProvider{}}, &stubContextBuilder{}) + if err := service.ApproveCurrentPlan(context.Background(), ApproveCurrentPlanInput{ + SessionID: " " + seed.ID + " ", + PlanID: "plan-trim", + Revision: 1, + }); err != nil { + t.Fatalf("ApproveCurrentPlan() error = %v", err) + } + + saved, err := store.LoadSession(context.Background(), seed.ID) + if err != nil { + t.Fatalf("LoadSession() error = %v", err) + } + if saved.CurrentPlan == nil || saved.CurrentPlan.Status != agentsession.PlanStatusApproved { + t.Fatalf("expected approved plan after trimming session id, got %+v", saved.CurrentPlan) + } +} + func TestServiceRunBuildModeIgnoresPlanningJSON(t *testing.T) { t.Parallel()