This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/billing-service/internal/services/task_service_test.go
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

398 lines
9.9 KiB
Go

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)
}
})
}
}