Comprehensive security hardening from audit findings: - Add validation tags to all DTO request structs (max lengths, ranges, enums) - Replace unsafe type assertions with MustGetAuthUser helper across all handlers - Remove query-param token auth from admin middleware (prevents URL token leakage) - Add request validation calls in handlers that were missing c.Validate() - Remove goroutines in handlers (timezone update now synchronous) - Add sanitize middleware and path traversal protection (path_utils) - Stop resetting admin passwords on migration restart - Warn on well-known default SECRET_KEY - Add ~30 new test files covering security regressions, auth safety, repos, and services - Add deploy/ config, audit digests, and AUDIT_FINDINGS documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
package repositories
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/treytartt/casera-api/internal/models"
|
|
"github.com/treytartt/casera-api/internal/testutil"
|
|
)
|
|
|
|
func TestGetOrCreate_New_CreatesFreeTier(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
repo := NewSubscriptionRepository(db)
|
|
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
|
|
sub, err := repo.GetOrCreate(user.ID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sub)
|
|
|
|
assert.Equal(t, user.ID, sub.UserID)
|
|
assert.Equal(t, models.TierFree, sub.Tier)
|
|
assert.True(t, sub.AutoRenew)
|
|
|
|
// Verify persisted
|
|
var count int64
|
|
db.Model(&models.UserSubscription{}).Where("user_id = ?", user.ID).Count(&count)
|
|
assert.Equal(t, int64(1), count, "should have exactly one subscription record")
|
|
}
|
|
|
|
func TestGetOrCreate_AlreadyExists_Returns(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
repo := NewSubscriptionRepository(db)
|
|
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
|
|
// Create a pro subscription manually
|
|
existing := &models.UserSubscription{
|
|
UserID: user.ID,
|
|
Tier: models.TierPro,
|
|
AutoRenew: true,
|
|
}
|
|
err := db.Create(existing).Error
|
|
require.NoError(t, err)
|
|
|
|
// GetOrCreate should return existing, not overwrite with free defaults
|
|
sub, err := repo.GetOrCreate(user.ID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sub)
|
|
|
|
assert.Equal(t, existing.ID, sub.ID, "should return the existing record by ID")
|
|
assert.Equal(t, models.TierPro, sub.Tier, "should preserve existing pro tier, not overwrite with free")
|
|
|
|
// Verify still only one record
|
|
var count int64
|
|
db.Model(&models.UserSubscription{}).Where("user_id = ?", user.ID).Count(&count)
|
|
assert.Equal(t, int64(1), count, "should still have exactly one subscription record")
|
|
}
|
|
|
|
func TestGetOrCreate_Idempotent(t *testing.T) {
|
|
db := testutil.SetupTestDB(t)
|
|
repo := NewSubscriptionRepository(db)
|
|
|
|
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
|
|
|
sub1, err := repo.GetOrCreate(user.ID)
|
|
require.NoError(t, err)
|
|
|
|
sub2, err := repo.GetOrCreate(user.ID)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, sub1.ID, sub2.ID)
|
|
|
|
var count int64
|
|
db.Model(&models.UserSubscription{}).Where("user_id = ?", user.ID).Count(&count)
|
|
assert.Equal(t, int64(1), count, "should have exactly one subscription record after two calls")
|
|
}
|