fleet/server/activity/internal/testutils/testutils.go
Victor Lyuboslavsky 61f635dd44
Activity bounded context: Complete read operations (#38555)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #38534

moved `/api/_version_/fleet/hosts/{id:[0-9]+}/activities` endpoint and
`MarkActivitiesAsStreamed` to activity bounded context

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added new endpoint to retrieve host-specific past activities with
pagination metadata.
  
* **Refactor**
* Refactored activity service architecture and authorization layer to
improve data provider integration and activity streaming capabilities.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-09 15:29:12 -06:00

135 lines
4.2 KiB
Go

// Package testutils provides shared test utilities for the activity bounded context.
package testutils
import (
"encoding/json"
"testing"
"time"
common_mysql "github.com/fleetdm/fleet/v4/server/platform/mysql"
mysql_testing_utils "github.com/fleetdm/fleet/v4/server/platform/mysql/testing_utils"
"github.com/go-kit/log"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
// TestDB holds the database connection for tests.
type TestDB struct {
DB *sqlx.DB
Logger log.Logger
}
// SetupTestDB creates a test database with the Fleet schema loaded.
func SetupTestDB(t *testing.T, testNamePrefix string) *TestDB {
t.Helper()
testName, opts := mysql_testing_utils.ProcessOptions(t, &mysql_testing_utils.DatastoreTestOptions{
UniqueTestName: testNamePrefix + "_" + t.Name(),
})
mysql_testing_utils.LoadDefaultSchema(t, testName, opts)
config := mysql_testing_utils.MysqlTestConfig(testName)
db, err := common_mysql.NewDB(config, &common_mysql.DBOptions{}, "")
require.NoError(t, err)
t.Cleanup(func() { db.Close() })
return &TestDB{
DB: db,
Logger: log.NewNopLogger(),
}
}
// Conns returns DBConnections for creating a datastore.
func (tdb *TestDB) Conns() *common_mysql.DBConnections {
return &common_mysql.DBConnections{Primary: tdb.DB, Replica: tdb.DB}
}
// TruncateTables clears the tables used by activity bounded context.
func (tdb *TestDB) TruncateTables(t *testing.T) {
t.Helper()
mysql_testing_utils.TruncateTables(t, tdb.DB, tdb.Logger, nil, "host_activities", "activities", "hosts", "users")
}
// InsertUser creates a user in the database and returns the user ID.
func (tdb *TestDB) InsertUser(t *testing.T, name, email string) uint {
t.Helper()
ctx := t.Context()
result, err := tdb.DB.ExecContext(ctx, `
INSERT INTO users (name, email, password, salt, created_at, updated_at)
VALUES (?, ?, 'password', 'salt', NOW(), NOW())
`, name, email)
require.NoError(t, err)
id, err := result.LastInsertId()
require.NoError(t, err)
return uint(id)
}
// InsertActivity creates an activity in the database and returns the activity ID.
// Pass nil for userID to create a system activity (no associated user).
func (tdb *TestDB) InsertActivity(t *testing.T, userID *uint, activityType string, details map[string]any) uint {
t.Helper()
return tdb.InsertActivityWithTime(t, userID, activityType, details, time.Now().UTC())
}
// InsertActivityWithTime creates an activity with a specific timestamp.
// Pass nil for userID to create a system activity (no associated user).
func (tdb *TestDB) InsertActivityWithTime(t *testing.T, userID *uint, activityType string, details map[string]any, createdAt time.Time) uint {
t.Helper()
ctx := t.Context()
detailsJSON, err := json.Marshal(details)
require.NoError(t, err)
var userName *string
var userEmail string // NOT NULL DEFAULT '' in schema
if userID != nil {
var user struct {
Name string `db:"name"`
Email string `db:"email"`
}
err = sqlx.GetContext(ctx, tdb.DB, &user, "SELECT name, email FROM users WHERE id = ?", *userID)
require.NoError(t, err)
userName = &user.Name
userEmail = user.Email
}
result, err := tdb.DB.ExecContext(ctx, `
INSERT INTO activities (user_id, user_name, user_email, activity_type, details, created_at, host_only, streamed)
VALUES (?, ?, ?, ?, ?, ?, false, false)
`, userID, userName, userEmail, activityType, detailsJSON, createdAt)
require.NoError(t, err)
id, err := result.LastInsertId()
require.NoError(t, err)
return uint(id)
}
// InsertHost creates a host in the database and returns the host ID.
func (tdb *TestDB) InsertHost(t *testing.T, hostname string, teamID *uint) uint {
t.Helper()
ctx := t.Context()
result, err := tdb.DB.ExecContext(ctx, `
INSERT INTO hosts (hostname, team_id, created_at, updated_at)
VALUES (?, ?, NOW(), NOW())
`, hostname, teamID)
require.NoError(t, err)
id, err := result.LastInsertId()
require.NoError(t, err)
return uint(id)
}
// InsertHostActivity creates a link between a host and an activity in the host_activities junction table.
func (tdb *TestDB) InsertHostActivity(t *testing.T, hostID, activityID uint) {
t.Helper()
ctx := t.Context()
_, err := tdb.DB.ExecContext(ctx, `
INSERT INTO host_activities (host_id, activity_id) VALUES (?, ?)
`, hostID, activityID)
require.NoError(t, err)
}