mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
test: Improve discovery and Docker agent test coverage
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user