Pulse/internal/updates/manager_more_test.go
rcourtman a6a8efaa65 test: Add comprehensive test coverage across packages
New test files with expanded coverage:

API tests:
- ai_handler_test.go: AI handler unit tests with mocking
- agent_profiles_tools_test.go: Profile management tests
- alerts_endpoints_test.go: Alert API endpoint tests
- alerts_test.go: Updated for interface changes
- audit_handlers_test.go: Audit handler tests
- frontend_embed_test.go: Frontend embedding tests
- metadata_handlers_test.go, metadata_provider_test.go: Metadata tests
- notifications_test.go: Updated for interface changes
- profile_suggestions_test.go: Profile suggestion tests
- saml_service_test.go: SAML authentication tests
- sensor_proxy_gate_test.go: Sensor proxy tests
- updates_test.go: Updated for interface changes

Agent tests:
- dockeragent/signature_test.go: Docker agent signature tests
- hostagent/agent_metrics_test.go: Host agent metrics tests
- hostagent/commands_test.go: Command execution tests
- hostagent/network_helpers_test.go: Network helper tests
- hostagent/proxmox_setup_test.go: Updated setup tests
- kubernetesagent/*_test.go: Kubernetes agent tests

Core package tests:
- monitoring/kubernetes_agents_test.go, reload_test.go
- remoteconfig/client_test.go, signature_test.go
- sensors/collector_test.go
- updates/adapter_installsh_*_test.go: Install adapter tests
- updates/manager_*_test.go: Update manager tests
- websocket/hub_*_test.go: WebSocket hub tests

Library tests:
- pkg/audit/export_test.go: Audit export tests
- pkg/metrics/store_test.go: Metrics store tests
- pkg/proxmox/*_test.go: Proxmox client tests
- pkg/reporting/reporting_test.go: Reporting tests
- pkg/server/*_test.go: Server tests
- pkg/tlsutil/extra_test.go: TLS utility tests

Total: ~8000 lines of new test code
2026-01-19 19:26:18 +00:00

173 lines
5 KiB
Go

package updates
import (
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
func TestResolveChannel(t *testing.T) {
manager := NewManager(&config.Config{UpdateChannel: "stable"})
if got := manager.resolveChannel("rc", nil); got != "rc" {
t.Fatalf("expected requested channel, got %s", got)
}
if got := manager.resolveChannel("", nil); got != "stable" {
t.Fatalf("expected config channel, got %s", got)
}
if got := manager.resolveChannel("", &VersionInfo{Channel: "rc"}); got != "stable" {
t.Fatalf("expected config to win, got %s", got)
}
manager.config.UpdateChannel = ""
if got := manager.resolveChannel("", &VersionInfo{Channel: "rc"}); got != "rc" {
t.Fatalf("expected version channel, got %s", got)
}
if got := manager.resolveChannel("", nil); got != "stable" {
t.Fatalf("expected default channel, got %s", got)
}
}
func TestGetCachedUpdateInfo(t *testing.T) {
manager := NewManager(&config.Config{UpdateChannel: "stable"})
expected := &UpdateInfo{Available: true, LatestVersion: "v1.2.3"}
manager.statusMu.Lock()
manager.checkCache["stable"] = expected
manager.cacheTime["stable"] = time.Now()
manager.statusMu.Unlock()
if got := manager.GetCachedUpdateInfo(); got != expected {
t.Fatalf("expected cached info, got %+v", got)
}
}
func TestManagerUpdateStatus(t *testing.T) {
manager := NewManager(&config.Config{})
manager.updateStatus("checking", 12, "progress", errors.New("boom"))
status := manager.GetStatus()
if status.Status != "checking" || status.Progress != 12 || status.Message != "progress" {
t.Fatalf("unexpected status: %+v", status)
}
if status.Error == "" || !strings.Contains(status.Error, "boom") {
t.Fatalf("unexpected status error: %s", status.Error)
}
select {
case got := <-manager.GetProgressChannel():
if got.Status != "checking" || got.Progress != 12 {
t.Fatalf("unexpected progress: %+v", got)
}
default:
t.Fatal("expected progress update on channel")
}
}
func TestConfiguredStageDelay(t *testing.T) {
stageDelayOnce = sync.Once{}
stageDelayValue = 0
t.Setenv("PULSE_UPDATE_STAGE_DELAY_MS", "15")
if got := configuredStageDelay(); got != 15*time.Millisecond {
t.Fatalf("expected 15ms, got %v", got)
}
if got := statusDelayForStage("downloading"); got != 15*time.Millisecond {
t.Fatalf("expected 15ms delay for downloading, got %v", got)
}
if got := statusDelayForStage("idle"); got != 0 {
t.Fatalf("expected 0 delay for idle, got %v", got)
}
stageDelayOnce = sync.Once{}
stageDelayValue = 0
t.Setenv("PULSE_UPDATE_STAGE_DELAY_MS", "bad")
if got := configuredStageDelay(); got != 0 {
t.Fatalf("expected 0 delay for invalid value, got %v", got)
}
}
func TestGetLatestReleaseFromFeedMocked(t *testing.T) {
feed := `<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<entry><title>Pulse v5.0.0-rc.1</title></entry>
<entry><title>Pulse v4.36.2</title></entry>
</feed>`
origTransport := http.DefaultTransport
http.DefaultTransport = roundTripperFunc(func(req *http.Request) (*http.Response, error) {
body := io.NopCloser(strings.NewReader(feed))
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Body: body,
Header: http.Header{"Content-Type": []string{"application/atom+xml"}},
Request: req,
}, nil
})
t.Cleanup(func() { http.DefaultTransport = origTransport })
manager := NewManager(&config.Config{})
release, err := manager.getLatestReleaseFromFeed(context.Background(), "stable")
if err != nil {
t.Fatalf("stable feed error: %v", err)
}
if release.TagName != "v4.36.2" {
t.Fatalf("unexpected stable tag: %s", release.TagName)
}
release, err = manager.getLatestReleaseFromFeed(context.Background(), "rc")
if err != nil {
t.Fatalf("rc feed error: %v", err)
}
if release.TagName != "v5.0.0-rc.1" {
t.Fatalf("unexpected rc tag: %s", release.TagName)
}
feed = `<?xml version="1.0" encoding="UTF-8"?><feed></feed>`
if _, err := manager.getLatestReleaseFromFeed(context.Background(), "stable"); err == nil {
t.Fatal("expected error for empty feed")
}
}
func TestManagerDownloadFile(t *testing.T) {
content := "payload"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(content))
}))
defer server.Close()
manager := NewManager(&config.Config{})
dest := filepath.Join(t.TempDir(), "file.bin")
n, err := manager.downloadFile(context.Background(), server.URL, dest)
if err != nil {
t.Fatalf("downloadFile error: %v", err)
}
if n != int64(len(content)) {
t.Fatalf("expected %d bytes, got %d", len(content), n)
}
data, err := os.ReadFile(dest)
if err != nil {
t.Fatalf("read file error: %v", err)
}
if string(data) != content {
t.Fatalf("unexpected file content: %s", string(data))
}
}