diff --git a/admin/handlers/post.go b/admin/handlers/post.go index 0b79b55c..4b93bed2 100644 --- a/admin/handlers/post.go +++ b/admin/handlers/post.go @@ -156,17 +156,17 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque adminErrorResponse(w, "error creating query", http.StatusInternalServerError, err) return } - // Temporary list of UUIDs to calculate Expected - var expected []string + + // List all the nodes that match the query + var expected []uint + + targetNodesID := []uint{} + // TODO: Refactor this to use osctrl-api instead of direct DB queries // Create environment target if len(q.Environments) > 0 { + expected = []uint{} for _, e := range q.Environments { if (e != "") && h.Envs.Exists(e) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetEnvironment, e); err != nil { - adminErrorResponse(w, "error creating query environment target", http.StatusInternalServerError, err) - h.Inc(metricAdminErr) - return - } nodes, err := h.Nodes.GetByEnv(e, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { adminErrorResponse(w, "error getting nodes by environment", http.StatusInternalServerError, err) @@ -174,21 +174,18 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque return } for _, n := range nodes { - expected = append(expected, n.UUID) + expected = append(expected, n.ID) } } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create platform target if len(q.Platforms) > 0 { + expected = []uint{} platforms, _ := h.Nodes.GetAllPlatforms() for _, p := range q.Platforms { if (p != "") && checkValidPlatform(platforms, p) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetPlatform, p); err != nil { - adminErrorResponse(w, "error creating query platform target", http.StatusInternalServerError, err) - h.Inc(metricAdminErr) - return - } nodes, err := h.Nodes.GetByPlatform(p, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { adminErrorResponse(w, "error getting nodes by platform", http.StatusInternalServerError, err) @@ -196,53 +193,53 @@ func (h *HandlersAdmin) QueryRunPOSTHandler(w http.ResponseWriter, r *http.Reque return } for _, n := range nodes { - expected = append(expected, n.UUID) + expected = append(expected, n.ID) } } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create UUIDs target if len(q.UUIDs) > 0 { + expected = []uint{} for _, u := range q.UUIDs { - if (u != "") && h.Nodes.CheckByUUID(u) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetUUID, u); err != nil { - adminErrorResponse(w, "error creating query UUID target", http.StatusInternalServerError, err) - h.Inc(metricAdminErr) - return + if u != "" { + node, err := h.Nodes.GetByUUID(u) + if err != nil { + log.Err(err).Msgf("error getting node %s and failed to create node query for it", u) + continue } - expected = append(expected, u) + expected = append(expected, node.ID) } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create hostnames target if len(q.Hosts) > 0 { + expected = []uint{} for _, _h := range q.Hosts { - if (_h != "") && h.Nodes.CheckByHost(_h) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetLocalname, _h); err != nil { - adminErrorResponse(w, "error creating query hostname target", http.StatusInternalServerError, err) - h.Inc(metricAdminErr) - return + if _h != "" { + node, err := h.Nodes.GetByIdentifier(_h) + if err != nil { + log.Err(err).Msgf("error getting node %s and failed to create node query for it", _h) + continue } - expected = append(expected, _h) + expected = append(expected, node.ID) } } + targetNodesID = utils.Intersect(targetNodesID, expected) } - // Remove duplicates from expected - expectedClear := removeStringDuplicates(expected) - // Create new record for query list - for _, nodeUUID := range expectedClear { - node, err := h.Nodes.GetByUUID(nodeUUID) - if err != nil { - log.Err(err).Msgf("error getting node %s and failed to create node query for it", nodeUUID) - continue - } - if err := h.Queries.CreateNodeQuery(node.ID, newQuery.ID); err != nil { - log.Err(err).Msgf("error creating node query for query %s and node %s", newQuery.Name, nodeUUID) + // If the list is empty, we don't need to create node queries + if len(targetNodesID) != 0 { + if err := h.Queries.CreateNodeQueries(targetNodesID, newQuery.ID); err != nil { + log.Err(err).Msgf("error creating node queries for query %s", newQuery.Name) + adminErrorResponse(w, "error creating node queries", http.StatusInternalServerError, err) + return } } // Update value for expected - if err := h.Queries.SetExpected(newQuery.Name, len(expectedClear), env.ID); err != nil { + if err := h.Queries.SetExpected(newQuery.Name, len(targetNodesID), env.ID); err != nil { adminErrorResponse(w, "error setting expected", http.StatusInternalServerError, err) h.Inc(metricAdminErr) return diff --git a/admin/main.go b/admin/main.go index 429e71d5..be3f78d5 100644 --- a/admin/main.go +++ b/admin/main.go @@ -756,6 +756,12 @@ func osctrlAdminService() { log.Err(err).Msg("Error getting all environments") } for _, e := range allEnvs { + // Periotically check if the queries are completed + // not sure if we need to complete the Carves + if err := queriesmgr.CleanupCompletedQueries(e.ID); err != nil { + log.Err(err).Msg("Error completing expired queries") + } + // Periotically check if the queries are expired if err := queriesmgr.CleanupExpiredQueries(e.ID); err != nil { log.Err(err).Msg("Error cleaning up expired queries") } diff --git a/api/handlers/queries.go b/api/handlers/queries.go index 37b7c9e4..75dcd03a 100644 --- a/api/handlers/queries.go +++ b/api/handlers/queries.go @@ -150,17 +150,17 @@ func (h *HandlersApi) QueriesRunHandler(w http.ResponseWriter, r *http.Request) return } - // Temporary list of UUIDs to calculate Expected - var expected []string - // Create targets + // List all the nodes that match the query + var expected []uint + + targetNodesID := []uint{} + // Current logic is to select nodes meeting all criteria in the query + // TODO: I believe we should only allow to list nodes in one environment in URL paths + // We will refactor this part to be tag based queries and add more options to the query if len(q.Environments) > 0 { + expected = []uint{} for _, e := range q.Environments { if (e != "") && h.Envs.Exists(e) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetEnvironment, e); err != nil { - apiErrorResponse(w, "error creating query environment target", http.StatusInternalServerError, err) - h.Inc(metricAPIQueriesErr) - return - } nodes, err := h.Nodes.GetByEnv(e, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { apiErrorResponse(w, "error getting nodes by environment", http.StatusInternalServerError, err) @@ -168,21 +168,18 @@ func (h *HandlersApi) QueriesRunHandler(w http.ResponseWriter, r *http.Request) return } for _, n := range nodes { - expected = append(expected, n.UUID) + expected = append(expected, n.ID) } } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create platform target if len(q.Platforms) > 0 { + expected = []uint{} platforms, _ := h.Nodes.GetAllPlatforms() for _, p := range q.Platforms { if (p != "") && checkValidPlatform(platforms, p) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetPlatform, p); err != nil { - apiErrorResponse(w, "error creating query platform target", http.StatusInternalServerError, err) - h.Inc(metricAPIQueriesErr) - return - } nodes, err := h.Nodes.GetByPlatform(p, "active", h.Settings.InactiveHours(settings.NoEnvironmentID)) if err != nil { apiErrorResponse(w, "error getting nodes by platform", http.StatusInternalServerError, err) @@ -190,55 +187,56 @@ func (h *HandlersApi) QueriesRunHandler(w http.ResponseWriter, r *http.Request) return } for _, n := range nodes { - expected = append(expected, n.UUID) + expected = append(expected, n.ID) } } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create UUIDs target if len(q.UUIDs) > 0 { + expected = []uint{} for _, u := range q.UUIDs { - if (u != "") && h.Nodes.CheckByUUID(u) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetUUID, u); err != nil { - apiErrorResponse(w, "error creating query UUID target", http.StatusInternalServerError, err) - h.Inc(metricAPIQueriesErr) - return + if u != "" { + node, err := h.Nodes.GetByUUID(u) + if err != nil { + log.Warn().Msgf("error getting node %s and failed to create node query for it", u) + continue } - expected = append(expected, u) + expected = append(expected, node.ID) } } + targetNodesID = utils.Intersect(targetNodesID, expected) } // Create hostnames target + // Currently we are using the GetByIdentifier function and it need be more clear + // about the definition of the identifier if len(q.Hosts) > 0 { - for _, _h := range q.Hosts { - if (_h != "") && h.Nodes.CheckByHost(_h) { - if err := h.Queries.CreateTarget(newQuery.Name, queries.QueryTargetLocalname, _h); err != nil { - apiErrorResponse(w, "error creating query hostname target", http.StatusInternalServerError, err) - h.Inc(metricAPIQueriesErr) - return + expected = []uint{} + for _, hostName := range q.Hosts { + if hostName != "" { + node, err := h.Nodes.GetByIdentifier(hostName) + if err != nil { + log.Warn().Msgf("error getting node %s and failed to create node query for it", hostName) + continue } - expected = append(expected, _h) + expected = append(expected, node.ID) } } + targetNodesID = utils.Intersect(targetNodesID, expected) } - // Remove duplicates from expected - expectedClear := removeStringDuplicates(expected) - - // Create new record for query list - for _, nodeUUID := range expectedClear { - node, err := h.Nodes.GetByUUID(nodeUUID) - if err != nil { - log.Err(err).Msgf("error getting node %s and failed to create node query for it", nodeUUID) - continue - } - if err := h.Queries.CreateNodeQuery(node.ID, newQuery.ID); err != nil { - log.Err(err).Msgf("error creating node query for query %s and node %s", newQuery.Name, nodeUUID) + // If the list is empty, we don't need to create node queries + if len(targetNodesID) != 0 { + if err := h.Queries.CreateNodeQueries(targetNodesID, newQuery.ID); err != nil { + log.Err(err).Msgf("error creating node queries for query %s", newQuery.Name) + apiErrorResponse(w, "error creating node queries", http.StatusInternalServerError, err) + return } } // Update value for expected - if err := h.Queries.SetExpected(queryName, len(expectedClear), env.ID); err != nil { + if err := h.Queries.SetExpected(queryName, len(targetNodesID), env.ID); err != nil { apiErrorResponse(w, "error setting expected", http.StatusInternalServerError, err) h.Inc(metricAPICarvesErr) return diff --git a/api/handlers/utils.go b/api/handlers/utils.go index 2c4f63cf..0a642e67 100644 --- a/api/handlers/utils.go +++ b/api/handlers/utils.go @@ -53,18 +53,3 @@ func checkValidPlatform(platforms []string, platform string) bool { } return false } - -// Helper to remove duplicates from []string -func removeStringDuplicates(s []string) []string { - seen := make(map[string]struct{}, len(s)) - i := 0 - for _, v := range s { - if _, ok := seen[v]; ok { - continue - } - seen[v] = struct{}{} - s[i] = v - i++ - } - return s[:i] -} diff --git a/logging/db.go b/logging/db.go index ae483e5f..fa0849a8 100644 --- a/logging/db.go +++ b/logging/db.go @@ -273,14 +273,6 @@ func (logDB *LoggerDB) CleanQueryLogs(entries int64) error { if err := logDB.Database.Conn.Unscoped().Delete(&queriesTargets).Error; err != nil { return err } - // Get query executions - var queriesExecutions []queries.DistributedQueryExecution - if err := logDB.Database.Conn.Where("name = ?", q.Name).Find(&queriesExecutions).Error; err != nil { - return err - } - if err := logDB.Database.Conn.Unscoped().Delete(&queriesExecutions).Error; err != nil { - return err - } // Delete query if err := logDB.Database.Conn.Unscoped().Delete(&q).Error; err != nil { return err diff --git a/logging/process.go b/logging/process.go index 82eebd3e..f1d47f79 100644 --- a/logging/process.go +++ b/logging/process.go @@ -81,18 +81,9 @@ func (l *LoggerTLS) ProcessLogQueryResult(queriesWrite types.QueryWriteRequest, if err != nil { log.Err(err).Msg("error updating query") } - // TODO: This TrackExeuction need be removed - // Add a record for this query - if err := l.Queries.TrackExecution(q, node.UUID, queriesWrite.Statuses[q]); err != nil { - log.Err(err).Msg("error adding query execution") - } - // Instead of creating a new record in a separate table, we can just update the query status + // Update query status if err := l.Queries.UpdateQueryStatus(q, node.ID, queriesWrite.Statuses[q]); err != nil { log.Err(err).Msg("error updating query status") } - // Check if query is completed - if err := l.Queries.VerifyComplete(q, envid); err != nil { - log.Err(err).Msg("error verifying and completing query") - } } } diff --git a/nodes/nodes.go b/nodes/nodes.go index 4bfe99ac..a327a6ca 100644 --- a/nodes/nodes.go +++ b/nodes/nodes.go @@ -347,7 +347,7 @@ func (n *NodeManager) UpdateMetadataByUUID(uuid string, metadata NodeMetadata) e return fmt.Errorf("RecordUsername %v", err) } if metadata.Username != node.Username && metadata.Username != "" { - updates["username"] =metadata.Username + updates["username"] = metadata.Username } // Record hostname if err := n.RecordHostname(metadata.Hostname, node); err != nil { diff --git a/queries/queries.go b/queries/queries.go index 94385039..66a769d6 100644 --- a/queries/queries.go +++ b/queries/queries.go @@ -102,14 +102,6 @@ type DistributedQueryTarget struct { Value string } -// DistributedQueryExecution to keep track of queries executing -type DistributedQueryExecution struct { - gorm.Model - Name string `gorm:"index"` - UUID string `gorm:"index"` - Result int -} - // QueryReadQueries to hold all the on-demand queries type QueryReadQueries map[string]string @@ -131,10 +123,6 @@ func CreateQueries(backend *gorm.DB) *Queries { if err := backend.AutoMigrate(&DistributedQuery{}); err != nil { log.Fatal().Msgf("Failed to AutoMigrate table (distributed_queries): %v", err) } - // table distributed_query_executions - if err := backend.AutoMigrate(&DistributedQueryExecution{}); err != nil { - log.Fatal().Msgf("Failed to AutoMigrate table (distributed_query_executions): %v", err) - } // table distributed_query_targets if err := backend.AutoMigrate(&DistributedQueryTarget{}); err != nil { log.Fatal().Msgf("Failed to AutoMigrate table (distributed_query_targets): %v", err) @@ -309,20 +297,6 @@ func (q *Queries) Complete(name string, envid uint) error { return nil } -// VerifyComplete to mark query as completed if the expected executions are done -func (q *Queries) VerifyComplete(name string, envid uint) error { - query, err := q.Get(name, envid) - if err != nil { - return err - } - if (query.Executions + query.Errors) >= query.Expected { - if err := q.DB.Model(&query).Updates(map[string]interface{}{"completed": true, "active": false}).Error; err != nil { - return err - } - } - return nil -} - // Activate to mark query as active func (q *Queries) Activate(name string, envid uint) error { query, err := q.Get(name, envid) @@ -359,6 +333,23 @@ func (q *Queries) Expire(name string, envid uint) error { return nil } +// CleanupCompletedQueries to set all completed queries as inactive by environment +func (q *Queries) CleanupCompletedQueries(envid uint) error { + qs, err := q.GetQueries(TargetActive, envid) + if err != nil { + return err + } + for _, query := range qs { + executionReached := (query.Executions + query.Errors) >= query.Expected + if executionReached { + if err := q.DB.Model(&query).Updates(map[string]interface{}{"completed": true, "active": false}).Error; err != nil { + return err + } + } + } + return nil +} + // CleanupExpiredQueries to set all expired queries as inactive by environment func (q *Queries) CleanupExpiredQueries(envid uint) error { qs, err := q.GetQueries(TargetActive, envid) @@ -399,13 +390,19 @@ func (q *Queries) Create(query DistributedQuery) error { return nil } -// CreateNodeQuery to link a node to a query -func (q *Queries) CreateNodeQuery(nodeID, queryID uint) error { - nodeQuery := NodeQuery{ - NodeID: nodeID, - QueryID: queryID, +// CreateNodeQueries to link multiple nodes to a query +func (q *Queries) CreateNodeQueries(nodeIDs []uint, queryID uint) error { + if len(nodeIDs) == 0 { + return fmt.Errorf("no nodes to link to query") + } + var nodeQueries []NodeQuery + for _, nodeID := range nodeIDs { + nodeQueries = append(nodeQueries, NodeQuery{ + NodeID: nodeID, + QueryID: queryID, + }) } - if err := q.DB.Create(&nodeQuery).Error; err != nil { + if err := q.DB.Create(&nodeQueries).Error; err != nil { return err } return nil @@ -433,13 +430,6 @@ func (q *Queries) GetTargets(name string) ([]DistributedQueryTarget, error) { return targets, nil } -// NotYetExecuted to check if query already executed or it is within the interval -func (q *Queries) NotYetExecuted(name, uuid string) bool { - var results int64 - q.DB.Model(&DistributedQueryExecution{}).Where("name = ? AND uuid = ?", name, uuid).Count(&results) - return (results == 0) -} - // IncExecution to increase the execution count for this query func (q *Queries) IncExecution(name string, envid uint) error { query, err := q.Get(name, envid) @@ -504,39 +494,3 @@ func (q *Queries) UpdateQueryStatus(queryName string, nodeID uint, statusCode in } return nil } - -// TrackExecution to keep track of where queries have already ran -func (q *Queries) TrackExecution(name, uuid string, result int) error { - queryExecution := DistributedQueryExecution{ - Name: name, - UUID: uuid, - Result: result, - } - if err := q.DB.Create(&queryExecution).Error; err != nil { - return err - } - return nil -} - -// Helper to decide whether if the query targets apply to a give node -func isQueryTarget(node nodes.OsqueryNode, targets []DistributedQueryTarget) bool { - for _, t := range targets { - // Check for environment match - if t.Type == QueryTargetEnvironment && t.Value == node.Environment { - return true - } - // Check for platform match - if t.Type == QueryTargetPlatform && node.Platform == t.Value { - return true - } - // Check for UUID match - if t.Type == QueryTargetUUID && node.UUID == t.Value { - return true - } - // Check for localname match - if t.Type == QueryTargetLocalname && node.Localname == t.Value { - return true - } - } - return false -} diff --git a/queries/queries_test.go b/queries/queries_test.go index cb1e642a..528a3d56 100644 --- a/queries/queries_test.go +++ b/queries/queries_test.go @@ -131,7 +131,8 @@ func TestUpdateQueryStatus(t *testing.T) { assert.Equal(t, queries.DistributedQueryStatusCompleted, updatedNodeQuery.Status, "Status does not match expected value") } -func TestCreateNodeQuery(t *testing.T) { + +func TestCreateNodeQueries(t *testing.T) { db, err := setupTestDB() if err != nil { t.Fatalf("Failed to setup test database: %v", err) @@ -142,9 +143,12 @@ func TestCreateNodeQuery(t *testing.T) { nodes.CreateNodes(db) // Create test data - node := nodes.OsqueryNode{ + node1 := nodes.OsqueryNode{ Model: gorm.Model{ID: 1}, } + node2 := nodes.OsqueryNode{ + Model: gorm.Model{ID: 2}, + } distributedQuery := queries.DistributedQuery{ Model: gorm.Model{ID: 1}, Name: "test_query", @@ -153,24 +157,31 @@ func TestCreateNodeQuery(t *testing.T) { Expiration: time.Now().Add(24 * time.Hour), } - if err := db.Create(&node).Error; err != nil { - t.Fatalf("Failed to create test node: %v", err) + if err := db.Create(&node1).Error; err != nil { + t.Fatalf("Failed to create test node1: %v", err) + } + if err := db.Create(&node2).Error; err != nil { + t.Fatalf("Failed to create test node2: %v", err) } if err := db.Create(&distributedQuery).Error; err != nil { t.Fatalf("Failed to create test distributed query: %v", err) } - // Test CreateNodeQuery function - err = q.CreateNodeQuery(1, 1) + // Test CreateNodeQueries function + nodeIDs := []uint{1, 2} + err = q.CreateNodeQueries(nodeIDs, 1) if err != nil { - t.Fatalf("CreateNodeQuery returned an error: %v", err) + t.Fatalf("CreateNodeQueries returned an error: %v", err) } - var nodeQuery queries.NodeQuery - if err := db.Where("node_id = ? AND query_id = ?", 1, 1).Find(&nodeQuery).Error; err != nil { - t.Fatalf("Failed to find created node query: %v", err) + var nodeQueries []queries.NodeQuery + if err := db.Where("query_id = ?", 1).Find(&nodeQueries).Error; err != nil { + t.Fatalf("Failed to find created node queries: %v", err) } - assert.Equal(t, uint(1), nodeQuery.NodeID, "NodeID does not match expected value") - assert.Equal(t, uint(1), nodeQuery.QueryID, "QueryID does not match expected value") + assert.Len(t, nodeQueries, 2, "Expected 2 node queries to be created") + assert.Equal(t, uint(1), nodeQueries[0].NodeID, "First NodeID does not match expected value") + assert.Equal(t, uint(1), nodeQueries[0].QueryID, "First QueryID does not match expected value") + assert.Equal(t, uint(2), nodeQueries[1].NodeID, "Second NodeID does not match expected value") + assert.Equal(t, uint(1), nodeQueries[1].QueryID, "Second QueryID does not match expected value") } diff --git a/utils/utils.go b/utils/utils.go index 4a15b669..9127d61f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -31,3 +31,28 @@ func RandomForNames() string { _, _ = hasher.Write([]byte(fmt.Sprintf("%x", b))) return hex.EncodeToString(hasher.Sum(nil)) } + +func Intersect(slice1, slice2 []uint) []uint { + if len(slice1) == 0 { + return slice2 + } + // If slice2 is empty, return slice1 + if len(slice2) == 0 { + return slice1 + } + + set := make(map[uint]struct{}) + for _, item := range slice1 { + set[item] = struct{}{} // Add items from slice1 to the set + } + + intersection := []uint{} + for _, item := range slice2 { + if _, exists := set[item]; exists { + intersection = append(intersection, item) + delete(set, item) // Ensure uniqueness in the result + } + } + + return intersection +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 9cfa196c..9c9e35f6 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -1,27 +1,44 @@ -package utils +package utils_test import ( "testing" + "github.com/jmpsec/osctrl/utils" "github.com/stretchr/testify/assert" ) func TestBytesReceivedConversionBytes(t *testing.T) { - assert.NotEmpty(t, BytesReceivedConversion(123)) - assert.Equal(t, "123 bytes", BytesReceivedConversion(123)) + assert.NotEmpty(t, utils.BytesReceivedConversion(123)) + assert.Equal(t, "123 bytes", utils.BytesReceivedConversion(123)) } func TestBytesReceivedConversionKBytes(t *testing.T) { - assert.NotEmpty(t, BytesReceivedConversion(1024)) - assert.Equal(t, "1.0 KB", BytesReceivedConversion(1024)) + assert.NotEmpty(t, utils.BytesReceivedConversion(1024)) + assert.Equal(t, "1.0 KB", utils.BytesReceivedConversion(1024)) } func TestBytesReceivedConversionMBytes(t *testing.T) { - assert.NotEmpty(t, BytesReceivedConversion(1048576)) - assert.Equal(t, "1.0 MB", BytesReceivedConversion(1048576)) + assert.NotEmpty(t, utils.BytesReceivedConversion(1048576)) + assert.Equal(t, "1.0 MB", utils.BytesReceivedConversion(1048576)) } func TestRandomForNames(t *testing.T) { - assert.NotEmpty(t, RandomForNames()) - assert.Equal(t, 32, len(RandomForNames())) + assert.NotEmpty(t, utils.RandomForNames()) + assert.Equal(t, 32, len(utils.RandomForNames())) +} + +func TestIntersect(t *testing.T) { + var slice1 = []uint{1, 2, 3, 4, 5} + var slice2 = []uint{3, 4, 5, 6, 7} + var expected = []uint{3, 4, 5} + assert.Equal(t, expected, utils.Intersect(slice1, slice2)) + slice1 = utils.Intersect(slice1, slice2) + assert.Equal(t, expected, slice1) +} + +func TestIntersectEmpty(t *testing.T) { + var slice1 = []uint{} + var slice2 = []uint{3, 4, 5, 6, 7} + var expected = []uint{3, 4, 5, 6, 7} + assert.Equal(t, expected, utils.Intersect(slice1, slice2)) }