package batching import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestNewGitBatcher(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) assert.NotNil(t, batcher) assert.Equal(t, 0, batcher.QueueSize()) } func TestEnqueueOperation(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{ OpType: "commit", Branch: "main", Message: "Add feature", Files: []string{"file1.go"}, } batcher.Enqueue(op) assert.Equal(t, 1, batcher.QueueSize()) } func TestEnqueueMultipleOps(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) for i := 0; i < 5; i++ { op := &GitOp{ OpType: "commit", Branch: "main", Message: "Commit", } batcher.Enqueue(op) } assert.Equal(t, 5, batcher.QueueSize()) } func TestAutoFlushOnMaxBatchSize(t *testing.T) { batcher := NewGitBatcher(5, 10*time.Second) for i := 0; i < 5; i++ { op := &GitOp{ OpType: "commit", Branch: "main", Message: "Commit", } batcher.Enqueue(op) } // After 5 ops, should auto-flush assert.Equal(t, 0, batcher.QueueSize()) assert.Equal(t, 1, batcher.PendingBatchCount()) } func TestManualFlush(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{ OpType: "commit", Branch: "main", Message: "Commit", } batcher.Enqueue(op) assert.Equal(t, 1, batcher.QueueSize()) batcher.Flush() assert.Equal(t, 0, batcher.QueueSize()) assert.Equal(t, 1, batcher.PendingBatchCount()) } func TestGetPendingBatch(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{ OpType: "commit", Branch: "main", Message: "Commit", } batcher.Enqueue(op) batcher.Flush() batch := batcher.GetPendingBatch() assert.NotNil(t, batch) assert.Equal(t, 1, len(batch.Operations)) } func TestMarkBatchExecuting(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() batch := batcher.GetPendingBatch() batcher.MarkBatchExecuting(batch.ID) updated := batcher.GetBatchByID(batch.ID) assert.Equal(t, "executing", updated.Status) } func TestMarkBatchCompleted(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() batch := batcher.GetPendingBatch() batcher.MarkBatchCompleted(batch.ID) executed := batcher.GetExecutedBatches() assert.Equal(t, 1, len(executed)) assert.Equal(t, "completed", executed[0].Status) } func TestMarkBatchFailed(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() batch := batcher.GetPendingBatch() testErr := assert.AnError batcher.MarkBatchFailed(batch.ID, testErr) failed := batcher.GetBatchByID(batch.ID) assert.Equal(t, "failed", failed.Status) assert.Error(t, failed.Error) } func TestGetStats(t *testing.T) { batcher := NewGitBatcher(5, 5*time.Second) // Add 10 ops (will create 2 batches of 5 each) for i := 0; i < 10; i++ { op := &GitOp{OpType: "commit"} batcher.Enqueue(op) } stats := batcher.GetStats() assert.Equal(t, 10, stats.TotalOps) assert.Equal(t, 2, stats.TotalBatches) assert.Equal(t, 5.0, stats.AvgOpsPerBatch) // 10 ops in 2 batches saves 8 round trips (5-1 + 5-1) assert.Equal(t, 8, stats.NetworkSavings) } func TestQueueSize(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) assert.Equal(t, 1, batcher.QueueSize()) } func TestPendingBatchCount(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() assert.Equal(t, 1, batcher.PendingBatchCount()) } func TestGetExecutedBatches(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) // Create and execute batches for i := 0; i < 2; i++ { op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() batch := batcher.GetPendingBatch() batcher.MarkBatchCompleted(batch.ID) } executed := batcher.GetExecutedBatches() assert.Equal(t, 2, len(executed)) } func TestTimeSinceLastFlush(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() time.Sleep(100 * time.Millisecond) elapsed := batcher.TimeSinceLastFlush() assert.Greater(t, elapsed, 50*time.Millisecond) assert.Less(t, elapsed, 200*time.Millisecond) } func TestShouldFlush(t *testing.T) { batcher := NewGitBatcher(100, 100*time.Millisecond) // Empty queue should not flush assert.False(t, batcher.ShouldFlush()) // Enqueue but not old enough op := &GitOp{OpType: "commit"} batcher.Enqueue(op) assert.False(t, batcher.ShouldFlush()) // Wait for age to exceed max age time.Sleep(150 * time.Millisecond) assert.True(t, batcher.ShouldFlush()) } func TestClear(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() assert.Equal(t, 1, batcher.PendingBatchCount()) batcher.Clear() assert.Equal(t, 0, batcher.QueueSize()) assert.Equal(t, 0, batcher.PendingBatchCount()) } func TestGetQueuedOps(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) ops := []*GitOp{ {OpType: "commit", Message: "Commit 1"}, {OpType: "commit", Message: "Commit 2"}, {OpType: "commit", Message: "Commit 3"}, } for _, op := range ops { batcher.Enqueue(op) } queued := batcher.GetQueuedOps() assert.Equal(t, 3, len(queued)) assert.Equal(t, "Commit 1", queued[0].Message) assert.Equal(t, "Commit 3", queued[2].Message) } func TestCalculateNetworkSavings(t *testing.T) { batcher := NewGitBatcher(3, 5*time.Second) // Add 6 ops (will create 2 batches of 3 each) for i := 0; i < 6; i++ { op := &GitOp{OpType: "commit"} batcher.Enqueue(op) } // Mark both batches as completed for i := 0; i < 2; i++ { batch := batcher.GetPendingBatch() if batch != nil { batcher.MarkBatchCompleted(batch.ID) } } savings := batcher.CalculateNetworkSavings() // 2 batches of 3 each saves 4 round trips (3-1 + 3-1) assert.Equal(t, 4, savings) } func TestGetBatchByID(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) op := &GitOp{OpType: "commit"} batcher.Enqueue(op) batcher.Flush() batch := batcher.GetPendingBatch() retrieved := batcher.GetBatchByID(batch.ID) assert.NotNil(t, retrieved) assert.Equal(t, batch.ID, retrieved.ID) } func TestGetBatchInfo(t *testing.T) { batch := &GitBatch{ ID: "test-batch", Status: "completed", CreatedAt: time.Now(), ExecutedAt: time.Now().Add(1 * time.Second), } info := batch.GetInfo() assert.Equal(t, "test-batch", info["id"]) assert.Equal(t, "completed", info["status"]) } func TestMultipleBatches(t *testing.T) { batcher := NewGitBatcher(3, 5*time.Second) // Create 3 batches for batch := 0; batch < 3; batch++ { for i := 0; i < 3; i++ { op := &GitOp{ OpType: "commit", Branch: "main", } batcher.Enqueue(op) } } // All 3 batches should be pending assert.Equal(t, 3, batcher.PendingBatchCount()) assert.Equal(t, 0, batcher.QueueSize()) } func TestEnqueueNil(t *testing.T) { batcher := NewGitBatcher(10, 5*time.Second) // Enqueueing nil should not fail batcher.Enqueue(nil) assert.Equal(t, 0, batcher.QueueSize()) } func BenchmarkEnqueue(b *testing.B) { batcher := NewGitBatcher(1000, 10*time.Second) for i := 0; i < b.N; i++ { op := &GitOp{ OpType: "commit", Branch: "main", Message: "Commit", } batcher.Enqueue(op) } } func BenchmarkFlush(b *testing.B) { batcher := NewGitBatcher(1000, 10*time.Second) for i := 0; i < b.N; i++ { op := &GitOp{OpType: "commit"} batcher.Enqueue(op) if (i + 1) % 100 == 0 { batcher.Flush() } } }