Files
fleet/ee/server/service/setup_experience_test.go
T
Magnus Jensen 96dcaeec1b manual agent install should only block macOS SE software (#38434)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #37008 

# 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
2026-01-20 11:37:39 -05:00

311 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"bytes"
"context"
"io"
"testing"
"github.com/fleetdm/fleet/v4/pkg/optjson"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mock"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSetupExperienceNextStep(t *testing.T) {
ctx := context.Background()
ds := new(mock.Store)
svc := newTestService(t, ds)
requestedInstalls := make(map[uint][]uint)
requestedUpdateSetupExperience := []*fleet.SetupExperienceStatusResult{}
requestedScriptExecution := []*fleet.HostScriptRequestPayload{}
resetIndicators := func() {
ds.InsertSoftwareInstallRequestFuncInvoked = false
ds.InsertHostVPPSoftwareInstallFuncInvoked = false
ds.NewHostScriptExecutionRequestFuncInvoked = false
ds.UpdateSetupExperienceStatusResultFuncInvoked = false
clear(requestedInstalls)
requestedUpdateSetupExperience = []*fleet.SetupExperienceStatusResult{}
requestedScriptExecution = []*fleet.HostScriptRequestPayload{}
}
host1UUID := "123"
host1ID := uint(1)
installerID1 := uint(2)
scriptID1 := uint(3)
scriptContentID1 := uint(4)
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{
MDM: fleet.MDM{
EnabledAndConfigured: true,
},
}, nil
}
ds.IsHostConnectedToFleetMDMFunc = func(ctx context.Context, host *fleet.Host) (bool, error) {
return true, nil
}
var mockListSetupExperience []*fleet.SetupExperienceStatusResult
ds.ListSetupExperienceResultsByHostUUIDFunc = func(ctx context.Context, hostUUID string) ([]*fleet.SetupExperienceStatusResult, error) {
return mockListSetupExperience, nil
}
var mockListHostsLite []*fleet.Host
ds.ListHostsLiteByUUIDsFunc = func(ctx context.Context, filter fleet.TeamFilter, uuids []string) ([]*fleet.Host, error) {
return mockListHostsLite, nil
}
ds.InsertSoftwareInstallRequestFunc = func(ctx context.Context, hostID, softwareInstallerID uint, opts fleet.HostSoftwareInstallOptions) (string, error) {
requestedInstalls[hostID] = append(requestedInstalls[hostID], softwareInstallerID)
return "install-uuid", nil
}
ds.UpdateSetupExperienceStatusResultFunc = func(ctx context.Context, status *fleet.SetupExperienceStatusResult) error {
requestedUpdateSetupExperience = append(requestedUpdateSetupExperience, status)
return nil
}
ds.NewHostScriptExecutionRequestFunc = func(ctx context.Context, request *fleet.HostScriptRequestPayload) (*fleet.HostScriptResult, error) {
requestedScriptExecution = append(requestedScriptExecution, request)
return &fleet.HostScriptResult{
ExecutionID: "script-uuid",
}, nil
}
mockListHostsLite = append(mockListHostsLite, &fleet.Host{UUID: host1UUID, ID: host1ID})
finished, err := svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.True(t, finished)
assert.False(t, ds.InsertSoftwareInstallRequestFuncInvoked)
assert.False(t, ds.InsertHostVPPSoftwareInstallFuncInvoked)
assert.False(t, ds.NewHostScriptExecutionRequestFuncInvoked)
assert.False(t, ds.UpdateSetupExperienceStatusResultFuncInvoked)
resetIndicators()
// Only installer queued
mockListSetupExperience = []*fleet.SetupExperienceStatusResult{
{
HostUUID: host1UUID,
SoftwareInstallerID: &installerID1,
Status: fleet.SetupExperienceStatusPending,
},
}
finished, err = svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.False(t, finished)
assert.True(t, ds.InsertSoftwareInstallRequestFuncInvoked)
assert.False(t, ds.InsertHostVPPSoftwareInstallFuncInvoked)
assert.False(t, ds.NewHostScriptExecutionRequestFuncInvoked)
assert.True(t, ds.UpdateSetupExperienceStatusResultFuncInvoked)
assert.Len(t, requestedInstalls, 1)
assert.Len(t, requestedUpdateSetupExperience, 1)
assert.Equal(t, "install-uuid", *requestedUpdateSetupExperience[0].HostSoftwareInstallsExecutionID)
mockListSetupExperience[0].Status = fleet.SetupExperienceStatusSuccess
finished, err = svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.True(t, finished)
resetIndicators()
// TODO VPP app queueing is better done in an integration
// test, the setup required would be too much
// Only script queued
mockListSetupExperience = []*fleet.SetupExperienceStatusResult{
{
HostUUID: host1UUID,
SetupExperienceScriptID: &scriptID1,
ScriptContentID: &scriptContentID1,
Status: fleet.SetupExperienceStatusPending,
},
}
finished, err = svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.False(t, finished)
assert.False(t, ds.InsertSoftwareInstallRequestFuncInvoked)
assert.False(t, ds.InsertHostVPPSoftwareInstallFuncInvoked)
assert.True(t, ds.NewHostScriptExecutionRequestFuncInvoked)
assert.True(t, ds.UpdateSetupExperienceStatusResultFuncInvoked)
assert.Len(t, requestedScriptExecution, 1)
assert.Len(t, requestedUpdateSetupExperience, 1)
assert.Equal(t, "script-uuid", *requestedUpdateSetupExperience[0].ScriptExecutionID)
mockListSetupExperience[0].Status = fleet.SetupExperienceStatusSuccess
finished, err = svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.True(t, finished)
resetIndicators()
// Both installer and script
mockListSetupExperience = []*fleet.SetupExperienceStatusResult{
{
HostUUID: host1UUID,
SoftwareInstallerID: &installerID1,
Status: fleet.SetupExperienceStatusPending,
},
{
HostUUID: host1UUID,
SetupExperienceScriptID: &scriptID1,
ScriptContentID: &scriptContentID1,
Status: fleet.SetupExperienceStatusPending,
},
}
// Only installer is queued
finished, err = svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.False(t, finished)
assert.True(t, ds.InsertSoftwareInstallRequestFuncInvoked)
assert.False(t, ds.InsertHostVPPSoftwareInstallFuncInvoked)
assert.False(t, ds.NewHostScriptExecutionRequestFuncInvoked)
assert.True(t, ds.UpdateSetupExperienceStatusResultFuncInvoked)
assert.Len(t, requestedInstalls, 1)
assert.Len(t, requestedScriptExecution, 0)
assert.Len(t, requestedUpdateSetupExperience, 1)
// install finished, call it again. This time script is queued
mockListSetupExperience[0].Status = fleet.SetupExperienceStatusSuccess
finished, err = svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.False(t, finished)
assert.True(t, ds.InsertSoftwareInstallRequestFuncInvoked)
assert.False(t, ds.InsertHostVPPSoftwareInstallFuncInvoked)
assert.True(t, ds.NewHostScriptExecutionRequestFuncInvoked)
assert.True(t, ds.UpdateSetupExperienceStatusResultFuncInvoked)
assert.Len(t, requestedInstalls, 1)
assert.Len(t, requestedScriptExecution, 1)
assert.Len(t, requestedUpdateSetupExperience, 2)
// both finished, now we're done
mockListSetupExperience[1].Status = fleet.SetupExperienceStatusFailure
finished, err = svc.SetupExperienceNextStep(ctx, &fleet.Host{
UUID: host1UUID,
Platform: "darwin",
})
require.NoError(t, err)
assert.True(t, finished)
}
func TestSetupExperienceSetWithManualAgentInstall(t *testing.T) {
ctx := test.UserContext(context.Background(), test.UserAdmin)
ds := new(mock.Store)
svc, baseSvc := newTestServiceWithMock(t, ds)
appConfig := fleet.AppConfig{}
team := fleet.TeamLite{}
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &appConfig, nil
}
ds.TeamLiteFunc = func(ctx context.Context, tid uint) (*fleet.TeamLite, error) {
return &team, nil
}
ds.SetSetupExperienceSoftwareTitlesFunc = func(ctx context.Context, platform string, teamID uint, titleIDs []uint) error {
return nil
}
ds.ValidateEmbeddedSecretsFunc = func(ctx context.Context, documents []string) error {
return nil
}
ds.SetSetupExperienceScriptFunc = func(ctx context.Context, script *fleet.Script) error {
return nil
}
baseSvc.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
return nil
}
// No manual agent install, we good
// No team
err := svc.SetSetupExperienceSoftware(ctx, "darwin", 0, []uint{1, 2})
require.NoError(t, err)
scriptReader := bytes.NewReader([]byte("hello"))
err = svc.SetSetupExperienceScript(ctx, nil, "potato.sh", scriptReader)
require.NoError(t, err)
_, _ = scriptReader.Seek(0, io.SeekStart)
// Team
err = svc.SetSetupExperienceSoftware(ctx, "darwin", 1, []uint{1, 2})
require.NoError(t, err)
err = svc.SetSetupExperienceScript(ctx, ptr.Uint(1), "potato.sh", scriptReader)
require.NoError(t, err)
_, _ = scriptReader.Seek(0, io.SeekStart)
// Manual agent install
appConfig.MDM.MacOSSetup.ManualAgentInstall = optjson.SetBool(true)
team.Config.MDM.MacOSSetup.ManualAgentInstall = optjson.SetBool(true)
// No team
err = svc.SetSetupExperienceSoftware(ctx, "darwin", 0, []uint{1, 2})
require.ErrorContains(t, err, "Couldnt add setup experience software. To add software, first disable manual_agent_install.")
err = svc.SetSetupExperienceScript(ctx, nil, "potato.sh", scriptReader)
require.ErrorContains(t, err, "Couldnt add setup experience script. To add script, first disable manual_agent_install.")
_, _ = scriptReader.Seek(0, io.SeekStart)
// Team
err = svc.SetSetupExperienceSoftware(ctx, "darwin", 1, []uint{1, 2})
require.ErrorContains(t, err, "Couldnt add setup experience software. To add software, first disable manual_agent_install.")
err = svc.SetSetupExperienceScript(ctx, ptr.Uint(1), "potato.sh", scriptReader)
require.ErrorContains(t, err, "Couldnt add setup experience script. To add script, first disable manual_agent_install.")
_, _ = scriptReader.Seek(0, io.SeekStart)
// We can still set software to none though
err = svc.SetSetupExperienceSoftware(ctx, "darwin", 0, []uint{})
require.NoError(t, err)
err = svc.SetSetupExperienceSoftware(ctx, "darwin", 1, []uint{})
require.NoError(t, err)
t.Run("should not block for non darwin hosts", func(t *testing.T) {
for _, platform := range fleet.SetupExperienceSupportedPlatforms {
if platform == "darwin" {
continue
}
err := svc.SetSetupExperienceSoftware(ctx, platform, 0, []uint{1, 2})
require.NoError(t, err)
}
})
}