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>
320 lines
8.1 KiB
Go
320 lines
8.1 KiB
Go
package models
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestCarryoverMonthsCap(t *testing.T) {
|
|
// Verify the constant is set correctly
|
|
if CarryoverMonthsCap != 5 {
|
|
t.Errorf("CarryoverMonthsCap should be 5, got %d", CarryoverMonthsCap)
|
|
}
|
|
}
|
|
|
|
func TestCalculateMaxTaskBalance(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
monthlyAllowance int
|
|
expected int
|
|
}{
|
|
{"Basic plan", 30, 150},
|
|
{"Standard plan", 100, 500},
|
|
{"Premium plan", 1000, 5000},
|
|
{"Zero allowance", 0, 0},
|
|
{"Single task", 1, 5},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := CalculateMaxTaskBalance(tt.monthlyAllowance)
|
|
if result != tt.expected {
|
|
t.Errorf("CalculateMaxTaskBalance(%d) = %d, expected %d",
|
|
tt.monthlyAllowance, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetDefaultPlans(t *testing.T) {
|
|
plans := GetDefaultPlans()
|
|
|
|
if len(plans) != 3 {
|
|
t.Fatalf("Expected 3 plans, got %d", len(plans))
|
|
}
|
|
|
|
// Test Basic plan
|
|
basic := plans[0]
|
|
if basic.ID != PlanBasic {
|
|
t.Errorf("First plan should be Basic, got %s", basic.ID)
|
|
}
|
|
if basic.PriceCents != 990 {
|
|
t.Errorf("Basic price should be 990 cents, got %d", basic.PriceCents)
|
|
}
|
|
if basic.Features.MonthlyTaskAllowance != 30 {
|
|
t.Errorf("Basic monthly allowance should be 30, got %d", basic.Features.MonthlyTaskAllowance)
|
|
}
|
|
if basic.Features.MaxTaskBalance != 150 {
|
|
t.Errorf("Basic max balance should be 150, got %d", basic.Features.MaxTaskBalance)
|
|
}
|
|
if basic.Features.FairUseMode {
|
|
t.Error("Basic should not have FairUseMode")
|
|
}
|
|
|
|
// Test Standard plan
|
|
standard := plans[1]
|
|
if standard.ID != PlanStandard {
|
|
t.Errorf("Second plan should be Standard, got %s", standard.ID)
|
|
}
|
|
if standard.PriceCents != 1990 {
|
|
t.Errorf("Standard price should be 1990 cents, got %d", standard.PriceCents)
|
|
}
|
|
if standard.Features.MonthlyTaskAllowance != 100 {
|
|
t.Errorf("Standard monthly allowance should be 100, got %d", standard.Features.MonthlyTaskAllowance)
|
|
}
|
|
if !standard.Features.BatchProcessing {
|
|
t.Error("Standard should have BatchProcessing")
|
|
}
|
|
if !standard.Features.CustomTemplates {
|
|
t.Error("Standard should have CustomTemplates")
|
|
}
|
|
|
|
// Test Premium plan
|
|
premium := plans[2]
|
|
if premium.ID != PlanPremium {
|
|
t.Errorf("Third plan should be Premium, got %s", premium.ID)
|
|
}
|
|
if premium.PriceCents != 3990 {
|
|
t.Errorf("Premium price should be 3990 cents, got %d", premium.PriceCents)
|
|
}
|
|
if !premium.Features.FairUseMode {
|
|
t.Error("Premium should have FairUseMode")
|
|
}
|
|
if !premium.Features.PrioritySupport {
|
|
t.Error("Premium should have PrioritySupport")
|
|
}
|
|
if !premium.Features.CustomBranding {
|
|
t.Error("Premium should have CustomBranding")
|
|
}
|
|
}
|
|
|
|
func TestPlanIDConstants(t *testing.T) {
|
|
if PlanBasic != "basic" {
|
|
t.Errorf("PlanBasic should be 'basic', got '%s'", PlanBasic)
|
|
}
|
|
if PlanStandard != "standard" {
|
|
t.Errorf("PlanStandard should be 'standard', got '%s'", PlanStandard)
|
|
}
|
|
if PlanPremium != "premium" {
|
|
t.Errorf("PlanPremium should be 'premium', got '%s'", PlanPremium)
|
|
}
|
|
}
|
|
|
|
func TestSubscriptionStatusConstants(t *testing.T) {
|
|
statuses := []struct {
|
|
status SubscriptionStatus
|
|
expected string
|
|
}{
|
|
{StatusTrialing, "trialing"},
|
|
{StatusActive, "active"},
|
|
{StatusPastDue, "past_due"},
|
|
{StatusCanceled, "canceled"},
|
|
{StatusExpired, "expired"},
|
|
}
|
|
|
|
for _, tt := range statuses {
|
|
if string(tt.status) != tt.expected {
|
|
t.Errorf("Status %s should be '%s'", tt.status, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTaskTypeConstants(t *testing.T) {
|
|
types := []struct {
|
|
taskType TaskType
|
|
expected string
|
|
}{
|
|
{TaskTypeCorrection, "correction"},
|
|
{TaskTypeLetter, "letter"},
|
|
{TaskTypeMeeting, "meeting"},
|
|
{TaskTypeBatch, "batch"},
|
|
{TaskTypeOther, "other"},
|
|
}
|
|
|
|
for _, tt := range types {
|
|
if string(tt.taskType) != tt.expected {
|
|
t.Errorf("TaskType %s should be '%s'", tt.taskType, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPlanFeatures_CarryoverCalculation(t *testing.T) {
|
|
plans := GetDefaultPlans()
|
|
|
|
for _, plan := range plans {
|
|
expectedMax := plan.Features.MonthlyTaskAllowance * CarryoverMonthsCap
|
|
if plan.Features.MaxTaskBalance != expectedMax {
|
|
t.Errorf("Plan %s: MaxTaskBalance should be %d (allowance * 5), got %d",
|
|
plan.ID, expectedMax, plan.Features.MaxTaskBalance)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBillingPlan_AllPlansActive(t *testing.T) {
|
|
plans := GetDefaultPlans()
|
|
|
|
for _, plan := range plans {
|
|
if !plan.IsActive {
|
|
t.Errorf("Plan %s should be active", plan.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBillingPlan_CurrencyIsEuro(t *testing.T) {
|
|
plans := GetDefaultPlans()
|
|
|
|
for _, plan := range plans {
|
|
if plan.Currency != "eur" {
|
|
t.Errorf("Plan %s currency should be 'eur', got '%s'", plan.ID, plan.Currency)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBillingPlan_IntervalIsMonth(t *testing.T) {
|
|
plans := GetDefaultPlans()
|
|
|
|
for _, plan := range plans {
|
|
if plan.Interval != "month" {
|
|
t.Errorf("Plan %s interval should be 'month', got '%s'", plan.ID, plan.Interval)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBillingPlan_SortOrder(t *testing.T) {
|
|
plans := GetDefaultPlans()
|
|
|
|
for i, plan := range plans {
|
|
expectedOrder := i + 1
|
|
if plan.SortOrder != expectedOrder {
|
|
t.Errorf("Plan %s sort order should be %d, got %d",
|
|
plan.ID, expectedOrder, plan.SortOrder)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTaskUsageInfo_FormatStrings(t *testing.T) {
|
|
usage := TaskUsageInfo{
|
|
TasksAvailable: 45,
|
|
MaxTasks: 150,
|
|
InfoText: "Aufgaben verfuegbar: 45 von max. 150",
|
|
TooltipText: "Aufgaben koennen sich bis zu 5 Monate ansammeln.",
|
|
}
|
|
|
|
if usage.TasksAvailable != 45 {
|
|
t.Errorf("TasksAvailable should be 45, got %d", usage.TasksAvailable)
|
|
}
|
|
if usage.MaxTasks != 150 {
|
|
t.Errorf("MaxTasks should be 150, got %d", usage.MaxTasks)
|
|
}
|
|
}
|
|
|
|
func TestCheckTaskAllowedResponse_Allowed(t *testing.T) {
|
|
response := CheckTaskAllowedResponse{
|
|
Allowed: true,
|
|
TasksAvailable: 50,
|
|
MaxTasks: 150,
|
|
PlanID: PlanBasic,
|
|
}
|
|
|
|
if !response.Allowed {
|
|
t.Error("Response should be allowed")
|
|
}
|
|
if response.Message != "" {
|
|
t.Errorf("Message should be empty for allowed response, got '%s'", response.Message)
|
|
}
|
|
}
|
|
|
|
func TestCheckTaskAllowedResponse_NotAllowed(t *testing.T) {
|
|
response := CheckTaskAllowedResponse{
|
|
Allowed: false,
|
|
TasksAvailable: 0,
|
|
MaxTasks: 150,
|
|
PlanID: PlanBasic,
|
|
Message: "Dein Aufgaben-Kontingent ist aufgebraucht.",
|
|
}
|
|
|
|
if response.Allowed {
|
|
t.Error("Response should not be allowed")
|
|
}
|
|
if response.TasksAvailable != 0 {
|
|
t.Errorf("TasksAvailable should be 0, got %d", response.TasksAvailable)
|
|
}
|
|
}
|
|
|
|
func TestTaskLimitError(t *testing.T) {
|
|
err := TaskLimitError{
|
|
Error: "TASK_LIMIT_REACHED",
|
|
CurrentBalance: 0,
|
|
Plan: PlanBasic,
|
|
}
|
|
|
|
if err.Error != "TASK_LIMIT_REACHED" {
|
|
t.Errorf("Error should be 'TASK_LIMIT_REACHED', got '%s'", err.Error)
|
|
}
|
|
if err.CurrentBalance != 0 {
|
|
t.Errorf("CurrentBalance should be 0, got %d", err.CurrentBalance)
|
|
}
|
|
if err.Plan != PlanBasic {
|
|
t.Errorf("Plan should be basic, got '%s'", err.Plan)
|
|
}
|
|
}
|
|
|
|
func TestConsumeTaskRequest(t *testing.T) {
|
|
req := ConsumeTaskRequest{
|
|
UserID: "550e8400-e29b-41d4-a716-446655440000",
|
|
TaskType: TaskTypeCorrection,
|
|
}
|
|
|
|
if req.UserID == "" {
|
|
t.Error("UserID should not be empty")
|
|
}
|
|
if req.TaskType != TaskTypeCorrection {
|
|
t.Errorf("TaskType should be correction, got '%s'", req.TaskType)
|
|
}
|
|
}
|
|
|
|
func TestConsumeTaskResponse_Success(t *testing.T) {
|
|
resp := ConsumeTaskResponse{
|
|
Success: true,
|
|
TaskID: "task-123",
|
|
TasksRemaining: 49,
|
|
}
|
|
|
|
if !resp.Success {
|
|
t.Error("Response should be successful")
|
|
}
|
|
if resp.TasksRemaining != 49 {
|
|
t.Errorf("TasksRemaining should be 49, got %d", resp.TasksRemaining)
|
|
}
|
|
}
|
|
|
|
func TestEntitlementInfo_Premium(t *testing.T) {
|
|
premium := GetDefaultPlans()[2]
|
|
|
|
info := EntitlementInfo{
|
|
Features: premium.Features.FeatureFlags,
|
|
MaxTeamMembers: premium.Features.MaxTeamMembers,
|
|
PrioritySupport: premium.Features.PrioritySupport,
|
|
CustomBranding: premium.Features.CustomBranding,
|
|
BatchProcessing: premium.Features.BatchProcessing,
|
|
CustomTemplates: premium.Features.CustomTemplates,
|
|
FairUseMode: premium.Features.FairUseMode,
|
|
}
|
|
|
|
if !info.FairUseMode {
|
|
t.Error("Premium should have FairUseMode")
|
|
}
|
|
if info.MaxTeamMembers != 10 {
|
|
t.Errorf("Premium MaxTeamMembers should be 10, got %d", info.MaxTeamMembers)
|
|
}
|
|
}
|