Pulse/internal/ai/tools/docker_updates_test.go
2026-03-18 16:06:30 +00:00

178 lines
5.9 KiB
Go

package tools
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExecuteCheckDockerUpdates(t *testing.T) {
ctx := context.Background()
exec := NewPulseToolExecutor(ExecutorConfig{})
result, err := exec.executeCheckDockerUpdates(ctx, map[string]interface{}{})
require.NoError(t, err)
assert.Equal(t, "Docker update checking not available. Ensure updates provider is configured.", result.Content[0].Text)
updatesProvider := &mockUpdatesProvider{}
exec = NewPulseToolExecutor(ExecutorConfig{UpdatesProvider: updatesProvider})
result, err = exec.executeCheckDockerUpdates(ctx, map[string]interface{}{})
require.NoError(t, err)
assert.True(t, result.IsError)
assert.Contains(t, result.Content[0].Text, "host is required")
state := models.StateSnapshot{
DockerHosts: []models.DockerHost{
{ID: "host1", Hostname: "dock1", DisplayName: "Dock One"},
},
}
updatesProvider.On("TriggerUpdateCheck", "host1").Return(DockerCommandStatus{
ID: "cmd1",
Type: "check",
Status: "queued",
}, nil).Once()
exec = NewPulseToolExecutor(ExecutorConfig{
UpdatesProvider: updatesProvider,
StateProvider: &mockStateProvider{state: state},
})
result, err = exec.executeCheckDockerUpdates(ctx, map[string]interface{}{
"host": "dock1",
})
require.NoError(t, err)
var resp DockerCheckUpdatesResponse
require.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &resp))
assert.Equal(t, "host1", resp.TargetID)
assert.Equal(t, "Dock One", resp.HostName)
assert.Equal(t, "cmd1", resp.CommandID)
updatesProvider.On("TriggerUpdateCheck", "host1").Return(DockerCommandStatus{}, errors.New("boom")).Once()
exec = NewPulseToolExecutor(ExecutorConfig{
UpdatesProvider: updatesProvider,
StateProvider: &mockStateProvider{state: state},
})
result, err = exec.executeCheckDockerUpdates(ctx, map[string]interface{}{
"host": "dock1",
})
require.NoError(t, err)
assert.Contains(t, result.Content[0].Text, "Failed to trigger update check")
}
func TestExecuteCheckDockerUpdates_RetriesTransientError(t *testing.T) {
origSleep := dockerUpdateQueueSleepFn
dockerUpdateQueueSleepFn = func(context.Context, time.Duration) error { return nil }
t.Cleanup(func() { dockerUpdateQueueSleepFn = origSleep })
state := models.StateSnapshot{
DockerHosts: []models.DockerHost{
{ID: "host1", Hostname: "dock1", DisplayName: "Dock One"},
},
}
updatesProvider := &mockUpdatesProvider{}
updatesProvider.On("TriggerUpdateCheck", "host1").Return(DockerCommandStatus{}, errors.New("queue temporarily unavailable")).Once()
updatesProvider.On("TriggerUpdateCheck", "host1").Return(DockerCommandStatus{
ID: "cmd-retry",
Type: "check",
Status: "queued",
}, nil).Once()
exec := NewPulseToolExecutor(ExecutorConfig{
UpdatesProvider: updatesProvider,
StateProvider: &mockStateProvider{state: state},
})
result, err := exec.executeCheckDockerUpdates(context.Background(), map[string]interface{}{
"host": "dock1",
})
require.NoError(t, err)
require.False(t, result.IsError)
var resp DockerCheckUpdatesResponse
require.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &resp))
assert.Equal(t, "cmd-retry", resp.CommandID)
updatesProvider.AssertExpectations(t)
}
func TestExecuteCheckDockerUpdates_DoesNotRetryNonTransientError(t *testing.T) {
origSleep := dockerUpdateQueueSleepFn
dockerUpdateQueueSleepFn = func(context.Context, time.Duration) error { return nil }
t.Cleanup(func() { dockerUpdateQueueSleepFn = origSleep })
state := models.StateSnapshot{
DockerHosts: []models.DockerHost{
{ID: "host1", Hostname: "dock1", DisplayName: "Dock One"},
},
}
updatesProvider := &mockUpdatesProvider{}
updatesProvider.On("TriggerUpdateCheck", "host1").Return(DockerCommandStatus{}, errors.New("invalid host id")).Once()
exec := NewPulseToolExecutor(ExecutorConfig{
UpdatesProvider: updatesProvider,
StateProvider: &mockStateProvider{state: state},
})
result, err := exec.executeCheckDockerUpdates(context.Background(), map[string]interface{}{
"host": "dock1",
})
require.NoError(t, err)
require.False(t, result.IsError)
assert.Contains(t, result.Content[0].Text, "Failed to trigger update check")
assert.NotContains(t, result.Content[0].Text, "after 3 attempts")
updatesProvider.AssertNumberOfCalls(t, "TriggerUpdateCheck", 1)
}
func TestExecuteUpdateDockerContainer_RetriesTransientError(t *testing.T) {
origSleep := dockerUpdateQueueSleepFn
dockerUpdateQueueSleepFn = func(context.Context, time.Duration) error { return nil }
t.Cleanup(func() { dockerUpdateQueueSleepFn = origSleep })
state := models.StateSnapshot{
DockerHosts: []models.DockerHost{
{
ID: "host1",
Hostname: "dock1",
DisplayName: "Dock One",
Containers: []models.DockerContainer{
{ID: "c1", Name: "/nginx"},
},
},
},
}
updatesProvider := &mockUpdatesProvider{}
updatesProvider.On("IsUpdateActionsEnabled").Return(true).Once()
updatesProvider.On("UpdateContainer", "host1", "c1", "nginx").Return(DockerCommandStatus{}, errors.New("i/o timeout")).Once()
updatesProvider.On("UpdateContainer", "host1", "c1", "nginx").Return(DockerCommandStatus{
ID: "cmd-update",
Type: "update",
Status: "queued",
}, nil).Once()
exec := NewPulseToolExecutor(ExecutorConfig{
UpdatesProvider: updatesProvider,
StateProvider: &mockStateProvider{state: state},
ControlLevel: ControlLevelAutonomous,
})
result, err := exec.executeUpdateDockerContainer(context.Background(), map[string]interface{}{
"host": "dock1",
"container": "c1",
})
require.NoError(t, err)
require.False(t, result.IsError)
var resp DockerUpdateContainerResponse
require.NoError(t, json.Unmarshal([]byte(result.Content[0].Text), &resp))
assert.Equal(t, "cmd-update", resp.CommandID)
updatesProvider.AssertExpectations(t)
}