Pulse/internal/updates/updater_test.go
rcourtman c1fdb77b2c Add unit tests for UpdaterRegistry (internal/updates)
27 test cases covering:
- NewUpdaterRegistry initialization
- Register and Get operations
- Overwrite behavior when re-registering
- Error handling for unregistered deployment types
- Multiple deployment types
- UpdateRequest, UpdatePlan, UpdateProgress struct fields
- Mock updater interface compliance

Coverage 41.1% → 41.5%.
2025-11-30 22:19:06 +00:00

328 lines
8.4 KiB
Go

package updates
import (
"context"
"errors"
"testing"
)
// mockUpdater implements the Updater interface for testing
type mockUpdater struct {
deploymentType string
supportsApply bool
prepareErr error
executeErr error
rollbackErr error
}
func (m *mockUpdater) SupportsApply() bool {
return m.supportsApply
}
func (m *mockUpdater) PrepareUpdate(ctx context.Context, request UpdateRequest) (*UpdatePlan, error) {
if m.prepareErr != nil {
return nil, m.prepareErr
}
return &UpdatePlan{
CanAutoUpdate: m.supportsApply,
}, nil
}
func (m *mockUpdater) Execute(ctx context.Context, request UpdateRequest, progressCb ProgressCallback) error {
return m.executeErr
}
func (m *mockUpdater) Rollback(ctx context.Context, eventID string) error {
return m.rollbackErr
}
func (m *mockUpdater) GetDeploymentType() string {
return m.deploymentType
}
func TestNewUpdaterRegistry(t *testing.T) {
registry := NewUpdaterRegistry()
if registry == nil {
t.Fatal("NewUpdaterRegistry() returned nil")
}
if registry.updaters == nil {
t.Fatal("NewUpdaterRegistry() did not initialize updaters map")
}
}
func TestUpdaterRegistry_Register(t *testing.T) {
registry := NewUpdaterRegistry()
updater := &mockUpdater{deploymentType: "docker"}
registry.Register("docker", updater)
// Verify the updater was registered
got, err := registry.Get("docker")
if err != nil {
t.Fatalf("Get() after Register() failed: %v", err)
}
if got != updater {
t.Error("Get() returned different updater than registered")
}
}
func TestUpdaterRegistry_Register_Overwrites(t *testing.T) {
registry := NewUpdaterRegistry()
updater1 := &mockUpdater{deploymentType: "docker", supportsApply: false}
updater2 := &mockUpdater{deploymentType: "docker", supportsApply: true}
registry.Register("docker", updater1)
registry.Register("docker", updater2)
got, err := registry.Get("docker")
if err != nil {
t.Fatalf("Get() failed: %v", err)
}
if got != updater2 {
t.Error("Register() did not overwrite previous updater")
}
if !got.SupportsApply() {
t.Error("Expected second updater with SupportsApply=true")
}
}
func TestUpdaterRegistry_Get_NotFound(t *testing.T) {
registry := NewUpdaterRegistry()
_, err := registry.Get("nonexistent")
if err == nil {
t.Fatal("Get() should return error for unregistered deployment type")
}
expectedMsg := "no updater registered for deployment type: nonexistent"
if err.Error() != expectedMsg {
t.Errorf("Get() error = %q, want %q", err.Error(), expectedMsg)
}
}
func TestUpdaterRegistry_MultipleDeploymentTypes(t *testing.T) {
registry := NewUpdaterRegistry()
dockerUpdater := &mockUpdater{deploymentType: "docker"}
installshUpdater := &mockUpdater{deploymentType: "install.sh"}
aurUpdater := &mockUpdater{deploymentType: "aur"}
registry.Register("docker", dockerUpdater)
registry.Register("install.sh", installshUpdater)
registry.Register("aur", aurUpdater)
tests := []struct {
deploymentType string
expectedType string
}{
{"docker", "docker"},
{"install.sh", "install.sh"},
{"aur", "aur"},
}
for _, tc := range tests {
t.Run(tc.deploymentType, func(t *testing.T) {
got, err := registry.Get(tc.deploymentType)
if err != nil {
t.Fatalf("Get(%q) failed: %v", tc.deploymentType, err)
}
if got.GetDeploymentType() != tc.expectedType {
t.Errorf("GetDeploymentType() = %q, want %q", got.GetDeploymentType(), tc.expectedType)
}
})
}
}
func TestUpdateRequest_Fields(t *testing.T) {
req := UpdateRequest{
Version: "1.2.3",
Channel: "stable",
Force: true,
DryRun: false,
}
if req.Version != "1.2.3" {
t.Errorf("Version = %q, want %q", req.Version, "1.2.3")
}
if req.Channel != "stable" {
t.Errorf("Channel = %q, want %q", req.Channel, "stable")
}
if !req.Force {
t.Error("Force should be true")
}
if req.DryRun {
t.Error("DryRun should be false")
}
}
func TestUpdatePlan_Fields(t *testing.T) {
plan := UpdatePlan{
CanAutoUpdate: true,
Instructions: []string{"step1", "step2"},
Prerequisites: []string{"prereq1"},
EstimatedTime: "5 minutes",
RequiresRoot: true,
RollbackSupport: true,
DownloadURL: "https://example.com/download",
}
if !plan.CanAutoUpdate {
t.Error("CanAutoUpdate should be true")
}
if len(plan.Instructions) != 2 {
t.Errorf("Instructions length = %d, want 2", len(plan.Instructions))
}
if len(plan.Prerequisites) != 1 {
t.Errorf("Prerequisites length = %d, want 1", len(plan.Prerequisites))
}
if plan.EstimatedTime != "5 minutes" {
t.Errorf("EstimatedTime = %q, want %q", plan.EstimatedTime, "5 minutes")
}
if !plan.RequiresRoot {
t.Error("RequiresRoot should be true")
}
if !plan.RollbackSupport {
t.Error("RollbackSupport should be true")
}
if plan.DownloadURL != "https://example.com/download" {
t.Errorf("DownloadURL = %q, want %q", plan.DownloadURL, "https://example.com/download")
}
}
func TestUpdateProgress_Fields(t *testing.T) {
progress := UpdateProgress{
Stage: "downloading",
Progress: 50,
Message: "Downloading update...",
IsComplete: false,
Error: "",
}
if progress.Stage != "downloading" {
t.Errorf("Stage = %q, want %q", progress.Stage, "downloading")
}
if progress.Progress != 50 {
t.Errorf("Progress = %d, want 50", progress.Progress)
}
if progress.Message != "Downloading update..." {
t.Errorf("Message = %q, want %q", progress.Message, "Downloading update...")
}
if progress.IsComplete {
t.Error("IsComplete should be false")
}
if progress.Error != "" {
t.Errorf("Error = %q, want empty string", progress.Error)
}
}
func TestUpdateProgress_WithError(t *testing.T) {
progress := UpdateProgress{
Stage: "failed",
Progress: 0,
Message: "Update failed",
IsComplete: true,
Error: "connection timeout",
}
if !progress.IsComplete {
t.Error("IsComplete should be true for failed state")
}
if progress.Error != "connection timeout" {
t.Errorf("Error = %q, want %q", progress.Error, "connection timeout")
}
}
func TestMockUpdater_SupportsApply(t *testing.T) {
tests := []struct {
name string
supports bool
}{
{"supports apply", true},
{"does not support apply", false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
updater := &mockUpdater{supportsApply: tc.supports}
if updater.SupportsApply() != tc.supports {
t.Errorf("SupportsApply() = %v, want %v", updater.SupportsApply(), tc.supports)
}
})
}
}
func TestMockUpdater_PrepareUpdate(t *testing.T) {
ctx := context.Background()
req := UpdateRequest{Version: "1.0.0"}
t.Run("success", func(t *testing.T) {
updater := &mockUpdater{supportsApply: true}
plan, err := updater.PrepareUpdate(ctx, req)
if err != nil {
t.Fatalf("PrepareUpdate() failed: %v", err)
}
if !plan.CanAutoUpdate {
t.Error("CanAutoUpdate should be true")
}
})
t.Run("error", func(t *testing.T) {
expectedErr := errors.New("prepare failed")
updater := &mockUpdater{prepareErr: expectedErr}
_, err := updater.PrepareUpdate(ctx, req)
if err != expectedErr {
t.Errorf("PrepareUpdate() error = %v, want %v", err, expectedErr)
}
})
}
func TestMockUpdater_Execute(t *testing.T) {
ctx := context.Background()
req := UpdateRequest{Version: "1.0.0"}
progressCb := func(p UpdateProgress) {}
t.Run("success", func(t *testing.T) {
updater := &mockUpdater{}
err := updater.Execute(ctx, req, progressCb)
if err != nil {
t.Errorf("Execute() error = %v, want nil", err)
}
})
t.Run("error", func(t *testing.T) {
expectedErr := errors.New("execute failed")
updater := &mockUpdater{executeErr: expectedErr}
err := updater.Execute(ctx, req, progressCb)
if err != expectedErr {
t.Errorf("Execute() error = %v, want %v", err, expectedErr)
}
})
}
func TestMockUpdater_Rollback(t *testing.T) {
ctx := context.Background()
t.Run("success", func(t *testing.T) {
updater := &mockUpdater{}
err := updater.Rollback(ctx, "event-123")
if err != nil {
t.Errorf("Rollback() error = %v, want nil", err)
}
})
t.Run("error", func(t *testing.T) {
expectedErr := errors.New("rollback failed")
updater := &mockUpdater{rollbackErr: expectedErr}
err := updater.Rollback(ctx, "event-123")
if err != expectedErr {
t.Errorf("Rollback() error = %v, want %v", err, expectedErr)
}
})
}
func TestMockUpdater_GetDeploymentType(t *testing.T) {
updater := &mockUpdater{deploymentType: "custom-type"}
if got := updater.GetDeploymentType(); got != "custom-type" {
t.Errorf("GetDeploymentType() = %q, want %q", got, "custom-type")
}
}