fleet/server/service/queries_test.go
Juan Fernandez fdfef5adf1
30311: Fix race condition in test (#30903)
For #30311 

Refactored `AddHostsToTeam` so that batch size can be specified via a
parameter and not a global variable.
2025-07-17 10:20:49 -04:00

1024 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.TeamFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) {
if tid == team.ID {
return &team, nil
} else if tid == team2.ID {
return &team2, 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, *fleet.PaginationMetadata, error) {
return nil, 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) (map[string]*fleet.Label, error) {
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)
}