Files
honeyDueAPI/internal/repositories/subscription_repo_coverage_test.go
Trey T bec880886b Coverage priorities 1-5: test pure functions, extract interfaces, mock-based handler tests
- Priority 1: Test NewSendEmailTask + NewSendPushTask (5 tests)
- Priority 2: Test customHTTPErrorHandler — all 15+ branches (21 tests)
- Priority 3: Extract Enqueuer interface + payload builders in worker pkg (5 tests)
- Priority 4: Extract ClassifyFile/ComputeRelPath in migrate-encrypt (6 tests)
- Priority 5: Define Handler interfaces, refactor to accept them, mock-based tests (14 tests)
- Fix .gitignore: /worker instead of worker to stop ignoring internal/worker/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 20:30:09 -05:00

419 lines
13 KiB
Go

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