test: Improve discovery and Docker agent test coverage

This commit is contained in:
rcourtman
2025-12-29 23:37:10 +00:00
parent 4225f905b0
commit e42cbe38f0
8 changed files with 684 additions and 21 deletions
+3 -1
View File
@@ -11,11 +11,13 @@ import (
"github.com/rcourtman/pulse-go-rewrite/pkg/discovery/envdetect"
)
var detectEnvironmentFn = envdetect.DetectEnvironment
// BuildScanner creates a discovery scanner configured using the supplied discovery config.
func BuildScanner(cfg config.DiscoveryConfig) (*pkgdiscovery.Scanner, error) {
cfg = config.NormalizeDiscoveryConfig(cfg)
profile, err := envdetect.DetectEnvironment()
profile, err := detectEnvironmentFn()
if err != nil {
return nil, err
}
+270
View File
@@ -1,13 +1,29 @@
package discovery
import (
"errors"
"net"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
pkgdiscovery "github.com/rcourtman/pulse-go-rewrite/pkg/discovery"
"github.com/rcourtman/pulse-go-rewrite/pkg/discovery/envdetect"
)
func mustCIDR(t *testing.T, value string) net.IPNet {
t.Helper()
_, cidr, err := net.ParseCIDR(value)
if err != nil {
t.Fatalf("parse CIDR %s: %v", value, err)
}
return *cidr
}
func resetDetectEnvironment() {
detectEnvironmentFn = envdetect.DetectEnvironment
}
func TestParseCIDRs(t *testing.T) {
tests := []struct {
name string
@@ -271,6 +287,260 @@ func TestEnvironmentFromOverride(t *testing.T) {
}
}
func TestBuildScanner(t *testing.T) {
t.Cleanup(resetDetectEnvironment)
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Phases: []envdetect.SubnetPhase{{Name: "local", Subnets: []net.IPNet{mustCIDR(t, "192.168.1.0/24")}}},
Policy: envdetect.DefaultScanPolicy(),
}
detectEnvironmentFn = func() (*envdetect.EnvironmentProfile, error) {
return profile, nil
}
scanner, err := BuildScanner(config.DefaultDiscoveryConfig())
if err != nil {
t.Fatalf("BuildScanner error: %v", err)
}
if scanner == nil {
t.Fatalf("expected scanner")
}
}
func TestBuildScannerError(t *testing.T) {
t.Cleanup(resetDetectEnvironment)
detectEnvironmentFn = func() (*envdetect.EnvironmentProfile, error) {
return nil, errors.New("detect failed")
}
if _, err := BuildScanner(config.DefaultDiscoveryConfig()); err == nil {
t.Fatalf("expected error")
}
}
func TestApplyConfigToProfileNil(t *testing.T) {
ApplyConfigToProfile(nil, config.DefaultDiscoveryConfig())
}
func TestApplyConfigToProfileOverridesAndPolicies(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Unknown,
Phases: []envdetect.SubnetPhase{
{Name: "container_network", Subnets: []net.IPNet{mustCIDR(t, "10.0.0.0/24"), mustCIDR(t, "192.168.0.0/24")}},
{Name: "local", Subnets: []net.IPNet{mustCIDR(t, "172.16.0.0/24")}},
},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{
EnvironmentOverride: "docker_bridge",
SubnetBlocklist: []string{"192.168.0.0/24"},
SubnetAllowlist: []string{"10.0.0.0/24", "192.168.0.0/24", "invalid"},
MaxHostsPerScan: 10,
MaxConcurrent: 20,
EnableReverseDNS: true,
ScanGateways: true,
DialTimeout: 1500,
HTTPTimeout: 2500,
IPBlocklist: []string{"192.168.1.10", "invalid"},
}
ApplyConfigToProfile(profile, cfg)
if profile.Type != envdetect.DockerBridge {
t.Fatalf("expected DockerBridge env, got %v", profile.Type)
}
if len(profile.Phases) == 0 || profile.Phases[0].Name != "config_allowlist" {
t.Fatalf("expected allowlist phase first, got %#v", profile.Phases)
}
if len(profile.Phases[0].Subnets) != 1 {
t.Fatalf("expected allowlist to filter blocklisted subnet")
}
if profile.Policy.MaxHostsPerScan != 10 || profile.Policy.MaxConcurrent != 20 {
t.Fatalf("policy not updated: %+v", profile.Policy)
}
if !profile.Policy.EnableReverseDNS || !profile.Policy.ScanGateways {
t.Fatalf("policy flags not updated")
}
if profile.Policy.DialTimeout != 1500*time.Millisecond || profile.Policy.HTTPTimeout != 2500*time.Millisecond {
t.Fatalf("policy timeouts not updated: %+v", profile.Policy)
}
if len(profile.IPBlocklist) != 1 {
t.Fatalf("expected one IP in blocklist, got %d", len(profile.IPBlocklist))
}
}
func TestApplyConfigToProfileSkipsEmptyIPBlocklistEntries(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{
IPBlocklist: []string{"", " ", "192.168.1.10"},
}
ApplyConfigToProfile(profile, cfg)
if len(profile.IPBlocklist) != 1 {
t.Fatalf("expected 1 IP in blocklist, got %d", len(profile.IPBlocklist))
}
}
func TestApplyConfigToProfileInvalidEnvironmentOverride(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Phases: []envdetect.SubnetPhase{},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{EnvironmentOverride: "invalid_env"}
ApplyConfigToProfile(profile, cfg)
if len(profile.Warnings) == 0 {
t.Fatalf("expected warning for invalid environment override")
}
}
func TestApplyConfigToProfilePrunesContainerPhase(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.LXCUnprivileged,
Phases: []envdetect.SubnetPhase{
{Name: "container_phase", Subnets: []net.IPNet{mustCIDR(t, "10.0.0.0/24")}},
{Name: "lxc_parent", Subnets: []net.IPNet{mustCIDR(t, "192.168.0.0/24")}},
},
Policy: envdetect.DefaultScanPolicy(),
}
ApplyConfigToProfile(profile, config.DefaultDiscoveryConfig())
if len(profile.Phases) != 1 || profile.Phases[0].Name != "lxc_parent" {
t.Fatalf("expected container phase pruned, got %#v", profile.Phases)
}
}
func TestApplyConfigToProfileBlocklistWarnings(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Unknown,
Phases: []envdetect.SubnetPhase{},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{
SubnetBlocklist: []string{"invalid"},
}
ApplyConfigToProfile(profile, cfg)
if len(profile.Warnings) == 0 {
t.Fatalf("expected warnings for invalid CIDR")
}
}
func TestApplyConfigToProfileAllowsConfigAllowlist(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Phases: []envdetect.SubnetPhase{
{Name: "local", Subnets: []net.IPNet{mustCIDR(t, "10.0.0.0/24")}},
},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{
SubnetAllowlist: []string{"10.0.0.0/24"},
}
ApplyConfigToProfile(profile, cfg)
if len(profile.Phases) == 0 || profile.Phases[0].Name != "config_allowlist" {
t.Fatalf("expected allowlist phase, got %#v", profile.Phases)
}
}
func TestApplyConfigToProfileNoProfileWarnings(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Phases: []envdetect.SubnetPhase{},
Policy: envdetect.DefaultScanPolicy(),
Warnings: nil,
ExtraTargets: []net.IP{},
}
cfg := config.DiscoveryConfig{
IPBlocklist: []string{"bad"},
}
ApplyConfigToProfile(profile, cfg)
if len(profile.Warnings) == 0 {
t.Fatalf("expected warnings for invalid IP")
}
}
func TestApplyConfigToProfileBlocksSubnets(t *testing.T) {
subnet := mustCIDR(t, "10.0.0.0/24")
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Phases: []envdetect.SubnetPhase{
{Name: "local", Subnets: []net.IPNet{subnet}},
},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{
SubnetBlocklist: []string{"10.0.0.0/24"},
}
ApplyConfigToProfile(profile, cfg)
if len(profile.Phases) != 0 {
t.Fatalf("expected phases filtered, got %#v", profile.Phases)
}
}
func TestApplyConfigToProfileAllowsBlockedAllowlist(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Phases: []envdetect.SubnetPhase{},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{
SubnetAllowlist: []string{"10.0.0.0/24"},
SubnetBlocklist: []string{"10.0.0.0/24"},
}
ApplyConfigToProfile(profile, cfg)
if len(profile.Phases) != 0 {
t.Fatalf("expected allowlist filtered by blocklist")
}
}
func TestApplyConfigToProfileKeepsUnknownEnvironmentPhases(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Unknown,
Phases: []envdetect.SubnetPhase{
{Name: "local", Subnets: []net.IPNet{mustCIDR(t, "10.0.0.0/24")}},
},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DiscoveryConfig{
EnvironmentOverride: "auto",
}
ApplyConfigToProfile(profile, cfg)
if len(profile.Phases) != 1 {
t.Fatalf("expected phases kept for unknown env")
}
}
func TestApplyConfigToProfileUsesNewScanner(t *testing.T) {
profile := &envdetect.EnvironmentProfile{
Type: envdetect.Native,
Phases: []envdetect.SubnetPhase{},
Policy: envdetect.DefaultScanPolicy(),
}
cfg := config.DefaultDiscoveryConfig()
ApplyConfigToProfile(profile, cfg)
scanner, err := pkgdiscovery.NewScannerWithProfile(profile), error(nil)
if err != nil || scanner == nil {
t.Fatalf("expected scanner from profile")
}
}
func TestShouldPruneContainerNetworks(t *testing.T) {
tests := []struct {
name string
+6 -3
View File
@@ -90,6 +90,9 @@ type discoveryScanner interface {
type scannerFactory func(config.DiscoveryConfig) (discoveryScanner, error)
var (
newScannerFn = func() discoveryScanner {
return pkgdiscovery.NewScanner()
}
discoveryScanResults = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "pulse",
@@ -144,7 +147,7 @@ func NewService(wsHub *websocket.Hub, interval time.Duration, subnet string, cfg
}
return &Service{
scanner: pkgdiscovery.NewScanner(),
scanner: newScannerFn(),
wsHub: wsHub,
cache: &DiscoveryCache{},
interval: interval,
@@ -366,11 +369,11 @@ func (s *Service) performScan() {
}
if err != nil {
log.Warn().Err(err).Msg("Environment detection failed during discovery; falling back to default scanner configuration")
newScanner = pkgdiscovery.NewScanner()
newScanner = newScannerFn()
}
if newScanner == nil {
log.Warn().Msg("Discovery scanner factory returned nil; using default scanner configuration")
newScanner = pkgdiscovery.NewScanner()
newScanner = newScannerFn()
}
s.mu.Lock()
s.scanner = newScanner
+366
View File
@@ -34,6 +34,19 @@ func (f *fakeScanner) DiscoverServersWithCallbacks(ctx context.Context, subnet s
return f.result, f.err
}
type countingScanner struct {
result *pkgdiscovery.DiscoveryResult
err error
calls chan struct{}
}
func (c *countingScanner) DiscoverServersWithCallbacks(ctx context.Context, subnet string, serverCallback pkgdiscovery.ServerCallback, progressCallback pkgdiscovery.ProgressCallback) (*pkgdiscovery.DiscoveryResult, error) {
if c.calls != nil {
c.calls <- struct{}{}
}
return c.result, c.err
}
func TestPerformScanRecordsHistoryAndMetrics(t *testing.T) {
service := NewService(nil, time.Minute, "192.168.1.0/24", func() config.DiscoveryConfig {
cfg := config.DefaultDiscoveryConfig()
@@ -149,3 +162,356 @@ func TestPerformScanRecordsPartialFailure(t *testing.T) {
t.Fatalf("expected errorCount %d, got %d", len(scanner.result.StructuredErrors), entry.errorCount)
}
}
func resetNewScannerFn() {
newScannerFn = func() discoveryScanner {
return pkgdiscovery.NewScanner()
}
}
func TestHistoryEntryAccessors(t *testing.T) {
started := time.Now().Add(-time.Minute)
completed := time.Now()
entry := historyEntry{
startedAt: started,
completedAt: completed,
subnet: "10.0.0.0/24",
serverCount: 3,
errorCount: 1,
duration: time.Second,
blocklistLength: 2,
status: "success",
}
if entry.StartedAt() != started {
t.Fatalf("StartedAt mismatch")
}
if entry.CompletedAt() != completed {
t.Fatalf("CompletedAt mismatch")
}
if entry.Subnet() != "10.0.0.0/24" {
t.Fatalf("Subnet mismatch")
}
if entry.ServerCount() != 3 {
t.Fatalf("ServerCount mismatch")
}
if entry.ErrorCount() != 1 {
t.Fatalf("ErrorCount mismatch")
}
if entry.Duration() != time.Second {
t.Fatalf("Duration mismatch")
}
if entry.BlocklistLength() != 2 {
t.Fatalf("BlocklistLength mismatch")
}
if entry.Status() != "success" {
t.Fatalf("Status mismatch")
}
}
func TestNewServiceDefaults(t *testing.T) {
service := NewService(nil, 0, "", nil)
if service.interval != 5*time.Minute {
t.Fatalf("expected default interval, got %v", service.interval)
}
if service.subnet != "auto" {
t.Fatalf("expected auto subnet, got %s", service.subnet)
}
if service.cfgProvider == nil {
t.Fatalf("expected default cfgProvider")
}
if service.scannerFactory == nil {
t.Fatalf("expected scannerFactory")
}
}
func TestAppendHistoryTrim(t *testing.T) {
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.historyLimit = 1
service.appendHistory(historyEntry{status: "first"})
service.appendHistory(historyEntry{status: "second"})
history := service.GetHistory(2)
if len(history) != 1 || history[0].status != "second" {
t.Fatalf("expected trimmed history with latest entry")
}
}
func TestGetHistoryEmpty(t *testing.T) {
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
if history := service.GetHistory(5); history != nil {
t.Fatalf("expected nil history")
}
}
func TestGetCachedResultEmpty(t *testing.T) {
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
result, updated := service.GetCachedResult()
if result == nil {
t.Fatalf("expected result")
}
if !updated.IsZero() {
t.Fatalf("expected zero updated timestamp")
}
}
func TestGetCachedResultWithData(t *testing.T) {
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
now := time.Now()
service.cache.mu.Lock()
service.cache.result = &pkgdiscovery.DiscoveryResult{
Servers: []pkgdiscovery.DiscoveredServer{{IP: "10.0.0.1"}},
Errors: []string{},
}
service.cache.updated = now
service.cache.mu.Unlock()
result, updated := service.GetCachedResult()
if result == nil || len(result.Servers) != 1 {
t.Fatalf("expected cached result")
}
if !updated.Equal(now) {
t.Fatalf("expected updated timestamp")
}
}
func TestIsScanning(t *testing.T) {
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.mu.Lock()
service.isScanning = true
service.mu.Unlock()
if !service.IsScanning() {
t.Fatalf("expected scanning to be true")
}
}
func TestSetInterval(t *testing.T) {
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.SetInterval(2 * time.Minute)
if service.interval != 2*time.Minute {
t.Fatalf("expected interval update")
}
}
func TestGetStatus(t *testing.T) {
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.mu.Lock()
service.isScanning = true
service.lastScan = time.Unix(10, 0)
service.mu.Unlock()
status := service.GetStatus()
if status["subnet"] != "auto" {
t.Fatalf("expected subnet in status")
}
if status["interval"] == "" {
t.Fatalf("expected interval in status")
}
if scanning, ok := status["is_scanning"].(bool); !ok || !scanning {
t.Fatalf("expected is_scanning true")
}
}
func TestForceRefresh(t *testing.T) {
scanner := &countingScanner{
result: &pkgdiscovery.DiscoveryResult{},
calls: make(chan struct{}, 1),
}
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.ctx = context.Background()
service.scannerFactory = func(config.DiscoveryConfig) (discoveryScanner, error) {
return scanner, nil
}
service.ForceRefresh()
select {
case <-scanner.calls:
case <-time.After(2 * time.Second):
t.Fatalf("expected scan to run")
}
}
func TestForceRefreshSkippedWhenScanning(t *testing.T) {
scanner := &countingScanner{
result: &pkgdiscovery.DiscoveryResult{},
calls: make(chan struct{}, 1),
}
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.ctx = context.Background()
service.scannerFactory = func(config.DiscoveryConfig) (discoveryScanner, error) {
return scanner, nil
}
service.mu.Lock()
service.isScanning = true
service.mu.Unlock()
service.ForceRefresh()
select {
case <-scanner.calls:
t.Fatalf("expected scan to be skipped")
case <-time.After(100 * time.Millisecond):
}
}
func TestSetSubnetTriggersScan(t *testing.T) {
scanner := &countingScanner{
result: &pkgdiscovery.DiscoveryResult{},
calls: make(chan struct{}, 1),
}
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.ctx = context.Background()
service.scannerFactory = func(config.DiscoveryConfig) (discoveryScanner, error) {
return scanner, nil
}
service.SetSubnet("10.0.0.0/24")
select {
case <-scanner.calls:
case <-time.After(2 * time.Second):
t.Fatalf("expected scan to run")
}
}
func TestSetSubnetWhileScanning(t *testing.T) {
scanner := &countingScanner{
result: &pkgdiscovery.DiscoveryResult{},
calls: make(chan struct{}, 1),
}
service := NewService(nil, time.Minute, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.ctx = context.Background()
service.scannerFactory = func(config.DiscoveryConfig) (discoveryScanner, error) {
return scanner, nil
}
service.mu.Lock()
service.isScanning = true
service.mu.Unlock()
service.SetSubnet("10.0.0.0/24")
select {
case <-scanner.calls:
t.Fatalf("expected scan to be skipped")
case <-time.After(100 * time.Millisecond):
}
}
func TestScanLoopStopsOnStopChan(t *testing.T) {
scanner := &countingScanner{
result: &pkgdiscovery.DiscoveryResult{},
calls: make(chan struct{}, 2),
}
service := NewService(nil, 10*time.Millisecond, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.ctx = context.Background()
service.scannerFactory = func(config.DiscoveryConfig) (discoveryScanner, error) {
return scanner, nil
}
done := make(chan struct{})
go func() {
service.scanLoop()
close(done)
}()
select {
case <-scanner.calls:
case <-time.After(2 * time.Second):
t.Fatalf("expected scan")
}
service.Stop()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatalf("expected scanLoop to stop")
}
}
func TestScanLoopStopsOnContextCancel(t *testing.T) {
scanner := &countingScanner{
result: &pkgdiscovery.DiscoveryResult{},
calls: make(chan struct{}, 2),
}
service := NewService(nil, 10*time.Millisecond, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
ctx, cancel := context.WithCancel(context.Background())
service.ctx = ctx
service.scannerFactory = func(config.DiscoveryConfig) (discoveryScanner, error) {
return scanner, nil
}
done := make(chan struct{})
go func() {
service.scanLoop()
close(done)
}()
select {
case <-scanner.calls:
case <-time.After(2 * time.Second):
t.Fatalf("expected scan")
}
cancel()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatalf("expected scanLoop to stop")
}
}
func TestStartAndStop(t *testing.T) {
scanner := &countingScanner{
result: &pkgdiscovery.DiscoveryResult{},
calls: make(chan struct{}, 2),
}
service := NewService(nil, 10*time.Millisecond, "auto", func() config.DiscoveryConfig {
return config.DefaultDiscoveryConfig()
})
service.scannerFactory = func(config.DiscoveryConfig) (discoveryScanner, error) {
return scanner, nil
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
service.Start(ctx)
select {
case <-scanner.calls:
case <-time.After(2 * time.Second):
t.Fatalf("expected scan to run")
}
service.Stop()
}
+6 -2
View File
@@ -52,6 +52,7 @@ func TestCollectContainer(t *testing.T) {
Health: &containertypes.Health{Status: "healthy"},
}
inspect.Config.Env = []string{"PASSWORD=secret", "PATH=/bin"}
inspect.Config.Image = "nginx@sha256:abc123"
inspect.NetworkSettings.Networks["net1"].IPAddress = "10.0.0.2"
inspect.Mounts = []containertypes.MountPoint{
{Type: "bind", Source: "/data", Destination: "/data", RW: true},
@@ -107,8 +108,11 @@ func TestCollectContainer(t *testing.T) {
if container.Podman == nil || container.Podman.PodName != "mypod" {
t.Fatalf("expected podman metadata")
}
if container.UpdateStatus == nil || container.UpdateStatus.Error == "" {
t.Fatalf("expected update status for digest-pinned image")
if container.UpdateStatus == nil {
t.Fatal("expected update status for digest-pinned image, got nil")
}
if container.UpdateStatus.Error == "" {
t.Fatalf("expected update status for digest-pinned image, got empty error. Status: %+v", container.UpdateStatus)
}
if len(container.Networks) == 0 {
t.Fatalf("expected networks to be populated")
@@ -57,7 +57,7 @@ func TestUpdateContainer_Errors(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if result.Error == "" {
t.Fatal("expected error for inspect failure")
}
@@ -76,7 +76,7 @@ func TestUpdateContainer_Errors(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if result.Error == "" {
t.Fatal("expected error for pull failure")
}
@@ -98,7 +98,7 @@ func TestUpdateContainer_Errors(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if result.Error == "" {
t.Fatal("expected error for stop failure")
}
@@ -128,7 +128,7 @@ func TestUpdateContainer_Errors(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if result.Error == "" {
t.Fatal("expected error for rename failure")
}
@@ -166,7 +166,7 @@ func TestUpdateContainer_Errors(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if result.Error == "" {
t.Fatal("expected error for create failure")
}
@@ -212,7 +212,7 @@ func TestUpdateContainer_Errors(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if result.Error == "" {
t.Fatal("expected error for start failure")
}
@@ -276,7 +276,7 @@ func TestUpdateContainer_Success(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if !result.Success {
t.Fatalf("expected success, got error %q", result.Error)
}
@@ -339,7 +339,7 @@ func TestUpdateContainer_CleanupError(t *testing.T) {
logger: logger,
}
result := agent.updateContainer(context.Background(), "container1")
result := agent.updateContainerWithProgress(context.Background(), "container1", nil)
if !result.Success {
t.Fatalf("expected success, got error %q", result.Error)
}
@@ -200,7 +200,7 @@ func TestRegistryChecker_FetchDigest_StatusErrors(t *testing.T) {
},
}
_, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
_, _, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
if err == nil || err.Error() != tt.wantErr {
t.Fatalf("Expected error %q, got %v", tt.wantErr, err)
}
@@ -220,7 +220,7 @@ func TestRegistryChecker_FetchDigest_RequestError(t *testing.T) {
},
}
_, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
_, _, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
if err == nil || !strings.Contains(err.Error(), "request:") {
t.Fatalf("Expected request error, got %v", err)
}
@@ -229,7 +229,7 @@ func TestRegistryChecker_FetchDigest_RequestError(t *testing.T) {
func TestRegistryChecker_FetchDigest_RequestCreationError(t *testing.T) {
checker := &RegistryChecker{httpClient: &http.Client{}}
_, err := checker.fetchDigest(context.Background(), "bad host", "repo", "tag", "", "", "")
_, _, err := checker.fetchDigest(context.Background(), "bad host", "repo", "tag", "", "", "")
if err == nil || !strings.Contains(err.Error(), "create request:") {
t.Fatalf("Expected create request error, got %v", err)
}
@@ -257,7 +257,7 @@ func TestRegistryChecker_FetchDigest_DigestHeaders(t *testing.T) {
},
}
digest, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
digest, _, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
if err != nil {
t.Fatalf("Expected digest, got error %v", err)
}
@@ -281,7 +281,7 @@ func TestRegistryChecker_FetchDigest_DigestHeaders(t *testing.T) {
},
}
digest, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
digest, _, err := checker.fetchDigest(context.Background(), "example.test", "repo", "tag", "", "", "")
if err != nil {
t.Fatalf("Expected digest, got error %v", err)
}
@@ -304,7 +304,7 @@ func TestRegistryChecker_FetchDigest_AuthPaths(t *testing.T) {
},
}
_, err := checker.fetchDigest(context.Background(), "registry-1.docker.io", "library/nginx", "latest", "", "", "")
_, _, err := checker.fetchDigest(context.Background(), "registry-1.docker.io", "library/nginx", "latest", "", "", "")
if err == nil || err.Error() != "auth: token request failed: 500" {
t.Fatalf("Expected auth error, got %v", err)
}
@@ -331,7 +331,7 @@ func TestRegistryChecker_FetchDigest_AuthPaths(t *testing.T) {
},
}
digest, err := checker.fetchDigest(context.Background(), "registry-1.docker.io", "library/nginx", "latest", "", "", "")
digest, _, err := checker.fetchDigest(context.Background(), "registry-1.docker.io", "library/nginx", "latest", "", "", "")
if err != nil {
t.Fatalf("Expected digest, got error %v", err)
}
+18
View File
@@ -138,6 +138,24 @@ func TestRegistryChecker_DigestsDiffer(t *testing.T) {
latest: "abc123",
want: false, // Should match after normalization
},
{
name: "match second digest in list",
current: "sha256:abc123",
latest: "def456,abc123",
want: false, // Should match one of them
},
{
name: "match first digest in list",
current: "sha256:def456",
latest: "def456,abc123",
want: false, // Should match one of them
},
{
name: "no match in list",
current: "sha256:xyz789",
latest: "def456,abc123",
want: true, // No match found
},
}
for _, tt := range tests {