fleet/server/mdm/apple/apple_mdm_test.go
Victor Lyuboslavsky aaac4b1dfe
Changes needed before gokit/log to slog transition. (#39527)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #38889

PLEASE READ BELOW before looking at file changes

Before converting individual files/packages to slog, we generally need
to make these 2 changes to make the conversion easier:
- Replace uses of `kitlog.With` since they are not fully compatible with
our kitlog adapter
- Directly use the kitlog adapter logger type instead of the kitlog
interface, which will let us have direct access to the underlying slog
logger: `*logging.Logger`

Note: that I did not replace absolutely all uses of `kitlog.Logger`, but
I did remove all uses of `kitlog.With` except for these due to
complexity:
- server/logging/filesystem.go and the other log writers (webhook,
firehose, kinesis, lambda, pubsub, nats)
- server/datastore/mysql/nanomdm_storage.go (adapter pattern)
- server/vulnerabilities/nvd/* (cascades to CLI tools)
- server/service/osquery_utils/queries.go (callback type signatures
cascade broadly)
- cmd/maintained-apps/ (standalone, so can be transitioned later all at
once)

Most of the changes in this PR follow these patterns:
- `kitlog.Logger` type → `*logging.Logger`
- `kitlog.With(logger, ...)` → `logger.With(...)`
- `kitlog.NewNopLogger() → logging.NewNopLogger()`, including similar
variations such as `logging.NewLogfmtLogger(w)` and
`logging.NewJSONLogger(w)`
- removed many now-unused kitlog imports

Unique changes that the PR review should focus on:
- server/platform/logging/kitlog_adapter.go: Core adapter changes
- server/platform/logging/logging.go: New convenience functions
- server/service/integration_logger_test.go: Test changes for slog

# 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`.
  - Was added in previous PR

## 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

* **Refactor**
* Migrated the codebase to a unified internal structured logging system
for more consistent, reliable logs and observability.
* No user-facing functionality changed; runtime behavior and APIs remain
compatible.
* **Tests**
* Updated tests to use the new logging helpers to ensure consistent test
logging and validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-11 10:08:33 -06:00

275 lines
8.2 KiB
Go

package apple_mdm
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep"
"github.com/fleetdm/fleet/v4/server/mock"
nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep"
"github.com/fleetdm/fleet/v4/server/platform/logging"
"github.com/micromdm/plist"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDEPService(t *testing.T) {
t.Run("EnsureDefaultSetupAssistant", func(t *testing.T) {
ds := new(mock.Store)
ctx := context.Background()
logger := logging.NewNopLogger()
depStorage := new(nanodep_mock.Storage)
depSvc := NewDEPService(ds, depStorage, logger)
defaultProfile := depSvc.getDefaultProfile()
serverURL := "https://example.com/"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
switch r.URL.Path {
case "/session":
_, _ = w.Write([]byte(`{"auth_session_token": "xyz"}`))
case "/profile":
_, _ = w.Write([]byte(`{"profile_uuid": "abcd"}`))
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
var got godep.Profile
err = json.Unmarshal(body, &got)
require.NoError(t, err)
require.Contains(t, got.URL, serverURL+"api/mdm/apple/enroll?token=")
assert.Empty(t, got.ConfigurationWebURL)
got.URL = ""
got.ConfigurationWebURL = ""
defaultProfile.AwaitDeviceConfigured = true // this is now always set to true
require.Equal(t, defaultProfile, &got)
default:
require.Fail(t, "unexpected path: %s", r.URL.Path)
}
}))
t.Cleanup(srv.Close)
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
appCfg := &fleet.AppConfig{}
appCfg.ServerSettings.ServerURL = serverURL
return appCfg, nil
}
var savedProfile *fleet.MDMAppleEnrollmentProfile
ds.NewMDMAppleEnrollmentProfileFunc = func(ctx context.Context, p fleet.MDMAppleEnrollmentProfilePayload) (*fleet.MDMAppleEnrollmentProfile, error) {
require.Equal(t, fleet.MDMAppleEnrollmentTypeAutomatic, p.Type)
require.NotEmpty(t, p.Token)
res := &fleet.MDMAppleEnrollmentProfile{
Token: p.Token,
Type: p.Type,
DEPProfile: p.DEPProfile,
UpdateCreateTimestamps: fleet.UpdateCreateTimestamps{
UpdateTimestamp: fleet.UpdateTimestamp{UpdatedAt: time.Now()},
},
}
savedProfile = res
return res, nil
}
ds.GetMDMAppleEnrollmentProfileByTypeFunc = func(ctx context.Context, typ fleet.MDMAppleEnrollmentType) (*fleet.MDMAppleEnrollmentProfile, error) {
require.Equal(t, fleet.MDMAppleEnrollmentTypeAutomatic, typ)
if savedProfile == nil {
return nil, notFoundError{}
}
return savedProfile, nil
}
var defaultProfileUUID string
ds.GetMDMAppleDefaultSetupAssistantFunc = func(ctx context.Context, teamID *uint, orgName string) (profileUUID string, updatedAt time.Time, err error) {
if defaultProfileUUID == "" {
return "", time.Time{}, nil
}
return defaultProfileUUID, time.Now(), nil
}
ds.SetMDMAppleDefaultSetupAssistantProfileUUIDFunc = func(ctx context.Context, teamID *uint, profileUUID, orgName string) error {
require.Nil(t, teamID)
defaultProfileUUID = profileUUID
return nil
}
ds.SaveAppConfigFunc = func(ctx context.Context, info *fleet.AppConfig) error {
return nil
}
depStorage.RetrieveConfigFunc = func(ctx context.Context, name string) (*client.Config, error) {
return &client.Config{BaseURL: srv.URL}, nil
}
depStorage.RetrieveAuthTokensFunc = func(ctx context.Context, name string) (*client.OAuth1Tokens, error) {
return &client.OAuth1Tokens{}, nil
}
depStorage.StoreAssignerProfileFunc = func(ctx context.Context, name string, profileUUID string) error {
require.NotEmpty(t, profileUUID)
return nil
}
ds.GetABMTokenOrgNamesAssociatedWithTeamFunc = func(ctx context.Context, teamID *uint) ([]string, error) {
return []string{"org1"}, nil
}
ds.CountABMTokensWithTermsExpiredFunc = func(ctx context.Context) (int, error) {
return 0, nil
}
profUUID, modTime, err := depSvc.EnsureDefaultSetupAssistant(ctx, nil, "org1")
require.NoError(t, err)
require.Equal(t, "abcd", profUUID)
require.NotZero(t, modTime)
require.True(t, ds.NewMDMAppleEnrollmentProfileFuncInvoked)
require.True(t, ds.GetMDMAppleEnrollmentProfileByTypeFuncInvoked)
require.True(t, ds.GetMDMAppleDefaultSetupAssistantFuncInvoked)
require.True(t, ds.SetMDMAppleDefaultSetupAssistantProfileUUIDFuncInvoked)
require.True(t, depStorage.RetrieveConfigFuncInvoked)
require.False(t, depStorage.StoreAssignerProfileFuncInvoked) // not used anymore
})
t.Run("EnrollURL", func(t *testing.T) {
const serverURL = "https://example.com/"
appCfg := &fleet.AppConfig{}
appCfg.ServerSettings.ServerURL = serverURL
url, err := EnrollURL("token", appCfg)
require.NoError(t, err)
require.Equal(t, url, serverURL+"api/mdm/apple/enroll?token=token")
})
}
func TestAddEnrollmentRefToFleetURL(t *testing.T) {
const (
baseFleetURL = "https://example.com"
reference = "enroll-ref"
)
tests := []struct {
name string
fleetURL string
reference string
expectedOutput string
expectError bool
}{
{
name: "empty Reference",
fleetURL: baseFleetURL,
reference: "",
expectedOutput: baseFleetURL,
expectError: false,
},
{
name: "valid URL and Reference",
fleetURL: baseFleetURL,
reference: reference,
expectedOutput: baseFleetURL + "?" + mobileconfig.FleetEnrollReferenceKey + "=" + reference,
expectError: false,
},
{
name: "invalid URL",
fleetURL: "://invalid-url",
reference: reference,
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
output, err := AddEnrollmentRefToFleetURL(tc.fleetURL, tc.reference)
if tc.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tc.expectedOutput, output)
}
})
}
}
func TestGenerateEnrollmentProfileMobileconfig(t *testing.T) {
type scepPayload struct {
Challenge string
URL string
}
type enrollmentPayload struct {
PayloadType string
ServerURL string // used by the enrollment payload
PayloadContent scepPayload // scep contains a nested payload content dict
}
type enrollmentProfile struct {
PayloadIdentifier string
PayloadContent []enrollmentPayload
}
tests := []struct {
name string
orgName string
fleetURL string
scepChallenge string
expectError bool
}{
{
name: "valid input with simple values",
orgName: "Fleet",
fleetURL: "https://example.com",
scepChallenge: "testChallenge",
expectError: false,
},
{
name: "organization name and enroll secret with special characters",
orgName: `Fleet & Co. "Special" <Org>`,
fleetURL: "https://example.com",
scepChallenge: "test/&Challenge",
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := GenerateEnrollmentProfileMobileconfig(tt.orgName, tt.fleetURL, tt.scepChallenge, "com.foo.bar")
if tt.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.NotNil(t, result)
var profile enrollmentProfile
require.NoError(t, plist.Unmarshal(result, &profile))
for _, p := range profile.PayloadContent {
switch p.PayloadType {
case "com.apple.security.scep":
scepURL, err := ResolveAppleSCEPURL(tt.fleetURL)
require.NoError(t, err)
require.Equal(t, scepURL, p.PayloadContent.URL)
require.Equal(t, tt.scepChallenge, p.PayloadContent.Challenge)
case "com.apple.mdm":
mdmURL, err := ResolveAppleMDMURL(tt.fleetURL)
require.NoError(t, err)
require.Contains(t, mdmURL, p.ServerURL)
default:
require.Failf(t, "unrecognized payload type in enrollment profile: %s", p.PayloadType)
}
}
}
})
}
}
type notFoundError struct{}
func (e notFoundError) IsNotFound() bool { return true }
func (e notFoundError) Error() string { return "not found" }