fleet/server/platform/mysql/testing_utils/testing_utils.go

239 lines
6.8 KiB
Go
Raw Normal View History

package testing_utils
import (
"context"
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
common_mysql "github.com/fleetdm/fleet/v4/server/platform/mysql"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
const (
TestUsername = "root"
TestPassword = "toor"
TestReplicaDatabaseSuffix = "_replica"
)
var TestReplicaAddress = getTestReplicaAddress()
// getTestReplicaAddress returns the MySQL replica test server address from environment variable
// FLEET_MYSQL_REPLICA_TEST_PORT or defaults to localhost:3310
func getTestReplicaAddress() string {
if port := os.Getenv("FLEET_MYSQL_REPLICA_TEST_PORT"); port != "" {
return "localhost:" + port
}
return "localhost:3310"
}
API endpoints for Linux setup experience (#32493) For #32040. --- Backend changes to unblock the development of the orbit and frontend changes. New GET and PUT APIs for setting/getting software for Linux Setup Experience: ``` curl -k -X GET -H "Authorization: Bearer $TEST_TOKEN" https://localhost:8080/api/latest/fleet/setup_experience/linux/software?team_id=8&per_page=3000 curl -k -X PUT -H "Authorization: Bearer $TEST_TOKEN" https://localhost:8080/api/latest/fleet/setup_experience/linux/software -d '{"team_id":8,"software_title_ids":[3000, 3001, 3007]}' ``` New setup_experience/init API called by orbit to trigger the Linux setup experience on the device: ``` curl -v -k -X POST -H "Content-Type: application/json" "https://localhost:8080/api/fleet/orbit/setup_experience/init" -d '{"orbit_node_key": "ynYEtFsvv9xZ7rX619UE8of1I28H+GCj"}' ``` Get status API to call on "My device": ``` curl -v -k -X POST "https://localhost:8080/api/latest/fleet/device/7d940b6e-130a-493b-b58a-2b6e9f9f8bfc/setup_experience/status" ``` --- - [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] 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 ## New Fleet configuration settings - [X] Verified that the setting is exported via `fleetctl generate-gitops` - [X] Verified the setting is documented in a separate PR to [the GitOps documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485) - [X] Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Added Linux support for Setup Experience alongside macOS. - Introduced platform-specific admin APIs to configure and retrieve Setup Experience software (macOS/Linux). - Added device API to report Setup Experience status and an Orbit API to initialize Setup Experience on non-macOS devices. - Setup Experience now gates policy queries on Linux until setup is complete. - New activity log entry when Setup Experience software is edited (includes platform and team). - Documentation - Updated audit logs reference to include the new “edited setup experience software” event. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-04 15:58:47 +00:00
var TestAddress = getTestAddress()
// getTestAddress returns the MySQL test server address from environment variable
// FLEET_MYSQL_TEST_PORT or defaults to localhost:3307
func getTestAddress() string {
if port := os.Getenv("FLEET_MYSQL_TEST_PORT"); port != "" {
return "localhost:" + port
}
return "localhost:3307"
}
// TruncateTables truncates the specified tables, in order, using ds.writer.
// Note that the order is typically not important because FK checks are
// disabled while truncating. If no table is provided, all tables (except
// those that are seeded by the SQL schema file) are truncated.
func TruncateTables(t testing.TB, db *sqlx.DB, logger *slog.Logger, nonEmptyTables map[string]bool, tables ...string) {
// By setting DISABLE_TRUNCATE_TABLES a developer can troubleshoot tests
// by inspecting mysql tables.
if os.Getenv("DISABLE_TRUNCATE_TABLES") != "" {
return
}
ctx := context.Background()
require.NoError(t, common_mysql.WithTxx(ctx, db, func(tx sqlx.ExtContext) error {
var skipSeeded bool
if len(tables) == 0 {
skipSeeded = true
sql := `
SELECT
table_name
FROM
information_schema.tables
WHERE
table_schema = database() AND
table_type = 'BASE TABLE'
`
if err := sqlx.SelectContext(ctx, tx, &tables, sql); err != nil {
return err
}
}
if _, err := tx.ExecContext(ctx, `SET FOREIGN_KEY_CHECKS=0`); err != nil {
return err
}
for _, tbl := range tables {
if nonEmptyTables[tbl] {
if skipSeeded {
continue
}
return fmt.Errorf("cannot truncate table %s, it contains seed data from schema.sql", tbl)
}
if _, err := tx.ExecContext(ctx, "TRUNCATE TABLE "+tbl); err != nil {
return err
}
}
if _, err := tx.ExecContext(ctx, `SET FOREIGN_KEY_CHECKS=1`); err != nil {
return err
}
return nil
}, logger))
}
// DatastoreTestOptions configures how the test datastore is created
// by CreateMySQLDSWithOptions.
type DatastoreTestOptions struct {
// DummyReplica indicates that a read replica test database should be created.
DummyReplica bool
// RunReplication is the function to call to execute the replication of all
// missing changes from the primary to the replica. The function is created
// and set automatically by CreateMySQLDSWithOptions. The test is in full
// control of when the replication is executed. Only applies to DummyReplica.
// Note that not all changes to data show up in the information_schema
// update_time timestamp, so to work around that limitation, explicit table
// names can be provided to force their replication.
RunReplication func(forceTables ...string)
// RealReplica indicates that the replica should be a real DB replica, with a dedicated connection.
RealReplica bool
Add SCEP endpoint for host identity. (#30589) Fixes #30458 Contributor docs PR: https://github.com/fleetdm/fleet/pull/30651 # Checklist for submitter - We will add changes file later. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [x] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Added/updated automated tests - Did not do manual QA since the SCEP client I have doesn't support ECC. Will rely on next subtasks for manual QA. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced Host Identity SCEP (Simple Certificate Enrollment Protocol) support, enabling secure host identity certificate enrollment and management. * Added new API endpoints for Host Identity SCEP, including certificate issuance and retrieval. * Implemented MySQL-backed storage and management for host identity SCEP certificates and serials. * Added new database tables for storing host identity SCEP certificates and serial numbers. * Provided utilities for encoding certificates and keys, and handling ECDSA public keys. * **Bug Fixes** * None. * **Tests** * Added comprehensive integration and unit tests for Host Identity SCEP functionality, including certificate issuance, validation, and error scenarios. * **Chores** * Updated test utilities to support unique test names and new SCEP storage options. * Extended mock datastore and interfaces for new host identity certificate methods. * **Documentation** * Added comments and documentation for new SCEP-related interfaces, methods, and database schema changes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-11 14:44:07 +00:00
UniqueTestName string
}
// LoadDefaultSchema loads the default database schema for testing.
func LoadDefaultSchema(t testing.TB, testName string, opts *DatastoreTestOptions) {
_, thisFile, _, _ := runtime.Caller(0)
schemaPath := filepath.Join(filepath.Dir(thisFile), "../../../datastore/mysql/schema.sql")
LoadSchema(t, testName, opts, schemaPath)
}
// LoadSchema loads a database schema from the specified path for testing.
func LoadSchema(t testing.TB, testName string, opts *DatastoreTestOptions, schemaPath string) {
schema, err := os.ReadFile(schemaPath)
if err != nil {
t.Error(err)
t.FailNow()
}
// execute the schema for the test db, and once more for the replica db if
// that option is set.
dbs := []string{testName}
if opts.DummyReplica {
dbs = append(dbs, testName+TestReplicaDatabaseSuffix)
}
for _, dbName := range dbs {
// Load schema from dumpfile
sqlCommands := fmt.Sprintf(
"DROP DATABASE IF EXISTS %s; CREATE DATABASE %s; USE %s; SET FOREIGN_KEY_CHECKS=0; %s;",
dbName, dbName, dbName, schema,
)
cmd := exec.Command(
"docker", "compose", "exec", "-T", "mysql_test",
// Command run inside container
"mysql",
"-u"+TestUsername, "-p"+TestPassword,
)
cmd.Stdin = strings.NewReader(sqlCommands)
out, err := cmd.CombinedOutput()
if err != nil {
t.Error(err)
t.Error(string(out))
t.FailNow()
}
}
if opts.RealReplica {
// Load schema from dumpfile
sqlCommands := fmt.Sprintf(
"DROP DATABASE IF EXISTS %s; CREATE DATABASE %s; USE %s; SET FOREIGN_KEY_CHECKS=0; %s;",
testName, testName, testName, schema,
)
cmd := exec.Command(
"docker", "compose", "exec", "-T", "mysql_replica_test",
// Command run inside container
"mysql",
"-u"+TestUsername, "-p"+TestPassword,
)
cmd.Stdin = strings.NewReader(sqlCommands)
out, err := cmd.CombinedOutput()
if err != nil {
t.Error(err)
t.Error(string(out))
t.FailNow()
}
}
}
Refactor common_mysql (#37245) <!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #37244 Goal: Make common_mysql package independent of domain packages so it can be reused by future bounded contexts. Changes made: 1. List options decoupling The AppendListOptionsToSQL functions previously required fleet.ListOptions directly. Now common_mysql defines its own interface that describes what a list options type must provide (page number, per-page limit, sort order, etc.). The fleet.ListOptions type implements this interface through new getter methods. This lets any bounded context use the SQL helpers without importing the fleet package. 2. Error types moved Database-specific error types like IsDuplicate and IsChildForeignKeyError were moved from fleet package to common_mysql where they belong. A new http/errors.go file was created for the HTTP-specific error helpers that remain in the platform layer. 3. Configuration restructuring MySQL configuration types and functions were moved to common_mysql/config.go, reducing coupling between packages. 4. Architecture tests added A new arch_test.go file enforces that common_mysql doesn't import domain packages like fleet, preventing future regressions. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [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] Added/updated automated tests - [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 cursor-based pagination support for list queries with improved sorting capabilities including secondary order keys. * **Bug Fixes** * Improved database connection initialization with separate connection management and error handling. * **Refactor** * Consolidated error handling interfaces and decoupled configuration structures for better modularity. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-01-07 22:26:44 +00:00
func MysqlTestConfig(testName string) *common_mysql.MysqlConfig {
return &common_mysql.MysqlConfig{
Username: TestUsername,
Password: TestPassword,
Database: testName,
Address: TestAddress,
}
}
func ProcessOptions(t testing.TB, opts *DatastoreTestOptions) (string, *DatastoreTestOptions) {
if _, ok := os.LookupEnv("MYSQL_TEST"); !ok {
t.Skip("MySQL tests are disabled")
}
if opts == nil {
// so it is never nil in internal helper functions
opts = new(DatastoreTestOptions)
}
if tt, ok := t.(*testing.T); ok && !opts.RealReplica {
tt.Parallel()
}
if opts.RealReplica {
if _, ok := os.LookupEnv("MYSQL_REPLICA_TEST"); !ok {
t.Skip("MySQL replica tests are disabled. Set env var MYSQL_REPLICA_TEST=1 to enable.")
}
}
Add SCEP endpoint for host identity. (#30589) Fixes #30458 Contributor docs PR: https://github.com/fleetdm/fleet/pull/30651 # Checklist for submitter - We will add changes file later. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [x] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [x] Added/updated automated tests - Did not do manual QA since the SCEP client I have doesn't support ECC. Will rely on next subtasks for manual QA. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced Host Identity SCEP (Simple Certificate Enrollment Protocol) support, enabling secure host identity certificate enrollment and management. * Added new API endpoints for Host Identity SCEP, including certificate issuance and retrieval. * Implemented MySQL-backed storage and management for host identity SCEP certificates and serials. * Added new database tables for storing host identity SCEP certificates and serial numbers. * Provided utilities for encoding certificates and keys, and handling ECDSA public keys. * **Bug Fixes** * None. * **Tests** * Added comprehensive integration and unit tests for Host Identity SCEP functionality, including certificate issuance, validation, and error scenarios. * **Chores** * Updated test utilities to support unique test names and new SCEP storage options. * Extended mock datastore and interfaces for new host identity certificate methods. * **Documentation** * Added comments and documentation for new SCEP-related interfaces, methods, and database schema changes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-11 14:44:07 +00:00
var cleanTestName string
if opts.UniqueTestName != "" {
cleanTestName = opts.UniqueTestName
} else {
const numberOfStackFramesFromTest = 3
pc, _, _, ok := runtime.Caller(numberOfStackFramesFromTest)
details := runtime.FuncForPC(pc)
if !ok || details == nil {
t.FailNow()
}
cleanTestName = strings.ReplaceAll(
strings.TrimPrefix(details.Name(), "github.com/fleetdm/fleet/v4/"), "/", "_",
)
}
cleanTestName = strings.ReplaceAll(cleanTestName, ".", "_")
if len(cleanTestName) > 60 {
// the later parts are more unique than the start, with the package names,
// so trim from the start.
cleanTestName = cleanTestName[len(cleanTestName)-60:]
}
return cleanTestName, opts
}