Skip to content

Add tests for serve.go pure helpers (#33370) - #44929

Merged
getvictor merged 5 commits into
fleetdm:mainfrom
raju249:33370-pure-helper-tests
May 12, 2026
Merged

Add tests for serve.go pure helpers (#33370)#44929
getvictor merged 5 commits into
fleetdm:mainfrom
raju249:33370-pure-helper-tests

Conversation

@raju249

@raju249 raju249 commented May 7, 2026

Copy link
Copy Markdown
Contributor

First PR in the staged plan from #33370. Adds unit tests for several testable helpers in cmd/fleet/serve.go — argument stringification, TLS profile config, license initialization, and the missing-migrations warning.

The migrations-warning test required threading io.Writer through printMissingMigrationsWarning so it can pass *bytes.Buffer instead of mutating os.Stdout. The other two database-state print functions stay as-is since they aren't tested in this PR.

Related issue: Part of #33370 (intentionally not using auto-close keywords since this is the first of multiple PRs against this issue).

Checklist for submitter

If some of the following don't apply, delete the relevant line.

Testing

  • Added/updated automated tests

Database migrations

N/A — no database migrations in this PR.

New Fleet configuration settings

N/A — no new configuration settings.

fleetd/orbit/Fleet Desktop

N/A — no agent code changes.

Covers argsToString, getTLSConfig, initLicense, nopPusher.Push, and the
three database/migration print warnings. Adds a small captureStdout helper
reused across the print tests. No production code changes.
@raju249
raju249 requested a review from a team as a code owner May 7, 2026 14:30

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Serve printing helpers were changed to accept an io.Writer and runServeCmd now calls them with os.Stdout. Tests were added in cmd/fleet/serve_test.go: TestArgsToString, TestGetTLSConfig (modern/intermediate), TestInitLicense, and TestPrintMissingMigrationsWarning; test imports were updated to support TLS types, driver.NamedValue, and stdout capture.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding tests for pure helper functions in serve.go, which matches the actual changeset of +169 test lines in serve_test.go.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description clearly outlines the purpose (adding unit tests for testable helpers), references the related issue (#33370), identifies specific functions being tested, explains the io.Writer threading change, and provides context about the staged plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/fleet/serve_test.go`:
- Around line 1478-1498: The captureStdout helper mutates the process-global
os.Stdout without synchronization causing flaky parallel tests; serialize it by
adding a package-level mutex (e.g., captureStdoutMu) and lock it at the start of
captureStdout and unlock only after os.Stdout is restored, so only one test can
swap stdout at a time; also explicitly close the read end (r.Close()) after the
copy goroutine finishes (after <-done) and ensure the write end is closed
(w.Close()) before waiting, keeping the existing helpers and return behavior
intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc219da8-57c9-47b2-9a9a-9379f6eca4a8

📥 Commits

Reviewing files that changed from the base of the PR and between 292bab3 and 82467a5.

📒 Files selected for processing (1)
  • cmd/fleet/serve_test.go

Comment thread cmd/fleet/serve_test.go Outdated
Adds a package-level mutex around the os.Stdout swap so concurrent test
use is safe, and explicitly closes the read end of os.Pipe to avoid a
file-descriptor leak.
@codecov

codecov Bot commented May 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.85%. Comparing base (9bee62b) to head (e7d9070).
⚠️ Report is 148 commits behind head on main.

Files with missing lines Patch % Lines
cmd/fleet/serve.go 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #44929      +/-   ##
==========================================
+ Coverage   66.73%   66.85%   +0.12%     
==========================================
  Files        2684     2689       +5     
  Lines      216067   217154    +1087     
  Branches    10066    10066              
==========================================
+ Hits       144190   145180     +990     
- Misses      58768    58807      +39     
- Partials    13109    13167      +58     
Flag Coverage Δ
backend 68.71% <0.00%> (+0.13%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@raju249

raju249 commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@lucasmrod - Can I please get a review on this PR?

Thanks! 🙏 🙇

@getvictor

Copy link
Copy Markdown
Member

@coderabbitai

  1. Is "capture stdout" a good industry standard pattern here? I feel like it is better to inject stdout for testability and to enable these tests to run in parallel.
  2. Are all the tests here high value?

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look at the test file
fd "serve_test.go" cmd/fleet --exec cat {}

echo "---PRODUCTION CODE---"
# Look at the relevant print functions in serve.go
rg -n "func printDatabaseNotInitializedError|func printMissingMigrationsWarning|func printFleetv4732|func argsToString|func nopPusher|func getTLSConfig|func initLicense" cmd/fleet/serve.go -A 30

Length of output: 56072


@getvictor Great questions. Here's my analysis:


1. captureStdout vs. injecting io.Writer

The captureStdout approach is not the idiomatic Go pattern for this situation. The concerns you raised are valid:

Problems with the current approach:

  • It mutates the global os.Stdout, which is a process-wide side effect.
  • The package-level captureStdoutMu mutex serializes all tests that use captureStdout, meaning those tests cannot run in parallel even if t.Parallel() were added later.
  • It's fragile: any background goroutine (e.g. from the runtime or other tests) that writes to os.Stdout during the swap could corrupt the captured output or produce flaky tests.

The idiomatic Go alternative is to refactor the three print functions to accept an io.Writer:

// Before
func printDatabaseNotInitializedError() {
    fmt.Printf("...", os.Args[0])
}

// After
func printDatabaseNotInitializedError(w io.Writer) {
    fmt.Fprintf(w, "...", os.Args[0])
}

Production call sites pass os.Stdout; tests pass a *bytes.Buffer. This eliminates the mutex entirely, enables t.Parallel(), and is the pattern used throughout the Go standard library itself. The captureStdout mutex is a band-aid around a design issue in the production functions.


2. Are all the tests high value?

Test Value Rationale
TestGetTLSConfig ✅ High Security-critical — validates exact cipher suites, min TLS version, and curve preferences
TestInitLicense ✅ High Real branching logic (dev / expired / no-key), meaningful invariants
TestArgsToString ✅ Medium-High Documents boundary cases (nil, named, positional, mixed types) of a utility formatter
TestPrintMissingMigrationsWarning ⚠️ Medium Worth keeping for the tablesAndDataToString branch coverage (tables-only, data-only, both, neither)
TestPrintDatabaseNotInitializedError ⚠️ Low Just asserts static substrings from a fmt.Printf with no branching logic
TestPrintFleetv4732FixNeededMessage ⚠️ Low Same — the function is a single static fmt.Printf, nothing to go wrong
TestNopPusher ❌ Very Low nopPusher.Push unconditionally returns nil, nil — there is no logic to verify

The two low-value print tests and TestNopPusher could be dropped without meaningfully reducing safety. The effort would be better spent on the io.Writer refactor, which would also make TestPrintMissingMigrationsWarning (the one with actual branches) both simpler and parallelizable.

@getvictor

Copy link
Copy Markdown
Member

@raju249 please address the Code Rabbit comment above.

raju249 added 2 commits May 11, 2026 13:10
Per review feedback, narrowed this PR's scope to test-only additions for
the high-value pure helpers. Removed TestNopPusher and the three
print-function tests along with the captureStdout helper they relied on.
The print-function refactor to accept io.Writer for proper test
injection will land in a separate follow-up PR.
Make the three database-state print functions take an io.Writer so
tests can pass a *bytes.Buffer instead of mutating os.Stdout globally.
Production callers in runServeCmd pass os.Stdout. The migrations-warning
test is restored under this pattern, and all four added tests now run
with t.Parallel() — which the previous captureStdout/mutex approach
could not support.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
cmd/fleet/serve_test.go (1)

1406-1445: ⚡ Quick win

Consider assert.Equal instead of assert.ElementsMatch for order-sensitive slices.

The test uses ElementsMatch for CurvePreferences and CipherSuites, which ignores order. However, the order of these slices determines TLS preference order when PreferServerCipherSuites is true, making it security-critical. Using assert.Equal would catch accidental reordering bugs.

🔒 Suggested hardening for TLS config validation
-		assert.ElementsMatch(t, expectedCurves, cfg.CurvePreferences)
-		assert.ElementsMatch(t, []uint16{
+		assert.Equal(t, expectedCurves, cfg.CurvePreferences)
+		assert.Equal(t, []uint16{
 			tls.TLS_AES_128_GCM_SHA256,
 			tls.TLS_AES_256_GCM_SHA384,
 			tls.TLS_CHACHA20_POLY1305_SHA256,
 			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
 		}, cfg.CipherSuites)

Apply the same change to the "intermediate" subtest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/fleet/serve_test.go` around lines 1406 - 1445, The test TestGetTLSConfig
uses assert.ElementsMatch for order-sensitive fields which hides regressions in
preference order; change the assertions in both subtests to use assert.Equal on
cfg.CurvePreferences and cfg.CipherSuites (and keep the existing checks for
cfg.MinVersion and cfg.PreferServerCipherSuites) so the test compares order as
well as content when calling getTLSConfig; update assertions in the "modern" and
"intermediate" subtests to assert.Equal against the expected ordered slices.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/fleet/serve_test.go`:
- Around line 1406-1445: The test TestGetTLSConfig uses assert.ElementsMatch for
order-sensitive fields which hides regressions in preference order; change the
assertions in both subtests to use assert.Equal on cfg.CurvePreferences and
cfg.CipherSuites (and keep the existing checks for cfg.MinVersion and
cfg.PreferServerCipherSuites) so the test compares order as well as content when
calling getTLSConfig; update assertions in the "modern" and "intermediate"
subtests to assert.Equal against the expected ordered slices.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e7e87c7a-af40-46ad-902c-e2c2b50ae4e2

📥 Commits

Reviewing files that changed from the base of the PR and between 12eefe1 and a8131c0.

📒 Files selected for processing (2)
  • cmd/fleet/serve.go
  • cmd/fleet/serve_test.go

@raju249

raju249 commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai - Can you take a review after your last suggestion?

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

@raju249 — Based on the commit history and AI summary, it looks like you've already addressed both recommendations:

  1. io.Writer refactor ✅ — printDatabaseNotInitializedError, printMissingMigrationsWarning, and printFleetv4732FixNeededMessage now accept an io.Writer; production callers pass os.Stdout and tests pass a *bytes.Buffer.
  2. Low-value tests & captureStdout removed ✅ — TestNopPusher, TestPrintDatabaseNotInitializedError, TestPrintFleetv4732FixNeededMessage, and the captureStdout helper have all been dropped.

Let me trigger a fresh review of the current state of the PR.

✅ Actions performed

Full review triggered.

@raju249

raju249 commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

@getvictor - I think this is ready for another round of review. Addressed suggestions from coderabbit.

Can you take a look, please?

Thanks! 🙏 🙇

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. Just a couple more small changes. Thank you.

Comment thread cmd/fleet/serve.go Outdated
Comment thread cmd/fleet/serve.go Outdated
Comment thread cmd/fleet/serve_test.go Outdated
Comment thread cmd/fleet/serve_test.go Outdated
Comment thread cmd/fleet/serve_test.go Outdated
Comment thread cmd/fleet/serve_test.go Outdated
Comment thread cmd/fleet/serve_test.go Outdated
Comment thread cmd/fleet/serve_test.go Outdated
Comment thread cmd/fleet/serve_test.go Outdated
Revert printDatabaseNotInitializedError and printFleetv4732FixNeededMessage
to their original parameter-less signatures since they have no test coverage.
Keep printMissingMigrationsWarning's io.Writer parameter, which the test
exercises. Drop t.Parallel from subtests per review — outer-level parallel
coverage is sufficient and avoids the per-subtest overhead.
@raju249

raju249 commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @getvictor

This is ready for another round of review.

Thanks! 🙏 🙇

@raju249
raju249 requested a review from getvictor May 12, 2026 06:03

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Thank you.

@getvictor
getvictor merged commit ace3804 into fleetdm:main May 12, 2026
46 of 48 checks passed
getvictor pushed a commit that referenced this pull request May 29, 2026
Extracts the OTEL trace, metric, and log provider setup out of
`runServeCmd` and into `initOTELProviders` in a new `cmd/fleet/otel.go`.
Same pattern as the prior extractions on this issue (#44929, #45343,
#45583, #46166). Side effects (`otel.SetTracerProvider`,
`otel.SetMeterProvider`) are preserved inside the extracted function, so
runtime behavior is identical.

Three unit tests in `cmd/fleet/otel_test.go`:
- OTEL disabled (the common production path) returns `(nil, nil, nil)`
and never calls `initFatal`.
- OTEL enabled without log export returns non-nil trace and meter
providers; logger provider stays nil.
- Log export enabled returns all three providers non-nil.

One honest note on coverage: the four `initFatal` sites inside the
function are paranoid wrapping for OTEL SDK constructors that don't dial
at construction time, so the error paths are hard to drive in tests
without mocking the SDK. The tests above exercise the success paths and
the disabled gate, which is the bulk of the realistic flow.

This continues the path toward `serve.go` >60% coverage per the
discussion on #33370 — `serve.go` is now ~100 lines shorter and the OTEL
phase is testable as a unit. Remaining slices per the broader plan: MDM
Apple init, datastore init, Redis init.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- Changes file: not applicable — internal refactor with no user-visible
behavior change


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

* **Refactor**
* Centralized OpenTelemetry provider initialization into a single setup
path, simplifying startup and shutdown behavior and making observability
configuration clearer.

* **Tests**
* Added unit tests covering disabled/enabled telemetry paths and
optional log export, plus cleanup logic to ensure providers are shut
down correctly.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/46421?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
MagnusHJensen pushed a commit that referenced this pull request Jun 3, 2026
Extracts the Apple MDM initialization out of `runServeCmd` into testable
functions in a new `cmd/fleet/mdm_apple.go`. Continues the chain of
extractions on this issue (#44929, #45343, #45583, #46166, #46421)
toward the `serve.go` >60% coverage target discussed on #33370.

Five functions come out of the inline block:

- `initAppleMDMStorages` — constructs the MDM, DEP, and SCEP storages.
- `initAppleMDMPushService` — picks the no-op pusher under
`FLEET_DEV_MDM_APPLE_DISABLE_PUSH=1`, otherwise the real APNs pusher.
- `checkMDMAssetsExist` — promotes the inline `checkMDMAssets` closure
to a package function. It was already used at several call sites; they
now all share this one.
- `reconcileAppleMDMAPNsAndSCEPAssets` / `reconcileAppleMDMABMAssets` —
the APNs/SCEP and ABM asset reconciliation blocks.

Behavior is preserved — `runServeCmd` calls these in the same order with
the same arguments, and the full `cmd/fleet` suite passes unchanged
against MySQL + Redis. Each function returns early after `initFatal` so
it's also safe when the caller's `initFatal` doesn't terminate (the case
in tests).

On test scope: the new unit tests cover the dev-mode push gate, all four
branches of `checkMDMAssetsExist`, and the no-op and missing-private-key
paths of both reconcilers. The storage construction and the actual
asset-insert paths need a real datastore, so those stay covered by the
existing integration tests rather than new unit tests — I didn't want to
stand up a full datastore mock for paths that are already exercised
end-to-end.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- Changes file: not applicable — internal refactor with no user-visible
behavior change


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

* **New Features**
* Added Apple MDM initialization and configuration management for APNs,
SCEP, and Apple Business Manager with automatic reconciliation of
missing assets and a dev-mode option to disable push.

* **Tests**
* Added unit tests covering push-service behavior, asset-existence
checks, reconciliation logic, and fail-fast handling when required key
material is missing.

* **Refactor**
* Simplified Apple MDM initialization flow by extracting initialization,
push-service, and reconciliation logic into helpers.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
MagnusHJensen pushed a commit that referenced this pull request Jun 4, 2026
Extracts the MySQL datastore initialization out of `runServeCmd` and
into a new `cmd/fleet/datastore.go`. Same pattern as the prior
extractions on this issue (#44929, #45343, #45583, #46166, #46421,
#46517). Continues the path toward `serve.go` >60% coverage per the
discussion on #33370.

Three functions come out of the inline block:

- `initDatastore` — builds the shared DB connections, the datastore, and
the carve store (S3-backed when configured, otherwise the datastore
itself).
- `buildMySQLOpts` — assembles the DB options: base logger and config,
plus the optional read replica, dev SQL interceptor, and tracing.
- `evalMigrationStatus` — prints any operator guidance for the migration
status and returns whether `runServeCmd` should exit. The `os.Exit`
stays in `runServeCmd`, so the boot/refuse-to-boot decision becomes
unit-testable without the function terminating the test binary.

Behavior is preserved — `runServeCmd` calls these in the same order with
the same arguments, the migration-exit conditions are unchanged, and the
full `cmd/fleet` suite passes against MySQL + Redis. `initDatastore`
returns early after `initFatal` so it's safe when the caller's
`initFatal` doesn't terminate (the case in tests).

On test scope: `TestEvalMigrationStatus` covers every migration status
code across the dev-mode and allow-missing-migrations combinations —
that's the real decision logic. I deliberately didn't add unit tests for
`initDatastore`/`buildMySQLOpts`: their only failure paths are paranoid
`initFatal` wrapping around constructors that don't dial at construction
time, and the option builder returns opaque option closures. Those
success paths are already exercised by booting the server, so a full
datastore mock wasn't worth it for coverage's sake.

Remaining slice per the broader plan: Redis init.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- Changes file: not applicable — internal refactor with no user-visible
behavior change

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

## Summary by CodeRabbit

* **Refactor**
* Reorganized database startup initialization and migration status
evaluation for improved maintainability.

* **Tests**
* Added comprehensive test coverage for database migration status
handling across various scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
MagnusHJensen pushed a commit that referenced this pull request Jun 5, 2026
Extracts the Redis pool and the cached_mysql / mysqlredis datastore
wrappers out of `runServeCmd` and into a new `cmd/fleet/redis.go`. Same
pattern as the prior extractions on this issue (#44929, #45343, #45583,
#46166, #46421, #46517, #46742). Continues the path toward `serve.go`
>60% coverage per the discussion on #33370.

Three functions come out of the inline block:

- `initRedis` — builds the Redis pool, wraps the datastore with
`cached_mysql.New`, and applies `mysqlredis.New` with the
license-enforced host limit and host-cache options. Returns the pool,
the fully wrapped `fleet.Datastore`, and the outermost
`*mysqlredis.Datastore` (a few callers need the concrete type).
- `buildRedisPoolConfig` — translates `config.RedisConfig` into the
`redis.PoolConfig`, including the `redis://` scheme strip.
- `validateRedisConfig` — encodes the host-cache invariant:
`HostCacheEnabled` requires `HostCacheTTL > 0`. Returns an error so the
caller (or in this case `initRedis` via `initFatal`) can refuse boot
without that decision being buried inside a pure builder.

Behavior is preserved — `runServeCmd` calls these in the same order with
the same arguments, the host-cache validation still aborts startup when
violated, and the full `cmd/fleet` suite passes against MySQL + Redis.
`initRedis` returns early after `initFatal` so it's safe when the
caller's `initFatal` doesn't terminate (the case in tests). Following
the precedent established on #46742, the caller also has a loud
`initFatal` + `return` guard against a nil pool (covers the same nilaway
flow we hit on the datastore slice).

On test scope: `TestValidateRedisConfig` covers all four combinations of
`HostCacheEnabled` and `HostCacheTTL` — that's the real
boot/refuse-to-boot decision. `TestBuildRedisPoolConfigStripsScheme`
pins the `redis://` scheme-strip contract for Render-style URIs. I
didn't add a `buildRedisPoolConfig` field-mapping matrix or an
`initRedis` happy-path unit test: the former would just re-state the
struct literal, and the latter needs a real Redis pool (the smoke boot
exercises it end-to-end instead).

This completes the four named init-block extractions on this issue. If
further coverage gains are needed beyond what these have already moved,
the next conversation is whether to test `runServeCmd` directly via the
injected `initFatal`.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- Changes file: not applicable — internal refactor with no user-visible
behavior change

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

* **Refactor**
* Consolidated Redis initialization and datastore wrapping into a
dedicated helper; startup now validates the Redis pool and handles
initialization failures explicitly.

* **Tests**
* Added unit tests for Redis address handling and host-cache TTL
validation to ensure config behavior is enforced.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
MagnusHJensen pushed a commit that referenced this pull request Jun 8, 2026
Extracts the osquery status, result, and audit JSON logger setup out of
`runServeCmd` and into a new `cmd/fleet/logging.go`. Same pattern as the
prior extractions on this issue (#44929, #45343, #45583, #46166, #46421,
#46517, #46742, #46830). Continues trimming `runServeCmd` toward the
`serve.go` coverage goal on #33370 — this is the largest single slice so
far (~100 lines out).

Three functions come out of the inline block:

- `initOsqueryLogging` — builds the status and result loggers, plus the
audit logger when enabled. Mutates the shared `logging.Config` per
logger in the same sequence as before, so the constructed loggers are
identical.
- `buildLoggingConfig` — maps `config.FleetConfig` into the common
`logging.Config` shared by all three loggers.
- `shouldEnableAuditLog` — the premium-and-enabled gate for the audit
logger, pulled out so the decision is its own testable unit.

Behavior is preserved — `runServeCmd` calls this in the same place with
the same arguments, the per-logger config mutation order is unchanged,
and the full `cmd/fleet` suite passes against MySQL + Redis.
`initOsqueryLogging` returns early after `initFatal` so it's safe when
the caller's `initFatal` doesn't terminate (the case in tests), and it
guards a nil license up front since the audit gate dereferences it
(matching the nil-guard precedent from #46742/#46830).

On test scope: `TestShouldEnableAuditLog` covers all four combinations
of license tier and the config flag — audit logging is a premium
feature, so the gate is the meaningful decision here.
`TestBuildLoggingConfigMapsConfig` is a light check that the config
mapping is wired through. I didn't add a full `initOsqueryLogging`
happy-path unit test: `logging.NewJSONLogger` constructs real log sinks,
so that path is exercised by booting the server rather than by standing
up logger backends in a unit test.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- Changes file: not applicable — internal refactor with no user-visible
behavior change

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

## Summary by CodeRabbit

* **New Features**
  * Audit logging support is now available for premium license holders.

* **Refactor**
  * Improved logging initialization and configuration management.

* **Tests**
* Added test coverage for audit logging enablement and configuration
mapping.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
MagnusHJensen pushed a commit that referenced this pull request Jun 9, 2026
)

Extracts the geoIP provider and mail service setup out of `runServeCmd`
and into new `cmd/fleet/geoip.go` and `cmd/fleet/mail.go`. Same pattern
as the prior extractions on this issue (#44929, #45343, #45583, #46166,
#46421, #46517, #46742, #46830, #46893). Both are best-effort startup
providers — they log and fall back rather than aborting boot — so they
group naturally.

Functions:

- `initGeoIP` — returns the GeoIP provider. When no database path is
configured, or the MaxMind database fails to load, it returns a no-op
provider and logs rather than aborting startup.
- `initMailService` — configures the mail service; a construction
failure is logged and the (possibly nil) service is returned, matching
the prior best-effort behavior.
- `shouldForceSMTPBackend` — the SMTP-vs-custom-backend rule, pulled out
so the decision is its own testable unit: SMTP and a custom email
backend are mutually exclusive, and an already-enabled SMTP
configuration wins.

Behavior is preserved — `runServeCmd` calls these in the same place with
the same arguments, and the full `cmd/fleet` suite passes against MySQL
+ Redis. The mail block's `config.Email.EmailBackend` reset is local to
mail construction (nothing downstream reads it), so moving it into
`initMailService` is behavior-identical.

On test scope: `TestInitGeoIP` pins the not-fatal fallback for both the
missing-path and invalid-path cases — GeoIP being best-effort is a real
guarantee worth locking. `TestShouldForceSMTPBackend` covers the backend
mutual-exclusion decision, including the nil app config / nil SMTP
settings edges. I didn't add a full `initMailService` happy-path unit
test: `mail.NewService` builds real SMTP/SES backends, so that path is
exercised by booting the server.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- Changes file: not applicable — internal refactor with no user-visible
behavior change

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

## Summary by CodeRabbit

## Release Notes

* **Refactor**
* Improved GeoIP initialization with automatic fallback when database
configuration is unavailable
* Enhanced mail service initialization with better error handling during
startup
  * Refined SMTP backend precedence logic

* **Tests**
* Added comprehensive unit tests for GeoIP and mail service
initialization scenarios

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
MagnusHJensen pushed a commit that referenced this pull request Jun 17, 2026
Extracts the cron schedule registration out of `runServeCmd` and into a
new `cmd/fleet/cron_registration.go`. Same pattern as the prior
extractions on this issue (#44929, #45343, #45583, #46166, #46421,
#46517, #46742, #46830, #46893, #47151). This is the largest slice so
far — `runServeCmd` drops from ~1300 to ~1000 lines, and `serve.go` from
1776 to 1472.

The 33 `StartCronSchedule` registrations move into one
`startCronSchedules` entry point backed by a `cronSchedulesDeps` struct
(the dependencies the closures previously captured from `runServeCmd`).
Registration is grouped by domain:

- `registerCleanupAndMaintenanceCrons` — chart data collection, the
`cron_stats` cleanup goroutine, software migrations, frequent cleanups,
cleanups-then-aggregation, query results cleanup, upcoming activities,
usage statistics, batch activities.
- `registerVulnerabilityCrons` — the vulnerabilities schedule, or the
remote-trigger proxy when processing is disabled on this instance.
- `registerWorkerCrons` — automations and worker integrations.
- `registerMDMCrons` — Apple MDM worker, DEP profile assigner, service
discovery, the Apple/Windows/Android profile managers, the Android
device reconciler, the Android policy migrations, and the APNs pusher.
- `registerPremiumCrons` — iPhone/iPad refetcher and reviver, maintained
apps, VPP app version refresh (and the one-shot VPP country backfill),
recovery lock passwords, managed local account rotation, activities
streaming, and the calendar schedule.
- `registerMiscCrons` — host vitals label membership and the batch
activity completion checker.

Behavior is preserved — the schedules register in the same order with
the same arguments, the same conditionals gate them (premium, audit log,
env vars, software store presence), and the `config` is threaded as a
pointer so the `&config` and `config.Calendar` mutations inside the
calendar closure keep their original semantics. `cmd/fleet/cron.go` (the
schedule definitions) is intentionally untouched; only the wiring moved.

One unit test added: `TestVulnerabilityProcessingDisabled` covers the
vuln enable/disable predicate extracted into
`vulnerabilityProcessingDisabled`, including the legacy
`current_instance_checks` `"0"` value. The rest of the file is
dependency-wiring relocation with no further decision logic to unit-test
— those paths construct real schedules, so they stay covered by the
existing suite and integration tests. The full `cmd/fleet` suite passes
against MySQL + Redis, and a local server boot confirms the same 30 cron
schedules start as before (verified against the "started cron schedules"
log line).

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually (verified via local
server boot — same 30 cron schedules start)
- Changes file: not applicable — internal refactor with no user-visible
behavior change


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

* **Refactor**
* Centralized background cron schedule startup and standardized job
initialization sequencing for maintenance, vulnerability handling,
integrations, MDM workflows, and premium tasks.
* **New Features / Behavior**
* Added config- and license-controlled enablement for vulnerability
processing (local vs remote triggering), MDM automation (including APNs
delivery and device reconciliation), and premium-only refresh/recovery
behaviors.
* Made chart data collection and optional activity streaming
configurable, with safe fallbacks for scheduling periodicity.
* **Tests**
* Added coverage for vulnerability-schedule enable/disable decision
logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
JordanMontgomery pushed a commit that referenced this pull request Jun 24, 2026
Extracts the `/api/` request timeout/body-size override middleware out
of `runServeCmd` and into `apiTimeoutOverrideHandler` in a new
`cmd/fleet/http_middleware.go`. Same pattern as the prior extractions on
this issue (#44929, #45343, #45583, #46166, #46421, #46517, #46742,
#46830, #46893, #47151, #47562). `runServeCmd` drops from ~1000 to ~900
lines, and `serve.go` from 1475 to 1373.

The middleware is the `~100`-line `rootMux.HandleFunc("/api/", ...)`
closure that applies per-route read/write deadline overrides for
endpoints that legitimately run long — synchronous script runs, large
software-installer and bootstrap-package uploads, the Android enterprise
signup SSE stream, and large MDM profile batch operations — and, for
package-upload routes, caps the request body and threads the configured
max installer size through the request context.

Behavior is preserved — the handler is moved verbatim and wired into
`rootMux` via a single `apiTimeoutOverrideHandler(apiHandler, config,
logger)` call, so the same routes get the same overrides and every
request still falls through to `apiHandler.ServeHTTP`. The now-unused
`scripts` and `installersize` imports drop out of `serve.go`.

On test scope: `TestAPITimeoutOverrideHandler` verifies the real
decision in this middleware — that package-upload paths thread the
configured max installer size into the request context (and non-upload
requests keep the default) — and that the wrapped API handler is always
invoked. The deadline overrides themselves go through
`http.ResponseController`, which a unit-test `ResponseRecorder` doesn't
support (the handler logs and proceeds, as in production), so those are
exercised by booting the server rather than asserted in a unit test.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually (verified via local
server boot)
- Changes file: not applicable — internal refactor with no user-visible
behavior change

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

## Summary by CodeRabbit

* **Refactor**
* Improved timeout handling for long-running operations across the API.
Script execution, file uploads, Server-Sent Event streams, and batch
operations now have optimized request timeouts and body size limits.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
juan-fdz-hawa pushed a commit that referenced this pull request Jun 30, 2026
)

Adds an end-to-end boot test for `runServeCmd`, the main server entry
point. This is the coverage milestone for #33370: `serve.go` goes from
~7% to ~64%, and `runServeCmd` itself from 0% to ~62%.

The earlier PRs on this issue (#44929, #45343, #45583, #46166, #46421,
#46517, #46742, #46830, #46893, #47151, #47562, #47891) extracted
testable pieces out of `runServeCmd`, but the function itself stayed at
0% — it blocks on an OS signal and wires the entire server together, so
the only way to cover it is to actually boot it. This PR does that.

`TestRunServeCmd` (gated behind `MYSQL_TEST` + `REDIS_TEST`) boots the
full server against a real migrated test MySQL and Redis, waits for
`/healthz`, then cancels the command context to trigger a graceful
shutdown. It covers two paths:

- **Full boot with Apple MDM enabled** — a 32-byte server private key
brings up the Apple MDM protocol services and the host-identity /
conditional-access SCEP setup, so the boot exercises the MDM startup
path as well as the core wiring, cron schedules, and HTTP server.
- **Fail-fast on bad config** — an invalid Redis host-cache
configuration (enabled with a non-positive TTL) aborts startup through
`initFatal` and returns rather than serving, covering the Redis-init
error path and the nil-pool guard.

Beyond coverage, this doubles as a regression net for the ongoing
`runServeCmd` slicing: a future change that breaks startup now fails
this test instead of reaching a release.

**One production change**, in `runServeCmd`'s shutdown `select`: it now
also watches `cmd.Context().Done()`. This is inert in production — the
root command runs via `Execute()` (not `ExecuteContext()`), so
`cmd.Context()` is `context.Background()` and never cancels. Only the
test runs the command with a cancelable context, which is how it shuts
the server down without sending a real signal (a `SIGTERM` would kill
the test binary).

A couple of notes for reviewers:
- The test uses `os.Setenv` (not `t.Setenv`) because the MySQL test
helper marks the test parallel; the boot scenarios run as serial
subtests so the process-global config env doesn't race.
- `runServeCmd` registers metrics with the process-global Prometheus
registry, which can only happen once per process, so there is a single
full boot here; the error-path scenario fails before that registration.
- The test DB is loaded from a schema dump that doesn't mark every data
migration as applied, so the boot runs with
`FLEET_UPGRADES_ALLOW_MISSING_MIGRATIONS=1`.

It adds ~2s to the `cmd/fleet` (`main`) test bundle, which is well off
the CI critical path.

**Related issue:** Refs #33370

# Checklist for submitter

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually (verified locally:
boots to /healthz, graceful shutdown, ~64% serve.go coverage)
- Changes file: not applicable — internal test coverage with no
user-visible behavior change

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

* **Bug Fixes**
* Improved server shutdown handling to stop cleanly when the running
command’s context is canceled, not only on OS signals.
* Added stronger startup validation to fail fast for invalid Redis
host-cache configuration (e.g., non-positive TTL).
* **Tests**
* Added an end-to-end test that boots the server against real
MySQL/Redis, verifies graceful startup/shutdown, and confirms fast-fail
behavior for misconfiguration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants