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>
This commit is contained in:
418
internal/repositories/subscription_repo_coverage_test.go
Normal file
418
internal/repositories/subscription_repo_coverage_test.go
Normal file
@@ -0,0 +1,418 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user