Files
honeyDueAPI/internal/repositories/user_repo_test.go
T
Trey t 81578f6e27
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
feat(auth): replace hand-rolled auth with Ory Kratos — phase 2 backend
Delegates all credential management (login, register, password reset,
email verification, social sign-in) to Ory Kratos. The Go API now acts
as a resource server: the new KratosAuth middleware validates sessions
against the Kratos whoami endpoint, writes the local User mirror into
Echo context, and all existing domain handlers continue working
unchanged. Hand-rolled token auth, AuthToken model, apple_auth/
google_auth services, and the auth refresh flow are removed. Tests are
updated to use the fake-token middleware pattern so existing integration
assertions require no rewrite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:55:56 -05:00

321 lines
8.9 KiB
Go

package repositories
import (
"testing"
"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 TestUserRepository_Create(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
user := &models.User{
Username: "testuser",
Email: "test@example.com",
IsActive: true,
}
err := repo.Create(user)
require.NoError(t, err)
assert.NotZero(t, user.ID)
}
func TestUserRepository_FindByID(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
// Find by ID
found, err := repo.FindByID(user.ID)
require.NoError(t, err)
assert.Equal(t, user.ID, found.ID)
assert.Equal(t, "testuser", found.Username)
assert.Equal(t, "test@example.com", found.Email)
}
func TestUserRepository_FindByID_NotFound(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
_, err := repo.FindByID(9999)
assert.Error(t, err)
}
func TestUserRepository_FindByUsername(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
// Find by username
found, err := repo.FindByUsername("testuser")
require.NoError(t, err)
assert.Equal(t, user.ID, found.ID)
}
func TestUserRepository_FindByEmail(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
// Find by email
found, err := repo.FindByEmail("test@example.com")
require.NoError(t, err)
assert.Equal(t, user.ID, found.ID)
}
func TestUserRepository_FindByUsernameOrEmail(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
tests := []struct {
name string
input string
expected uint
}{
{"find by username", "testuser", user.ID},
{"find by email", "test@example.com", user.ID},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
found, err := repo.FindByUsernameOrEmail(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.expected, found.ID)
})
}
}
func TestUserRepository_Update(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
// Update user
user.FirstName = "John"
user.LastName = "Doe"
err := repo.Update(user)
require.NoError(t, err)
// Verify update
found, err := repo.FindByID(user.ID)
require.NoError(t, err)
assert.Equal(t, "John", found.FirstName)
assert.Equal(t, "Doe", found.LastName)
}
func TestUserRepository_ExistsByUsername(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "Password123")
tests := []struct {
name string
username string
expected bool
}{
{"existing user", "existinguser", true},
{"non-existing user", "nonexistent", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exists, err := repo.ExistsByUsername(tt.username)
require.NoError(t, err)
assert.Equal(t, tt.expected, exists)
})
}
}
func TestUserRepository_ExistsByEmail(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "Password123")
tests := []struct {
name string
email string
expected bool
}{
{"existing email", "existing@example.com", true},
{"non-existing email", "nonexistent@example.com", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exists, err := repo.ExistsByEmail(tt.email)
require.NoError(t, err)
assert.Equal(t, tt.expected, exists)
})
}
}
func TestUserRepository_GetOrCreateProfile(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
// Create user
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
// First call should create
profile1, err := repo.GetOrCreateProfile(user.ID)
require.NoError(t, err)
assert.NotZero(t, profile1.ID)
// Second call should return same profile
profile2, err := repo.GetOrCreateProfile(user.ID)
require.NoError(t, err)
assert.Equal(t, profile1.ID, profile2.ID)
}
func TestUserRepository_FindAuthProvider(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
t.Run("kratos user", func(t *testing.T) {
user := testutil.CreateTestUser(t, db, "emailuser", "email@test.com", "Password123")
provider, err := repo.FindAuthProvider(user.ID)
require.NoError(t, err)
assert.Equal(t, "kratos", provider) // All users are Kratos-managed
})
}
func TestUserRepository_DeleteUserCascade(t *testing.T) {
t.Run("deletes user with no residences", func(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
user := testutil.CreateTestUser(t, db, "deletebare", "deletebare@test.com", "Password123")
// Create profile
profile := &models.UserProfile{UserID: user.ID, Verified: true}
require.NoError(t, db.Create(profile).Error)
var fileURLs []string
txErr := repo.Transaction(func(txRepo *UserRepository) error {
urls, err := txRepo.DeleteUserCascade(user.ID)
if err != nil {
return err
}
fileURLs = urls
return nil
})
require.NoError(t, txErr)
assert.Empty(t, fileURLs)
// Verify user is gone
var count int64
db.Model(&models.User{}).Where("id = ?", user.ID).Count(&count)
assert.Equal(t, int64(0), count)
// Verify profile is gone
db.Model(&models.UserProfile{}).Where("user_id = ?", user.ID).Count(&count)
assert.Equal(t, int64(0), count)
})
t.Run("returns file URLs for cleanup", func(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
user := testutil.CreateTestUser(t, db, "deletefiles", "deletefiles@test.com", "Password123")
residence := testutil.CreateTestResidence(t, db, user.ID, "Test Home")
// Create document with file
doc := &models.Document{
ResidenceID: residence.ID,
CreatedByID: user.ID,
Title: "Test Doc",
FileURL: "/uploads/documents/test.pdf",
}
require.NoError(t, db.Create(doc).Error)
// Create document image
docImage := &models.DocumentImage{
DocumentID: doc.ID,
ImageURL: "/uploads/images/docimg.jpg",
}
require.NoError(t, db.Create(docImage).Error)
var fileURLs []string
txErr := repo.Transaction(func(txRepo *UserRepository) error {
urls, err := txRepo.DeleteUserCascade(user.ID)
if err != nil {
return err
}
fileURLs = urls
return nil
})
require.NoError(t, txErr)
// Should return the file URLs
assert.Contains(t, fileURLs, "/uploads/documents/test.pdf")
assert.Contains(t, fileURLs, "/uploads/images/docimg.jpg")
// Verify everything deleted
var count int64
db.Model(&models.User{}).Where("id = ?", user.ID).Count(&count)
assert.Equal(t, int64(0), count)
db.Model(&models.Residence{}).Where("id = ?", residence.ID).Count(&count)
assert.Equal(t, int64(0), count)
db.Model(&models.Document{}).Where("id = ?", doc.ID).Count(&count)
assert.Equal(t, int64(0), count)
})
t.Run("handles user with owned and shared residences", func(t *testing.T) {
db := testutil.SetupTestDB(t)
repo := NewUserRepository(db)
owner := testutil.CreateTestUser(t, db, "deleteowner", "deleteowner@test.com", "Password123")
otherUser := testutil.CreateTestUser(t, db, "otheruser", "other@test.com", "Password123")
otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other Home")
// Owner's residence
ownedResidence := testutil.CreateTestResidence(t, db, owner.ID, "Owner Home")
// Add owner as member of other user's residence
db.Exec("INSERT INTO residence_residence_users (residence_id, user_id) VALUES (?, ?)", otherResidence.ID, owner.ID)
txErr := repo.Transaction(func(txRepo *UserRepository) error {
_, err := txRepo.DeleteUserCascade(owner.ID)
return err
})
require.NoError(t, txErr)
// Owner's residence should be deleted
var count int64
db.Model(&models.Residence{}).Where("id = ?", ownedResidence.ID).Count(&count)
assert.Equal(t, int64(0), count)
// Other user's residence should still exist
db.Model(&models.Residence{}).Where("id = ?", otherResidence.ID).Count(&count)
assert.Equal(t, int64(1), count)
// Owner should no longer be a member of other's residence
db.Raw("SELECT COUNT(*) FROM residence_residence_users WHERE user_id = ?", owner.ID).Count(&count)
assert.Equal(t, int64(0), count)
// Other user should still exist
db.Model(&models.User{}).Where("id = ?", otherUser.ID).Count(&count)
assert.Equal(t, int64(1), count)
})
}