fleet/server/datastore/mysql/queries_test.go
Victor Lyuboslavsky e94d23e9db
Fixing PR review comments (#31550)
Fixing PR review comments from
https://github.com/fleetdm/fleet/pull/31445

Fixes #31444
2025-08-03 16:41:38 +02:00

1487 lines
45 KiB
Go

package mysql
import (
"context"
"database/sql"
"errors"
"fmt"
"math"
"math/rand"
"sort"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestQueries(t *testing.T) {
ds := CreateMySQLDS(t)
cases := []struct {
name string
fn func(t *testing.T, ds *Datastore)
}{
{"Apply", testQueriesApply},
{"Delete", testQueriesDelete},
{"GetByName", testQueriesGetByName},
{"DeleteMany", testQueriesDeleteMany},
{"Save", testQueriesSave},
{"List", testQueriesList},
{"LoadPacksForQueries", testQueriesLoadPacksForQueries},
{"DuplicateNew", testQueriesDuplicateNew},
{"ObserverCanRunQuery", testObserverCanRunQuery},
{"ListQueriesFiltersByTeamID", testListQueriesFiltersByTeamID},
{"ListQueriesFiltersByIsScheduled", testListQueriesFiltersByIsScheduled},
{"ListScheduledQueriesForAgents", testListScheduledQueriesForAgents},
{"IsSavedQuery", testIsSavedQuery},
{"SaveQueryLabels", testSaveQueryLabels},
{"ListScheduledQueriesForAgentsWithLabels", testListScheduledQueriesForAgentsWithLabels},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer TruncateTables(t, ds)
c.fn(t, ds)
})
}
}
func testQueriesApply(t *testing.T, ds *Datastore) {
test.AddAllHostsLabel(t, ds)
// Add a user-defined label
fooLabel, err := ds.NewLabel(
context.Background(),
&fleet.Label{
Name: "Foo",
Query: "select 1",
LabelType: fleet.LabelTypeRegular,
LabelMembershipType: fleet.LabelMembershipTypeManual,
},
)
require.NoError(t, err)
barLabel, err := ds.NewLabel(
context.Background(),
&fleet.Label{
Name: "Bar",
Query: "select 1",
LabelType: fleet.LabelTypeRegular,
LabelMembershipType: fleet.LabelMembershipTypeManual,
},
)
require.NoError(t, err)
zwass := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
groob := test.NewUser(t, ds, "Victor", "victor@fleet.co", true)
expectedQueries := []*fleet.Query{
{
Name: "foo",
Description: "get the foos",
Query: "select * from foo",
ObserverCanRun: true,
Interval: 10,
Platform: "darwin",
MinOsqueryVersion: "5.2.1",
AutomationsEnabled: true,
Logging: fleet.LoggingDifferential,
DiscardData: true,
LabelsIncludeAny: []fleet.LabelIdent{{LabelID: fooLabel.ID, LabelName: fooLabel.Name}},
},
{
Name: "bar",
Description: "do some bars",
Query: "select baz from bar",
Logging: fleet.LoggingSnapshot,
DiscardData: true,
},
}
// Zach creates some queries
err = ds.ApplyQueries(context.Background(), zwass.ID, expectedQueries, nil)
require.NoError(t, err)
queries, count, _, err := ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.NoError(t, err)
require.Len(t, queries, len(expectedQueries))
require.Equal(t, count, len(expectedQueries))
test.QueryElementsMatch(t, expectedQueries, queries)
// Check all queries were authored by zwass
for _, q := range queries {
require.Equal(t, &zwass.ID, q.AuthorID)
require.Equal(t, zwass.Email, q.AuthorEmail)
require.Equal(t, zwass.Name, q.AuthorName)
require.True(t, q.Saved)
}
// Update the first query to have a different label
expectedQueries[0].LabelsIncludeAny = []fleet.LabelIdent{{LabelID: barLabel.ID, LabelName: barLabel.Name}}
err = ds.ApplyQueries(context.Background(), zwass.ID, expectedQueries, nil)
require.NoError(t, err)
queries, count, _, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.NoError(t, err)
require.Len(t, queries, len(expectedQueries))
require.Equal(t, count, len(expectedQueries))
test.QueryElementsMatch(t, expectedQueries, queries)
// Victor modifies a query (but also pushes the same version of the
// first query)
expectedQueries[1].Query = "not really a valid query ;)"
err = ds.ApplyQueries(context.Background(), groob.ID, expectedQueries, nil)
require.NoError(t, err)
queries, count, _, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.NoError(t, err)
require.Len(t, queries, len(expectedQueries))
require.Equal(t, count, len(expectedQueries))
test.QueryElementsMatch(t, expectedQueries, queries)
// Check queries were authored by groob
for _, q := range queries {
assert.Equal(t, &groob.ID, q.AuthorID)
require.Equal(t, groob.Email, q.AuthorEmail)
require.Equal(t, groob.Name, q.AuthorName)
require.True(t, q.Saved)
}
// Zach adds a third query (but does not re-apply the others)
expectedQueries = append(expectedQueries,
&fleet.Query{
Name: "trouble",
Description: "Look out!",
Query: "select * from time",
DiscardData: true,
Logging: fleet.LoggingDifferential,
},
)
err = ds.ApplyQueries(context.Background(), zwass.ID, []*fleet.Query{expectedQueries[2]}, nil)
require.NoError(t, err)
queries, count, _, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.NoError(t, err)
require.Len(t, queries, len(expectedQueries))
require.Equal(t, count, len(expectedQueries))
test.QueryElementsMatch(t, expectedQueries, queries)
for _, q := range queries {
require.True(t, q.Saved)
switch q.Name {
case "foo", "bar":
require.Equal(t, &groob.ID, q.AuthorID)
require.Equal(t, groob.Email, q.AuthorEmail)
require.Equal(t, groob.Name, q.AuthorName)
default:
require.Equal(t, &zwass.ID, q.AuthorID)
require.Equal(t, zwass.Email, q.AuthorEmail)
require.Equal(t, zwass.Name, q.AuthorName)
}
}
// Zach tries to add a query with an invalid platform string
invalidQueries := []*fleet.Query{
{
Name: "foo",
Description: "get the foos",
Query: "select * from foo",
ObserverCanRun: true,
Interval: 10,
Platform: "not valid",
MinOsqueryVersion: "5.2.1",
AutomationsEnabled: true,
Logging: fleet.LoggingDifferential,
},
}
err = ds.ApplyQueries(context.Background(), zwass.ID, invalidQueries, nil)
require.ErrorIs(t, err, fleet.ErrQueryInvalidPlatform)
}
func testQueriesDelete(t *testing.T, ds *Datastore) {
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
hostID := uint(1)
query := &fleet.Query{
Name: "foo",
Query: "bar",
AuthorID: &user.ID,
Logging: fleet.LoggingDifferential,
}
query, err := ds.NewQuery(context.Background(), query)
require.NoError(t, err)
require.NotNil(t, query)
assert.NotEqual(t, query.ID, 0)
lastExecuted := time.Now().Add(-time.Hour).Round(time.Second) // TIMESTAMP precision is seconds by default in MySQL
err = ds.UpdateLiveQueryStats(
context.Background(), query.ID, []*fleet.LiveQueryStats{
{
HostID: hostID,
Executions: 1,
LastExecuted: lastExecuted,
},
},
)
require.NoError(t, err)
// Check that the stats were saved correctly
stats, err := ds.GetLiveQueryStats(context.Background(), query.ID, []uint{hostID})
require.NoError(t, err)
require.Len(t, stats, 1)
assert.Equal(t, hostID, stats[0].HostID)
assert.Equal(t, uint64(1), stats[0].Executions)
assert.Equal(t, lastExecuted.UTC(), stats[0].LastExecuted.UTC())
err = ds.CalculateAggregatedPerfStatsPercentiles(context.Background(), fleet.AggregatedStatsTypeScheduledQuery, query.ID)
require.NoError(t, err)
err = ds.DeleteQuery(context.Background(), query.TeamID, query.Name)
require.NoError(t, err)
require.NotEqual(t, query.ID, 0)
_, err = ds.Query(context.Background(), query.ID)
require.Error(t, err)
require.True(t, fleet.IsNotFound(err))
// Ensure stats were deleted.
// The actual delete occurs asynchronously, so we for-loop.
statsGone := make(chan bool)
go func() {
for {
stats, err := ds.GetLiveQueryStats(context.Background(), query.ID, []uint{hostID})
require.NoError(t, err)
if len(stats) == 0 {
_, err = GetAggregatedStats(context.Background(), ds, fleet.AggregatedStatsTypeScheduledQuery, query.ID)
if errors.Is(err, sql.ErrNoRows) {
statsGone <- true
break
}
}
}
}()
select {
case <-statsGone:
case <-time.After(10 * time.Second):
t.Error("Timeout: stats not deleted for testQueriesDelete")
}
}
func testQueriesGetByName(t *testing.T, ds *Datastore) {
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
// Test we can get global queries by name
globalQ := test.NewQuery(t, ds, nil, "q1", "select * from time", user.ID, true)
actual, err := ds.QueryByName(context.Background(), nil, globalQ.Name)
require.NoError(t, err)
require.Nil(t, actual.TeamID)
require.Equal(t, "q1", actual.Name)
require.Equal(t, "select * from time", actual.Query)
_, err = ds.QueryByName(context.Background(), nil, "xxx")
require.Error(t, err)
require.True(t, fleet.IsNotFound(err))
// Test we can get queries in a team
teamRocket, err := ds.NewTeam(context.Background(), &fleet.Team{
Name: "Team Rocket",
Description: "Something cheesy",
})
require.NoError(t, err)
teamRocketQ := test.NewQuery(t, ds, &teamRocket.ID, "q1", "select * from time", user.ID, true)
actual, err = ds.QueryByName(context.Background(), &teamRocket.ID, teamRocketQ.Name)
require.NoError(t, err)
require.Equal(t, "q1", actual.Name)
require.Equal(t, teamRocket.ID, *actual.TeamID)
require.Equal(t, "select * from time", actual.Query)
_, err = ds.QueryByName(context.Background(), &teamRocket.ID, "xxx")
require.Error(t, err)
require.True(t, fleet.IsNotFound(err))
}
func testQueriesDeleteMany(t *testing.T, ds *Datastore) {
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
q1 := test.NewQuery(t, ds, nil, "q1", "select * from time", user.ID, true)
q2 := test.NewQuery(t, ds, nil, "q2", "select * from processes", user.ID, true)
q3 := test.NewQuery(t, ds, nil, "q3", "select 1", user.ID, true)
q4 := test.NewQuery(t, ds, nil, "q4", "select * from osquery_info", user.ID, true)
queries, count, _, err := ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.Nil(t, err)
assert.Len(t, queries, 4)
require.Equal(t, count, 4)
// Add query stats
hostIDs := []uint{10, 20}
err = ds.UpdateLiveQueryStats(
context.Background(), q1.ID, []*fleet.LiveQueryStats{
{
HostID: hostIDs[0],
Executions: 1,
},
{
HostID: hostIDs[1],
Executions: 1,
},
},
)
require.NoError(t, err)
err = ds.UpdateLiveQueryStats(
context.Background(), q3.ID, []*fleet.LiveQueryStats{
{
HostID: hostIDs[0],
Executions: 1,
},
},
)
require.NoError(t, err)
err = ds.CalculateAggregatedPerfStatsPercentiles(context.Background(), fleet.AggregatedStatsTypeScheduledQuery, q1.ID)
require.NoError(t, err)
err = ds.CalculateAggregatedPerfStatsPercentiles(context.Background(), fleet.AggregatedStatsTypeScheduledQuery, q3.ID)
require.NoError(t, err)
deleted, err := ds.DeleteQueries(context.Background(), []uint{q1.ID, q3.ID})
require.Nil(t, err)
assert.Equal(t, uint(2), deleted)
queries, count, _, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.Nil(t, err)
assert.Len(t, queries, 2)
assert.Equal(t, count, 2)
// Ensure stats were deleted.
// The actual delete occurs asynchronously, so we for-loop.
statsGone := make(chan bool)
go func() {
for {
stats, err := ds.GetLiveQueryStats(context.Background(), q1.ID, hostIDs)
require.NoError(t, err)
if len(stats) == 0 {
_, err = GetAggregatedStats(context.Background(), ds, fleet.AggregatedStatsTypeScheduledQuery, q1.ID)
if errors.Is(err, sql.ErrNoRows) {
statsGone <- true
break
}
}
time.Sleep(100 * time.Millisecond) // Add a small delay between checks
}
}()
select {
case <-statsGone:
case <-time.After(10 * time.Second):
t.Error("Timeout: stats not deleted for testQueriesDeleteMany")
}
stats, err := ds.GetLiveQueryStats(context.Background(), q3.ID, hostIDs)
require.NoError(t, err)
require.Equal(t, 0, len(stats))
_, err = GetAggregatedStats(context.Background(), ds, fleet.AggregatedStatsTypeScheduledQuery, q3.ID)
require.ErrorIs(t, err, sql.ErrNoRows)
deleted, err = ds.DeleteQueries(context.Background(), []uint{q2.ID})
require.Nil(t, err)
assert.Equal(t, uint(1), deleted)
queries, count, _, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.Nil(t, err)
assert.Len(t, queries, 1)
assert.Equal(t, count, 1)
deleted, err = ds.DeleteQueries(context.Background(), []uint{q2.ID, q4.ID})
require.Nil(t, err)
assert.Equal(t, uint(1), deleted)
queries, count, _, err = ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.Nil(t, err)
assert.Len(t, queries, 0)
assert.Equal(t, count, 0)
}
func testQueriesSave(t *testing.T, ds *Datastore) {
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
query := &fleet.Query{
Name: "foo",
Query: "bar",
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
}
query, err := ds.NewQuery(context.Background(), query)
require.NoError(t, err)
require.NotNil(t, query)
require.NotEqual(t, 0, query.ID)
team, err := ds.NewTeam(context.Background(), &fleet.Team{
Name: "some kind of nature",
Description: "some kind of goal",
})
require.NoError(t, err)
query.Query = "baz"
query.ObserverCanRun = true
query.TeamID = &team.ID
query.Interval = 10
query.Platform = "darwin"
query.MinOsqueryVersion = "5.2.1"
query.AutomationsEnabled = true
query.Logging = fleet.LoggingDifferential
query.DiscardData = true
err = ds.SaveQuery(context.Background(), query, true, false)
require.NoError(t, err)
actual, err := ds.Query(context.Background(), query.ID)
require.NoError(t, err)
require.NotNil(t, actual)
test.QueriesMatch(t, actual, query)
require.Equal(t, "baz", actual.Query)
require.Equal(t, "Zach", actual.AuthorName)
require.Equal(t, "zwass@fleet.co", actual.AuthorEmail)
// Now save again and delete old stats.
// First we create stats which will be deleted.
const hostID = 1
err = ds.UpdateLiveQueryStats(
context.Background(), query.ID, []*fleet.LiveQueryStats{
{
HostID: hostID,
Executions: 1,
},
},
)
require.NoError(t, err)
err = ds.CalculateAggregatedPerfStatsPercentiles(context.Background(), fleet.AggregatedStatsTypeScheduledQuery, query.ID)
require.NoError(t, err)
// Update/save query.
query.Query = "baz2"
err = ds.SaveQuery(context.Background(), query, true, true)
require.NoError(t, err)
// Ensure stats were deleted.
// The actual delete occurs asynchronously, so we for-loop.
aggStatsGone := make(chan bool)
go func() {
for {
actual, err = ds.Query(context.Background(), query.ID)
require.NoError(t, err)
require.NotNil(t, actual)
if actual.AggregatedStats.TotalExecutions == nil {
aggStatsGone <- true
break
}
}
}()
select {
case <-aggStatsGone:
case <-time.After(10 * time.Second):
t.Error("Timeout: aggregated stats not deleted for query")
}
test.QueriesMatch(t, query, actual)
stats, err := ds.GetLiveQueryStats(context.Background(), query.ID, []uint{hostID})
require.NoError(t, err)
require.Equal(t, 0, len(stats))
_, err = GetAggregatedStats(context.Background(), ds, fleet.AggregatedStatsTypeScheduledQuery, query.ID)
require.ErrorIs(t, err, sql.ErrNoRows)
}
func testQueriesList(t *testing.T, ds *Datastore) {
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
for i := 0; i < 10; i++ {
// populate platform field of first 4 queries
var p string
switch i {
case 0:
p = "darwin"
case 1:
p = "windows"
case 2:
p = "linux"
case 3:
p = "darwin,windows,linux"
}
_, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("name%02d", i),
Query: fmt.Sprintf("query%02d", i),
Saved: true,
AuthorID: &user.ID,
DiscardData: true,
ObserverCanRun: rand.Intn(2) == 0, //nolint:gosec
Logging: fleet.LoggingSnapshot,
Platform: p,
})
require.Nil(t, err)
}
// One unsaved query should not be returned
_, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "unsaved",
Query: "select * from time",
Saved: false,
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
opts := fleet.ListQueryOptions{}
opts.IncludeMetadata = true
opts.Platform = ptr.String("darwin")
// filtered by platform
results, count, meta, err := ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 8, len(results))
assert.Equal(t, count, 8)
assert.False(t, meta.HasPreviousResults)
assert.False(t, meta.HasNextResults)
require.Equal(t, "darwin", results[0].Platform)
require.Equal(t, "darwin,windows,linux", results[1].Platform)
opts.Platform = ptr.String("windows")
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 8, len(results))
assert.Equal(t, count, 8)
assert.False(t, meta.HasPreviousResults)
assert.False(t, meta.HasNextResults)
require.Equal(t, "windows", results[0].Platform)
require.Equal(t, "darwin,windows,linux", results[1].Platform)
opts.Platform = ptr.String("linux")
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 8, len(results))
assert.Equal(t, count, 8)
assert.False(t, meta.HasPreviousResults)
assert.False(t, meta.HasNextResults)
require.Equal(t, "linux", results[0].Platform)
require.Equal(t, "darwin,windows,linux", results[1].Platform)
opts.Platform = ptr.String("lucas")
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
// only returns queries set to run on all platforms with platform == ""
require.Equal(t, 6, len(results))
assert.Equal(t, count, 6)
assert.False(t, meta.HasPreviousResults)
assert.False(t, meta.HasNextResults)
opts.Platform = nil
// paginated - beginning
opts.PerPage = 3
opts.Page = 0
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 3, len(results))
require.Equal(t, "Zach", results[0].AuthorName)
require.Equal(t, "zwass@fleet.co", results[0].AuthorEmail)
require.True(t, results[0].DiscardData)
assert.Equal(t, count, 10)
assert.False(t, meta.HasPreviousResults)
assert.True(t, meta.HasNextResults)
// paginated - middle
opts.Page = 1
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 3, len(results))
require.Equal(t, "Zach", results[0].AuthorName)
require.Equal(t, "zwass@fleet.co", results[0].AuthorEmail)
require.True(t, results[0].DiscardData)
assert.Equal(t, count, 10)
assert.True(t, meta.HasPreviousResults)
assert.True(t, meta.HasNextResults)
// paginated - end
opts.Page = 3
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 1, len(results))
require.Equal(t, "Zach", results[0].AuthorName)
require.Equal(t, "zwass@fleet.co", results[0].AuthorEmail)
require.True(t, results[0].DiscardData)
assert.Equal(t, count, 10)
assert.True(t, meta.HasPreviousResults)
assert.False(t, meta.HasNextResults)
// paginated - past end
opts.Page = 4
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 0, len(results))
assert.Equal(t, count, 10)
assert.True(t, meta.HasPreviousResults)
assert.False(t, meta.HasNextResults)
opts.PerPage = 0
opts.Page = 0
results, count, meta, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 10, len(results))
require.Equal(t, "Zach", results[0].AuthorName)
require.Equal(t, "zwass@fleet.co", results[0].AuthorEmail)
require.True(t, results[0].DiscardData)
assert.Equal(t, count, 10)
assert.False(t, meta.HasPreviousResults)
assert.False(t, meta.HasNextResults)
idWithAgg := results[0].ID
_, err = ds.writer(context.Background()).Exec(
`INSERT INTO aggregated_stats(id,global_stats,type,json_value) VALUES (?,?,?,?)`,
idWithAgg, false, fleet.AggregatedStatsTypeScheduledQuery,
`{"user_time_p50": 10.5777, "user_time_p95": 111.7308, "system_time_p50": 0.6936, "system_time_p95": 95.8654, "total_executions": 5038}`,
)
require.NoError(t, err)
results, _, _, err = ds.ListQueries(context.Background(), opts)
require.NoError(t, err)
require.Equal(t, 10, len(results))
foundAgg := false
for _, q := range results {
if q.ID == idWithAgg {
foundAgg = true
require.NotNil(t, q.SystemTimeP50)
require.NotNil(t, q.SystemTimeP95)
assert.Equal(t, 0.6936, *q.SystemTimeP50)
assert.Equal(t, 95.8654, *q.SystemTimeP95)
}
}
require.True(t, foundAgg)
}
func testQueriesLoadPacksForQueries(t *testing.T, ds *Datastore) {
zwass := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
queries := []*fleet.Query{
{Name: "q1", Query: "select * from time", Logging: fleet.LoggingSnapshot},
{Name: "q2", Query: "select * from osquery_info", Logging: fleet.LoggingDifferential},
}
err := ds.ApplyQueries(context.Background(), zwass.ID, queries, nil)
require.NoError(t, err)
specs := []*fleet.PackSpec{
{Name: "p1"},
{Name: "p2"},
{Name: "p3"},
}
err = ds.ApplyPackSpecs(context.Background(), specs)
require.Nil(t, err)
q0, err := ds.QueryByName(context.Background(), nil, queries[0].Name)
require.Nil(t, err)
assert.Empty(t, q0.Packs)
q1, err := ds.QueryByName(context.Background(), nil, queries[1].Name)
require.Nil(t, err)
assert.Empty(t, q1.Packs)
specs = []*fleet.PackSpec{
{
Name: "p2",
Queries: []fleet.PackSpecQuery{
{
Name: "q0",
QueryName: queries[0].Name,
Interval: 60,
},
},
},
}
err = ds.ApplyPackSpecs(context.Background(), specs)
require.Nil(t, err)
q0, err = ds.QueryByName(context.Background(), nil, queries[0].Name)
require.Nil(t, err)
if assert.Len(t, q0.Packs, 1) {
assert.Equal(t, "p2", q0.Packs[0].Name)
}
q1, err = ds.QueryByName(context.Background(), nil, queries[1].Name)
require.Nil(t, err)
assert.Empty(t, q1.Packs)
specs = []*fleet.PackSpec{
{
Name: "p1",
Queries: []fleet.PackSpecQuery{
{
QueryName: queries[1].Name,
Interval: 60,
},
},
},
{
Name: "p3",
Queries: []fleet.PackSpecQuery{
{
QueryName: queries[1].Name,
Interval: 60,
},
},
},
}
err = ds.ApplyPackSpecs(context.Background(), specs)
require.Nil(t, err)
q0, err = ds.QueryByName(context.Background(), nil, queries[0].Name)
require.Nil(t, err)
if assert.Len(t, q0.Packs, 1) {
assert.Equal(t, "p2", q0.Packs[0].Name)
}
q1, err = ds.QueryByName(context.Background(), nil, queries[1].Name)
require.Nil(t, err)
if assert.Len(t, q1.Packs, 2) {
sort.Slice(q1.Packs, func(i, j int) bool { return q1.Packs[i].Name < q1.Packs[j].Name })
assert.Equal(t, "p1", q1.Packs[0].Name)
assert.Equal(t, "p3", q1.Packs[1].Name)
}
specs = []*fleet.PackSpec{
{
Name: "p3",
Queries: []fleet.PackSpecQuery{
{
Name: "q0",
QueryName: queries[0].Name,
Interval: 60,
},
{
Name: "q1",
QueryName: queries[1].Name,
Interval: 60,
},
},
},
}
err = ds.ApplyPackSpecs(context.Background(), specs)
require.Nil(t, err)
q0, err = ds.QueryByName(context.Background(), nil, queries[0].Name)
require.Nil(t, err)
if assert.Len(t, q0.Packs, 2) {
sort.Slice(q0.Packs, func(i, j int) bool { return q0.Packs[i].Name < q0.Packs[j].Name })
assert.Equal(t, "p2", q0.Packs[0].Name)
assert.Equal(t, "p3", q0.Packs[1].Name)
}
q1, err = ds.QueryByName(context.Background(), nil, queries[1].Name)
require.Nil(t, err)
if assert.Len(t, q1.Packs, 2) {
sort.Slice(q1.Packs, func(i, j int) bool { return q1.Packs[i].Name < q1.Packs[j].Name })
assert.Equal(t, "p1", q1.Packs[0].Name)
assert.Equal(t, "p3", q1.Packs[1].Name)
}
}
func testQueriesDuplicateNew(t *testing.T, ds *Datastore) {
user := test.NewUser(t, ds, "Mike Arpaia", "mike@fleet.co", true)
// The uniqueness of 'global' queries should be based on their name alone.
globalQ1, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "foo",
Query: "select * from time;",
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
require.NotZero(t, globalQ1.ID)
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: "foo",
Query: "select * from osquery_info;",
Logging: fleet.LoggingSnapshot,
})
require.Contains(t, err.Error(), "already exists")
// Check uniqueness constraint on queries that belong to a team
team, err := ds.NewTeam(context.Background(), &fleet.Team{
Name: "some kind of nature",
Description: "some kind of goal",
})
require.NoError(t, err)
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: "foo",
Query: "select * from osquery_info;",
TeamID: &team.ID,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: "foo",
Query: "select * from osquery_info;",
TeamID: &team.ID,
Logging: fleet.LoggingSnapshot,
})
require.Contains(t, err.Error(), "already exists")
}
func testObserverCanRunQuery(t *testing.T, ds *Datastore) {
_, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "canRunTrue",
Query: "select 1;",
ObserverCanRun: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: "canRunFalse",
Query: "select 1;",
ObserverCanRun: false,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: "canRunOmitted",
Query: "select 1;",
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
queries, _, _, err := ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.NoError(t, err)
for _, q := range queries {
canRun, err := ds.ObserverCanRunQuery(context.Background(), q.ID)
require.NoError(t, err)
require.Equal(t, q.ObserverCanRun, canRun)
}
}
func testListQueriesFiltersByTeamID(t *testing.T, ds *Datastore) {
globalQ1, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query1",
Query: "select 1;",
Saved: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
globalQ2, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query2",
Query: "select 1;",
Saved: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
globalQ3, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query3",
Query: "select 1;",
Saved: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
queries, count, _, err := ds.ListQueries(context.Background(), fleet.ListQueryOptions{})
require.NoError(t, err)
test.QueryElementsMatch(t, queries, []*fleet.Query{globalQ1, globalQ2, globalQ3})
assert.Equal(t, count, 3)
team, err := ds.NewTeam(context.Background(), &fleet.Team{
Name: "some kind of nature",
Description: "some kind of goal",
})
require.NoError(t, err)
teamQ1, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query1",
Query: "select 1;",
Saved: true,
TeamID: &team.ID,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
teamQ2, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query2",
Query: "select 1;",
Saved: true,
TeamID: &team.ID,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
teamQ3, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query3",
Query: "select 1;",
Saved: true,
TeamID: &team.ID,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
queries, count, _, err = ds.ListQueries(
context.Background(),
fleet.ListQueryOptions{
TeamID: &team.ID,
},
)
require.NoError(t, err)
test.QueryElementsMatch(t, queries, []*fleet.Query{teamQ1, teamQ2, teamQ3})
assert.Equal(t, count, 3)
// test merge inherited
queries, count, _, err = ds.ListQueries(
context.Background(),
fleet.ListQueryOptions{
TeamID: &team.ID,
MergeInherited: true,
},
)
require.NoError(t, err)
test.QueryElementsMatch(t, queries, []*fleet.Query{globalQ1, globalQ2, globalQ3, teamQ1, teamQ2, teamQ3})
assert.Equal(t, count, 6)
// merge inherited ignored for global queries
queries, count, _, err = ds.ListQueries(
context.Background(),
fleet.ListQueryOptions{
MergeInherited: true,
},
)
require.NoError(t, err)
test.QueryElementsMatch(t, queries, []*fleet.Query{globalQ1, globalQ2, globalQ3})
assert.Equal(t, count, 3)
}
func testListQueriesFiltersByIsScheduled(t *testing.T, ds *Datastore) {
q1, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query1",
Query: "select 1;",
Saved: true,
Interval: 0,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
q2, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query2",
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: false,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
q3, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: "query3",
Query: "select 1;",
Saved: true,
Interval: 20,
AutomationsEnabled: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
testCases := []struct {
opts fleet.ListQueryOptions
expected []*fleet.Query
}{
{
opts: fleet.ListQueryOptions{},
expected: []*fleet.Query{q1, q2, q3},
},
{
opts: fleet.ListQueryOptions{IsScheduled: ptr.Bool(true)},
expected: []*fleet.Query{q3},
},
{
opts: fleet.ListQueryOptions{IsScheduled: ptr.Bool(false)},
expected: []*fleet.Query{q1, q2},
},
}
for i, tCase := range testCases {
queries, count, _, err := ds.ListQueries(
context.Background(),
tCase.opts,
)
require.NoError(t, err)
test.QueryElementsMatch(t, queries, tCase.expected, i)
assert.Equal(t, count, len(tCase.expected))
}
}
func testListScheduledQueriesForAgents(t *testing.T, ds *Datastore) {
ctx := context.Background()
team, err := ds.NewTeam(context.Background(), &fleet.Team{
Name: "Team 1",
Description: "Team 1",
})
require.NoError(t, err)
for i, teamID := range []*uint{nil, &team.ID} {
var teamIDStr string
if teamID != nil {
teamIDStr = fmt.Sprintf("%d", *teamID)
}
// Non saved queries should not be returned here.
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query1", teamIDStr),
Query: "select 1;",
Saved: false,
Interval: 10,
AutomationsEnabled: false,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=0, DiscardData=0, Snapshot=0
_, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query2", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
TeamID: teamID,
AutomationsEnabled: false,
DiscardData: false,
Logging: fleet.LoggingDifferential,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=0, DiscardData=0, Snapshot=1
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query3", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
TeamID: teamID,
AutomationsEnabled: false,
DiscardData: false,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=0, DiscardData=1, Snapshot=0
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query4", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
AutomationsEnabled: false,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingDifferential,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=0, DiscardData=1, Snapshot=1
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query5", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
AutomationsEnabled: false,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=1, DiscardData=0, Snapshot=0
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query6", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: false,
Logging: fleet.LoggingDifferential,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=1, DiscardData=0, Snapshot=1
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query7", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: false,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=1, DiscardData=1, Snapshot=0
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query8", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingDifferential,
})
require.NoError(t, err)
// Interval=0, AutomationsEnabled=1, DiscardData=1, Snapshot=1
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query9", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 0,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=0, DiscardData=0, Snapshot=0
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query10", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: false,
TeamID: teamID,
DiscardData: false,
Logging: fleet.LoggingDifferentialIgnoreRemovals,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=0, DiscardData=0, Snapshot=1
q11, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query11", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: false,
TeamID: teamID,
DiscardData: false,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=0, DiscardData=1, Snapshot=0
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query12", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: false,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingDifferentialIgnoreRemovals,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=0, DiscardData=1, Snapshot=1
_, err = ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query13", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: false,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=1, DiscardData=0, Snapshot=0
q14, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query14", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: false,
Logging: fleet.LoggingDifferential,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=1, DiscardData=0, Snapshot=1
q15, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query15", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: false,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=1, DiscardData=1, Snapshot=0
q16, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query16", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingDifferential,
})
require.NoError(t, err)
// Interval=1, AutomationsEnabled=1, DiscardData=1, Snapshot=1
q17, err := ds.NewQuery(context.Background(), &fleet.Query{
Name: fmt.Sprintf("%s query17", teamIDStr),
Query: "select 1;",
Saved: true,
Interval: 10,
AutomationsEnabled: true,
TeamID: teamID,
DiscardData: true,
Logging: fleet.LoggingSnapshot,
})
require.NoError(t, err)
queryReportsDisabled := false
result, err := ds.ListScheduledQueriesForAgents(ctx, teamID, nil, queryReportsDisabled)
require.NoError(t, err)
sort.Slice(result, func(i, j int) bool {
return result[i].ID < result[j].ID
})
test.QueryElementsMatch(t, result, []*fleet.Query{q11, q14, q15, q16, q17}, i)
queryReportsDisabled = true
result, err = ds.ListScheduledQueriesForAgents(ctx, teamID, nil, queryReportsDisabled)
require.NoError(t, err)
sort.Slice(result, func(i, j int) bool {
return result[i].ID < result[j].ID
})
test.QueryElementsMatch(t, result, []*fleet.Query{q14, q15, q16, q17}, i)
}
}
func testIsSavedQuery(t *testing.T, ds *Datastore) {
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
// NOT saved query
query := &fleet.Query{
Name: "foo",
Query: "bar",
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
Saved: false,
}
query, err := ds.NewQuery(context.Background(), query)
require.NoError(t, err)
isSaved, err := ds.IsSavedQuery(context.Background(), query.ID)
require.NoError(t, err)
assert.False(t, isSaved)
// Saved query
query = &fleet.Query{
Name: "foo2",
Query: "bar",
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
Saved: true,
}
query, err = ds.NewQuery(context.Background(), query)
require.NoError(t, err)
isSaved, err = ds.IsSavedQuery(context.Background(), query.ID)
require.NoError(t, err)
assert.True(t, isSaved)
// error case
_, err = ds.IsSavedQuery(context.Background(), math.MaxUint)
require.Error(t, err)
}
func testSaveQueryLabels(t *testing.T, ds *Datastore) {
ctx := context.Background()
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
label1, err := ds.NewLabel(ctx, &fleet.Label{Name: "label1"})
require.NoError(t, err)
label2, err := ds.NewLabel(ctx, &fleet.Label{Name: "label2"})
require.NoError(t, err)
// Create query with label
query1, err := ds.NewQuery(ctx, &fleet.Query{
Name: "query1",
Query: "SELECT 1",
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
Saved: true,
LabelsIncludeAny: []fleet.LabelIdent{
{LabelName: label1.Name},
},
})
require.NoError(t, err)
require.Len(t, query1.LabelsIncludeAny, 1)
require.Equal(t, label1.Name, query1.LabelsIncludeAny[0].LabelName)
require.Equal(t, label1.ID, query1.LabelsIncludeAny[0].LabelID)
// Change the label
query1.LabelsIncludeAny = []fleet.LabelIdent{{LabelName: label2.Name}}
err = ds.SaveQuery(ctx, query1, true, true)
require.NoError(t, err)
require.Len(t, query1.LabelsIncludeAny, 1)
require.Equal(t, label2.Name, query1.LabelsIncludeAny[0].LabelName)
require.Equal(t, label2.ID, query1.LabelsIncludeAny[0].LabelID)
// Two labels
query1.LabelsIncludeAny = []fleet.LabelIdent{{LabelName: label1.Name}, {LabelName: label2.Name}}
err = ds.SaveQuery(ctx, query1, true, true)
require.NoError(t, err)
require.Len(t, query1.LabelsIncludeAny, 2)
require.Equal(t, label1.Name, query1.LabelsIncludeAny[0].LabelName)
require.Equal(t, label1.ID, query1.LabelsIncludeAny[0].LabelID)
require.Equal(t, label2.Name, query1.LabelsIncludeAny[1].LabelName)
require.Equal(t, label2.ID, query1.LabelsIncludeAny[1].LabelID)
// Remove all labels
query1.LabelsIncludeAny = []fleet.LabelIdent{}
err = ds.SaveQuery(ctx, query1, true, true)
require.NoError(t, err)
require.Len(t, query1.LabelsIncludeAny, 0)
}
func testListScheduledQueriesForAgentsWithLabels(t *testing.T, ds *Datastore) {
requireQueries := func(t *testing.T, queries []*fleet.Query, names []string) {
require.Len(t, queries, len(names))
for _, name := range names {
found := false
for _, query := range queries {
if name == query.Name {
found = true
break
}
}
if !found {
foundNames := []string{}
for _, query := range queries {
foundNames = append(foundNames, query.Name)
}
require.Truef(t, found, "failed to find query %d in list %#v", name, foundNames)
}
}
}
ctx := context.Background()
user := test.NewUser(t, ds, "Zach", "zwass@fleet.co", true)
label1, err := ds.NewLabel(ctx, &fleet.Label{Name: "label1"})
require.NoError(t, err)
label2, err := ds.NewLabel(ctx, &fleet.Label{Name: "label2"})
require.NoError(t, err)
hostLabel1 := test.NewHost(t, ds, "host1", "10.0.0.1", "asdf", "host1", time.Now())
err = ds.AddLabelsToHost(ctx, hostLabel1.ID, []uint{label1.ID})
require.NoError(t, err)
hostLabel2 := test.NewHost(t, ds, "host2", "10.0.0.2", "asdg", "host2", time.Now())
err = ds.AddLabelsToHost(ctx, hostLabel2.ID, []uint{label2.ID})
require.NoError(t, err)
hostLabel1And2 := test.NewHost(t, ds, "host3", "10.0.0.3", "asdh", "host3", time.Now())
err = ds.AddLabelsToHost(ctx, hostLabel1And2.ID, []uint{label1.ID, label2.ID})
require.NoError(t, err)
hostNoLabels := test.NewHost(t, ds, "host4", "10.0.0.4", "asdj", "host4", time.Now())
queryLabel1, err := ds.NewQuery(ctx, &fleet.Query{
Name: "query1",
Query: "SELECT 1",
DiscardData: false,
AutomationsEnabled: true,
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
Interval: 10,
Saved: true,
LabelsIncludeAny: []fleet.LabelIdent{
{LabelName: label1.Name},
},
})
require.NoError(t, err)
queryLabel2, err := ds.NewQuery(ctx, &fleet.Query{
Name: "query2",
Query: "SELECT 1",
DiscardData: false,
AutomationsEnabled: true,
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
Interval: 10,
Saved: true,
LabelsIncludeAny: []fleet.LabelIdent{
{LabelName: label2.Name},
},
})
require.NoError(t, err)
queryLabel1And2, err := ds.NewQuery(ctx, &fleet.Query{
Name: "query3",
Query: "SELECT 1",
DiscardData: false,
AutomationsEnabled: true,
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
Interval: 10,
Saved: true,
LabelsIncludeAny: []fleet.LabelIdent{
{LabelName: label1.Name},
{LabelName: label2.Name},
},
})
require.NoError(t, err)
queryNoLabel, err := ds.NewQuery(ctx, &fleet.Query{
Name: "query4",
Query: "SELECT 1",
DiscardData: false,
AutomationsEnabled: true,
AuthorID: &user.ID,
Logging: fleet.LoggingSnapshot,
Interval: 10,
Saved: true,
})
require.NoError(t, err)
// No host specified, list all queries on team, regardless of tag
queries, err := ds.ListScheduledQueriesForAgents(ctx, nil, nil, false)
require.NoError(t, err)
requireQueries(t, queries, []string{queryLabel1.Name, queryLabel2.Name, queryLabel1And2.Name, queryNoLabel.Name})
// Label 1 queries
queries, err = ds.ListScheduledQueriesForAgents(ctx, nil, &hostLabel1.ID, false)
require.NoError(t, err)
requireQueries(t, queries, []string{queryLabel1.Name, queryLabel1And2.Name, queryNoLabel.Name})
// Label 2 queries
queries, err = ds.ListScheduledQueriesForAgents(ctx, nil, &hostLabel2.ID, false)
require.NoError(t, err)
requireQueries(t, queries, []string{queryLabel2.Name, queryLabel1And2.Name, queryNoLabel.Name})
// Labels 1 and 2 queries
queries, err = ds.ListScheduledQueriesForAgents(ctx, nil, &hostLabel1And2.ID, false)
require.NoError(t, err)
requireQueries(t, queries, []string{queryLabel1.Name, queryLabel2.Name, queryLabel1And2.Name, queryNoLabel.Name})
// No label queries
queries, err = ds.ListScheduledQueriesForAgents(ctx, nil, &hostNoLabels.ID, false)
require.NoError(t, err)
requireQueries(t, queries, []string{queryNoLabel.Name})
}