package services import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/treytartt/honeydue-api/internal/config" "github.com/treytartt/honeydue-api/internal/models" "github.com/treytartt/honeydue-api/internal/repositories" ) func setupRefreshTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err) err = db.AutoMigrate(&models.User{}, &models.UserProfile{}, &models.AuthToken{}) require.NoError(t, err) return db } func createRefreshTestUser(t *testing.T, db *gorm.DB) *models.User { t.Helper() user := &models.User{ Username: "refreshtest", Email: "refresh@test.com", IsActive: true, } require.NoError(t, user.SetPassword("Password123")) require.NoError(t, db.Create(user).Error) return user } func createTokenWithAge(t *testing.T, db *gorm.DB, userID uint, ageDays int) *models.AuthToken { t.Helper() token := &models.AuthToken{ UserID: userID, } require.NoError(t, db.Create(token).Error) // Backdate the token's Created timestamp after creation to bypass autoCreateTime backdated := time.Now().UTC().AddDate(0, 0, -ageDays) require.NoError(t, db.Model(token).Update("created", backdated).Error) token.Created = backdated return token } func newTestAuthService(db *gorm.DB) *AuthService { userRepo := repositories.NewUserRepository(db) cfg := &config.Config{ Security: config.SecurityConfig{ SecretKey: "test-secret", TokenExpiryDays: 90, TokenRefreshDays: 60, }, } return NewAuthService(userRepo, cfg) } func TestRefreshToken_FreshToken_ReturnsExisting(t *testing.T) { db := setupRefreshTestDB(t) user := createRefreshTestUser(t, db) token := createTokenWithAge(t, db, user.ID, 30) // 30 days old, well within fresh window svc := newTestAuthService(db) resp, err := svc.RefreshToken(token.Key, user.ID) require.NoError(t, err) assert.Equal(t, token.Key, resp.Token, "fresh token should return the same token") assert.Contains(t, resp.Message, "still valid") } func TestRefreshToken_InRenewalWindow_ReturnsNewToken(t *testing.T) { db := setupRefreshTestDB(t) user := createRefreshTestUser(t, db) token := createTokenWithAge(t, db, user.ID, 75) // 75 days old, in renewal window (60-90) svc := newTestAuthService(db) resp, err := svc.RefreshToken(token.Key, user.ID) require.NoError(t, err) assert.NotEqual(t, token.Key, resp.Token, "should return a new token") assert.Contains(t, resp.Message, "refreshed") // Verify old token was deleted var count int64 db.Model(&models.AuthToken{}).Where("key = ?", token.Key).Count(&count) assert.Equal(t, int64(0), count, "old token should be deleted") // Verify new token exists in DB db.Model(&models.AuthToken{}).Where("key = ?", resp.Token).Count(&count) assert.Equal(t, int64(1), count, "new token should exist in DB") // Verify new token belongs to the same user var newToken models.AuthToken require.NoError(t, db.Where("key = ?", resp.Token).First(&newToken).Error) assert.Equal(t, user.ID, newToken.UserID) } func TestRefreshToken_ExpiredToken_Returns401(t *testing.T) { db := setupRefreshTestDB(t) user := createRefreshTestUser(t, db) token := createTokenWithAge(t, db, user.ID, 91) // 91 days old, past 90-day expiry svc := newTestAuthService(db) resp, err := svc.RefreshToken(token.Key, user.ID) require.Error(t, err) assert.Nil(t, resp) assert.Contains(t, err.Error(), "error.token_expired") } func TestRefreshToken_AtExactBoundary60Days(t *testing.T) { db := setupRefreshTestDB(t) user := createRefreshTestUser(t, db) // Exactly 60 days: token age == refreshDays, so tokenAge < refreshDuration is false, // meaning it enters the renewal window token := createTokenWithAge(t, db, user.ID, 61) svc := newTestAuthService(db) resp, err := svc.RefreshToken(token.Key, user.ID) require.NoError(t, err) assert.NotEqual(t, token.Key, resp.Token, "token at 61 days should be refreshed") } func TestRefreshToken_InvalidToken_Returns401(t *testing.T) { db := setupRefreshTestDB(t) user := createRefreshTestUser(t, db) svc := newTestAuthService(db) resp, err := svc.RefreshToken("nonexistent-token-key", user.ID) require.Error(t, err) assert.Nil(t, resp) assert.Contains(t, err.Error(), "error.invalid_token") } func TestRefreshToken_WrongUser_Returns401(t *testing.T) { db := setupRefreshTestDB(t) user := createRefreshTestUser(t, db) token := createTokenWithAge(t, db, user.ID, 75) svc := newTestAuthService(db) // Try to refresh with a different user ID resp, err := svc.RefreshToken(token.Key, user.ID+999) require.Error(t, err) assert.Nil(t, resp) assert.Contains(t, err.Error(), "error.invalid_token") } func TestRefreshToken_FreshTokenAt59Days_ReturnsExisting(t *testing.T) { db := setupRefreshTestDB(t) user := createRefreshTestUser(t, db) token := createTokenWithAge(t, db, user.ID, 59) // 59 days, just under the 60-day threshold svc := newTestAuthService(db) resp, err := svc.RefreshToken(token.Key, user.ID) require.NoError(t, err) assert.Equal(t, token.Key, resp.Token, "token at 59 days should NOT be refreshed") }