From 14ca0d8809537bd2355b2433ad945ba348a3a11e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 29 Mar 2026 21:59:43 -0600 Subject: [PATCH] Tighten user linking --- .../backend/service/CustomerService.java | 16 +++++++ .../migration/V9__backfill_user_accounts.sql | 12 +++-- .../backend/service/CustomerServiceTest.java | 45 ++++++++++++++++++- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/petshop/backend/service/CustomerService.java b/backend/src/main/java/com/petshop/backend/service/CustomerService.java index fb4f75c0..d3c1354d 100644 --- a/backend/src/main/java/com/petshop/backend/service/CustomerService.java +++ b/backend/src/main/java/com/petshop/backend/service/CustomerService.java @@ -52,6 +52,8 @@ public class CustomerService { @Transactional public CustomerResponse createCustomer(CustomerRequest request) { + ensureEmailAvailable(request.getEmail(), null); + Customer customer = new Customer(); customer.setFirstName(request.getFirstName()); customer.setLastName(request.getLastName()); @@ -78,6 +80,8 @@ public class CustomerService { Customer customer = customerRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + id)); + ensureEmailAvailable(request.getEmail(), customer.getUserId()); + customer.setFirstName(request.getFirstName()); customer.setLastName(request.getLastName()); customer.setEmail(request.getEmail()); @@ -142,4 +146,16 @@ public class CustomerService { private String generatePhone(Customer customer) { return String.format("200-000-%04d", customer.getCustomerId()); } + + private void ensureEmailAvailable(String email, Long currentUserId) { + if (email == null || email.isBlank()) { + return; + } + + userRepository.findByEmail(email).ifPresent(existing -> { + if (currentUserId == null || !existing.getId().equals(currentUserId)) { + throw new ResponseStatusException(CONFLICT, "Email already exists"); + } + }); + } } diff --git a/backend/src/main/resources/db/migration/V9__backfill_user_accounts.sql b/backend/src/main/resources/db/migration/V9__backfill_user_accounts.sql index 273313a3..05cee442 100644 --- a/backend/src/main/resources/db/migration/V9__backfill_user_accounts.sql +++ b/backend/src/main/resources/db/migration/V9__backfill_user_accounts.sql @@ -2,7 +2,10 @@ INSERT INTO users (username, password, email, fullName, phone, role, active, tok SELECT CONCAT('customer_', c.customerId) AS username, '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq' AS password, - c.email, + CASE + WHEN EXISTS (SELECT 1 FROM users u WHERE u.email = c.email) THEN CONCAT('customer_', c.customerId, '@petshop.local') + ELSE c.email + END AS email, CONCAT(c.firstName, ' ', c.lastName) AS fullName, CONCAT('200-000-', LPAD(c.customerId, 4, '0')) AS phone, 'CUSTOMER' AS role, @@ -14,14 +17,16 @@ WHERE c.user_id IS NULL SELECT 1 FROM users u WHERE u.username = CONCAT('customer_', c.customerId) - OR u.email = c.email ); INSERT INTO users (username, password, email, fullName, phone, role, active, tokenVersion) SELECT CONCAT('employee_', e.employeeId) AS username, '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq' AS password, - e.email, + CASE + WHEN EXISTS (SELECT 1 FROM users u WHERE u.email = e.email) THEN CONCAT('employee_', e.employeeId, '@petshop.local') + ELSE e.email + END AS email, CONCAT(e.firstName, ' ', e.lastName) AS fullName, CONCAT('300-000-', LPAD(e.employeeId, 4, '0')) AS phone, CASE @@ -36,7 +41,6 @@ WHERE e.user_id IS NULL SELECT 1 FROM users u WHERE u.username = CONCAT('employee_', e.employeeId) - OR u.email = e.email ); UPDATE customer c diff --git a/backend/src/test/java/com/petshop/backend/service/CustomerServiceTest.java b/backend/src/test/java/com/petshop/backend/service/CustomerServiceTest.java index 6d2bfad2..4b18a1cd 100644 --- a/backend/src/test/java/com/petshop/backend/service/CustomerServiceTest.java +++ b/backend/src/test/java/com/petshop/backend/service/CustomerServiceTest.java @@ -98,9 +98,52 @@ class CustomerServiceTest { existing.setEmail("pat@example.com"); existing.setRole(User.Role.STAFF); - when(customerRepository.save(any(Customer.class))).thenAnswer(invocation -> invocation.getArgument(0)); when(userRepository.findByEmail("pat@example.com")).thenReturn(Optional.of(existing)); assertThrows(ResponseStatusException.class, () -> customerService.createCustomer(request)); } + + @Test + void createCustomerRejectsExistingCustomerEmail() { + CustomerRequest request = new CustomerRequest(); + request.setFirstName("Pat"); + request.setLastName("Owner"); + request.setEmail("pat@example.com"); + + User existing = new User(); + existing.setId(22L); + existing.setUsername("customer1"); + existing.setEmail("pat@example.com"); + existing.setRole(User.Role.CUSTOMER); + + when(userRepository.findByEmail("pat@example.com")).thenReturn(Optional.of(existing)); + + assertThrows(ResponseStatusException.class, () -> customerService.createCustomer(request)); + } + + @Test + void updateCustomerRejectsExistingEmailFromOtherUser() { + Customer customer = new Customer(); + customer.setCustomerId(7L); + customer.setUserId(11L); + customer.setFirstName("Pat"); + customer.setLastName("Owner"); + customer.setEmail("old@example.com"); + + CustomerRequest request = new CustomerRequest(); + request.setFirstName("Pat"); + request.setLastName("Owner"); + request.setEmail("pat@example.com"); + + User existing = new User(); + existing.setId(22L); + existing.setUsername("customer2"); + existing.setEmail("pat@example.com"); + existing.setRole(User.Role.CUSTOMER); + + when(customerRepository.findById(7L)).thenReturn(Optional.of(customer)); + when(userRepository.findByEmail("pat@example.com")).thenReturn(Optional.of(existing)); + + assertThrows(ResponseStatusException.class, () -> customerService.updateCustomer(7L, request)); + } }