package repositories import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/honeydue-api/internal/models" "github.com/treytartt/honeydue-api/internal/testutil" ) func TestSubscriptionRepository_FindByUserID(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") sub := &models.UserSubscription{ UserID: user.ID, Tier: models.TierPro, } require.NoError(t, db.Create(sub).Error) found, err := repo.FindByUserID(user.ID) require.NoError(t, err) assert.Equal(t, models.TierPro, found.Tier) } func TestSubscriptionRepository_FindByUserID_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) _, err := repo.FindByUserID(9999) assert.Error(t, err) } func TestSubscriptionRepository_Update(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") sub, err := repo.GetOrCreate(user.ID) require.NoError(t, err) sub.Tier = models.TierPro err = repo.Update(sub) require.NoError(t, err) found, err := repo.FindByUserID(user.ID) require.NoError(t, err) assert.Equal(t, models.TierPro, found.Tier) } func TestSubscriptionRepository_UpgradeToPro(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") _, err := repo.GetOrCreate(user.ID) require.NoError(t, err) expiresAt := time.Now().UTC().AddDate(1, 0, 0) err = repo.UpgradeToPro(user.ID, expiresAt, "apple") require.NoError(t, err) found, err := repo.FindByUserID(user.ID) require.NoError(t, err) assert.Equal(t, models.TierPro, found.Tier) assert.Equal(t, "apple", found.Platform) assert.True(t, found.AutoRenew) require.NotNil(t, found.SubscribedAt) require.NotNil(t, found.ExpiresAt) } func TestSubscriptionRepository_DowngradeToFree(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") _, err := repo.GetOrCreate(user.ID) require.NoError(t, err) // First upgrade err = repo.UpgradeToPro(user.ID, time.Now().UTC().AddDate(1, 0, 0), "apple") require.NoError(t, err) // Then downgrade err = repo.DowngradeToFree(user.ID) require.NoError(t, err) found, err := repo.FindByUserID(user.ID) require.NoError(t, err) assert.Equal(t, models.TierFree, found.Tier) assert.False(t, found.AutoRenew) require.NotNil(t, found.CancelledAt) } func TestSubscriptionRepository_SetAutoRenew(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") _, err := repo.GetOrCreate(user.ID) require.NoError(t, err) err = repo.SetAutoRenew(user.ID, false) require.NoError(t, err) found, err := repo.FindByUserID(user.ID) require.NoError(t, err) assert.False(t, found.AutoRenew) } func TestSubscriptionRepository_UpdateReceiptData(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") _, err := repo.GetOrCreate(user.ID) require.NoError(t, err) err = repo.UpdateReceiptData(user.ID, "receipt_data_abc123") require.NoError(t, err) var sub models.UserSubscription db.Where("user_id = ?", user.ID).First(&sub) require.NotNil(t, sub.AppleReceiptData) assert.Equal(t, "receipt_data_abc123", *sub.AppleReceiptData) } func TestSubscriptionRepository_UpdatePurchaseToken(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") _, err := repo.GetOrCreate(user.ID) require.NoError(t, err) err = repo.UpdatePurchaseToken(user.ID, "google_token_xyz") require.NoError(t, err) var sub models.UserSubscription db.Where("user_id = ?", user.ID).First(&sub) require.NotNil(t, sub.GooglePurchaseToken) assert.Equal(t, "google_token_xyz", *sub.GooglePurchaseToken) } func TestSubscriptionRepository_FindByAppleReceiptContains(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") // Use raw SQL to ensure apple_receipt_data is stored correctly (GORM may omit pointer fields) require.NoError(t, db.Exec( "INSERT INTO subscription_usersubscription (user_id, tier, apple_receipt_data, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))", user.ID, models.TierPro, "transactionid=txnabc123data", ).Error) // Avoid underscores in search term: escapeLikeWildcards escapes _ with \ // which PostgreSQL handles but SQLite does not (no default ESCAPE clause) found, err := repo.FindByAppleReceiptContains("txnabc123") require.NoError(t, err) assert.Equal(t, user.ID, found.UserID) } func TestSubscriptionRepository_FindByAppleReceiptContains_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) _, err := repo.FindByAppleReceiptContains("nonexistent_txn") assert.Error(t, err) } func TestSubscriptionRepository_FindByGoogleToken(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") purchaseToken := "google_purchase_abc" sub := &models.UserSubscription{ UserID: user.ID, Tier: models.TierPro, GooglePurchaseToken: &purchaseToken, } require.NoError(t, db.Create(sub).Error) found, err := repo.FindByGoogleToken("google_purchase_abc") require.NoError(t, err) assert.Equal(t, user.ID, found.UserID) } func TestSubscriptionRepository_FindByGoogleToken_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) _, err := repo.FindByGoogleToken("nonexistent_token") assert.Error(t, err) } func TestSubscriptionRepository_SetCancelledAt(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") _, err := repo.GetOrCreate(user.ID) require.NoError(t, err) cancelTime := time.Now().UTC() err = repo.SetCancelledAt(user.ID, cancelTime) require.NoError(t, err) found, err := repo.FindByUserID(user.ID) require.NoError(t, err) require.NotNil(t, found.CancelledAt) } func TestSubscriptionRepository_ClearCancelledAt(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") _, err := repo.GetOrCreate(user.ID) require.NoError(t, err) // Set then clear err = repo.SetCancelledAt(user.ID, time.Now().UTC()) require.NoError(t, err) err = repo.ClearCancelledAt(user.ID) require.NoError(t, err) found, err := repo.FindByUserID(user.ID) require.NoError(t, err) assert.Nil(t, found.CancelledAt) } func TestSubscriptionRepository_GetTierLimits_Defaults(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) // No tier limits in DB, should return defaults freeLimits, err := repo.GetTierLimits(models.TierFree) require.NoError(t, err) assert.NotNil(t, freeLimits) proLimits, err := repo.GetTierLimits(models.TierPro) require.NoError(t, err) assert.NotNil(t, proLimits) } func TestSubscriptionRepository_GetAllTierLimits(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) // No limits seeded = empty limits, err := repo.GetAllTierLimits() require.NoError(t, err) assert.Empty(t, limits) } func TestSubscriptionRepository_GetSettings_Defaults(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) // No settings in DB, should return default (limitations disabled) settings, err := repo.GetSettings() require.NoError(t, err) assert.NotNil(t, settings) assert.False(t, settings.EnableLimitations) } func TestSubscriptionRepository_GetUpgradeTrigger(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) trigger := &models.UpgradeTrigger{ TriggerKey: "max_residences", Title: "Upgrade for more homes", Message: "Free tier supports 1 home", IsActive: true, } require.NoError(t, db.Create(trigger).Error) found, err := repo.GetUpgradeTrigger("max_residences") require.NoError(t, err) assert.Equal(t, "max_residences", found.TriggerKey) } func TestSubscriptionRepository_GetUpgradeTrigger_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) _, err := repo.GetUpgradeTrigger("nonexistent_key") assert.Error(t, err) } func TestSubscriptionRepository_GetAllUpgradeTriggers(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) trigger1 := &models.UpgradeTrigger{TriggerKey: "key1", Title: "T1", Message: "M1", IsActive: true} trigger2 := &models.UpgradeTrigger{TriggerKey: "key2", Title: "T2", Message: "M2", IsActive: true} require.NoError(t, db.Create(trigger1).Error) require.NoError(t, db.Create(trigger2).Error) // Use raw SQL for inactive record to avoid GORM default:true overriding IsActive=false require.NoError(t, db.Exec( "INSERT INTO subscription_upgradetrigger (trigger_key, title, message, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))", "key3", "T3", "M3", false, ).Error) triggers, err := repo.GetAllUpgradeTriggers() require.NoError(t, err) assert.Len(t, triggers, 2) // Only active } func TestSubscriptionRepository_GetFeatureBenefits(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) b1 := &models.FeatureBenefit{FeatureName: "Benefit 1", FreeTierText: "1 home", ProTierText: "Unlimited", DisplayOrder: 1, IsActive: true} require.NoError(t, db.Create(b1).Error) // Use raw SQL for inactive record to avoid GORM default:true overriding IsActive=false require.NoError(t, db.Exec( "INSERT INTO subscription_featurebenefit (feature_name, free_tier_text, pro_tier_text, display_order, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))", "Benefit 2", "3 tasks", "Unlimited", 2, false, ).Error) benefits, err := repo.GetFeatureBenefits() require.NoError(t, err) assert.Len(t, benefits, 1) // Only active } func TestSubscriptionRepository_GetActivePromotions(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) now := time.Now().UTC() active := &models.Promotion{ PromotionID: "promo_active", TargetTier: models.TierPro, Title: "Active Promo", Message: "Get Pro now!", StartDate: now.AddDate(0, 0, -1), EndDate: now.AddDate(0, 0, 1), IsActive: true, } expired := &models.Promotion{ PromotionID: "promo_expired", TargetTier: models.TierPro, Title: "Expired Promo", Message: "Too late!", StartDate: now.AddDate(0, 0, -10), EndDate: now.AddDate(0, 0, -5), IsActive: true, } require.NoError(t, db.Create(active).Error) require.NoError(t, db.Create(expired).Error) promos, err := repo.GetActivePromotions(models.TierPro) require.NoError(t, err) assert.Len(t, promos, 1) assert.Equal(t, "promo_active", promos[0].PromotionID) } func TestSubscriptionRepository_GetPromotionByID(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) promo := &models.Promotion{ PromotionID: "promo_find", TargetTier: models.TierPro, Title: "Find Me", Message: "Found!", StartDate: time.Now().UTC(), EndDate: time.Now().UTC().AddDate(0, 0, 30), IsActive: true, } require.NoError(t, db.Create(promo).Error) found, err := repo.GetPromotionByID("promo_find") require.NoError(t, err) assert.Equal(t, "Find Me", found.Title) } func TestSubscriptionRepository_GetPromotionByID_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) _, err := repo.GetPromotionByID("nonexistent") assert.Error(t, err) } func TestSubscriptionRepository_FindByStripeSubscriptionID(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") subID := "sub_test_123" sub := &models.UserSubscription{ UserID: user.ID, Tier: models.TierPro, StripeSubscriptionID: &subID, } require.NoError(t, db.Create(sub).Error) found, err := repo.FindByStripeSubscriptionID("sub_test_123") require.NoError(t, err) assert.Equal(t, user.ID, found.UserID) } func TestSubscriptionRepository_FindByStripeSubscriptionID_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewSubscriptionRepository(db) _, err := repo.FindByStripeSubscriptionID("sub_nonexistent") assert.Error(t, err) }