package services import ( "testing" "time" ) func TestMonthsBetween(t *testing.T) { tests := []struct { name string start time.Time end time.Time expected int }{ { name: "Same day", start: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), end: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), expected: 0, }, { name: "Less than one month", start: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), end: time.Date(2025, 2, 10, 0, 0, 0, 0, time.UTC), expected: 0, }, { name: "Exactly one month", start: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), end: time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC), expected: 1, }, { name: "One month and one day", start: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), end: time.Date(2025, 2, 16, 0, 0, 0, 0, time.UTC), expected: 1, }, { name: "Two months", start: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), end: time.Date(2025, 3, 15, 0, 0, 0, 0, time.UTC), expected: 2, }, { name: "Five months exactly", start: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), end: time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC), expected: 5, }, { name: "Year boundary", start: time.Date(2024, 11, 15, 0, 0, 0, 0, time.UTC), end: time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC), expected: 3, }, { name: "Leap year February to March", start: time.Date(2024, 2, 29, 0, 0, 0, 0, time.UTC), end: time.Date(2024, 3, 29, 0, 0, 0, 0, time.UTC), expected: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := monthsBetween(tt.start, tt.end) if result != tt.expected { t.Errorf("monthsBetween(%v, %v) = %d, expected %d", tt.start.Format("2006-01-02"), tt.end.Format("2006-01-02"), result, tt.expected) } }) } } func TestCarryoverLogic(t *testing.T) { // Test the carryover calculation logic tests := []struct { name string currentBalance int monthlyAllowance int maxBalance int monthsSinceRenewal int expectedNewBalance int }{ { name: "Normal renewal - add allowance", currentBalance: 50, monthlyAllowance: 30, maxBalance: 150, monthsSinceRenewal: 1, expectedNewBalance: 80, }, { name: "Two months missed", currentBalance: 50, monthlyAllowance: 30, maxBalance: 150, monthsSinceRenewal: 2, expectedNewBalance: 110, }, { name: "Cap at max balance", currentBalance: 140, monthlyAllowance: 30, maxBalance: 150, monthsSinceRenewal: 1, expectedNewBalance: 150, }, { name: "Already at max - no change", currentBalance: 150, monthlyAllowance: 30, maxBalance: 150, monthsSinceRenewal: 1, expectedNewBalance: 150, }, { name: "Multiple months - cap applies", currentBalance: 100, monthlyAllowance: 30, maxBalance: 150, monthsSinceRenewal: 5, expectedNewBalance: 150, }, { name: "Empty balance - add one month", currentBalance: 0, monthlyAllowance: 30, maxBalance: 150, monthsSinceRenewal: 1, expectedNewBalance: 30, }, { name: "Empty balance - add five months", currentBalance: 0, monthlyAllowance: 30, maxBalance: 150, monthsSinceRenewal: 5, expectedNewBalance: 150, }, { name: "Standard plan - normal case", currentBalance: 200, monthlyAllowance: 100, maxBalance: 500, monthsSinceRenewal: 1, expectedNewBalance: 300, }, { name: "Premium plan - Fair Use", currentBalance: 1000, monthlyAllowance: 1000, maxBalance: 5000, monthsSinceRenewal: 1, expectedNewBalance: 2000, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Simulate the carryover logic newBalance := tt.currentBalance for i := 0; i < tt.monthsSinceRenewal; i++ { newBalance += tt.monthlyAllowance if newBalance > tt.maxBalance { newBalance = tt.maxBalance break } } if newBalance != tt.expectedNewBalance { t.Errorf("Carryover for balance=%d, allowance=%d, max=%d, months=%d = %d, expected %d", tt.currentBalance, tt.monthlyAllowance, tt.maxBalance, tt.monthsSinceRenewal, newBalance, tt.expectedNewBalance) } }) } } func TestTaskBalanceAfterConsumption(t *testing.T) { tests := []struct { name string currentBalance int tasksToConsume int expectedBalance int shouldBeAllowed bool }{ { name: "Normal consumption", currentBalance: 50, tasksToConsume: 1, expectedBalance: 49, shouldBeAllowed: true, }, { name: "Last task", currentBalance: 1, tasksToConsume: 1, expectedBalance: 0, shouldBeAllowed: true, }, { name: "Empty balance - not allowed", currentBalance: 0, tasksToConsume: 1, expectedBalance: 0, shouldBeAllowed: false, }, { name: "Multiple tasks", currentBalance: 50, tasksToConsume: 5, expectedBalance: 45, shouldBeAllowed: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test if allowed allowed := tt.currentBalance > 0 if allowed != tt.shouldBeAllowed { t.Errorf("Task allowed with balance=%d: got %v, expected %v", tt.currentBalance, allowed, tt.shouldBeAllowed) } // Test balance calculation if allowed { newBalance := tt.currentBalance - tt.tasksToConsume if newBalance != tt.expectedBalance { t.Errorf("Balance after consuming %d tasks from %d: got %d, expected %d", tt.tasksToConsume, tt.currentBalance, newBalance, tt.expectedBalance) } } }) } } func TestTaskServiceErrors(t *testing.T) { // Test error constants if ErrTaskLimitReached == nil { t.Error("ErrTaskLimitReached should not be nil") } if ErrTaskLimitReached.Error() != "TASK_LIMIT_REACHED" { t.Errorf("ErrTaskLimitReached should be 'TASK_LIMIT_REACHED', got '%s'", ErrTaskLimitReached.Error()) } if ErrNoSubscription == nil { t.Error("ErrNoSubscription should not be nil") } if ErrNoSubscription.Error() != "NO_SUBSCRIPTION" { t.Errorf("ErrNoSubscription should be 'NO_SUBSCRIPTION', got '%s'", ErrNoSubscription.Error()) } } func TestRenewalDateCalculation(t *testing.T) { tests := []struct { name string lastRenewal time.Time monthsToAdd int expectedRenewal time.Time }{ { name: "Add one month", lastRenewal: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), monthsToAdd: 1, expectedRenewal: time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC), }, { name: "Add three months", lastRenewal: time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC), monthsToAdd: 3, expectedRenewal: time.Date(2025, 4, 15, 0, 0, 0, 0, time.UTC), }, { name: "Year boundary", lastRenewal: time.Date(2024, 11, 15, 0, 0, 0, 0, time.UTC), monthsToAdd: 3, expectedRenewal: time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC), }, { name: "End of month adjustment", lastRenewal: time.Date(2025, 1, 31, 0, 0, 0, 0, time.UTC), monthsToAdd: 1, // Go's AddDate handles this - February doesn't have 31 days expectedRenewal: time.Date(2025, 3, 3, 0, 0, 0, 0, time.UTC), // Feb 31 -> March 3 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := tt.lastRenewal.AddDate(0, tt.monthsToAdd, 0) if !result.Equal(tt.expectedRenewal) { t.Errorf("AddDate(%v, %d months) = %v, expected %v", tt.lastRenewal.Format("2006-01-02"), tt.monthsToAdd, result.Format("2006-01-02"), tt.expectedRenewal.Format("2006-01-02")) } }) } } func TestFairUseModeLogic(t *testing.T) { // Test that Fair Use mode always allows tasks regardless of balance tests := []struct { name string fairUseMode bool balance int shouldAllow bool }{ { name: "Fair Use - zero balance still allowed", fairUseMode: true, balance: 0, shouldAllow: true, }, { name: "Fair Use - normal balance allowed", fairUseMode: true, balance: 1000, shouldAllow: true, }, { name: "Not Fair Use - zero balance not allowed", fairUseMode: false, balance: 0, shouldAllow: false, }, { name: "Not Fair Use - positive balance allowed", fairUseMode: false, balance: 50, shouldAllow: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Simulate the check logic var allowed bool if tt.fairUseMode { allowed = true // Fair Use always allows } else { allowed = tt.balance > 0 } if allowed != tt.shouldAllow { t.Errorf("FairUseMode=%v, balance=%d: allowed=%v, expected=%v", tt.fairUseMode, tt.balance, allowed, tt.shouldAllow) } }) } } func TestBalanceDecrementLogic(t *testing.T) { // Test that Fair Use mode doesn't decrement balance tests := []struct { name string fairUseMode bool initialBalance int expectedAfter int }{ { name: "Normal plan - decrement", fairUseMode: false, initialBalance: 50, expectedAfter: 49, }, { name: "Fair Use - no decrement", fairUseMode: true, initialBalance: 1000, expectedAfter: 1000, }, { name: "Normal plan - last task", fairUseMode: false, initialBalance: 1, expectedAfter: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { newBalance := tt.initialBalance if !tt.fairUseMode { newBalance = tt.initialBalance - 1 } if newBalance != tt.expectedAfter { t.Errorf("FairUseMode=%v, initial=%d: got %d, expected %d", tt.fairUseMode, tt.initialBalance, newBalance, tt.expectedAfter) } }) } }