Files
honeyDueAPI/internal/models/subscription_test.go
Trey t 72db9050f8 Add Stripe billing, free trials, and cross-platform subscription guards
- Stripe integration: add StripeService with checkout sessions, customer
  portal, and webhook handling for subscription lifecycle events.
- Free trials: auto-start configurable trial on first subscription check,
  with admin-controllable duration and enable/disable toggle.
- Cross-platform guard: prevent duplicate subscriptions across iOS, Android,
  and Stripe by checking existing platform before allowing purchase.
- Subscription model: add Stripe fields (customer_id, subscription_id,
  price_id), trial fields (trial_start, trial_end, trial_used), and
  SubscriptionSource/IsTrialActive helpers.
- API: add trial and source fields to status response, update OpenAPI spec.
- Clean up stale migration and audit docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:36:14 -06:00

188 lines
4.2 KiB
Go

package models
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestIsTrialActive(t *testing.T) {
now := time.Now().UTC()
future := now.Add(24 * time.Hour)
past := now.Add(-24 * time.Hour)
tests := []struct {
name string
sub *UserSubscription
expected bool
}{
{
name: "trial_end in future returns true",
sub: &UserSubscription{TrialEnd: &future},
expected: true,
},
{
name: "trial_end in past returns false",
sub: &UserSubscription{TrialEnd: &past},
expected: false,
},
{
name: "trial_end nil returns false",
sub: &UserSubscription{TrialEnd: nil},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.sub.IsTrialActive()
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsPro(t *testing.T) {
now := time.Now().UTC()
future := now.Add(24 * time.Hour)
past := now.Add(-24 * time.Hour)
tests := []struct {
name string
sub *UserSubscription
expected bool
}{
{
name: "tier=pro, expires_at in future returns true",
sub: &UserSubscription{
Tier: TierPro,
ExpiresAt: &future,
},
expected: true,
},
{
name: "tier=pro, expires_at in past returns false",
sub: &UserSubscription{
Tier: TierPro,
ExpiresAt: &past,
},
expected: false,
},
{
name: "tier=free, trial active returns true",
sub: &UserSubscription{
Tier: TierFree,
TrialEnd: &future,
},
expected: true,
},
{
name: "tier=free, trial expired returns false",
sub: &UserSubscription{
Tier: TierFree,
TrialEnd: &past,
},
expected: false,
},
{
name: "tier=free, no trial returns false",
sub: &UserSubscription{
Tier: TierFree,
TrialEnd: nil,
},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.sub.IsPro()
assert.Equal(t, tt.expected, result)
})
}
}
func TestHasSubscriptionHelpers(t *testing.T) {
empty := ""
validStripeID := "sub_1234567890"
validReceipt := "MIIT..."
validToken := "google-purchase-token-123"
tests := []struct {
name string
sub *UserSubscription
method string
expected bool
}{
// HasStripeSubscription
{
name: "HasStripeSubscription with nil returns false",
sub: &UserSubscription{StripeSubscriptionID: nil},
method: "stripe",
expected: false,
},
{
name: "HasStripeSubscription with empty string returns false",
sub: &UserSubscription{StripeSubscriptionID: &empty},
method: "stripe",
expected: false,
},
{
name: "HasStripeSubscription with valid ID returns true",
sub: &UserSubscription{StripeSubscriptionID: &validStripeID},
method: "stripe",
expected: true,
},
// HasAppleSubscription
{
name: "HasAppleSubscription with nil returns false",
sub: &UserSubscription{AppleReceiptData: nil},
method: "apple",
expected: false,
},
{
name: "HasAppleSubscription with empty string returns false",
sub: &UserSubscription{AppleReceiptData: &empty},
method: "apple",
expected: false,
},
{
name: "HasAppleSubscription with valid receipt returns true",
sub: &UserSubscription{AppleReceiptData: &validReceipt},
method: "apple",
expected: true,
},
// HasGoogleSubscription
{
name: "HasGoogleSubscription with nil returns false",
sub: &UserSubscription{GooglePurchaseToken: nil},
method: "google",
expected: false,
},
{
name: "HasGoogleSubscription with empty string returns false",
sub: &UserSubscription{GooglePurchaseToken: &empty},
method: "google",
expected: false,
},
{
name: "HasGoogleSubscription with valid token returns true",
sub: &UserSubscription{GooglePurchaseToken: &validToken},
method: "google",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result bool
switch tt.method {
case "stripe":
result = tt.sub.HasStripeSubscription()
case "apple":
result = tt.sub.HasAppleSubscription()
case "google":
result = tt.sub.HasGoogleSubscription()
}
assert.Equal(t, tt.expected, result)
})
}
}