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) }