fleet/server/service/middleware/mdmconfigured/mdmconfigured_test.go
Nathan Cochran e5f46dd4b1
Fix android profile download (#35196)
This commit fixes two related bugs with Android MDM:

1. Android profiles now download correctly as .json files instead of
.xml
   - Before: profiles downloaded as .xml with content '[object Object]'
- After: profiles download as .json with properly formatted JSON content
- Fixed by adding Android platform check in createProfileExtension() and
createFileContent()

2. Custom Settings page now recognizes Android MDM
- Before: showed 'MDM must be turned on' error even when Android MDM was
enabled
   - After: properly detects Android MDM and allows profile management
   - Fixed by adding android_enabled_and_configured check to mdmEnabled
   - Backend middleware now supports Android MDM for profile endpoints
a) Added VerifyAnyMDMConfigured() to support Apple, Windows, and Android
MDM
       b) Updated profile endpoints to use VerifyAnyMDM() middleware

**Related issue:** Resolves #35023

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

## Database migrations

_No database migrations in this PR_

## New Fleet configuration settings

_No new Fleet configuration settings in this PR_

## fleetd/orbit/Fleet Desktop

_This PR does not affect fleetd/orbit/Fleet Desktop_
2025-11-17 11:52:59 -03:00

175 lines
4.7 KiB
Go

package mdmconfigured
import (
"context"
"fmt"
"sync/atomic"
"testing"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
// mockService implements the minimal subset of fleet.Service required by the
// middleware tests. Each Verify* method gates execution via simple atomic flags
// so tests can toggle the configured MDM platforms.
type mockService struct {
mock.Mock
fleet.Service
mdmConfigured atomic.Bool
msMdmConfigured atomic.Bool
androidConfigured atomic.Bool
}
// VerifyMDMAppleConfigured marks whether Apple MDM is enabled for the test.
func (m *mockService) VerifyMDMAppleConfigured(ctx context.Context) error {
if !m.mdmConfigured.Load() {
return fleet.ErrMDMNotConfigured
}
return nil
}
// VerifyMDMWindowsConfigured marks whether Windows MDM is enabled for the test.
func (m *mockService) VerifyMDMWindowsConfigured(ctx context.Context) error {
if !m.msMdmConfigured.Load() {
return fleet.ErrWindowsMDMNotConfigured
}
return nil
}
// VerifyAnyMDMConfigured is the mock implementation that mirrors the production
// VerifyAnyMDMConfigured service method, adding Android to the Apple/Windows check.
func (m *mockService) VerifyAnyMDMConfigured(ctx context.Context) error {
if !m.mdmConfigured.Load() && !m.msMdmConfigured.Load() && !m.androidConfigured.Load() {
return fleet.ErrMDMNotConfigured
}
return nil
}
func TestMDMConfigured(t *testing.T) {
svc := mockService{}
svc.mdmConfigured.Store(true)
mw := NewMDMConfigMiddleware(&svc)
nextCalled := false
next := func(ctx context.Context, req interface{}) (interface{}, error) {
nextCalled = true
return struct{}{}, nil
}
f := mw.VerifyAppleMDM()(next)
_, err := f(context.Background(), struct{}{})
require.NoError(t, err)
require.True(t, nextCalled)
}
func TestMDMNotConfigured(t *testing.T) {
svc := mockService{}
svc.mdmConfigured.Store(false)
mw := NewMDMConfigMiddleware(&svc)
nextCalled := false
next := func(ctx context.Context, req interface{}) (interface{}, error) {
nextCalled = true
return struct{}{}, nil
}
f := mw.VerifyAppleMDM()(next)
_, err := f(context.Background(), struct{}{})
require.ErrorIs(t, err, fleet.ErrMDMNotConfigured)
require.False(t, nextCalled)
}
func TestWindowsMDMConfigured(t *testing.T) {
svc := mockService{}
svc.msMdmConfigured.Store(true)
mw := NewMDMConfigMiddleware(&svc)
nextCalled := false
next := func(ctx context.Context, req interface{}) (interface{}, error) {
nextCalled = true
return struct{}{}, nil
}
f := mw.VerifyWindowsMDM()(next)
_, err := f(context.Background(), struct{}{})
require.NoError(t, err)
require.True(t, nextCalled)
}
func TestWindowsMDMNotConfigured(t *testing.T) {
svc := mockService{}
svc.msMdmConfigured.Store(false)
mw := NewMDMConfigMiddleware(&svc)
nextCalled := false
next := func(ctx context.Context, req interface{}) (interface{}, error) {
nextCalled = true
return struct{}{}, nil
}
f := mw.VerifyWindowsMDM()(next)
_, err := f(context.Background(), struct{}{})
require.ErrorIs(t, err, fleet.ErrWindowsMDMNotConfigured)
require.False(t, nextCalled)
}
// TestAnyMDMConfigured exercises the new middleware that recognizes Apple,
// Windows, or Android MDM individually or in combination.
func TestAnyMDMConfigured(t *testing.T) {
svc := mockService{}
mw := NewMDMConfigMiddleware(&svc)
cases := []struct {
apple bool
windows bool
android bool
}{
{apple: true},
{windows: true},
{android: true},
{apple: true, windows: true},
{apple: true, android: true},
{windows: true, android: true},
{apple: true, windows: true, android: true},
}
for _, c := range cases {
t.Run(fmt.Sprintf("apple:%t;windows:%t;android:%t", c.apple, c.windows, c.android), func(t *testing.T) {
svc.mdmConfigured.Store(c.apple)
svc.msMdmConfigured.Store(c.windows)
svc.androidConfigured.Store(c.android)
nextCalled := false
next := func(ctx context.Context, req interface{}) (interface{}, error) {
nextCalled = true
return struct{}{}, nil
}
f := mw.VerifyAnyMDM()(next)
_, err := f(context.Background(), struct{}{})
require.NoError(t, err)
require.True(t, nextCalled)
})
}
}
// TestAnyMDMNotConfigured ensures the new middleware continues to return
// ErrMDMNotConfigured when no platform has MDM enabled.
func TestAnyMDMNotConfigured(t *testing.T) {
svc := mockService{}
mw := NewMDMConfigMiddleware(&svc)
nextCalled := false
next := func(ctx context.Context, req interface{}) (interface{}, error) {
nextCalled = true
return struct{}{}, nil
}
f := mw.VerifyAnyMDM()(next)
_, err := f(context.Background(), struct{}{})
require.ErrorIs(t, err, fleet.ErrMDMNotConfigured)
require.False(t, nextCalled)
}