mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #36093 This is a follow-up of https://github.com/fleetdm/fleet/pull/40717 # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually Verified that the manual test cases I described in https://github.com/fleetdm/fleet/pull/40717 still pass. Used the following setup: - 1 host on Servers. - 1 host on Servers (canary). - 9999 hosts on Unassigned. <img width="1292" height="448" alt="Screenshot 2026-03-10 at 9 41 33 PM" src="https://github.com/user-attachments/assets/37ba2ad9-aa7b-4d40-b134-56a943e2635c" /> Users: - Team user with these assignments for test cases 1 and 2. <img width="570" height="269" alt="Screenshot 2026-03-10 at 9 42 41 PM" src="https://github.com/user-attachments/assets/f4bcf180-b7cc-4d80-a727-26ce887cbe84" /> - Global observer user for test cases 3 to 5. ### Test case 1 Report on Workstations (canary) with observers_can_run=true <img width="470" height="538" alt="Screenshot 2026-03-10 at 9 42 30 PM" src="https://github.com/user-attachments/assets/11c02ee9-c6eb-463a-9d4b-168a6155feed" /> Tested that I'm only able to target that host using "All hosts", "macOS" and other labels. Also, searching for specific hosts under "Target specific hosts" only retrieves that host. https://github.com/user-attachments/assets/150d986a-b4f2-49ab-86d9-0308685873eb ### Test case 2 Confirmed that I'm not able to target `perf-host-1` from `Servers (canary)` using a manual label with the same report above. For this, I created a manual label and assigned only to `perf-host-1`: <img width="603" height="349" alt="Screenshot 2026-03-10 at 9 50 52 PM" src="https://github.com/user-attachments/assets/98b4a27a-4e46-466e-a377-622d36903feb" /> Note that 0 hosts are targeted and **Run** is disabled: <img width="950" height="814" alt="Screenshot 2026-03-10 at 9 52 26 PM" src="https://github.com/user-attachments/assets/3b42c0e9-3005-40cc-8733-85b9b729ce89" /> ### Test case 3 Accessed same report in `Workstations (canary)` above with a Global Observer user. Confirmed that no hosts can be targeted in any way: <img width="977" height="649" alt="Screenshot 2026-03-11 at 8 29 26 AM" src="https://github.com/user-attachments/assets/ac87ac7e-3097-4228-a724-1d9324dec504" /> <img width="986" height="746" alt="Screenshot 2026-03-11 at 8 30 06 AM" src="https://github.com/user-attachments/assets/5ca592d2-be8c-43c0-8a27-d18fdee35442" /> <img width="1017" height="812" alt="Screenshot 2026-03-11 at 8 30 12 AM" src="https://github.com/user-attachments/assets/fb92940d-3ab2-4136-9e04-825f2c5eb3fe" /> <img width="998" height="809" alt="Screenshot 2026-03-11 at 8 30 17 AM" src="https://github.com/user-attachments/assets/67cc9c0a-e1aa-49df-ad68-1988d6471d32" /> <img width="1444" height="311" alt="Screenshot 2026-03-11 at 8 30 35 AM" src="https://github.com/user-attachments/assets/4b725bf1-0d6d-4458-840e-a96666a34903" /> <img width="1444" height="303" alt="Screenshot 2026-03-11 at 8 30 42 AM" src="https://github.com/user-attachments/assets/54a9cd65-90f5-4454-a713-334e23118295" /> ### Test case 4 As a global observer, accessing a global report with observers_can_run=true, I can target all the hosts across all teams. <img width="951" height="640" alt="Screenshot 2026-03-11 at 8 34 58 AM" src="https://github.com/user-attachments/assets/3c235b3d-acd5-4801-834f-6fe6cd67d3dd" /> <img width="1448" height="527" alt="Screenshot 2026-03-11 at 8 35 06 AM" src="https://github.com/user-attachments/assets/0f5f663d-8597-4320-aceb-ee6f168ec552" /> <img width="1474" height="179" alt="Screenshot 2026-03-11 at 8 35 14 AM" src="https://github.com/user-attachments/assets/042eda04-e7f6-4c21-9503-878a23435fcd" /> ### Test case 5 With the same report from test case 4, but observers_can_run=false, I can't target any hosts. <img width="971" height="804" alt="Screenshot 2026-03-11 at 8 36 49 AM" src="https://github.com/user-attachments/assets/3a3a9fe3-a159-4ef9-8b08-4c987b9c0828" /> <img width="967" height="813" alt="Screenshot 2026-03-11 at 8 37 00 AM" src="https://github.com/user-attachments/assets/aba5588d-dd96-4b88-9911-ebdd743bfa65" />
423 lines
15 KiB
Go
423 lines
15 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mock"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/fleetdm/fleet/v4/server/pubsub"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type nopLiveQuery struct{}
|
|
|
|
func (nopLiveQuery) RunQuery(name, sql string, hostIDs []uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (nopLiveQuery) StopQuery(name string) error {
|
|
return nil
|
|
}
|
|
|
|
func (nopLiveQuery) QueriesForHost(hostID uint) (map[string]string, error) {
|
|
return map[string]string{}, nil
|
|
}
|
|
|
|
func (nopLiveQuery) QueryCompletedByHost(name string, hostID uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (nopLiveQuery) CleanupInactiveQueries(ctx context.Context, inactiveCampaignIDs []uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (q nopLiveQuery) LoadActiveQueryNames() ([]string, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (q nopLiveQuery) GetQueryResultsCounts([]uint) (map[uint]int, error) {
|
|
return make(map[uint]int), nil
|
|
}
|
|
|
|
func (q nopLiveQuery) IncrQueryResultsCounts(map[uint]int) error {
|
|
return nil
|
|
}
|
|
|
|
func (q nopLiveQuery) SetQueryResultsCount(uint, int) error {
|
|
return nil
|
|
}
|
|
|
|
func (q nopLiveQuery) DeleteQueryResultsCount(uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (q nopLiveQuery) LiveQueryStore() fleet.LiveQueryStore {
|
|
return q
|
|
}
|
|
|
|
func TestLiveQueryAuth(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
qr := pubsub.NewInmemQueryResults()
|
|
svc, ctx := newTestService(t, ds, qr, nopLiveQuery{})
|
|
|
|
teamMaintainer := &fleet.User{ID: 42, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}}
|
|
query1ObsCanRun := &fleet.Query{
|
|
ID: 1,
|
|
AuthorID: ptr.Uint(teamMaintainer.ID),
|
|
Name: "q1",
|
|
Query: "SELECT 1",
|
|
ObserverCanRun: true,
|
|
}
|
|
query2ObsCannotRun := &fleet.Query{
|
|
ID: 2,
|
|
AuthorID: ptr.Uint(teamMaintainer.ID),
|
|
Name: "q2",
|
|
Query: "SELECT 2",
|
|
ObserverCanRun: false,
|
|
}
|
|
|
|
var lastCreatedQuery *fleet.Query
|
|
ds.NewQueryFunc = func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) {
|
|
q := *query
|
|
vw, ok := viewer.FromContext(ctx)
|
|
q.ID = 123
|
|
if ok {
|
|
q.AuthorID = ptr.Uint(vw.User.ID)
|
|
}
|
|
lastCreatedQuery = &q
|
|
return &q, nil
|
|
}
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{ServerSettings: fleet.ServerSettings{LiveQueryDisabled: false}}, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignFunc = func(ctx context.Context, camp *fleet.DistributedQueryCampaign) (*fleet.DistributedQueryCampaign, error) {
|
|
return camp, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignTargetFunc = func(ctx context.Context, target *fleet.DistributedQueryCampaignTarget) (*fleet.DistributedQueryCampaignTarget, error) {
|
|
return target, nil
|
|
}
|
|
ds.HostIDsInTargetsFunc = func(ctx context.Context, filters fleet.TeamFilter, targets fleet.HostTargets) ([]uint, error) {
|
|
return []uint{1}, nil
|
|
}
|
|
ds.HostIDsByIdentifierFunc = func(ctx context.Context, filter fleet.TeamFilter, identifiers []string) ([]uint, error) {
|
|
return nil, nil
|
|
}
|
|
ds.LabelIDsByNameFunc = func(ctx context.Context, names []string, filter fleet.TeamFilter) (map[string]uint, error) {
|
|
return nil, nil
|
|
}
|
|
ds.CountHostsInTargetsFunc = func(ctx context.Context, filters fleet.TeamFilter, targets fleet.HostTargets, now time.Time) (fleet.TargetMetrics, error) {
|
|
return fleet.TargetMetrics{}, nil
|
|
}
|
|
ds.QueryFunc = func(ctx context.Context, id uint) (*fleet.Query, error) {
|
|
if id == 1 {
|
|
return query1ObsCanRun, nil
|
|
}
|
|
if id == 2 {
|
|
return query2ObsCannotRun, nil
|
|
}
|
|
if lastCreatedQuery != nil {
|
|
q := lastCreatedQuery
|
|
lastCreatedQuery = nil
|
|
return q, nil
|
|
}
|
|
return &fleet.Query{ID: 8888, AuthorID: ptr.Uint(6666)}, nil
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
user *fleet.User
|
|
teamID *uint // to use as host target
|
|
shouldFailRunNew bool
|
|
shouldFailRunObsCan bool
|
|
shouldFailRunObsCannot bool
|
|
}{
|
|
{
|
|
name: "global admin",
|
|
user: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "global maintainer",
|
|
user: &fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "global observer",
|
|
user: &fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
|
|
teamID: nil,
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team maintainer",
|
|
user: teamMaintainer,
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "team admin, no team target",
|
|
user: &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "team admin, target not set to own team",
|
|
user: &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
|
teamID: ptr.Uint(2),
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: true, // fails observer can run, as they are not part of that team, even as observer
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team admin, target set to own team",
|
|
user: &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
|
teamID: ptr.Uint(1),
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "team observer, no team target",
|
|
user: &fleet.User{ID: 48, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
|
teamID: nil,
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team observer, target not set to own team",
|
|
user: &fleet.User{ID: 48, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
|
teamID: ptr.Uint(2),
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: true,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team observer, target set to own team",
|
|
user: &fleet.User{ID: 48, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
|
teamID: ptr.Uint(1),
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
}
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
|
|
|
var tms []uint
|
|
// Testing RunNew is tricky, because RunNew authorization is done, then
|
|
// the query is created, and then the Run authorization is applied to
|
|
// that now-existing query, so we have to make sure that the Run does not
|
|
// cause a Forbidden error. To this end, the ds.NewQuery mock always sets
|
|
// the AuthorID to the context user, and if the user is member of a team,
|
|
// always set that team as a host target. This will prevent the Run
|
|
// action from failing, if RunNew did succeed.
|
|
if len(tt.user.Teams) > 0 {
|
|
tms = []uint{tt.user.Teams[0].ID}
|
|
}
|
|
_, err := svc.NewDistributedQueryCampaign(ctx, query1ObsCanRun.Query, nil, fleet.HostTargets{TeamIDs: tms})
|
|
checkAuthErr(t, tt.shouldFailRunNew, err)
|
|
|
|
if tt.teamID != nil {
|
|
tms = []uint{*tt.teamID}
|
|
}
|
|
_, err = svc.NewDistributedQueryCampaign(ctx, query1ObsCanRun.Query, ptr.Uint(query1ObsCanRun.ID), fleet.HostTargets{TeamIDs: tms})
|
|
checkAuthErr(t, tt.shouldFailRunObsCan, err)
|
|
|
|
_, err = svc.NewDistributedQueryCampaign(ctx, query2ObsCannotRun.Query, ptr.Uint(query2ObsCannotRun.ID), fleet.HostTargets{TeamIDs: tms})
|
|
checkAuthErr(t, tt.shouldFailRunObsCannot, err)
|
|
|
|
// tests with a team target cannot run the "ByNames" calls, as there's no way
|
|
// to pass a team target with this call.
|
|
if tt.teamID == nil {
|
|
_, err = svc.NewDistributedQueryCampaignByIdentifiers(ctx, query1ObsCanRun.Query, nil, nil, nil)
|
|
checkAuthErr(t, tt.shouldFailRunNew, err)
|
|
|
|
_, err = svc.NewDistributedQueryCampaignByIdentifiers(ctx, query1ObsCanRun.Query, ptr.Uint(query1ObsCanRun.ID), nil, nil)
|
|
checkAuthErr(t, tt.shouldFailRunObsCan, err)
|
|
|
|
_, err = svc.NewDistributedQueryCampaignByIdentifiers(ctx, query2ObsCannotRun.Query, ptr.Uint(query2ObsCannotRun.ID), nil, nil)
|
|
checkAuthErr(t, tt.shouldFailRunObsCannot, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLiveQueryLabelValidation(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
qr := pubsub.NewInmemQueryResults()
|
|
svc, ctx := newTestService(t, ds, qr, nopLiveQuery{})
|
|
|
|
user := &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}
|
|
query := &fleet.Query{
|
|
ID: 1,
|
|
Name: "q1",
|
|
Query: "SELECT 1",
|
|
ObserverCanRun: true,
|
|
}
|
|
ds.NewQueryFunc = func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) {
|
|
query.ID = 123
|
|
return query, nil
|
|
}
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{ServerSettings: fleet.ServerSettings{LiveQueryDisabled: false}}, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignFunc = func(ctx context.Context, camp *fleet.DistributedQueryCampaign) (*fleet.DistributedQueryCampaign, error) {
|
|
return camp, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignTargetFunc = func(ctx context.Context, target *fleet.DistributedQueryCampaignTarget) (*fleet.DistributedQueryCampaignTarget, error) {
|
|
return target, nil
|
|
}
|
|
ds.HostIDsInTargetsFunc = func(ctx context.Context, filters fleet.TeamFilter, targets fleet.HostTargets) ([]uint, error) {
|
|
return []uint{1}, nil
|
|
}
|
|
ds.HostIDsByIdentifierFunc = func(ctx context.Context, filter fleet.TeamFilter, identifiers []string) ([]uint, error) {
|
|
return nil, nil
|
|
}
|
|
ds.CountHostsInTargetsFunc = func(ctx context.Context, filters fleet.TeamFilter, targets fleet.HostTargets, now time.Time) (fleet.TargetMetrics, error) {
|
|
return fleet.TargetMetrics{}, nil
|
|
}
|
|
ds.QueryFunc = func(ctx context.Context, id uint) (*fleet.Query, error) {
|
|
return query, nil
|
|
}
|
|
|
|
ds.LabelIDsByNameFunc = func(ctx context.Context, names []string, filter fleet.TeamFilter) (map[string]uint, error) {
|
|
return map[string]uint{"label1": uint(1)}, nil
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
labels []string
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "no labels",
|
|
labels: []string{},
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "invalid label",
|
|
labels: []string{"iamnotalabel"},
|
|
expectedError: "Invalid label name(s): iamnotalabel.",
|
|
},
|
|
{
|
|
name: "valid label",
|
|
labels: []string{"label1"},
|
|
expectedError: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := viewer.NewContext(ctx, viewer.Viewer{User: user})
|
|
_, err := svc.NewDistributedQueryCampaignByIdentifiers(ctx, query.Query, nil, nil, tt.labels)
|
|
|
|
if tt.expectedError == "" {
|
|
require.Nil(t, err)
|
|
} else {
|
|
require.NotNil(t, err)
|
|
require.Contains(t, err.Error(), tt.expectedError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLiveQueryLabelBypassPrevented verifies that when a user is observer on team 1 and
|
|
// maintainer on team 2, running a team-2 query via a label target:
|
|
// - observer_can_run=false: HostIDsInTargets sees IncludeObserver=false, so team-1 observer
|
|
// hosts are excluded (label bypass prevented).
|
|
// - observer_can_run=true: HostIDsInTargets sees IncludeObserver=true but ObserverTeamID=2,
|
|
// so team-1 observer hosts are still excluded — observer_can_run is scoped to the query's
|
|
// own team (team 2), not to all teams the user observes.
|
|
func TestLiveQueryLabelBypassPrevented(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
qr := pubsub.NewInmemQueryResults()
|
|
svc, ctx := newTestService(t, ds, qr, nopLiveQuery{})
|
|
|
|
user := &fleet.User{
|
|
ID: 99,
|
|
Teams: []fleet.UserTeam{
|
|
{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver},
|
|
{Team: fleet.Team{ID: 2}, Role: fleet.RoleMaintainer},
|
|
},
|
|
}
|
|
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{ServerSettings: fleet.ServerSettings{LiveQueryDisabled: false}}, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignFunc = func(ctx context.Context, camp *fleet.DistributedQueryCampaign) (*fleet.DistributedQueryCampaign, error) {
|
|
return camp, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignTargetFunc = func(ctx context.Context, target *fleet.DistributedQueryCampaignTarget) (*fleet.DistributedQueryCampaignTarget, error) {
|
|
return target, nil
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
observerCanRun bool
|
|
expectIncludeObs bool
|
|
expectObserverTeamID *uint
|
|
}{
|
|
{
|
|
name: "observer_can_run=false: label expansion excludes observer-only team hosts",
|
|
observerCanRun: false,
|
|
expectIncludeObs: false,
|
|
expectObserverTeamID: ptr.Uint(2), // always set to query's team; harmless no-op when IncludeObserver=false
|
|
},
|
|
{
|
|
name: "observer_can_run=true: label expansion scoped to query's team only",
|
|
observerCanRun: true,
|
|
expectIncludeObs: true,
|
|
expectObserverTeamID: ptr.Uint(2), // query's team — team-1 observer hosts remain excluded
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
query := &fleet.Query{
|
|
ID: 10,
|
|
AuthorID: ptr.Uint(user.ID),
|
|
Name: "q",
|
|
Query: "SELECT 1",
|
|
ObserverCanRun: tt.observerCanRun,
|
|
TeamID: ptr.Uint(2),
|
|
}
|
|
ds.QueryFunc = func(_ context.Context, _ uint) (*fleet.Query, error) {
|
|
return query, nil
|
|
}
|
|
|
|
var capturedFilter fleet.TeamFilter
|
|
ds.HostIDsInTargetsFunc = func(_ context.Context, filter fleet.TeamFilter, _ fleet.HostTargets) ([]uint, error) {
|
|
capturedFilter = filter
|
|
return []uint{20}, nil
|
|
}
|
|
ds.CountHostsInTargetsFunc = func(_ context.Context, _ fleet.TeamFilter, _ fleet.HostTargets, _ time.Time) (fleet.TargetMetrics, error) {
|
|
return fleet.TargetMetrics{TotalHosts: 1}, nil
|
|
}
|
|
|
|
userCtx := viewer.NewContext(ctx, viewer.Viewer{User: user})
|
|
_, err := svc.NewDistributedQueryCampaign(userCtx, query.Query, ptr.Uint(query.ID), fleet.HostTargets{LabelIDs: []uint{5}})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectIncludeObs, capturedFilter.IncludeObserver, "IncludeObserver mismatch for observer_can_run=%v", tt.observerCanRun)
|
|
assert.Equal(t, tt.expectObserverTeamID, capturedFilter.ObserverTeamID, "ObserverTeamID mismatch for observer_can_run=%v", tt.observerCanRun)
|
|
assert.Equal(t, user, capturedFilter.User)
|
|
})
|
|
}
|
|
}
|