package projectinstance import ( "context" "testing" ) func TestTerminateInstance_UpdatesProjectLifecycle(t *testing.T) { ctx := context.Background() store := NewMemoryStore() _ = SeedProjectMeta(store, 300, LifecyclePlanning, "", 1, 0) svc := NewService(store, TestResolver(), nil, &StubDocChecker{States: map[string]string{"intake": "approved"}}) _, _ = svc.SaveWorkflowPlan(ctx, 300, 1, true, SavePlanInput{ Plan: ValidTestPlan(), ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1, }) preview, _ := svc.ActivationPreview(ctx, 300, 1, true) activated, _ := svc.Activate(ctx, 300, 1, true, ActivateInput{ ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion, ExpectedPlanRevision: preview.ExpectedPlanRevision, }) pv, err := svc.GetTerminationPreview(ctx, activated.Instance.ID, 300, 1, true) if err != nil { t.Fatalf("GetTerminationPreview: %v", err) } if !pv.CanTerminate { t.Fatal("expected can terminate") } res, err := svc.TerminateInstance(ctx, activated.Instance.ID, 300, 1, true, TerminateInput{ Reason: "测试终止", Confirm: true, ExpectedConfigRevision: activated.Instance.ConfigRevision, }) if err != nil { t.Fatalf("TerminateInstance: %v", err) } if res.Instance.Status != "terminated" { t.Fatalf("expected terminated, got %q", res.Instance.Status) } lifecycle, activeKey, _, _, err := store.GetProjectMeta(ctx, 300) if err != nil { t.Fatalf("GetProjectMeta: %v", err) } if lifecycle != LifecyclePlanning || activeKey != "" { t.Fatalf("expected planning with empty key, got lifecycle=%q key=%q", lifecycle, activeKey) } } func TestTerminateInstance_UnmanagedDocsFailClosed(t *testing.T) { ctx := context.Background() store := NewMemoryStore() _ = SeedProjectMeta(store, 301, LifecyclePlanning, "", 1, 0) svc := NewService(store, optionalDutyResolver(), nil, &StubDocChecker{States: map[string]string{"intake": "approved"}}) if _, err := svc.SaveWorkflowPlan(ctx, 301, 1, true, SavePlanInput{ Plan: optionalDutyPlan( DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3}, DocDuty{}, ), ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1, }); err != nil { t.Fatalf("save: %v", err) } preview, err := svc.ActivationPreview(ctx, 301, 1, true) if err != nil { t.Fatalf("preview: %v", err) } activated, err := svc.Activate(ctx, 301, 1, true, ActivateInput{ ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion, ExpectedPlanRevision: preview.ExpectedPlanRevision, }) if err != nil { t.Fatalf("activate: %v", err) } if _, err := svc.TerminateInstance(ctx, activated.Instance.ID, 301, 1, true, TerminateInput{ Reason: "关闭含 none 文档的实例", Confirm: true, ExpectedConfigRevision: activated.Instance.ConfigRevision, }); err != nil { t.Fatalf("terminate: %v", err) } d := svc.CheckDocTaskAccess(ctx, 301, 1, "research", DocAccessActionView, true) if d.Allowed || d.Code != AccessCodeInstanceClosed { t.Fatalf("terminated none docs must fail closed, got %+v", d) } }