mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
165 lines
4.3 KiB
Go
165 lines
4.3 KiB
Go
package audit
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/safedep/pmg/config"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// spawnRecorder is a detachedSpawner stub that captures invocations instead
|
|
// of actually forking. We swap it in via withMockSpawner.
|
|
type spawnRecorder struct {
|
|
mu sync.Mutex
|
|
calls []spawnCall
|
|
err error
|
|
}
|
|
|
|
type spawnCall struct {
|
|
name string
|
|
args []string
|
|
}
|
|
|
|
func (r *spawnRecorder) spawn(name string, args ...string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
cp := make([]string, len(args))
|
|
copy(cp, args)
|
|
r.calls = append(r.calls, spawnCall{name: name, args: cp})
|
|
return r.err
|
|
}
|
|
|
|
func (r *spawnRecorder) callCount() int {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return len(r.calls)
|
|
}
|
|
|
|
func withMockSpawner(t *testing.T) *spawnRecorder {
|
|
t.Helper()
|
|
rec := &spawnRecorder{}
|
|
prev := spawnDetached
|
|
spawnDetached = rec.spawn
|
|
t.Cleanup(func() { spawnDetached = prev })
|
|
return rec
|
|
}
|
|
|
|
func newAutoSyncConfig(t *testing.T) *config.RuntimeConfig {
|
|
t.Helper()
|
|
tmpDir := t.TempDir()
|
|
t.Setenv("PMG_CONFIG_DIR", tmpDir)
|
|
// Make sure no PMG_DISABLE_TELEMETRY leak from the host environment can
|
|
// fool the analytics short-circuit in MaybeSpawnBackgroundSync.
|
|
t.Setenv("PMG_DISABLE_TELEMETRY", "false")
|
|
|
|
// Re-init the config so configDir picks up the tmpDir we just set.
|
|
// Without this, cfg.CloudSyncLastRunPath() points at the user's real
|
|
// config dir, which may not even exist in CI.
|
|
config.Reload()
|
|
t.Cleanup(config.Reload)
|
|
|
|
cfg := config.Get()
|
|
cfg.Config.Cloud.Enabled = true
|
|
cfg.Config.Cloud.AutoSync.Enabled = true
|
|
cfg.Config.Cloud.AutoSync.MinInterval = 15 * time.Minute
|
|
cfg.Config.Cloud.AutoSync.Timeout = time.Minute
|
|
cfg.Config.DisableTelemetry = false
|
|
return cfg
|
|
}
|
|
|
|
func TestMaybeSpawnBackgroundSyncSpawnsByDefault(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
cfg := newAutoSyncConfig(t)
|
|
|
|
MaybeSpawnBackgroundSync(cfg)
|
|
|
|
require.Equal(t, 1, rec.callCount())
|
|
assert.Equal(t, []string{"cloud", "sync-background"}, rec.calls[0].args)
|
|
assert.NotEmpty(t, rec.calls[0].name, "spawned name should be a resolved binary path")
|
|
}
|
|
|
|
func TestMaybeSpawnBackgroundSyncIgnoresTelemetryDisabled(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
cfg := newAutoSyncConfig(t)
|
|
cfg.Config.DisableTelemetry = true
|
|
|
|
MaybeSpawnBackgroundSync(cfg)
|
|
assert.Equal(t, 1, rec.callCount())
|
|
}
|
|
|
|
func TestMaybeSpawnBackgroundSyncShortCircuits(t *testing.T) {
|
|
t.Run("nil config", func(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
MaybeSpawnBackgroundSync(nil)
|
|
assert.Equal(t, 0, rec.callCount())
|
|
})
|
|
|
|
t.Run("cloud disabled", func(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
cfg := newAutoSyncConfig(t)
|
|
cfg.Config.Cloud.Enabled = false
|
|
|
|
MaybeSpawnBackgroundSync(cfg)
|
|
assert.Equal(t, 0, rec.callCount())
|
|
})
|
|
|
|
t.Run("auto_sync disabled", func(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
cfg := newAutoSyncConfig(t)
|
|
cfg.Config.Cloud.AutoSync.Enabled = false
|
|
|
|
MaybeSpawnBackgroundSync(cfg)
|
|
assert.Equal(t, 0, rec.callCount())
|
|
})
|
|
|
|
t.Run("we are the sync-background child", func(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
cfg := newAutoSyncConfig(t)
|
|
|
|
t.Cleanup(func() { isBackgroundSyncChild = false })
|
|
MarkBackgroundSyncChild()
|
|
|
|
MaybeSpawnBackgroundSync(cfg)
|
|
assert.Equal(t, 0, rec.callCount())
|
|
})
|
|
}
|
|
|
|
func TestMaybeSpawnBackgroundSyncRespectsCooldown(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
cfg := newAutoSyncConfig(t)
|
|
|
|
// Recent attempt → cooldown still in effect → no spawn.
|
|
require.NoError(t, WriteLastSyncAttempt(cfg.CloudSyncLastRunPath()))
|
|
|
|
MaybeSpawnBackgroundSync(cfg)
|
|
assert.Equal(t, 0, rec.callCount())
|
|
}
|
|
|
|
func TestMaybeSpawnBackgroundSyncSpawnsAfterCooldownElapses(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
cfg := newAutoSyncConfig(t)
|
|
|
|
old := time.Now().Add(-time.Hour).Unix()
|
|
require.NoError(t, os.WriteFile(cfg.CloudSyncLastRunPath(),
|
|
[]byte(strconv.FormatInt(old, 10)), 0o600))
|
|
|
|
MaybeSpawnBackgroundSync(cfg)
|
|
assert.Equal(t, 1, rec.callCount())
|
|
}
|
|
|
|
func TestMaybeSpawnBackgroundSyncSwallowsSpawnError(t *testing.T) {
|
|
rec := withMockSpawner(t)
|
|
rec.err = os.ErrPermission
|
|
cfg := newAutoSyncConfig(t)
|
|
|
|
// We can't observe log.Warnf directly, but we can assert the function
|
|
// neither panics nor blocks when the underlying spawn fails.
|
|
assert.NotPanics(t, func() { MaybeSpawnBackgroundSync(cfg) })
|
|
assert.Equal(t, 1, rec.callCount(), "spawner should still be invoked once")
|
|
}
|