Add PDF reports, file uploads, admin auth, and comprehensive tests
Features: - PDF service for generating task reports with ReportLab-style formatting - Storage service for file uploads (local and S3-compatible) - Admin authentication middleware with JWT support - Admin user model and repository Infrastructure: - Updated Docker configuration for admin panel builds - Email service enhancements for task notifications - Updated router with admin and file upload routes - Environment configuration updates Tests: - Unit tests for handlers (auth, residence, task) - Unit tests for models (user, residence, task) - Unit tests for repositories (user, residence, task) - Unit tests for services (residence, task) - Integration test setup - Test utilities for mocking database and services Database: - Admin user seed data - Updated test data seeds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
334
internal/services/residence_service_test.go
Normal file
334
internal/services/residence_service_test.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/treytartt/mycrib-api/internal/config"
|
||||
"github.com/treytartt/mycrib-api/internal/dto/requests"
|
||||
"github.com/treytartt/mycrib-api/internal/repositories"
|
||||
"github.com/treytartt/mycrib-api/internal/testutil"
|
||||
)
|
||||
|
||||
func setupResidenceService(t *testing.T) (*ResidenceService, *repositories.ResidenceRepository, *repositories.UserRepository) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
return service, residenceRepo, userRepo
|
||||
}
|
||||
|
||||
func TestResidenceService_CreateResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
|
||||
req := &requests.CreateResidenceRequest{
|
||||
Name: "Test House",
|
||||
StreetAddress: "123 Main St",
|
||||
City: "Austin",
|
||||
StateProvince: "TX",
|
||||
PostalCode: "78701",
|
||||
}
|
||||
|
||||
resp, err := service.CreateResidence(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.Equal(t, "Test House", resp.Name)
|
||||
assert.Equal(t, "123 Main St", resp.StreetAddress)
|
||||
assert.Equal(t, "Austin", resp.City)
|
||||
assert.Equal(t, "TX", resp.StateProvince)
|
||||
assert.Equal(t, "USA", resp.Country) // Default country
|
||||
assert.True(t, resp.IsPrimary) // Default is_primary
|
||||
}
|
||||
|
||||
func TestResidenceService_CreateResidence_WithOptionalFields(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
|
||||
bedrooms := 3
|
||||
bathrooms := decimal.NewFromFloat(2.5)
|
||||
sqft := 2000
|
||||
isPrimary := false
|
||||
|
||||
req := &requests.CreateResidenceRequest{
|
||||
Name: "Test House",
|
||||
StreetAddress: "123 Main St",
|
||||
City: "Austin",
|
||||
StateProvince: "TX",
|
||||
PostalCode: "78701",
|
||||
Country: "Canada",
|
||||
Bedrooms: &bedrooms,
|
||||
Bathrooms: &bathrooms,
|
||||
SquareFootage: &sqft,
|
||||
IsPrimary: &isPrimary,
|
||||
}
|
||||
|
||||
resp, err := service.CreateResidence(req, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Canada", resp.Country)
|
||||
assert.Equal(t, 3, *resp.Bedrooms)
|
||||
assert.True(t, resp.Bathrooms.Equal(decimal.NewFromFloat(2.5)))
|
||||
assert.Equal(t, 2000, *resp.SquareFootage)
|
||||
// First residence defaults to primary regardless of request
|
||||
assert.True(t, resp.IsPrimary)
|
||||
}
|
||||
|
||||
func TestResidenceService_GetResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
resp, err := service.GetResidence(residence.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, residence.ID, resp.ID)
|
||||
assert.Equal(t, "Test House", resp.Name)
|
||||
}
|
||||
|
||||
func TestResidenceService_GetResidence_AccessDenied(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
_, err := service.GetResidence(residence.ID, otherUser.ID)
|
||||
assert.ErrorIs(t, err, ErrResidenceAccessDenied)
|
||||
}
|
||||
|
||||
func TestResidenceService_GetResidence_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "user", "user@test.com", "password")
|
||||
|
||||
_, err := service.GetResidence(9999, user.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResidenceService_ListResidences(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
testutil.CreateTestResidence(t, db, user.ID, "House 1")
|
||||
testutil.CreateTestResidence(t, db, user.ID, "House 2")
|
||||
|
||||
resp, err := service.ListResidences(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, resp, 2)
|
||||
}
|
||||
|
||||
func TestResidenceService_UpdateResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Original Name")
|
||||
|
||||
newName := "Updated Name"
|
||||
newCity := "Dallas"
|
||||
req := &requests.UpdateResidenceRequest{
|
||||
Name: &newName,
|
||||
City: &newCity,
|
||||
}
|
||||
|
||||
resp, err := service.UpdateResidence(residence.ID, user.ID, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Name", resp.Name)
|
||||
assert.Equal(t, "Dallas", resp.City)
|
||||
}
|
||||
|
||||
func TestResidenceService_UpdateResidence_NotOwner(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
// Share with user
|
||||
residenceRepo.AddUser(residence.ID, sharedUser.ID)
|
||||
|
||||
newName := "Updated"
|
||||
req := &requests.UpdateResidenceRequest{Name: &newName}
|
||||
|
||||
_, err := service.UpdateResidence(residence.ID, sharedUser.ID, req)
|
||||
assert.ErrorIs(t, err, ErrNotResidenceOwner)
|
||||
}
|
||||
|
||||
func TestResidenceService_DeleteResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
err := service.DeleteResidence(residence.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should not be found
|
||||
_, err = service.GetResidence(residence.ID, user.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResidenceService_DeleteResidence_NotOwner(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
residenceRepo.AddUser(residence.ID, sharedUser.ID)
|
||||
|
||||
err := service.DeleteResidence(residence.ID, sharedUser.ID)
|
||||
assert.ErrorIs(t, err, ErrNotResidenceOwner)
|
||||
}
|
||||
|
||||
func TestResidenceService_GenerateShareCode(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
resp, err := service.GenerateShareCode(residence.ID, user.ID, 24)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, resp.ShareCode.Code)
|
||||
assert.Len(t, resp.ShareCode.Code, 6)
|
||||
}
|
||||
|
||||
func TestResidenceService_JoinWithCode(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
newUser := testutil.CreateTestUser(t, db, "newuser", "new@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
// Generate share code
|
||||
shareResp, err := service.GenerateShareCode(residence.ID, owner.ID, 24)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Join with code
|
||||
joinResp, err := service.JoinWithCode(shareResp.ShareCode.Code, newUser.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, residence.ID, joinResp.Residence.ID)
|
||||
|
||||
// Verify access
|
||||
hasAccess, _ := residenceRepo.HasAccess(residence.ID, newUser.ID)
|
||||
assert.True(t, hasAccess)
|
||||
}
|
||||
|
||||
func TestResidenceService_JoinWithCode_AlreadyMember(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
shareResp, _ := service.GenerateShareCode(residence.ID, owner.ID, 24)
|
||||
|
||||
// Owner tries to join their own residence
|
||||
_, err := service.JoinWithCode(shareResp.ShareCode.Code, owner.ID)
|
||||
assert.ErrorIs(t, err, ErrUserAlreadyMember)
|
||||
}
|
||||
|
||||
func TestResidenceService_GetResidenceUsers(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
user1 := testutil.CreateTestUser(t, db, "user1", "user1@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
residenceRepo.AddUser(residence.ID, user1.ID)
|
||||
|
||||
users, err := service.GetResidenceUsers(residence.ID, owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, users, 2) // owner + shared user
|
||||
}
|
||||
|
||||
func TestResidenceService_RemoveUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
residenceRepo.AddUser(residence.ID, sharedUser.ID)
|
||||
|
||||
err := service.RemoveUser(residence.ID, sharedUser.ID, owner.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
hasAccess, _ := residenceRepo.HasAccess(residence.ID, sharedUser.ID)
|
||||
assert.False(t, hasAccess)
|
||||
}
|
||||
|
||||
func TestResidenceService_RemoveUser_CannotRemoveOwner(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
residenceRepo := repositories.NewResidenceRepository(db)
|
||||
userRepo := repositories.NewUserRepository(db)
|
||||
cfg := &config.Config{}
|
||||
service := NewResidenceService(residenceRepo, userRepo, cfg)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
err := service.RemoveUser(residence.ID, owner.ID, owner.ID)
|
||||
assert.ErrorIs(t, err, ErrCannotRemoveOwner)
|
||||
}
|
||||
Reference in New Issue
Block a user