package deploy_test import ( "context" "log/slog" "sync" "testing" "time" "github.com/stretchr/testify/assert" "git.eeqj.de/sneak/upaas/internal/service/deploy" ) func TestCancelActiveDeploy_NoExisting(t *testing.T) { t.Parallel() svc := deploy.NewTestService(slog.Default()) // Should not panic or block when no active deploy exists svc.CancelActiveDeploy("nonexistent-app") } func TestCancelActiveDeploy_CancelsAndWaits(t *testing.T) { t.Parallel() svc := deploy.NewTestService(slog.Default()) ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) svc.RegisterActiveDeploy("app-1", cancel, done) // Simulate a running deploy that respects cancellation var deployFinished bool go func() { <-ctx.Done() deployFinished = true close(done) }() svc.CancelActiveDeploy("app-1") assert.True(t, deployFinished, "deploy should have finished after cancellation") } func TestCancelActiveDeploy_BlocksUntilDone(t *testing.T) { t.Parallel() svc := deploy.NewTestService(slog.Default()) ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) svc.RegisterActiveDeploy("app-2", cancel, done) // Simulate slow cleanup after cancellation go func() { <-ctx.Done() time.Sleep(50 * time.Millisecond) close(done) }() start := time.Now() svc.CancelActiveDeploy("app-2") elapsed := time.Since(start) assert.GreaterOrEqual(t, elapsed, 50*time.Millisecond, "cancelActiveDeploy should block until the deploy finishes") } func TestTryLockApp_PreventsConcurrent(t *testing.T) { t.Parallel() svc := deploy.NewTestService(slog.Default()) assert.True(t, svc.TryLockApp("app-1"), "first lock should succeed") assert.False(t, svc.TryLockApp("app-1"), "second lock should fail") svc.UnlockApp("app-1") assert.True(t, svc.TryLockApp("app-1"), "lock after unlock should succeed") svc.UnlockApp("app-1") } func TestCancelActiveDeploy_AllowsNewDeploy(t *testing.T) { t.Parallel() svc := deploy.NewTestService(slog.Default()) // Simulate an active deploy holding the lock ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) svc.RegisterActiveDeploy("app-3", cancel, done) // Lock the app as if a deploy is in progress assert.True(t, svc.TryLockApp("app-3")) // Simulate deploy goroutine: release lock on cancellation var mu sync.Mutex released := false go func() { <-ctx.Done() svc.UnlockApp("app-3") mu.Lock() released = true mu.Unlock() close(done) }() // Cancel should cause the old deploy to release its lock svc.CancelActiveDeploy("app-3") mu.Lock() assert.True(t, released) mu.Unlock() // Now a new deploy should be able to acquire the lock assert.True(t, svc.TryLockApp("app-3"), "should be able to lock after cancellation") svc.UnlockApp("app-3") }