package projectinstance import ( "context" "errors" "testing" ) func TestResolveMessageScopeForTask_NeverActivatedPreActivation(t *testing.T) { ctx := context.Background() store := newMemStore() _ = SeedProjectMeta(store, 6071, LifecyclePlanning, "", 86, 0) svc := NewService(store, testResolver(), nil, nil) scope, err := svc.ResolveMessageScopeForTask(ctx, 6071, "intake") if err != nil { t.Fatalf("never-activated must resolve: %v", err) } if scope == nil || scope.ScopeKind != ScopePreActivation { t.Fatalf("want pre_activation, got %+v", scope) } if !MessageVisible(*scope, &StageAccess{Restricted: false}, 86, 86) { t.Fatal("session owner must see pre_activation task messages") } if MessageVisible(*scope, &StageAccess{Restricted: false}, 86, 99) { t.Fatal("non-owner must not see pre_activation task messages") } } func TestResolveMessageScopeForTask_LiveStageScope(t *testing.T) { ctx := context.Background() store := newMemStore() _ = SeedProjectMeta(store, 42, LifecyclePlanning, "", 1, 0) svc := NewService(store, testResolver(), nil, nil) mustActivateTestPlan(t, svc, 42, 1) scope, err := svc.ResolveMessageScopeForTask(ctx, 42, "intake") if err != nil { t.Fatalf("live resolve: %v", err) } if scope.ScopeKind != ScopeWorkflowStage || scope.StageKey == "" || scope.WorkflowInstanceID <= 0 { t.Fatalf("want workflow_stage with ids, got %+v", scope) } } func TestResolveMessageScopeForTask_ClosedNotPreActivation(t *testing.T) { ctx := context.Background() for _, status := range []string{InstanceCompleted, InstanceTerminated} { t.Run(status, func(t *testing.T) { store := newMemStore() _ = SeedProjectMeta(store, 71, LifecyclePlanning, "", 1, 0) svc := NewService(store, testResolver(), nil, nil) mustActivateTestPlan(t, svc, 71, 1) live, err := store.GetLiveInstance(ctx, 71) if err != nil || live == nil { t.Fatalf("live before close: %v", err) } live.Status = status ms := store ms.mu.Lock() ms.live[71] = live ms.mu.Unlock() // access gate 先拒 d := svc.CheckDocTaskAccess(ctx, 71, 1, "intake", DocAccessActionRun, true) if d.Allowed || d.Code != AccessCodeInstanceClosed { t.Fatalf("access gate must close, got %+v", d) } // 直接调用层也不得当预激活 _, scopeErr := svc.ResolveMessageScopeForTask(ctx, 71, "intake") if scopeErr == nil || !errors.Is(scopeErr, ErrTaskScopeNotResolved) { t.Fatalf("closed must ErrTaskScopeNotResolved, got %v", scopeErr) } }) } } func TestResolveMessageScopeForTask_StorageErrorPropagates(t *testing.T) { want := errors.New("db down") svc := NewService(&errGoverningStore{err: want}, testResolver(), nil, nil) _, err := svc.ResolveMessageScopeForTask(context.Background(), 1, "intake") if err == nil || !errors.Is(err, want) { t.Fatalf("want storage error %v, got %v", want, err) } } func TestMessageVisible_PreActivationOwnerOnly(t *testing.T) { access := &StageAccess{Restricted: true, AllowedStageKeys: map[string]bool{"V0.1_triage": true}} scope := MessageScope{ScopeKind: ScopePreActivation} if !MessageVisible(scope, access, 9, 9) { t.Fatal("owner should see pre_activation messages") } if MessageVisible(scope, access, 9, 99) { t.Fatal("non-owner must not see pre_activation messages") } } func TestMessageVisible_WorkflowStageRestricted(t *testing.T) { access := &StageAccess{ Restricted: true, AllowedStageKeys: map[string]bool{"V0.1_triage": true}, } scope := MessageScope{ScopeKind: ScopeWorkflowStage, StageKey: "V0.1_triage"} if !MessageVisible(scope, access, 9, 9) { t.Fatal("allowed stage should be visible") } scope.StageKey = "V0.5_feasibility" if MessageVisible(scope, access, 9, 9) { t.Fatal("disallowed stage must be hidden") } } func TestMessageVisible_LegacyUnscopedOwnerWhenRestricted(t *testing.T) { access := &StageAccess{Restricted: true} scope := MessageScope{ScopeKind: ScopeLegacyUnscoped} if !MessageVisible(scope, access, 9, 9) { t.Fatal("owner should see legacy_unscoped when restricted") } if MessageVisible(scope, access, 9, 99) { t.Fatal("non-owner must not see legacy_unscoped when restricted") } } func TestScopeFromMetadata_RoundTrip(t *testing.T) { meta := map[string]any{ "scope_kind": ScopeWorkflowStage, "workflow_instance_id": float64(89), "stage_instance_id": float64(201), "stage_key": "V0.1_triage", } scope := ScopeFromMetadata(meta) if scope.ScopeKind != ScopeWorkflowStage || scope.WorkflowInstanceID != 89 || scope.StageInstanceID != 201 || scope.StageKey != "V0.1_triage" { t.Fatalf("unexpected scope: %+v", scope) } }