fleet/server/service/queries_test.go
Nico e5849ee720
Show Manage Automations disabled button with tooltip on Queries page (#39302)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #39303 (child of #25080).

- Added `inherited_query_count` to `ListQueriesResponse` (thought of
adding a brand new endpoint just for counting, but felt like extending
the current one was good enough). In the parent task, [it was
suggested](https://github.com/fleetdm/fleet/issues/25080#issuecomment-3326071574)
to `"Depend on team list entity endpoint's count field / team entity
count endpoint for whether or not to disable the manage automations
button"`, which Rachael approved, so I went for this approach.
- The `ManageQueryAutomationsModal` now fetches its own data with
`merge_inherited = false` (meaning it only fetches non-inherited queries
only). Previously, queries were passed down as props to it, which would
not show the queries available to automate if the first page of queries
were all inherited and the second page contained queries for that team
(the user would have to navigate to the second page for the button to be
enabled).


^ The fact that the modal fetches its own data is similar behavior to
what is currently done in `Policies`. For queries, I noticed that we
would need to add pagination within the `Manage Automations` modal, but
that can be a follow-up.

<img width="2480" height="1309" alt="Screenshot 2026-02-04 at 11 48
42 AM"
src="https://github.com/user-attachments/assets/ebac79a5-a793-4708-9313-d9a697dfd7de"
/>


# 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] QA'd all new/changed functionality manually



https://github.com/user-attachments/assets/119f03b9-dde1-4bb9-9fee-6204b1a58879
2026-02-09 15:16:28 -03:00

1025 lines
24 KiB
Go

package service
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestQueryPayloadValidationCreate(t *testing.T) {
ds := new(mock.Store)
ds.NewQueryFunc = func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) {
return query, nil
}
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
act, ok := activity.(fleet.ActivityTypeCreatedSavedQuery)
assert.True(t, ok)
assert.NotEmpty(t, act.Name)
return nil
}
svc, ctx := newTestService(t, ds, nil, nil)
testCases := []struct {
name string
queryPayload fleet.QueryPayload
shouldErr bool
}{
{
"All valid",
fleet.QueryPayload{
Name: ptr.String("test query"),
Query: ptr.String("select 1"),
Logging: ptr.String("snapshot"),
Platform: ptr.String(""),
},
false,
},
{
"Invalid - empty string name",
fleet.QueryPayload{
Name: ptr.String(""),
Query: ptr.String("select 1"),
Logging: ptr.String("snapshot"),
Platform: ptr.String(""),
},
true,
},
{
"Empty SQL",
fleet.QueryPayload{
Name: ptr.String("bad sql"),
Query: ptr.String(""),
Logging: ptr.String("snapshot"),
Platform: ptr.String(""),
},
true,
},
{
"Invalid logging",
fleet.QueryPayload{
Name: ptr.String("bad logging"),
Query: ptr.String("select 1"),
Logging: ptr.String("hopscotch"),
Platform: ptr.String(""),
},
true,
},
{
"Unsupported platform",
fleet.QueryPayload{
Name: ptr.String("invalid platform"),
Query: ptr.String("select 1"),
Logging: ptr.String("differential"),
Platform: ptr.String("charles"),
},
true,
},
{
"Missing comma",
fleet.QueryPayload{
Name: ptr.String("invalid platform"),
Query: ptr.String("select 1"),
Logging: ptr.String("differential"),
Platform: ptr.String("darwin windows"),
},
true,
},
{
"Unsupported platform 'sphinx' ",
fleet.QueryPayload{
Name: ptr.String("invalid platform"),
Query: ptr.String("select 1"),
Logging: ptr.String("differential"),
Platform: ptr.String("darwin,windows,sphinx"),
},
true,
},
}
testAdmin := fleet.User{
ID: 1,
Teams: []fleet.UserTeam{},
GlobalRole: ptr.String(fleet.RoleAdmin),
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
viewerCtx := viewer.NewContext(ctx, viewer.Viewer{User: &testAdmin})
query, err := svc.NewQuery(viewerCtx, tt.queryPayload)
if tt.shouldErr {
assert.Error(t, err)
assert.Nil(t, query)
} else {
assert.NoError(t, err)
assert.NotEmpty(t, query)
}
})
}
}
// similar for modify
func TestQueryPayloadValidationModify(t *testing.T) {
ds := new(mock.Store)
ds.QueryFunc = func(ctx context.Context, id uint) (*fleet.Query, error) {
return &fleet.Query{
ID: id,
Name: "mock saved query",
Description: "some desc",
Query: "select 1;",
Platform: "",
Saved: true,
ObserverCanRun: false,
}, nil
}
ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query, shouldDiscardResults bool, shouldDeleteStats bool) error {
assert.NotEmpty(t, query)
return nil
}
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
act, ok := activity.(fleet.ActivityTypeEditedSavedQuery)
assert.True(t, ok)
assert.NotEmpty(t, act.Name)
return nil
}
svc, ctx := newTestService(t, ds, nil, nil)
testCases := []struct {
name string
queryPayload fleet.QueryPayload
shouldErr bool
}{
{
"All valid",
fleet.QueryPayload{
Name: ptr.String("updated test query"),
Query: ptr.String("select 1"),
Logging: ptr.String("snapshot"),
Platform: ptr.String(""),
},
false,
},
{
"Invalid - empty string name",
fleet.QueryPayload{
Name: ptr.String(""),
Query: ptr.String("select 1"),
Logging: ptr.String("snapshot"),
Platform: ptr.String(""),
},
true,
},
{
"Empty SQL",
fleet.QueryPayload{
Name: ptr.String("bad sql"),
Query: ptr.String(""),
Logging: ptr.String("snapshot"),
Platform: ptr.String(""),
},
true,
},
{
"Invalid logging",
fleet.QueryPayload{
Name: ptr.String("bad logging"),
Query: ptr.String("select 1"),
Logging: ptr.String("hopscotch"),
Platform: ptr.String(""),
},
true,
},
{
"Unsupported platform",
fleet.QueryPayload{
Name: ptr.String("invalid platform"),
Query: ptr.String("select 1"),
Logging: ptr.String("differential"),
Platform: ptr.String("charles"),
},
true,
},
{
"Missing comma delimeter in platform string",
fleet.QueryPayload{
Name: ptr.String("invalid platform"),
Query: ptr.String("select 1"),
Logging: ptr.String("differential"),
Platform: ptr.String("darwin windows"),
},
true,
},
{
"Unsupported platform 2",
fleet.QueryPayload{
Name: ptr.String("invalid platform"),
Query: ptr.String("select 1"),
Logging: ptr.String("differential"),
Platform: ptr.String("darwin,windows,sphinx"),
},
true,
},
}
testAdmin := fleet.User{
ID: 1,
Teams: []fleet.UserTeam{},
GlobalRole: ptr.String(fleet.RoleAdmin),
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
viewerCtx := viewer.NewContext(ctx, viewer.Viewer{User: &testAdmin})
_, err := svc.ModifyQuery(viewerCtx, 1, tt.queryPayload)
if tt.shouldErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestQueryAuth(t *testing.T) {
ds := new(mock.Store)
svc, ctx := newTestService(t, ds, nil, nil)
team := fleet.Team{
ID: 1,
Name: "Foobar",
}
team2 := fleet.Team{
ID: 2,
Name: "Barfoo",
}
teamAdmin := &fleet.User{
ID: 42,
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team.ID},
Role: fleet.RoleAdmin,
},
},
}
teamMaintainer := &fleet.User{
ID: 43,
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team.ID},
Role: fleet.RoleMaintainer,
},
},
}
teamObserver := &fleet.User{
ID: 44,
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team.ID},
Role: fleet.RoleObserver,
},
},
}
teamObserverPlus := &fleet.User{
ID: 45,
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team.ID},
Role: fleet.RoleObserverPlus,
},
},
}
teamGitOps := &fleet.User{
ID: 46,
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team.ID},
Role: fleet.RoleGitOps,
},
},
}
globalQuery := fleet.Query{
ID: 99,
Name: "global query",
TeamID: nil,
}
teamQuery := fleet.Query{
ID: 88,
Name: "team query",
TeamID: ptr.Uint(team.ID),
}
team2Query := fleet.Query{
ID: 77,
Name: "team2 query",
TeamID: ptr.Uint(team2.ID),
}
queriesMap := map[uint]fleet.Query{
globalQuery.ID: globalQuery,
teamQuery.ID: teamQuery,
team2Query.ID: team2Query,
}
ds.TeamLiteFunc = func(ctx context.Context, tid uint) (*fleet.TeamLite, error) {
if tid == team.ID {
return team.ToTeamLite(), nil
} else if tid == team2.ID {
return team2.ToTeamLite(), nil
}
return nil, newNotFoundError()
}
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
if name == team.Name {
return &team, nil
} else if name == team2.Name {
return &team2, nil
}
return nil, newNotFoundError()
}
ds.NewQueryFunc = func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) {
return query, nil
}
ds.QueryByNameFunc = func(ctx context.Context, teamID *uint, name string) (*fleet.Query, error) {
if teamID == nil && name == "global query" { //nolint:gocritic // ignore ifElseChain
return &globalQuery, nil
} else if teamID != nil && *teamID == team.ID && name == "team query" {
return &teamQuery, nil
} else if teamID != nil && *teamID == team2.ID && name == "team2 query" {
return &team2Query, nil
}
return nil, newNotFoundError()
}
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.QueryFunc = func(ctx context.Context, id uint) (*fleet.Query, error) {
if id == 99 { //nolint:gocritic // ignore ifElseChain
return &globalQuery, nil
} else if id == 88 {
return &teamQuery, nil
} else if id == 77 {
return &team2Query, nil
}
return nil, newNotFoundError()
}
ds.ResultCountForQueryFunc = func(ctx context.Context, queryID uint) (int, error) {
return 0, nil
}
ds.SaveQueryFunc = func(ctx context.Context, query *fleet.Query, shouldDiscardResults bool, shouldDeleteStats bool) error {
return nil
}
ds.DeleteQueryFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
}
ds.DeleteQueriesFunc = func(ctx context.Context, ids []uint) (uint, error) {
return 0, nil
}
ds.ListQueriesFunc = func(ctx context.Context, opts fleet.ListQueryOptions) ([]*fleet.Query, int, int, *fleet.PaginationMetadata, error) {
return nil, 0, 0, nil, nil
}
ds.ApplyQueriesFunc = func(ctx context.Context, authID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]struct{}) error {
return nil
}
testCases := []struct {
name string
user *fleet.User
qid uint
shouldFailWrite bool
shouldFailRead bool
shouldFailNew bool
}{
{
"global admin and global query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
globalQuery.ID,
false,
false,
false,
},
{
"global admin and team query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
teamQuery.ID,
false,
false,
false,
},
{
"global maintainer and global query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
globalQuery.ID,
false,
false,
false,
},
{
"global maintainer and team query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
teamQuery.ID,
false,
false,
false,
},
{
"global observer and global query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
globalQuery.ID,
true,
false,
true,
},
{
"global observer and team query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
teamQuery.ID,
true,
false,
true,
},
{
"global observer+ and global query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserverPlus)},
globalQuery.ID,
true,
false,
true,
},
{
"global observer+ and team query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserverPlus)},
teamQuery.ID,
true,
false,
true,
},
{
"global gitops and global query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
globalQuery.ID,
false,
false,
false,
},
{
"global gitops and team query",
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
teamQuery.ID,
false,
false,
false,
},
{
"team admin and global query",
teamAdmin,
globalQuery.ID,
true,
false,
true,
},
{
"team admin and team query",
teamAdmin,
teamQuery.ID,
false,
false,
false,
},
{
"team admin and team2 query",
teamAdmin,
team2Query.ID,
true,
true,
true,
},
{
"team maintainer and global query",
teamMaintainer,
globalQuery.ID,
true,
false,
true,
},
{
"team maintainer and team query",
teamMaintainer,
teamQuery.ID,
false,
false,
false,
},
{
"team maintainer and team2 query",
teamMaintainer,
team2Query.ID,
true,
true,
true,
},
{
"team observer and global query",
teamObserver,
globalQuery.ID,
true,
false,
true,
},
{
"team observer and team query",
teamObserver,
teamQuery.ID,
true,
false,
true,
},
{
"team observer and team2 query",
teamObserver,
team2Query.ID,
true,
true,
true,
},
{
"team observer+ and global query",
teamObserverPlus,
globalQuery.ID,
true,
false,
true,
},
{
"team observer+ and team query",
teamObserverPlus,
teamQuery.ID,
true,
false,
true,
},
{
"team observer+ and team2 query",
teamObserverPlus,
team2Query.ID,
true,
true,
true,
},
{
"team gitops and global query",
teamGitOps,
globalQuery.ID,
true,
true,
true,
},
{
"team gitops and team query",
teamGitOps,
teamQuery.ID,
false,
false,
false,
},
{
"team gitops and team2 query",
teamGitOps,
team2Query.ID,
true,
true,
true,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
query := queriesMap[tt.qid]
_, err := svc.NewQuery(ctx, fleet.QueryPayload{
Name: ptr.String("name"),
Query: ptr.String("select 1"),
TeamID: query.TeamID,
})
checkAuthErr(t, tt.shouldFailNew, err)
_, err = svc.ModifyQuery(ctx, tt.qid, fleet.QueryPayload{})
checkAuthErr(t, tt.shouldFailWrite, err)
err = svc.DeleteQuery(ctx, query.TeamID, query.Name)
checkAuthErr(t, tt.shouldFailWrite, err)
err = svc.DeleteQueryByID(ctx, tt.qid)
checkAuthErr(t, tt.shouldFailWrite, err)
_, err = svc.DeleteQueries(ctx, []uint{tt.qid})
checkAuthErr(t, tt.shouldFailWrite, err)
_, err = svc.GetQuery(ctx, tt.qid)
checkAuthErr(t, tt.shouldFailRead, err)
_, err = svc.QueryReportIsClipped(ctx, tt.qid, fleet.DefaultMaxQueryReportRows)
checkAuthErr(t, tt.shouldFailRead, err)
_, _, _, _, err = svc.ListQueries(ctx, fleet.ListOptions{}, query.TeamID, nil, false, nil)
checkAuthErr(t, tt.shouldFailRead, err)
teamName := ""
if query.TeamID != nil && *query.TeamID == team.ID {
teamName = team.Name
} else if query.TeamID != nil && *query.TeamID == team2.ID {
teamName = team2.Name
}
err = svc.ApplyQuerySpecs(ctx, []*fleet.QuerySpec{{
Name: query.Name,
Query: "SELECT 1",
TeamName: teamName,
}})
checkAuthErr(t, tt.shouldFailWrite, err)
_, err = svc.GetQuerySpecs(ctx, query.TeamID)
checkAuthErr(t, tt.shouldFailRead, err)
_, err = svc.GetQuerySpec(ctx, query.TeamID, query.Name)
checkAuthErr(t, tt.shouldFailRead, err)
})
}
}
func TestQueryReportIsClipped(t *testing.T) {
ds := new(mock.Store)
svc, ctx := newTestService(t, ds, nil, nil)
viewerCtx := viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
ID: 1,
GlobalRole: ptr.String(fleet.RoleAdmin),
}})
ds.QueryFunc = func(ctx context.Context, queryID uint) (*fleet.Query, error) {
return &fleet.Query{}, nil
}
ds.ResultCountForQueryFunc = func(ctx context.Context, queryID uint) (int, error) {
return 0, nil
}
isClipped, err := svc.QueryReportIsClipped(viewerCtx, 1, fleet.DefaultMaxQueryReportRows)
require.NoError(t, err)
require.False(t, isClipped)
ds.ResultCountForQueryFunc = func(ctx context.Context, queryID uint) (int, error) {
return fleet.DefaultMaxQueryReportRows, nil
}
isClipped, err = svc.QueryReportIsClipped(viewerCtx, 1, fleet.DefaultMaxQueryReportRows)
require.NoError(t, err)
require.True(t, isClipped)
}
func TestQueryReportReturnsNilIfDiscardDataIsTrue(t *testing.T) {
ds := new(mock.Store)
svc, ctx := newTestService(t, ds, nil, nil)
viewerCtx := viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{
ID: 1,
GlobalRole: ptr.String(fleet.RoleAdmin),
}})
ds.QueryFunc = func(ctx context.Context, queryID uint) (*fleet.Query, error) {
return &fleet.Query{
DiscardData: true,
}, nil
}
ds.QueryResultRowsFunc = func(ctx context.Context, queryID uint, opts fleet.TeamFilter) ([]*fleet.ScheduledQueryResultRow, error) {
return []*fleet.ScheduledQueryResultRow{
{
QueryID: 1,
HostID: 1,
Data: ptr.RawMessage(json.RawMessage(`{"foo": "bar"}`)),
LastFetched: time.Now(),
},
}, nil
}
results, reportClipped, err := svc.GetQueryReportResults(viewerCtx, 1, nil)
require.NoError(t, err)
require.Nil(t, results)
require.False(t, reportClipped)
}
func TestInheritedQueryReportTeamPermissions(t *testing.T) {
ds := mysql.CreateMySQLDS(t)
defer ds.Close()
svc, ctx := newTestService(t, ds, nil, nil)
team1, err := ds.NewTeam(ctx, &fleet.Team{
ID: 42,
Name: "team1",
Description: "desc team1",
})
require.NoError(t, err)
team2, err := ds.NewTeam(ctx, &fleet.Team{
Name: "team2",
Description: "desc team2",
})
require.NoError(t, err)
hostTeam2, err := ds.NewHost(ctx, &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: ptr.String("1"),
UUID: "1",
ComputerName: "Foo Local",
Hostname: "foo.local",
OsqueryHostID: ptr.String("1"),
PrimaryIP: "192.168.1.1",
PrimaryMac: "30-65-EC-6F-C4-61",
Platform: "darwin",
})
require.NoError(t, err)
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team2.ID, []uint{hostTeam2.ID}))
require.NoError(t, err)
hostTeam1, err := ds.NewHost(ctx, &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: ptr.String("42"),
UUID: "42",
ComputerName: "bar Local",
Hostname: "bar.local",
OsqueryHostID: ptr.String("42"),
PrimaryIP: "192.168.1.2",
PrimaryMac: "30-65-EC-6F-C4-62",
Platform: "darwin",
})
require.NoError(t, err)
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team1.ID, []uint{hostTeam1.ID}))
require.NoError(t, err)
globalQuery, err := ds.NewQuery(ctx, &fleet.Query{
ID: 77,
Name: "team2 query",
TeamID: nil,
Query: "select * from usb_devices;",
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Insert initial Result Rows
mockTime := time.Now().UTC().Truncate(time.Second)
host2Row := []*fleet.ScheduledQueryResultRow{
{
QueryID: globalQuery.ID,
HostID: hostTeam2.ID,
LastFetched: mockTime,
Data: ptr.RawMessage([]byte(`{"model": "USB Keyboard", "vendor": "Apple Inc."}`)),
},
}
_, err = ds.OverwriteQueryResultRows(ctx, host2Row, fleet.DefaultMaxQueryReportRows)
require.NoError(t, err)
host1Row := []*fleet.ScheduledQueryResultRow{
{
QueryID: globalQuery.ID,
HostID: hostTeam1.ID,
LastFetched: mockTime,
Data: ptr.RawMessage([]byte(`{"model": "USB Mouse", "vendor": "Apple Inc."}`)),
},
}
_, err = ds.OverwriteQueryResultRows(ctx, host1Row, fleet.DefaultMaxQueryReportRows)
require.NoError(t, err)
team2Admin := &fleet.User{
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team2.ID},
Role: fleet.RoleAdmin,
},
},
}
queryReportResults, _, err := svc.GetQueryReportResults(viewer.NewContext(ctx, viewer.Viewer{User: team2Admin}), globalQuery.ID, &team2.ID)
require.NoError(t, err)
require.Len(t, queryReportResults, 1)
// team admins requesting query results filtered to not-their-team should get no rows back
teamAdmin := &fleet.User{
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team1.ID},
Role: fleet.RoleAdmin,
},
},
}
teamMaintainer := &fleet.User{
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team1.ID},
Role: fleet.RoleMaintainer,
},
},
}
teamObserver := &fleet.User{
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team1.ID},
Role: fleet.RoleObserver,
},
},
}
teamObserverPlus := &fleet.User{
Teams: []fleet.UserTeam{
{
Team: fleet.Team{ID: team1.ID},
Role: fleet.RoleObserverPlus,
},
},
}
testCases := []struct {
name string
user *fleet.User
}{
{
name: "team admin",
user: teamAdmin,
},
{
name: "team maintainer",
user: teamMaintainer,
},
{
name: "team observer",
user: teamObserver,
},
{
name: "team observer+",
user: teamObserverPlus,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
queryReportResults, _, err := svc.GetQueryReportResults(viewer.NewContext(ctx, viewer.Viewer{User: tt.user}), globalQuery.ID, &team2.ID)
require.NoError(t, err)
require.Len(t, queryReportResults, 0)
})
}
}
func TestComparePlatforms(t *testing.T) {
for _, tc := range []struct {
name string
p1 string
p2 string
expected bool
}{
{
name: "equal single value",
p1: "linux",
p2: "linux",
expected: true,
},
{
name: "different single value",
p1: "macos",
p2: "linux",
expected: false,
},
{
name: "equal multiple values",
p1: "linux,windows",
p2: "linux,windows",
expected: true,
},
{
name: "equal multiple values out of order",
p1: "linux,windows",
p2: "windows,linux",
expected: true,
},
{
name: "different multiple values",
p1: "linux,windows",
p2: "linux,windows,darwin",
expected: false,
},
{
name: "no values set",
p1: "",
p2: "",
expected: true,
},
{
name: "no values set",
p1: "",
p2: "linux",
expected: false,
},
{
name: "single and multiple values",
p1: "linux",
p2: "windows,linux",
expected: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
actual := comparePlatforms(tc.p1, tc.p2)
require.Equal(t, tc.expected, actual)
})
}
}
func TestApplyQuerySpec(t *testing.T) {
ds := new(mock.Store)
ds.NewQueryFunc = func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) {
return query, nil
}
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
return nil
}
ds.QueryByNameFunc = func(ctx context.Context, teamID *uint, name string) (*fleet.Query, error) {
return nil, newNotFoundError()
}
ds.ApplyQueriesFunc = func(ctx context.Context, authID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]struct{}) error {
return nil
}
ds.LabelsByNameFunc = func(ctx context.Context, names []string, filter fleet.TeamFilter) (map[string]*fleet.Label, error) {
require.NotNil(t, filter.User)
labels := make(map[string]*fleet.Label, len(names))
for _, name := range names {
if name == "foo" {
labels["foo"] = &fleet.Label{
Name: "foo",
ID: 1,
}
}
}
return labels, nil
}
svc, ctx := newTestService(t, ds, nil, nil)
testAdmin := fleet.User{
ID: 1,
Teams: []fleet.UserTeam{},
GlobalRole: ptr.String(fleet.RoleAdmin),
}
viewerCtx := viewer.NewContext(ctx, viewer.Viewer{User: &testAdmin})
// Test that a query spec with a label that exists doesn't return an error
err := svc.ApplyQuerySpecs(viewerCtx, []*fleet.QuerySpec{
{
Name: "test query",
Query: "select 1",
LabelsIncludeAny: []string{"foo"},
Platform: "darwin,windows",
},
})
require.NoError(t, err)
// Test that a query spec with a label that doesn't exist returns an error.
err = svc.ApplyQuerySpecs(viewerCtx, []*fleet.QuerySpec{
{
Name: "test query",
Query: "select 1",
LabelsIncludeAny: []string{"foo", "bar"},
Platform: "darwin,windows",
},
})
assert.Error(t, err)
}