package projectinstance import ( "context" "errors" "testing" "chat-agent-system/internal/workflowdef" ) type countingMaterializer struct { calls int projectID int64 } func (c *countingMaterializer) MaterializeTemplateDocs(_ context.Context, projectID int64, _ WorkflowPlan) error { c.calls++ c.projectID = projectID return nil } func TestUpdateLivePlanKeepsWorkflowAndMaterializes(t *testing.T) { ctx := context.Background() store := newMemStore() _ = SeedProjectMeta(store, 42, LifecyclePlanning, "", 1, 0) mat := &countingMaterializer{} svc := NewService(store, testResolver(), nil, nil) svc.SetTemplateDocMaterializer(mat) if _, err := svc.SaveWorkflowPlan(ctx, 42, 1, true, SavePlanInput{ Plan: validPlan(), ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1, }); err != nil { t.Fatal(err) } preview, err := svc.ActivationPreview(ctx, 42, 1, true) if err != nil { t.Fatal(err) } if _, err := svc.Activate(ctx, 42, 1, true, ActivateInput{ ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion, ExpectedPlanRevision: preview.ExpectedPlanRevision, }); err != nil { t.Fatal(err) } if mat.calls != 1 || mat.projectID != 42 { t.Fatalf("activate materialize calls=%d project=%d", mat.calls, mat.projectID) } settings, err := svc.GetSettings(ctx, 42, 1, true) if err != nil { t.Fatal(err) } if settings.PlanEditMode != PlanEditLive { t.Fatalf("plan_edit_mode=%s", settings.PlanEditMode) } plan := *settings.Plan plan.Stages[0].DocTasks[0].Duties.ApproverUserID = 4 updated, err := svc.UpdateLivePlan(ctx, 42, 1, true, SavePlanInput{ Plan: plan, ExpectedPlanRevision: settings.WorkflowPlanRevision, LiveEdit: true, }) if err != nil { t.Fatal(err) } if updated.Plan.Stages[0].DocTasks[0].Duties.ApproverUserID != 4 { t.Fatalf("duty not updated: %+v", updated.Plan.Stages[0].DocTasks[0].Duties) } if updated.Plan.WorkflowDefinitionKey != plan.WorkflowDefinitionKey { t.Fatal("workflow key changed") } if mat.calls != 2 { t.Fatalf("live update materialize calls=%d", mat.calls) } switched := plan switched.WorkflowDefinitionKey = "other" _, err = svc.UpdateLivePlan(ctx, 42, 1, true, SavePlanInput{ Plan: switched, ExpectedPlanRevision: updated.WorkflowPlanRevision, }) if !errors.Is(err, ErrWorkflowSwitchFrozen) { t.Fatalf("expected switch frozen, got %v", err) } } func TestUpdateLivePlanRejectsReadonlyProject(t *testing.T) { svc := NewService(newMemStore(), testResolver(), nil, nil) _, err := svc.UpdateLivePlan(context.Background(), readonlyLivePlanProjectID, 1, true, SavePlanInput{ Plan: validPlan(), ExpectedPlanRevision: 1, LiveEdit: true, }) if !errors.Is(err, ErrPlanFrozen) { t.Fatalf("expected frozen, got %v", err) } } func TestPlanEditModeFrozenForReadonlyLiveProject(t *testing.T) { ctx := context.Background() store := newMemStore() _ = SeedProjectMeta(store, readonlyLivePlanProjectID, LifecycleActive, workflowdef.OfficialKeyMain, 2, 1) store.live[readonlyLivePlanProjectID] = &WorkflowInstance{ ID: 89, ProjectID: readonlyLivePlanProjectID, WorkflowVersionID: 1, Status: InstanceActive, ConfigRevision: 3, ConfigSnapshot: validPlan(), } svc := NewService(store, testResolver(), nil, nil) settings, err := svc.GetSettings(ctx, readonlyLivePlanProjectID, 1, true) if err != nil { t.Fatal(err) } if settings.PlanEditMode != PlanEditFrozen { t.Fatalf("plan_edit_mode=%s", settings.PlanEditMode) } }