mirror of
https://github.com/fleetdm/fleet
synced 2026-05-16 13:38:43 +00:00
300 lines
10 KiB
Go
300 lines
10 KiB
Go
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.Team{}
|
||
|
||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||
return &appConfig, nil
|
||
}
|
||
|
||
ds.TeamFunc = func(ctx context.Context, tid uint) (*fleet.Team, 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, "Couldn’t add setup experience software. To add software, first disable manual_agent_install.")
|
||
|
||
err = svc.SetSetupExperienceScript(ctx, nil, "potato.sh", scriptReader)
|
||
require.ErrorContains(t, err, "Couldn’t 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, "Couldn’t 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, "Couldn’t 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)
|
||
|
||
}
|