fleet/server/datastore/mysql/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

1488 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,
LabelsIncludeAny: []fleet.LabelIdent{{LabelID: fooLabel.ID, LabelName: fooLabel.Name}},
},
}
// 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})
}