From bef35a2595714338ca24cfe9ea12f18423ad1854 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:58:55 +0100 Subject: [PATCH 01/39] Adds AccountRepository --- .../example/repository/AccountRepository.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/org/example/repository/AccountRepository.java diff --git a/src/main/java/org/example/repository/AccountRepository.java b/src/main/java/org/example/repository/AccountRepository.java new file mode 100644 index 00000000..c84a68e0 --- /dev/null +++ b/src/main/java/org/example/repository/AccountRepository.java @@ -0,0 +1,16 @@ +package org.example.repository; + +import org.example.User; +import java.util.List; + +public interface AccountRepository { + User createAccount(User user); + User updateAccount(User user); + boolean deleteAccount(Long id); + + User findById(Long id); + User findByEmail(String email); + User findByUsername(String username); + + List findAllUsers(); +} From 067c402a78341318e179f83577f268c662cde7c9 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:05:25 +0100 Subject: [PATCH 02/39] Implemented Entity Manager Factory --- src/main/java/org/example/EMFactory.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/org/example/EMFactory.java diff --git a/src/main/java/org/example/EMFactory.java b/src/main/java/org/example/EMFactory.java new file mode 100644 index 00000000..1819e617 --- /dev/null +++ b/src/main/java/org/example/EMFactory.java @@ -0,0 +1,19 @@ +package org.example; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.Persistence; + +public class EMFactory { + private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system"); + + // Use this in try-with + public static EntityManager getEntityManager() { + return emf.createEntityManager(); + } + + // Close the factory + public static void close() { + emf.close(); + } +} From b102ca3609d3579bf6341d98f327a0a30ee2984b Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:37:07 +0100 Subject: [PATCH 03/39] Implemented JPA account repository --- .../repository/JpaAccountRepository.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/main/java/org/example/repository/JpaAccountRepository.java diff --git a/src/main/java/org/example/repository/JpaAccountRepository.java b/src/main/java/org/example/repository/JpaAccountRepository.java new file mode 100644 index 00000000..1abc3a60 --- /dev/null +++ b/src/main/java/org/example/repository/JpaAccountRepository.java @@ -0,0 +1,81 @@ +package org.example.repository; + +import jakarta.persistence.EntityManager; +import org.example.EMFactory; +import org.example.User; + +import java.util.List; + +public class JpaAccountRepository implements AccountRepository{ + + @Override + public User createAccount(User user) { + try (EntityManager em = EMFactory.getEntityManager()) { + em.getTransaction().begin(); + em.persist(user); + em.getTransaction().commit(); + return user; + } + } + + @Override + public User updateAccount(User user) { + try (EntityManager em = EMFactory.getEntityManager()) { + em.getTransaction().begin(); + User updatedUser = em.merge(user); + em.getTransaction().commit(); + return updatedUser; + } + } + + @Override + public boolean deleteAccount(Long id) { + try (EntityManager em = EMFactory.getEntityManager()) { + em.getTransaction().begin(); + User user = em.find(User.class, id); + if (user != null) { + em.remove(user); + em.getTransaction().commit(); + return true; + } + em.getTransaction().rollback(); + return false; + } + } + + @Override + public User findById(Long id) { + try (EntityManager em = EMFactory.getEntityManager()) { + return em.find(User.class, id); + } + } + + @Override + public User findByEmail(String email) { + try (EntityManager em = EMFactory.getEntityManager()) { + return em.createQuery("SELECT u FROM User u WHERE u.email = :email", User.class) + .setParameter("email", email) + .getSingleResult(); + } catch (Exception e) { + return null; + } + } + + @Override + public User findByUsername(String username) { + try (EntityManager em = EMFactory.getEntityManager()) { + return em.createQuery("SELECT u FROM User u WHERE u.username = :username", User.class) + .setParameter("username", username) + .getSingleResult(); + } catch (Exception e) { + return null; + } + } + + @Override + public List findAllUsers() { + try (EntityManager em = EMFactory.getEntityManager()) { + return em.createQuery("SELECT u FROM User u", User.class).getResultList(); + } + } +} From 9dae2e7d8d4c779d4791061212f3c863e6a4737a Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Thu, 8 Jan 2026 23:51:34 +0100 Subject: [PATCH 04/39] Implemented account business logic Implemented class AccountService with business logic for managing accounts. --- src/main/java/org/example/AccountService.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/main/java/org/example/AccountService.java diff --git a/src/main/java/org/example/AccountService.java b/src/main/java/org/example/AccountService.java new file mode 100644 index 00000000..f51fb870 --- /dev/null +++ b/src/main/java/org/example/AccountService.java @@ -0,0 +1,75 @@ +package org.example; + +import org.example.repository.AccountRepository; + +// Account business logic +public class AccountService { + + private AccountRepository accountRepository; + + public AccountService(AccountRepository accountRepository) { + this.accountRepository = accountRepository; + } + + public User login(String username, String password) { + User user = accountRepository.findByUsername(username); + + // Check if a user was found + if (user == null) { + throw new IllegalArgumentException("Couldn't find user with username " + username); + } + + // Check if the password is correct + if (!user.getPassword().equals(password) ) { + throw new IllegalArgumentException("Invalid password"); + } + + return user; + } + + public User createAccount(String firstName, String lastName, String email, String password) { + User newUser = new User(); + newUser.setFirstName(firstName); + newUser.setLastName(lastName); + newUser.setEmail(email); + newUser.setPassword(password); + return accountRepository.createAccount(newUser); + } + + public User updateAccount(Long id, String firstName, String lastName, String email, String password) { + // Get user by id + User user = accountRepository.findById(id); + + // Check if a user was found + if (user == null) { + throw new IllegalArgumentException("Couldn't find user with id " + id); + } + + // Update user + user.setFirstName(firstName); + user.setLastName(lastName); + user.setEmail(email); + user.setPassword(password); + return accountRepository.updateAccount(user); + } + + public boolean deleteAccount(Long id) { + return accountRepository.deleteAccount(id); + } + + public User findById(Long id) { + return accountRepository.findById(id); + } + + public User findByEmail(String email) { + return accountRepository.findByEmail(email); + } + + public User findByUsername(String username) { + return accountRepository.findByUsername(username); + } + + public Iterable findAll() { + return accountRepository.findAllUsers(); + } +} From 60b6a1998c9b43995255944e996f4307ce134c9b Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:43:18 +0100 Subject: [PATCH 05/39] Replaced deleteAccount Replaced deleteAccount with deleteAccountById and deleteAccountByUsername --- .../org/example/repository/AccountRepository.java | 3 ++- .../org/example/repository/JpaAccountRepository.java | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/repository/AccountRepository.java b/src/main/java/org/example/repository/AccountRepository.java index c84a68e0..a365ab94 100644 --- a/src/main/java/org/example/repository/AccountRepository.java +++ b/src/main/java/org/example/repository/AccountRepository.java @@ -6,7 +6,8 @@ public interface AccountRepository { User createAccount(User user); User updateAccount(User user); - boolean deleteAccount(Long id); + boolean deleteAccountById(Long id); + boolean deleteAccountByUsername(String username); User findById(Long id); User findByEmail(String email); diff --git a/src/main/java/org/example/repository/JpaAccountRepository.java b/src/main/java/org/example/repository/JpaAccountRepository.java index 1abc3a60..a5b86a58 100644 --- a/src/main/java/org/example/repository/JpaAccountRepository.java +++ b/src/main/java/org/example/repository/JpaAccountRepository.java @@ -29,7 +29,7 @@ public User updateAccount(User user) { } @Override - public boolean deleteAccount(Long id) { + public boolean deleteAccountById(Long id) { try (EntityManager em = EMFactory.getEntityManager()) { em.getTransaction().begin(); User user = em.find(User.class, id); @@ -43,6 +43,15 @@ public boolean deleteAccount(Long id) { } } + @Override + public boolean deleteAccountByUsername(String username) { + User user = findByUsername(username); + if (user != null) { + return deleteAccountById(user.getUserId()); + } + return false; + } + @Override public User findById(Long id) { try (EntityManager em = EMFactory.getEntityManager()) { From 6a02015c0d64c433f68382259b6c053580674c46 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:47:00 +0100 Subject: [PATCH 06/39] Added username generation logic --- src/main/java/org/example/AccountService.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/AccountService.java b/src/main/java/org/example/AccountService.java index f51fb870..f4d213a1 100644 --- a/src/main/java/org/example/AccountService.java +++ b/src/main/java/org/example/AccountService.java @@ -28,11 +28,15 @@ public User login(String username, String password) { } public User createAccount(String firstName, String lastName, String email, String password) { + // Generate username + String username = generateUsername(firstName, lastName); + User newUser = new User(); newUser.setFirstName(firstName); newUser.setLastName(lastName); newUser.setEmail(email); newUser.setPassword(password); + newUser.setUsername(username); return accountRepository.createAccount(newUser); } @@ -53,8 +57,12 @@ public User updateAccount(Long id, String firstName, String lastName, String ema return accountRepository.updateAccount(user); } - public boolean deleteAccount(Long id) { - return accountRepository.deleteAccount(id); + public boolean deleteAccountById(Long id) { + return accountRepository.deleteAccountById(id); + } + + public boolean deleteAccountByUsername(String username) { + return accountRepository.deleteAccountByUsername(username); } public User findById(Long id) { @@ -72,4 +80,24 @@ public User findByUsername(String username) { public Iterable findAll() { return accountRepository.findAllUsers(); } + + private String generateUsername(String firstName, String lastName) { + // Create username from first and last name (Dennis Dennisson = denden) + String concatUsername = (createSubstrings(firstName) + createSubstrings(lastName)).toLowerCase(); + String username = concatUsername; + + int counter = 1; + while(accountRepository.findByUsername(username) != null) { + username = concatUsername + counter; + counter++; + } + return username; + } + + private String createSubstrings(String name) { + // Length of 3 or length of name if shorter than 3 + int length = Math.min(name.length(), 3); + return name.substring(0, length); + } + } From bcbd4348a04184bb9598b7c4416da8524b0bdd8c Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:00:56 +0100 Subject: [PATCH 07/39] Added checks to account creation --- src/main/java/org/example/AccountService.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/AccountService.java b/src/main/java/org/example/AccountService.java index f4d213a1..2e4be1b8 100644 --- a/src/main/java/org/example/AccountService.java +++ b/src/main/java/org/example/AccountService.java @@ -2,9 +2,14 @@ import org.example.repository.AccountRepository; +import java.util.List; +import java.util.regex.Pattern; + // Account business logic public class AccountService { + static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"); + private AccountRepository accountRepository; public AccountService(AccountRepository accountRepository) { @@ -28,6 +33,26 @@ public User login(String username, String password) { } public User createAccount(String firstName, String lastName, String email, String password) { + // Check for null or empty + if (isInvalid(firstName) || isInvalid(lastName) || isInvalid(email) || isInvalid(password)) { + throw new IllegalArgumentException("No fields can be empty."); + } + + // Validate email with regex pattern + if (!EMAIL_PATTERN.matcher(email).matches()) { + throw new IllegalArgumentException("Invalid email format."); + } + + // Validate password length + if (password.length() < 3) { + throw new IllegalArgumentException("Password must be at least 3 characters long."); + } + + // Check for existing email + if (accountRepository.findByEmail(email) != null) { + throw new IllegalArgumentException("An account with this email already exists."); + } + // Generate username String username = generateUsername(firstName, lastName); @@ -77,7 +102,7 @@ public User findByUsername(String username) { return accountRepository.findByUsername(username); } - public Iterable findAll() { + public List findAllUsers() { return accountRepository.findAllUsers(); } @@ -100,4 +125,8 @@ private String createSubstrings(String name) { return name.substring(0, length); } + private boolean isInvalid(String value) { + return value == null || value.isBlank(); + } + } From 148d3ecf1d13b26bba6ef4f5e935c7ff5e162ed5 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:33:23 +0100 Subject: [PATCH 08/39] Created AccountService unit tests --- .../org/example/AccountServiceUnitTests.java | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 src/test/java/org/example/AccountServiceUnitTests.java diff --git a/src/test/java/org/example/AccountServiceUnitTests.java b/src/test/java/org/example/AccountServiceUnitTests.java new file mode 100644 index 00000000..2a6dbbfc --- /dev/null +++ b/src/test/java/org/example/AccountServiceUnitTests.java @@ -0,0 +1,272 @@ +package org.example; + +import org.example.repository.AccountRepository; + +import org.junit.jupiter.api.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.mockito.Mockito.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class AccountServiceUnitTests { + + @Test + void testLoginFailed_WrongUsername_ShowsErrorMessage() { + // Mock the repository + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Tell the mock to return null when trying to find 'testuser' + when(mockRepository.findByUsername("testuser")).thenReturn(null); + + assertThatThrownBy(() -> accountService.login("wronguser", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Couldn't find user with username wronguser"); + } + + @Test + void testLoginFailed_WrongPassword_ShowsErrorMessage() { + // Mock the repository + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Create a user for testing + User testUser = new User(); + testUser.setUsername("testuser"); + testUser.setPassword("rightpassword"); + + // Tell the mock to return 'testUser' when trying to find 'testuser' + when(mockRepository.findByUsername("testuser")).thenReturn(testUser); + + // Test login with wrong password + assertThatThrownBy(() -> accountService.login("testuser", "wrongPassword")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid password"); + } + + @Test + void testLogin_Success() { + // Mock the repository + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Create a user for testing + User testUser = new User(); + testUser.setUsername("testuser"); + testUser.setPassword("a$$word"); + + // Tell the mock to return 'testUser' when trying to find 'testuser' + when(mockRepository.findByUsername("testuser")).thenReturn(testUser); + + User result = accountService.login("testuser", "a$$word"); + + assertThat(result).isNotNull(); + assertThat(result.getUsername()).isEqualTo("testuser"); + } + + @Test + void testCreateAccount_Success() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // !Help from AI!: + // When createAccount is called, return the account that got sent in + when(mockRepository.createAccount(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + // Tell test there's no username collision + when(mockRepository.findByUsername(anyString())).thenReturn(null); + + // Create an account + User created = accountService.createAccount("Test", "User", "test@test.com", "a$$word"); + + // Make sure everything went through + assertThat(created.getUsername()).isEqualTo("tesuse"); + assertThat(created.getFirstName()).isEqualTo("Test"); + assertThat(created.getEmail()).isEqualTo("test@test.com"); + + verify(mockRepository, times(1)).createAccount(any(User.class)); + } + + @Test + void testCreateAccount_InvalidEmail_ThrowsException() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + assertThatThrownBy(() -> + accountService.createAccount("Test", "User", "invalidemail", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid email format"); + } + + @Test + void testCreateAccount_ShortPassword_ThrowsException() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + assertThatThrownBy(() -> + accountService.createAccount("Test", "User", "test@test.com", "12")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Password must be at least 3 characters long"); + } + + @Test + void testCreateAccount_EmptyFields_ThrowsException() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + assertThatThrownBy(() -> + accountService.createAccount("", "User", "test@test.com", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("No fields can be empty"); + } + + @Test + void testCreateAccount_DuplicateEmail_ThrowsException() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Simulera att e-posten redan finns + when(mockRepository.findByEmail("test@test.com")).thenReturn(new User()); + + assertThatThrownBy(() -> + accountService.createAccount("Test", "User", "test@test.com", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("An account with this email already exists"); + } + + @Test + void testDeleteAccountById_Success() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Mock successful delete + when(mockRepository.deleteAccountById(1337L)).thenReturn(true); + + // Run through delete method in service + boolean result = accountService.deleteAccountById(1337L); + + // Make sure it was successful + assertThat(result).isTrue(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteAccountById(1337L); + } + + @Test + void testDeleteAccountById_UserNotFound() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Mock failed delete + when(mockRepository.deleteAccountById(1337L)).thenReturn(false); + + // Run through delete method in service + boolean result = accountService.deleteAccountById(1337L); + + // Make sure it failed + assertThat(result).isFalse(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteAccountById(1337L); + } + + @Test + void testDeleteAccountByUsername_Success() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Mock successful delete + when(mockRepository.deleteAccountByUsername("testuser")).thenReturn(true); + + boolean result = accountService.deleteAccountByUsername("testuser"); + + assertThat(result).isTrue(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteAccountByUsername("testuser"); + } + + @Test + void testDeleteAccountByUsername_UserNotFound() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + // Mock return failed deletion + when(mockRepository.deleteAccountByUsername("testuser")).thenReturn(false); + + boolean result = accountService.deleteAccountByUsername("testuser"); + + assertThat(result).isFalse(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteAccountByUsername("testuser"); + } + + @Test + void testFindByUsername_Found() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + User testUser = new User(); + testUser.setUsername("Waldo"); + + when(mockRepository.findByUsername("Waldo")).thenReturn(testUser); + + User result = accountService.findByUsername("Waldo"); + + assertThat(result).isNotNull(); + assertThat(result.getUsername()).isEqualTo("Waldo"); + } + + @Test + void testFindByUsername_NotFound() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + when(mockRepository.findByUsername("Waldo")).thenReturn(null); + + User result = accountService.findByUsername("Waldo"); + + assertThat(result).isNull(); + } + + @Test + void testFindAllUsers_ReturnsMultipleUsers() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + User user1 = new User(); + user1.setUsername("user1"); + + User user2 = new User(); + user2.setUsername("user2"); + + // Create a list of users + List testUsers = Arrays.asList(user1, user2); + + // Tell the mock to return the list of users + when(mockRepository.findAllUsers()).thenReturn(testUsers); + + // Run through find users method in service + List result = accountService.findAllUsers(); + + assertThat(result).hasSize(2); + assertThat(result.get(0).getUsername()).isEqualTo("user1"); + assertThat(result.get(1).getUsername()).isEqualTo("user2"); + } + + @Test + void testFindAllUsers_ReturnsEmptyList() { + AccountRepository mockRepository = mock(AccountRepository.class); + AccountService accountService = new AccountService(mockRepository); + + when(mockRepository.findAllUsers()).thenReturn(Collections.emptyList()); + + List result = accountService.findAllUsers(); + + assertThat(result).isEmpty(); + } +} + + From 8339c002b0a334383ccc08bcb9b464e2ac9769e7 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sat, 10 Jan 2026 18:29:41 +0100 Subject: [PATCH 09/39] Moved into package "account" --- .../java/org/example/{ => account}/AccountService.java | 9 +++++---- .../{ => account}/repository/AccountRepository.java | 2 +- .../{ => account}/repository/JpaAccountRepository.java | 2 +- src/test/java/org/example/AccountServiceUnitTests.java | 7 ++++++- 4 files changed, 13 insertions(+), 7 deletions(-) rename src/main/java/org/example/{ => account}/AccountService.java (93%) rename src/main/java/org/example/{ => account}/repository/AccountRepository.java (90%) rename src/main/java/org/example/{ => account}/repository/JpaAccountRepository.java (98%) diff --git a/src/main/java/org/example/AccountService.java b/src/main/java/org/example/account/AccountService.java similarity index 93% rename from src/main/java/org/example/AccountService.java rename to src/main/java/org/example/account/AccountService.java index 2e4be1b8..7d6b644d 100644 --- a/src/main/java/org/example/AccountService.java +++ b/src/main/java/org/example/account/AccountService.java @@ -1,6 +1,7 @@ -package org.example; +package org.example.account; -import org.example.repository.AccountRepository; +import org.example.User; +import org.example.account.repository.AccountRepository; import java.util.List; import java.util.regex.Pattern; @@ -8,9 +9,9 @@ // Account business logic public class AccountService { - static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"); + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"); - private AccountRepository accountRepository; + private final AccountRepository accountRepository; public AccountService(AccountRepository accountRepository) { this.accountRepository = accountRepository; diff --git a/src/main/java/org/example/repository/AccountRepository.java b/src/main/java/org/example/account/repository/AccountRepository.java similarity index 90% rename from src/main/java/org/example/repository/AccountRepository.java rename to src/main/java/org/example/account/repository/AccountRepository.java index a365ab94..416ebacf 100644 --- a/src/main/java/org/example/repository/AccountRepository.java +++ b/src/main/java/org/example/account/repository/AccountRepository.java @@ -1,4 +1,4 @@ -package org.example.repository; +package org.example.account.repository; import org.example.User; import java.util.List; diff --git a/src/main/java/org/example/repository/JpaAccountRepository.java b/src/main/java/org/example/account/repository/JpaAccountRepository.java similarity index 98% rename from src/main/java/org/example/repository/JpaAccountRepository.java rename to src/main/java/org/example/account/repository/JpaAccountRepository.java index a5b86a58..c35b853a 100644 --- a/src/main/java/org/example/repository/JpaAccountRepository.java +++ b/src/main/java/org/example/account/repository/JpaAccountRepository.java @@ -1,4 +1,4 @@ -package org.example.repository; +package org.example.account.repository; import jakarta.persistence.EntityManager; import org.example.EMFactory; diff --git a/src/test/java/org/example/AccountServiceUnitTests.java b/src/test/java/org/example/AccountServiceUnitTests.java index 2a6dbbfc..053ef8e4 100644 --- a/src/test/java/org/example/AccountServiceUnitTests.java +++ b/src/test/java/org/example/AccountServiceUnitTests.java @@ -1,6 +1,7 @@ package org.example; -import org.example.repository.AccountRepository; +import org.example.account.AccountService; +import org.example.account.repository.AccountRepository; import org.junit.jupiter.api.*; @@ -13,6 +14,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +/* *********************************** */ +/* **** AccountService Unit Tests **** */ +/* *********************************** */ + class AccountServiceUnitTests { @Test From 071cefbd9c4016f6f993312d11754f5006529101 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sat, 10 Jan 2026 23:02:10 +0100 Subject: [PATCH 10/39] SessionManager handling user login, --- .../org/example/account/SessionManager.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/org/example/account/SessionManager.java diff --git a/src/main/java/org/example/account/SessionManager.java b/src/main/java/org/example/account/SessionManager.java new file mode 100644 index 00000000..4a9bc59f --- /dev/null +++ b/src/main/java/org/example/account/SessionManager.java @@ -0,0 +1,26 @@ +package org.example.account; + +import org.example.User; + +public class SessionManager { + private static User currentUser = null; + + // Log in user by setting currentUser to the passed in user + private static void login(User user) { + currentUser = user; + } + + // Log out user by setting currentUser to null + private static void logout() { + currentUser = null; + } + + public static User getCurrentUser() { + return currentUser; + } + + // Returns true if a user is logged in + public static boolean isLoggedIn() { + return currentUser != null; + } +} From c65394f63ad1e4d1d774aff53ac9c7cb7e6a0060 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:28:00 +0100 Subject: [PATCH 11/39] Changed names from account to user --- .../account/repository/AccountRepository.java | 17 -- .../{account => user}/SessionManager.java | 2 +- .../UserService.java} | 48 +-- .../repository/JpaUserRepository.java} | 14 +- .../user/repository/UserRepository.java | 17 ++ .../org/example/AccountServiceUnitTests.java | 277 ------------------ .../org/example/UserServiceUnitTests.java | 277 ++++++++++++++++++ 7 files changed, 326 insertions(+), 326 deletions(-) delete mode 100644 src/main/java/org/example/account/repository/AccountRepository.java rename src/main/java/org/example/{account => user}/SessionManager.java (95%) rename src/main/java/org/example/{account/AccountService.java => user/UserService.java} (67%) rename src/main/java/org/example/{account/repository/JpaAccountRepository.java => user/repository/JpaUserRepository.java} (86%) create mode 100644 src/main/java/org/example/user/repository/UserRepository.java delete mode 100644 src/test/java/org/example/AccountServiceUnitTests.java create mode 100644 src/test/java/org/example/UserServiceUnitTests.java diff --git a/src/main/java/org/example/account/repository/AccountRepository.java b/src/main/java/org/example/account/repository/AccountRepository.java deleted file mode 100644 index 416ebacf..00000000 --- a/src/main/java/org/example/account/repository/AccountRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.example.account.repository; - -import org.example.User; -import java.util.List; - -public interface AccountRepository { - User createAccount(User user); - User updateAccount(User user); - boolean deleteAccountById(Long id); - boolean deleteAccountByUsername(String username); - - User findById(Long id); - User findByEmail(String email); - User findByUsername(String username); - - List findAllUsers(); -} diff --git a/src/main/java/org/example/account/SessionManager.java b/src/main/java/org/example/user/SessionManager.java similarity index 95% rename from src/main/java/org/example/account/SessionManager.java rename to src/main/java/org/example/user/SessionManager.java index 4a9bc59f..4fc63245 100644 --- a/src/main/java/org/example/account/SessionManager.java +++ b/src/main/java/org/example/user/SessionManager.java @@ -1,4 +1,4 @@ -package org.example.account; +package org.example.user; import org.example.User; diff --git a/src/main/java/org/example/account/AccountService.java b/src/main/java/org/example/user/UserService.java similarity index 67% rename from src/main/java/org/example/account/AccountService.java rename to src/main/java/org/example/user/UserService.java index 7d6b644d..3bf5a107 100644 --- a/src/main/java/org/example/account/AccountService.java +++ b/src/main/java/org/example/user/UserService.java @@ -1,24 +1,24 @@ -package org.example.account; +package org.example.user; import org.example.User; -import org.example.account.repository.AccountRepository; +import org.example.user.repository.UserRepository; import java.util.List; import java.util.regex.Pattern; -// Account business logic -public class AccountService { +// User business logic +public class UserService { private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"); - private final AccountRepository accountRepository; + private final UserRepository userRepository; - public AccountService(AccountRepository accountRepository) { - this.accountRepository = accountRepository; + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; } public User login(String username, String password) { - User user = accountRepository.findByUsername(username); + User user = userRepository.findByUsername(username); // Check if a user was found if (user == null) { @@ -33,7 +33,7 @@ public User login(String username, String password) { return user; } - public User createAccount(String firstName, String lastName, String email, String password) { + public User createUser(String firstName, String lastName, String email, String password) { // Check for null or empty if (isInvalid(firstName) || isInvalid(lastName) || isInvalid(email) || isInvalid(password)) { throw new IllegalArgumentException("No fields can be empty."); @@ -50,8 +50,8 @@ public User createAccount(String firstName, String lastName, String email, Strin } // Check for existing email - if (accountRepository.findByEmail(email) != null) { - throw new IllegalArgumentException("An account with this email already exists."); + if (userRepository.findByEmail(email) != null) { + throw new IllegalArgumentException("A user with this email already exists."); } // Generate username @@ -63,12 +63,12 @@ public User createAccount(String firstName, String lastName, String email, Strin newUser.setEmail(email); newUser.setPassword(password); newUser.setUsername(username); - return accountRepository.createAccount(newUser); + return userRepository.createUser(newUser); } - public User updateAccount(Long id, String firstName, String lastName, String email, String password) { + public User updateUser(Long id, String firstName, String lastName, String email, String password) { // Get user by id - User user = accountRepository.findById(id); + User user = userRepository.findById(id); // Check if a user was found if (user == null) { @@ -80,31 +80,31 @@ public User updateAccount(Long id, String firstName, String lastName, String ema user.setLastName(lastName); user.setEmail(email); user.setPassword(password); - return accountRepository.updateAccount(user); + return userRepository.updateUser(user); } - public boolean deleteAccountById(Long id) { - return accountRepository.deleteAccountById(id); + public boolean deleteUserById(Long id) { + return userRepository.deleteUserById(id); } - public boolean deleteAccountByUsername(String username) { - return accountRepository.deleteAccountByUsername(username); + public boolean deleteUserByUsername(String username) { + return userRepository.deleteUserByUsername(username); } public User findById(Long id) { - return accountRepository.findById(id); + return userRepository.findById(id); } public User findByEmail(String email) { - return accountRepository.findByEmail(email); + return userRepository.findByEmail(email); } public User findByUsername(String username) { - return accountRepository.findByUsername(username); + return userRepository.findByUsername(username); } public List findAllUsers() { - return accountRepository.findAllUsers(); + return userRepository.findAllUsers(); } private String generateUsername(String firstName, String lastName) { @@ -113,7 +113,7 @@ private String generateUsername(String firstName, String lastName) { String username = concatUsername; int counter = 1; - while(accountRepository.findByUsername(username) != null) { + while(userRepository.findByUsername(username) != null) { username = concatUsername + counter; counter++; } diff --git a/src/main/java/org/example/account/repository/JpaAccountRepository.java b/src/main/java/org/example/user/repository/JpaUserRepository.java similarity index 86% rename from src/main/java/org/example/account/repository/JpaAccountRepository.java rename to src/main/java/org/example/user/repository/JpaUserRepository.java index c35b853a..d0b17f98 100644 --- a/src/main/java/org/example/account/repository/JpaAccountRepository.java +++ b/src/main/java/org/example/user/repository/JpaUserRepository.java @@ -1,4 +1,4 @@ -package org.example.account.repository; +package org.example.user.repository; import jakarta.persistence.EntityManager; import org.example.EMFactory; @@ -6,10 +6,10 @@ import java.util.List; -public class JpaAccountRepository implements AccountRepository{ +public class JpaUserRepository implements UserRepository { @Override - public User createAccount(User user) { + public User createUser(User user) { try (EntityManager em = EMFactory.getEntityManager()) { em.getTransaction().begin(); em.persist(user); @@ -19,7 +19,7 @@ public User createAccount(User user) { } @Override - public User updateAccount(User user) { + public User updateUser(User user) { try (EntityManager em = EMFactory.getEntityManager()) { em.getTransaction().begin(); User updatedUser = em.merge(user); @@ -29,7 +29,7 @@ public User updateAccount(User user) { } @Override - public boolean deleteAccountById(Long id) { + public boolean deleteUserById(Long id) { try (EntityManager em = EMFactory.getEntityManager()) { em.getTransaction().begin(); User user = em.find(User.class, id); @@ -44,10 +44,10 @@ public boolean deleteAccountById(Long id) { } @Override - public boolean deleteAccountByUsername(String username) { + public boolean deleteUserByUsername(String username) { User user = findByUsername(username); if (user != null) { - return deleteAccountById(user.getUserId()); + return deleteUserById(user.getUserId()); } return false; } diff --git a/src/main/java/org/example/user/repository/UserRepository.java b/src/main/java/org/example/user/repository/UserRepository.java new file mode 100644 index 00000000..83a625c8 --- /dev/null +++ b/src/main/java/org/example/user/repository/UserRepository.java @@ -0,0 +1,17 @@ +package org.example.user.repository; + +import org.example.User; +import java.util.List; + +public interface UserRepository { + User createUser(User user); + User updateUser(User user); + boolean deleteUserById(Long id); + boolean deleteUserByUsername(String username); + + User findById(Long id); + User findByEmail(String email); + User findByUsername(String username); + + List findAllUsers(); +} diff --git a/src/test/java/org/example/AccountServiceUnitTests.java b/src/test/java/org/example/AccountServiceUnitTests.java deleted file mode 100644 index 053ef8e4..00000000 --- a/src/test/java/org/example/AccountServiceUnitTests.java +++ /dev/null @@ -1,277 +0,0 @@ -package org.example; - -import org.example.account.AccountService; -import org.example.account.repository.AccountRepository; - -import org.junit.jupiter.api.*; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.mockito.Mockito.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/* *********************************** */ -/* **** AccountService Unit Tests **** */ -/* *********************************** */ - -class AccountServiceUnitTests { - - @Test - void testLoginFailed_WrongUsername_ShowsErrorMessage() { - // Mock the repository - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Tell the mock to return null when trying to find 'testuser' - when(mockRepository.findByUsername("testuser")).thenReturn(null); - - assertThatThrownBy(() -> accountService.login("wronguser", "a$$word")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Couldn't find user with username wronguser"); - } - - @Test - void testLoginFailed_WrongPassword_ShowsErrorMessage() { - // Mock the repository - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Create a user for testing - User testUser = new User(); - testUser.setUsername("testuser"); - testUser.setPassword("rightpassword"); - - // Tell the mock to return 'testUser' when trying to find 'testuser' - when(mockRepository.findByUsername("testuser")).thenReturn(testUser); - - // Test login with wrong password - assertThatThrownBy(() -> accountService.login("testuser", "wrongPassword")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Invalid password"); - } - - @Test - void testLogin_Success() { - // Mock the repository - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Create a user for testing - User testUser = new User(); - testUser.setUsername("testuser"); - testUser.setPassword("a$$word"); - - // Tell the mock to return 'testUser' when trying to find 'testuser' - when(mockRepository.findByUsername("testuser")).thenReturn(testUser); - - User result = accountService.login("testuser", "a$$word"); - - assertThat(result).isNotNull(); - assertThat(result.getUsername()).isEqualTo("testuser"); - } - - @Test - void testCreateAccount_Success() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // !Help from AI!: - // When createAccount is called, return the account that got sent in - when(mockRepository.createAccount(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0)); - - // Tell test there's no username collision - when(mockRepository.findByUsername(anyString())).thenReturn(null); - - // Create an account - User created = accountService.createAccount("Test", "User", "test@test.com", "a$$word"); - - // Make sure everything went through - assertThat(created.getUsername()).isEqualTo("tesuse"); - assertThat(created.getFirstName()).isEqualTo("Test"); - assertThat(created.getEmail()).isEqualTo("test@test.com"); - - verify(mockRepository, times(1)).createAccount(any(User.class)); - } - - @Test - void testCreateAccount_InvalidEmail_ThrowsException() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - assertThatThrownBy(() -> - accountService.createAccount("Test", "User", "invalidemail", "a$$word")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Invalid email format"); - } - - @Test - void testCreateAccount_ShortPassword_ThrowsException() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - assertThatThrownBy(() -> - accountService.createAccount("Test", "User", "test@test.com", "12")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Password must be at least 3 characters long"); - } - - @Test - void testCreateAccount_EmptyFields_ThrowsException() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - assertThatThrownBy(() -> - accountService.createAccount("", "User", "test@test.com", "a$$word")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("No fields can be empty"); - } - - @Test - void testCreateAccount_DuplicateEmail_ThrowsException() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Simulera att e-posten redan finns - when(mockRepository.findByEmail("test@test.com")).thenReturn(new User()); - - assertThatThrownBy(() -> - accountService.createAccount("Test", "User", "test@test.com", "a$$word")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("An account with this email already exists"); - } - - @Test - void testDeleteAccountById_Success() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Mock successful delete - when(mockRepository.deleteAccountById(1337L)).thenReturn(true); - - // Run through delete method in service - boolean result = accountService.deleteAccountById(1337L); - - // Make sure it was successful - assertThat(result).isTrue(); - // Verify that delete was called only once - verify(mockRepository, times(1)).deleteAccountById(1337L); - } - - @Test - void testDeleteAccountById_UserNotFound() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Mock failed delete - when(mockRepository.deleteAccountById(1337L)).thenReturn(false); - - // Run through delete method in service - boolean result = accountService.deleteAccountById(1337L); - - // Make sure it failed - assertThat(result).isFalse(); - // Verify that delete was called only once - verify(mockRepository, times(1)).deleteAccountById(1337L); - } - - @Test - void testDeleteAccountByUsername_Success() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Mock successful delete - when(mockRepository.deleteAccountByUsername("testuser")).thenReturn(true); - - boolean result = accountService.deleteAccountByUsername("testuser"); - - assertThat(result).isTrue(); - // Verify that delete was called only once - verify(mockRepository, times(1)).deleteAccountByUsername("testuser"); - } - - @Test - void testDeleteAccountByUsername_UserNotFound() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - // Mock return failed deletion - when(mockRepository.deleteAccountByUsername("testuser")).thenReturn(false); - - boolean result = accountService.deleteAccountByUsername("testuser"); - - assertThat(result).isFalse(); - // Verify that delete was called only once - verify(mockRepository, times(1)).deleteAccountByUsername("testuser"); - } - - @Test - void testFindByUsername_Found() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - User testUser = new User(); - testUser.setUsername("Waldo"); - - when(mockRepository.findByUsername("Waldo")).thenReturn(testUser); - - User result = accountService.findByUsername("Waldo"); - - assertThat(result).isNotNull(); - assertThat(result.getUsername()).isEqualTo("Waldo"); - } - - @Test - void testFindByUsername_NotFound() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - when(mockRepository.findByUsername("Waldo")).thenReturn(null); - - User result = accountService.findByUsername("Waldo"); - - assertThat(result).isNull(); - } - - @Test - void testFindAllUsers_ReturnsMultipleUsers() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - User user1 = new User(); - user1.setUsername("user1"); - - User user2 = new User(); - user2.setUsername("user2"); - - // Create a list of users - List testUsers = Arrays.asList(user1, user2); - - // Tell the mock to return the list of users - when(mockRepository.findAllUsers()).thenReturn(testUsers); - - // Run through find users method in service - List result = accountService.findAllUsers(); - - assertThat(result).hasSize(2); - assertThat(result.get(0).getUsername()).isEqualTo("user1"); - assertThat(result.get(1).getUsername()).isEqualTo("user2"); - } - - @Test - void testFindAllUsers_ReturnsEmptyList() { - AccountRepository mockRepository = mock(AccountRepository.class); - AccountService accountService = new AccountService(mockRepository); - - when(mockRepository.findAllUsers()).thenReturn(Collections.emptyList()); - - List result = accountService.findAllUsers(); - - assertThat(result).isEmpty(); - } -} - - diff --git a/src/test/java/org/example/UserServiceUnitTests.java b/src/test/java/org/example/UserServiceUnitTests.java new file mode 100644 index 00000000..c53dd832 --- /dev/null +++ b/src/test/java/org/example/UserServiceUnitTests.java @@ -0,0 +1,277 @@ +package org.example; + +import org.example.user.UserService; +import org.example.user.repository.UserRepository; + +import org.junit.jupiter.api.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.mockito.Mockito.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/* *********************************** */ +/* **** UserService Unit Tests **** */ +/* *********************************** */ + +class UserServiceUnitTests { + + @Test + void testLoginFailed_WrongUsername_ShowsErrorMessage() { + // Mock the repository + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Tell the mock to return null when trying to find 'testuser' + when(mockRepository.findByUsername("testuser")).thenReturn(null); + + assertThatThrownBy(() -> userService.login("wronguser", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Couldn't find user with username wronguser"); + } + + @Test + void testLoginFailed_WrongPassword_ShowsErrorMessage() { + // Mock the repository + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Create a user for testing + User testUser = new User(); + testUser.setUsername("testuser"); + testUser.setPassword("rightpassword"); + + // Tell the mock to return 'testUser' when trying to find 'testuser' + when(mockRepository.findByUsername("testuser")).thenReturn(testUser); + + // Test login with wrong password + assertThatThrownBy(() -> userService.login("testuser", "wrongPassword")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid password"); + } + + @Test + void testLogin_Success() { + // Mock the repository + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Create a user for testing + User testUser = new User(); + testUser.setUsername("testuser"); + testUser.setPassword("a$$word"); + + // Tell the mock to return 'testUser' when trying to find 'testuser' + when(mockRepository.findByUsername("testuser")).thenReturn(testUser); + + User result = userService.login("testuser", "a$$word"); + + assertThat(result).isNotNull(); + assertThat(result.getUsername()).isEqualTo("testuser"); + } + + @Test + void testCreateUser_Success() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // !Help from AI!: + // When createUser is called, return the user that got sent in + when(mockRepository.createUser(any(User.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + // Tell test there's no username collision + when(mockRepository.findByUsername(anyString())).thenReturn(null); + + // Create a user + User created = userService.createUser("Test", "User", "test@test.com", "a$$word"); + + // Make sure everything went through + assertThat(created.getUsername()).isEqualTo("tesuse"); + assertThat(created.getFirstName()).isEqualTo("Test"); + assertThat(created.getEmail()).isEqualTo("test@test.com"); + + verify(mockRepository, times(1)).createUser(any(User.class)); + } + + @Test + void testCreateUser_InvalidEmail_ThrowsException() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + assertThatThrownBy(() -> + userService.createUser("Test", "User", "invalidemail", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Invalid email format"); + } + + @Test + void testCreateUser_ShortPassword_ThrowsException() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + assertThatThrownBy(() -> + userService.createUser("Test", "User", "test@test.com", "12")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Password must be at least 3 characters long"); + } + + @Test + void testCreateUser_EmptyFields_ThrowsException() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + assertThatThrownBy(() -> + userService.createUser("", "User", "test@test.com", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("No fields can be empty"); + } + + @Test + void testCreateUser_DuplicateEmail_ThrowsException() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Simulera att e-posten redan finns + when(mockRepository.findByEmail("test@test.com")).thenReturn(new User()); + + assertThatThrownBy(() -> + userService.createUser("Test", "User", "test@test.com", "a$$word")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("A user with this email already exists"); + } + + @Test + void testDeleteUserById_Success() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Mock successful delete + when(mockRepository.deleteUserById(1337L)).thenReturn(true); + + // Run through delete method in service + boolean result = userService.deleteUserById(1337L); + + // Make sure it was successful + assertThat(result).isTrue(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteUserById(1337L); + } + + @Test + void testDeleteUserById_UserNotFound() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Mock failed delete + when(mockRepository.deleteUserById(1337L)).thenReturn(false); + + // Run through delete method in service + boolean result = userService.deleteUserById(1337L); + + // Make sure it failed + assertThat(result).isFalse(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteUserById(1337L); + } + + @Test + void testDeleteUserByUsername_Success() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Mock successful delete + when(mockRepository.deleteUserByUsername("testuser")).thenReturn(true); + + boolean result = userService.deleteUserByUsername("testuser"); + + assertThat(result).isTrue(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteUserByUsername("testuser"); + } + + @Test + void testDeleteUserByUsername_UserNotFound() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + // Mock return failed deletion + when(mockRepository.deleteUserByUsername("testuser")).thenReturn(false); + + boolean result = userService.deleteUserByUsername("testuser"); + + assertThat(result).isFalse(); + // Verify that delete was called only once + verify(mockRepository, times(1)).deleteUserByUsername("testuser"); + } + + @Test + void testFindByUsername_Found() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + User testUser = new User(); + testUser.setUsername("Waldo"); + + when(mockRepository.findByUsername("Waldo")).thenReturn(testUser); + + User result = userService.findByUsername("Waldo"); + + assertThat(result).isNotNull(); + assertThat(result.getUsername()).isEqualTo("Waldo"); + } + + @Test + void testFindByUsername_NotFound() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + when(mockRepository.findByUsername("Waldo")).thenReturn(null); + + User result = userService.findByUsername("Waldo"); + + assertThat(result).isNull(); + } + + @Test + void testFindAllUsers_ReturnsMultipleUsers() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + User user1 = new User(); + user1.setUsername("user1"); + + User user2 = new User(); + user2.setUsername("user2"); + + // Create a list of users + List testUsers = Arrays.asList(user1, user2); + + // Tell the mock to return the list of users + when(mockRepository.findAllUsers()).thenReturn(testUsers); + + // Run through find users method in service + List result = userService.findAllUsers(); + + assertThat(result).hasSize(2); + assertThat(result.get(0).getUsername()).isEqualTo("user1"); + assertThat(result.get(1).getUsername()).isEqualTo("user2"); + } + + @Test + void testFindAllUsers_ReturnsEmptyList() { + UserRepository mockRepository = mock(UserRepository.class); + UserService userService = new UserService(mockRepository); + + when(mockRepository.findAllUsers()).thenReturn(Collections.emptyList()); + + List result = userService.findAllUsers(); + + assertThat(result).isEmpty(); + } +} + + From 282a1e91ab98ff7f7efe83b0817ac32fe2a65152 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:28:54 +0100 Subject: [PATCH 12/39] IT for handling a full user lifecycle --- src/test/java/org/example/UserIT.java | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/test/java/org/example/UserIT.java diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java new file mode 100644 index 00000000..c1a89d1d --- /dev/null +++ b/src/test/java/org/example/UserIT.java @@ -0,0 +1,50 @@ +package org.example; + +import jakarta.persistence.EntityManager; +import org.example.user.UserService; +import org.example.user.repository.UserRepository; +import org.example.user.repository.JpaUserRepository; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/* *********************************** */ +/* **** User integration tests **** */ +/* *********************************** */ + +public class UserIT { + + private final UserRepository userRepository = new JpaUserRepository(); + private final UserService userService = new UserService(userRepository); + + // Makes sure we start with a clean slate after each test + @AfterEach + void deleteAllUsersAfterEachTest() { + try (EntityManager em = EMFactory.getEntityManager()) { + em.getTransaction().begin(); + em.createQuery("DELETE FROM User").executeUpdate(); + em.getTransaction().commit(); + } + } + + // Test a full user lifecycle to make sure database and business logic work together + @Test + void handlingFullUserLifecycle() { + // Create a new user in the database + User testUser = userService.createUser("Tester", "Testsson", "test@test.com", "assword"); + // Make sure a user was created and returned + assertThat(userService.login(testUser.getUsername(), "assword")).isNotNull(); + + // Update user in database + testUser = userService.updateUser(testUser.getUserId(), "Testor", "Nytest", "nytest@nytest.com", "newPassword"); + // Make sure the user was updated in the database + assertThat(userService.findById(testUser.getUserId()).getFirstName()).isEqualTo("Testor"); + + // Remove user from database + boolean deleted = userService.deleteUserById(testUser.getUserId()); + // Make sure the user was deleted from the database + assertThat(deleted).isTrue(); + assertThat(userService.findById(testUser.getUserId())).isNull(); + } + +} From 0f99774954dad7b0e22b6f92f6dd65ae94af466f Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:29:37 +0100 Subject: [PATCH 13/39] IT for handling username collisions --- src/test/java/org/example/UserIT.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java index c1a89d1d..6d82be4f 100644 --- a/src/test/java/org/example/UserIT.java +++ b/src/test/java/org/example/UserIT.java @@ -47,4 +47,14 @@ void handlingFullUserLifecycle() { assertThat(userService.findById(testUser.getUserId())).isNull(); } + @Test + void handlingUsernameCollisions() { + // Make sure the database can handle two users with the same username + userService.createUser("Tester", "Testsson", "test@test.com", "assword"); + User user2 = userService.createUser("Testor", "Testarn", "testing@testing.com", "assword"); + + // Make sure the second user got a different username than the first + assertThat(user2.getUsername()).isEqualTo("testes1"); + } + } From 23d3547db26842c2d5fc7ea63e3136c96773b531 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:44:29 +0100 Subject: [PATCH 14/39] IT for duplicate email exception handling --- src/test/java/org/example/UserIT.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java index 6d82be4f..29ae0001 100644 --- a/src/test/java/org/example/UserIT.java +++ b/src/test/java/org/example/UserIT.java @@ -5,6 +5,7 @@ import org.example.user.repository.UserRepository; import org.example.user.repository.JpaUserRepository; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -57,4 +58,12 @@ void handlingUsernameCollisions() { assertThat(user2.getUsername()).isEqualTo("testes1"); } + @Test + void throwExceptionWhenDuplicateEmails() { + userService.createUser("Tester", "Testsson", "test@test.com", "assword"); + + assertThatThrownBy(() -> userService.createUser("Testor", "Testarn", "test@test.com", "assword")) + .isInstanceOf(IllegalArgumentException.class); + } + } From 01ce9206efb90bd63c11da3dc780a052b0a83f17 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 01:03:06 +0100 Subject: [PATCH 15/39] IT for finding user by email (JPQL) --- src/test/java/org/example/UserIT.java | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java index 29ae0001..afb596ac 100644 --- a/src/test/java/org/example/UserIT.java +++ b/src/test/java/org/example/UserIT.java @@ -34,18 +34,22 @@ void handlingFullUserLifecycle() { // Create a new user in the database User testUser = userService.createUser("Tester", "Testsson", "test@test.com", "assword"); // Make sure a user was created and returned - assertThat(userService.login(testUser.getUsername(), "assword")).isNotNull(); + assertThat(userService.login(testUser.getUsername(), "assword")) + .isNotNull(); // Update user in database testUser = userService.updateUser(testUser.getUserId(), "Testor", "Nytest", "nytest@nytest.com", "newPassword"); // Make sure the user was updated in the database - assertThat(userService.findById(testUser.getUserId()).getFirstName()).isEqualTo("Testor"); + assertThat(userService.findById(testUser.getUserId()).getFirstName()) + .isEqualTo("Testor"); // Remove user from database boolean deleted = userService.deleteUserById(testUser.getUserId()); // Make sure the user was deleted from the database - assertThat(deleted).isTrue(); - assertThat(userService.findById(testUser.getUserId())).isNull(); + assertThat(deleted) + .isTrue(); + assertThat(userService.findById(testUser.getUserId())) + .isNull(); } @Test @@ -62,8 +66,21 @@ void handlingUsernameCollisions() { void throwExceptionWhenDuplicateEmails() { userService.createUser("Tester", "Testsson", "test@test.com", "assword"); - assertThatThrownBy(() -> userService.createUser("Testor", "Testarn", "test@test.com", "assword")) + assertThatThrownBy(() -> + userService.createUser("Testor", "Testarn", "test@test.com", "assword")) .isInstanceOf(IllegalArgumentException.class); } + // Make sure JPQL works correctly when searching for a user by email + @Test + void findUserByEmail() { + userService.createUser("Tester", "Testsson", "test@test.com", "assword"); + + User foundUser = userService.findByEmail("test@test.com"); + + assertThat(foundUser) + .isNotNull(); + assertThat(foundUser.getUsername()) + .isEqualTo("testes"); + } } From 5ed1039600be81281f917cdeaefa18948465bfbb Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 01:07:40 +0100 Subject: [PATCH 16/39] IT for finding user by username (JPQL) --- src/test/java/org/example/UserIT.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java index afb596ac..75ac0cd6 100644 --- a/src/test/java/org/example/UserIT.java +++ b/src/test/java/org/example/UserIT.java @@ -83,4 +83,16 @@ void findUserByEmail() { assertThat(foundUser.getUsername()) .isEqualTo("testes"); } + + @Test + void findUserByUsername(){ + userService.createUser("Tester", "Testsson", "test@test.com", "assword"); + + User foundUser = userService.findByUsername("testes"); + + assertThat(foundUser) + .isNotNull(); + assertThat(foundUser.getFirstName()) + .isEqualTo("Tester"); + } } From 41cf253c7b0b719b65d36d3f6718a7fa6f2ea965 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 01:14:11 +0100 Subject: [PATCH 17/39] IT for deleting wrong user --- src/test/java/org/example/UserIT.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java index 75ac0cd6..039f190d 100644 --- a/src/test/java/org/example/UserIT.java +++ b/src/test/java/org/example/UserIT.java @@ -63,7 +63,7 @@ void handlingUsernameCollisions() { } @Test - void throwExceptionWhenDuplicateEmails() { + void throwsExceptionWhenDuplicateEmails() { userService.createUser("Tester", "Testsson", "test@test.com", "assword"); assertThatThrownBy(() -> @@ -84,6 +84,7 @@ void findUserByEmail() { .isEqualTo("testes"); } + // Make sure JPQL works correctly when searching for a user by username @Test void findUserByUsername(){ userService.createUser("Tester", "Testsson", "test@test.com", "assword"); @@ -95,4 +96,10 @@ void findUserByUsername(){ assertThat(foundUser.getFirstName()) .isEqualTo("Tester"); } + + @Test + void receiveFalseWhenDeletingWrongUser() { + boolean result = userService.deleteUserById(1337L); + assertThat(result).isFalse(); + } } From 417f5ef92c115377faea4b52636e53aa515a578e Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 01:18:44 +0100 Subject: [PATCH 18/39] IT for finding all users (JPQL) --- src/test/java/org/example/UserIT.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java index 039f190d..c5733ef9 100644 --- a/src/test/java/org/example/UserIT.java +++ b/src/test/java/org/example/UserIT.java @@ -9,6 +9,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import java.util.List; + /* *********************************** */ /* **** User integration tests **** */ /* *********************************** */ @@ -102,4 +104,15 @@ void receiveFalseWhenDeletingWrongUser() { boolean result = userService.deleteUserById(1337L); assertThat(result).isFalse(); } + + @Test + void shouldFindAllUsers() { + userService.createUser("Tester", "Testsson", "test@test.com", "assword"); + userService.createUser("Testor", "Testarn", "testing@testing.com", "assword"); + + List users = userService.findAllUsers(); + + assertThat(users) + .hasSize(2); + } } From b271b793bb8bcc9df0a8218a913e3e5c48f86a61 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:47:17 +0100 Subject: [PATCH 19/39] Added initialization method --- src/main/java/org/example/EMFactory.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/example/EMFactory.java b/src/main/java/org/example/EMFactory.java index 1819e617..a9cd3dd1 100644 --- a/src/main/java/org/example/EMFactory.java +++ b/src/main/java/org/example/EMFactory.java @@ -7,11 +7,16 @@ public class EMFactory { private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system"); + public static void init(){ + // Call this once to initialize the factory att app start + } + // Use this in try-with public static EntityManager getEntityManager() { return emf.createEntityManager(); } + // Close the factory public static void close() { emf.close(); From faf72f7cc0815a74b716a70b80ef50579a091afe Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:47:44 +0100 Subject: [PATCH 20/39] Changed methods to public for better accessibility --- src/main/java/org/example/user/SessionManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/user/SessionManager.java b/src/main/java/org/example/user/SessionManager.java index 4fc63245..51f2bf68 100644 --- a/src/main/java/org/example/user/SessionManager.java +++ b/src/main/java/org/example/user/SessionManager.java @@ -6,12 +6,12 @@ public class SessionManager { private static User currentUser = null; // Log in user by setting currentUser to the passed in user - private static void login(User user) { + public static void login(User user) { currentUser = user; } // Log out user by setting currentUser to null - private static void logout() { + public static void logout() { currentUser = null; } From 9cc15b46df9c1f4cea267089bb3817c0d4098fb1 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:48:28 +0100 Subject: [PATCH 21/39] Changed exception messages to Swedish --- src/main/java/org/example/user/UserService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/user/UserService.java b/src/main/java/org/example/user/UserService.java index 3bf5a107..6fb11d77 100644 --- a/src/main/java/org/example/user/UserService.java +++ b/src/main/java/org/example/user/UserService.java @@ -36,22 +36,22 @@ public User login(String username, String password) { public User createUser(String firstName, String lastName, String email, String password) { // Check for null or empty if (isInvalid(firstName) || isInvalid(lastName) || isInvalid(email) || isInvalid(password)) { - throw new IllegalArgumentException("No fields can be empty."); + throw new IllegalArgumentException("Inga fält får lämnas tomma."); } // Validate email with regex pattern if (!EMAIL_PATTERN.matcher(email).matches()) { - throw new IllegalArgumentException("Invalid email format."); + throw new IllegalArgumentException("Ogiltigt emailformat."); } // Validate password length if (password.length() < 3) { - throw new IllegalArgumentException("Password must be at least 3 characters long."); + throw new IllegalArgumentException("Lösenordet måste vara minst 3 karaktärer långt."); } // Check for existing email if (userRepository.findByEmail(email) != null) { - throw new IllegalArgumentException("A user with this email already exists."); + throw new IllegalArgumentException("En användare med denna emailadress finns redan."); } // Generate username From c5f371da56b84d3988fb4af3af06165f1720046b Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:48:55 +0100 Subject: [PATCH 22/39] Added UserCLI class with user management functionalities --- src/main/java/org/example/user/UserCLI.java | 161 ++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/main/java/org/example/user/UserCLI.java diff --git a/src/main/java/org/example/user/UserCLI.java b/src/main/java/org/example/user/UserCLI.java new file mode 100644 index 00000000..77c61c51 --- /dev/null +++ b/src/main/java/org/example/user/UserCLI.java @@ -0,0 +1,161 @@ +package org.example.user; + +import org.example.User; +import java.util.Scanner; + +public class UserCLI { + private final UserService userService; + private final Scanner scanner; + + // Constructor + public UserCLI(UserService userService, Scanner scanner) { + this.userService = userService; + this.scanner = scanner; + } + + public void userMenu() { + // Make sure user is not already logged in + if (SessionManager.isLoggedIn()) return; + + boolean closeMenu = false; + while (!closeMenu) { + System.out.println("\n1. Logga in\n2. Skapa konto\n3. Tillbaka"); + System.out.print("Menyval: "); + String choice = scanner.nextLine(); + switch (choice) { + case "1" -> loginMenu(); + case "2" -> createUserMenu(); + case "3" -> closeMenu = true; + default -> System.out.println("Ogiltigt val."); + } + + // Check if user is logged in, if so, close menu + if (SessionManager.isLoggedIn()) { + closeMenu = true; + } + } + } + + public void manageUserMenu() { + // Make sure user is logged in + if (!SessionManager.isLoggedIn()) return; + + boolean closeMenu = false; + while (!closeMenu) { + System.out.println("\n1. Uppdatera användare\n2. Radera användare\n3. Tillbaka"); + System.out.print("Menyval: "); + String choice = scanner.nextLine(); + switch (choice) { + case "1" -> updateUserMenu(); + case "2" -> deleteUser(); + case "3" -> closeMenu = true; + default -> System.out.println("Ogiltigt val."); + } + } + } + + public void loginMenu() { + // Make sure user is not already logged in + if (SessionManager.isLoggedIn()) return; + + boolean success = false; + + while (!success) { + try { + System.out.println("************************\n******* LOGGA IN *******\n************************"); + // Ask for credentials + System.out.print("Användarnamn: "); + String username = scanner.nextLine(); + System.out.print("Lösenord: "); + String password = scanner.nextLine(); + + // Check credentials with database + User user = userService.login(username, password); + + // Tell SessionManager to remember user + SessionManager.login(user); + + if (SessionManager.isLoggedIn()){ + System.out.println("Inloggningen lyckades! Välkommen " + user.getFirstName()); + success = true; + } + else { + System.out.println("Inloggningen misslyckades."); + } + + } catch (IllegalArgumentException e) { + System.out.println("Felmeddelande: " + e.getMessage()); + } + } + } + + public void logout() { + SessionManager.logout(); + System.out.println("Du har loggats ut."); + } + + public void createUserMenu() { + // Make sure user is not already logged in + if (SessionManager.isLoggedIn()) return; + + boolean success = false; + + while (!success) { + try { + System.out.println("*************************\n**** SKAPA ANVÄNDARE ****\n*************************"); + System.out.print("Förnamn: "); + String firstName = scanner.nextLine(); + System.out.print("Efternamn: "); + String lastName = scanner.nextLine(); + System.out.print("Email: "); + String email = scanner.nextLine(); + String password = scanner.nextLine(); + + // Create user + User user = userService.createUser(firstName, lastName, email, password); + + System.out.println("Välkommen " + user.getFirstName() + "!"); + System.out.println("Ditt användarnamn är: " + user.getUsername()); + success = true; + } catch (IllegalArgumentException e) { + System.out.println("Felmeddelande: " + e.getMessage()); + } + } + } + + public void updateUserMenu() { + // Make sure user is logged in + if (!SessionManager.isLoggedIn()) return; + + User currentUser = SessionManager.getCurrentUser(); + try { + System.out.println("*************************\n**** UPPDATERA ANVÄNDARE ****\n*************************"); + System.out.print("Nytt förnamn [" + currentUser.getFirstName() + "]: "); + String firstName = scanner.nextLine(); + System.out.print("Nytt efternamn [" + currentUser.getLastName() + "]: "); + String lastName = scanner.nextLine(); + System.out.print("Ny email [" + currentUser.getEmail() + "]: "); + String email = scanner.nextLine(); + System.out.print("Nytt lösenord: "); + String password = scanner.nextLine(); + + User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password); + SessionManager.login(updatedUser); // Update session with new user + System.out.println("Användaren har uppdaterats!"); + } catch (IllegalArgumentException e) { + System.out.println("Kunde inte uppdatera: " + e.getMessage()); + } + } + + public void deleteUser() { + // Make sure user is logged in + if (!SessionManager.isLoggedIn()) return; + + System.out.print("Är du säker på att du vill radera ditt konto? (ja/nej): "); + if (scanner.nextLine().equalsIgnoreCase("ja")) { + userService.deleteUserById(SessionManager.getCurrentUser().getUserId()); + SessionManager.logout(); + System.out.println("Kontot har raderats och du har loggats ut."); + } + } +} From 5dd833824d32bc0bbecb3bffdb1a338cf23d3b47 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:44:51 +0100 Subject: [PATCH 23/39] Fixed wrong errormessages between UserService and tests --- src/main/java/org/example/user/UserService.java | 6 +++--- src/test/java/org/example/UserIT.java | 14 ++++++++++++-- .../java/org/example/UserServiceUnitTests.java | 12 ++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/example/user/UserService.java b/src/main/java/org/example/user/UserService.java index 6fb11d77..bf9742fb 100644 --- a/src/main/java/org/example/user/UserService.java +++ b/src/main/java/org/example/user/UserService.java @@ -22,12 +22,12 @@ public User login(String username, String password) { // Check if a user was found if (user == null) { - throw new IllegalArgumentException("Couldn't find user with username " + username); + throw new IllegalArgumentException("Kunde inte hitta användarnamn: " + username); } // Check if the password is correct if (!user.getPassword().equals(password) ) { - throw new IllegalArgumentException("Invalid password"); + throw new IllegalArgumentException("Ogiltigt lösenord"); } return user; @@ -72,7 +72,7 @@ public User updateUser(Long id, String firstName, String lastName, String email, // Check if a user was found if (user == null) { - throw new IllegalArgumentException("Couldn't find user with id " + id); + throw new IllegalArgumentException("Kunde inte hitta användare med id: " + id); } // Update user diff --git a/src/test/java/org/example/UserIT.java b/src/test/java/org/example/UserIT.java index c5733ef9..5e281fd5 100644 --- a/src/test/java/org/example/UserIT.java +++ b/src/test/java/org/example/UserIT.java @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.List; @@ -20,9 +21,18 @@ public class UserIT { private final UserRepository userRepository = new JpaUserRepository(); private final UserService userService = new UserService(userRepository); - // Makes sure we start with a clean slate after each test + // Makes sure we start with a clean slate before/after each test + @BeforeEach + void setUp() { + cleanDatabase(); + } + @AfterEach - void deleteAllUsersAfterEachTest() { + void tearDown() { + cleanDatabase(); + } + + private void cleanDatabase() { try (EntityManager em = EMFactory.getEntityManager()) { em.getTransaction().begin(); em.createQuery("DELETE FROM User").executeUpdate(); diff --git a/src/test/java/org/example/UserServiceUnitTests.java b/src/test/java/org/example/UserServiceUnitTests.java index c53dd832..bc84edcb 100644 --- a/src/test/java/org/example/UserServiceUnitTests.java +++ b/src/test/java/org/example/UserServiceUnitTests.java @@ -31,7 +31,7 @@ void testLoginFailed_WrongUsername_ShowsErrorMessage() { assertThatThrownBy(() -> userService.login("wronguser", "a$$word")) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Couldn't find user with username wronguser"); + .hasMessageContaining("Kunde inte hitta användarnamn: "); } @Test @@ -51,7 +51,7 @@ void testLoginFailed_WrongPassword_ShowsErrorMessage() { // Test login with wrong password assertThatThrownBy(() -> userService.login("testuser", "wrongPassword")) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Invalid password"); + .hasMessageContaining("Ogiltigt lösenord"); } @Test @@ -105,7 +105,7 @@ void testCreateUser_InvalidEmail_ThrowsException() { assertThatThrownBy(() -> userService.createUser("Test", "User", "invalidemail", "a$$word")) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Invalid email format"); + .hasMessageContaining("Ogiltigt emailformat."); } @Test @@ -116,7 +116,7 @@ void testCreateUser_ShortPassword_ThrowsException() { assertThatThrownBy(() -> userService.createUser("Test", "User", "test@test.com", "12")) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Password must be at least 3 characters long"); + .hasMessageContaining("Lösenordet måste vara minst 3 karaktärer långt."); } @Test @@ -127,7 +127,7 @@ void testCreateUser_EmptyFields_ThrowsException() { assertThatThrownBy(() -> userService.createUser("", "User", "test@test.com", "a$$word")) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("No fields can be empty"); + .hasMessageContaining("Inga fält får lämnas tomma."); } @Test @@ -141,7 +141,7 @@ void testCreateUser_DuplicateEmail_ThrowsException() { assertThatThrownBy(() -> userService.createUser("Test", "User", "test@test.com", "a$$word")) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("A user with this email already exists"); + .hasMessageContaining("En användare med denna emailadress finns redan."); } @Test From 986c38daccf9d40c8961aadb15fc8ce428106fb6 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 14:45:51 +0100 Subject: [PATCH 24/39] Added constructor to EMFactory to block instantiation Also added a check in the close method to make sure emf is open when closing it to not cause any unexpected errors. --- src/main/java/org/example/EMFactory.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/EMFactory.java b/src/main/java/org/example/EMFactory.java index a9cd3dd1..8a50eb34 100644 --- a/src/main/java/org/example/EMFactory.java +++ b/src/main/java/org/example/EMFactory.java @@ -7,6 +7,9 @@ public class EMFactory { private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system"); + // To block instantiation + private EMFactory(){} + public static void init(){ // Call this once to initialize the factory att app start } @@ -19,6 +22,8 @@ public static EntityManager getEntityManager() { // Close the factory public static void close() { - emf.close(); + if (emf.isOpen()){ + emf.close(); + } } } From e7bddfb059f570a699ad82a13360a542f10f0920 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 23:51:25 +0100 Subject: [PATCH 25/39] Added getFullName and getDisplayName methods to User --- src/main/java/org/example/User.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/example/User.java b/src/main/java/org/example/User.java index 73cd8888..aded1ba1 100644 --- a/src/main/java/org/example/User.java +++ b/src/main/java/org/example/User.java @@ -62,6 +62,14 @@ public void setFirstName(String firstName) { this.firstName = firstName; } + public String getFullName() { + return firstName + " " + lastName; + } + + public String getDisplayName() { + return getFullName() + " (" + username + ")"; + } + public Long getUserId() { return userId; } From f5671e4ab11918d3e8f9ff7ac91f11671fe031f4 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Sun, 11 Jan 2026 23:55:59 +0100 Subject: [PATCH 26/39] Added loggedInDisplayName method to SessionManager --- src/main/java/org/example/user/SessionManager.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/example/user/SessionManager.java b/src/main/java/org/example/user/SessionManager.java index 51f2bf68..97b06eef 100644 --- a/src/main/java/org/example/user/SessionManager.java +++ b/src/main/java/org/example/user/SessionManager.java @@ -19,6 +19,13 @@ public static User getCurrentUser() { return currentUser; } + public static String loggedInDisplayName() { + if (currentUser != null) + return currentUser.getDisplayName(); + else + return "Gäst"; + } + // Returns true if a user is logged in public static boolean isLoggedIn() { return currentUser != null; From e3d476bea7986450e5cc9422c4dedfc37254eef6 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:11:10 +0100 Subject: [PATCH 27/39] Improved UserCLI menus with enhanced prompts, streamlined flow, and added cancellation functionality --- src/main/java/org/example/user/UserCLI.java | 115 ++++++++++++++------ 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/example/user/UserCLI.java b/src/main/java/org/example/user/UserCLI.java index 77c61c51..15985b42 100644 --- a/src/main/java/org/example/user/UserCLI.java +++ b/src/main/java/org/example/user/UserCLI.java @@ -19,7 +19,9 @@ public void userMenu() { boolean closeMenu = false; while (!closeMenu) { - System.out.println("\n1. Logga in\n2. Skapa konto\n3. Tillbaka"); + System.out.println(appName()); + System.out.println("Konto\n========================================================="); + System.out.println("1. Logga in | 2. Skapa konto | 3. Tillbaka"); System.out.print("Menyval: "); String choice = scanner.nextLine(); switch (choice) { @@ -42,13 +44,16 @@ public void manageUserMenu() { boolean closeMenu = false; while (!closeMenu) { - System.out.println("\n1. Uppdatera användare\n2. Radera användare\n3. Tillbaka"); + System.out.println(appName()); + System.out.println("Hantera användare\n========================================================="); + System.out.println("1. Uppdatera användare | 2. Radera användare | 3. Logga ut | 4. Tillbaka"); System.out.print("Menyval: "); String choice = scanner.nextLine(); switch (choice) { case "1" -> updateUserMenu(); case "2" -> deleteUser(); - case "3" -> closeMenu = true; + case "3" -> logout(); + case "4" -> closeMenu = true; default -> System.out.println("Ogiltigt val."); } } @@ -62,12 +67,12 @@ public void loginMenu() { while (!success) { try { - System.out.println("************************\n******* LOGGA IN *******\n************************"); - // Ask for credentials - System.out.print("Användarnamn: "); - String username = scanner.nextLine(); - System.out.print("Lösenord: "); - String password = scanner.nextLine(); + System.out.println(appName()); + System.out.println("Logga in\n========================================================="); + System.out.println("(Skriv 'avbryt' för att avbryta inloggningen)"); + // Ask for credentials, prompt to enable exiting from menu + String username = prompt("Användarnamn: "); + String password = prompt("Lösenord: "); // Check credentials with database User user = userService.login(username, password); @@ -76,16 +81,25 @@ public void loginMenu() { SessionManager.login(user); if (SessionManager.isLoggedIn()){ - System.out.println("Inloggningen lyckades! Välkommen " + user.getFirstName()); + System.out.println("Inloggningen lyckades. Välkommen " + user.getFirstName() + "!"); success = true; } else { System.out.println("Inloggningen misslyckades."); } - - } catch (IllegalArgumentException e) { + } + // Catch validation errors + catch (IllegalArgumentException e) { System.out.println("Felmeddelande: " + e.getMessage()); } + // Catch our "exit" exception" + catch (RuntimeException e){ + if (e.getMessage().equals("AVBRUTET")) { + return; + } + // Catch any other exceptions + throw e; + } } } @@ -102,14 +116,14 @@ public void createUserMenu() { while (!success) { try { - System.out.println("*************************\n**** SKAPA ANVÄNDARE ****\n*************************"); - System.out.print("Förnamn: "); - String firstName = scanner.nextLine(); - System.out.print("Efternamn: "); - String lastName = scanner.nextLine(); - System.out.print("Email: "); - String email = scanner.nextLine(); - String password = scanner.nextLine(); + System.out.println(appName()); + System.out.println("Skapa användare\n========================================================="); + System.out.println("(Skriv 'avbryt' för att avbryta skapandet av ny användare)"); + + String firstName = prompt("Förnamn: "); + String lastName = prompt("Efternamn: "); + String email = prompt("Email: "); + String password = prompt("Lösenord: "); // Create user User user = userService.createUser(firstName, lastName, email, password); @@ -117,9 +131,19 @@ public void createUserMenu() { System.out.println("Välkommen " + user.getFirstName() + "!"); System.out.println("Ditt användarnamn är: " + user.getUsername()); success = true; - } catch (IllegalArgumentException e) { + } + // Catch validation errors + catch (IllegalArgumentException e) { System.out.println("Felmeddelande: " + e.getMessage()); } + // Catch our "exit" exception" + catch (RuntimeException e){ + if (e.getMessage().equals("AVBRUTET")) { + return; + } + // Catch any other exceptions + throw e; + } } } @@ -128,23 +152,33 @@ public void updateUserMenu() { if (!SessionManager.isLoggedIn()) return; User currentUser = SessionManager.getCurrentUser(); + try { - System.out.println("*************************\n**** UPPDATERA ANVÄNDARE ****\n*************************"); - System.out.print("Nytt förnamn [" + currentUser.getFirstName() + "]: "); - String firstName = scanner.nextLine(); - System.out.print("Nytt efternamn [" + currentUser.getLastName() + "]: "); - String lastName = scanner.nextLine(); - System.out.print("Ny email [" + currentUser.getEmail() + "]: "); - String email = scanner.nextLine(); - System.out.print("Nytt lösenord: "); - String password = scanner.nextLine(); + System.out.println(appName()); + System.out.println("UPPDATERA ANVÄNDARE\n========================================================="); + System.out.println("(Skriv 'avbryt' för att avbryta uppdateringen av användaren)"); + + String firstName = prompt("Nytt förnamn: "); + String lastName = prompt("Nytt efternamn: "); + String email = prompt("Ny email: "); + String password = prompt("Nytt lösenord: "); User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password); SessionManager.login(updatedUser); // Update session with new user System.out.println("Användaren har uppdaterats!"); - } catch (IllegalArgumentException e) { + } + // Catch validation errors + catch (IllegalArgumentException e) { System.out.println("Kunde inte uppdatera: " + e.getMessage()); } + // Catch our "exit" exception" + catch (RuntimeException e){ + if (e.getMessage().equals("AVBRUTET")) { + return; + } + // Catch any other exceptions + throw e; + } } public void deleteUser() { @@ -156,6 +190,25 @@ public void deleteUser() { userService.deleteUserById(SessionManager.getCurrentUser().getUserId()); SessionManager.logout(); System.out.println("Kontot har raderats och du har loggats ut."); + } else { + System.out.println("Du har valt att behålla kontot."); + } + } + + // Method to enable user to exit menu + private String prompt(String message) { + // Print the passed argument + System.out.print(message); + String input = scanner.nextLine(); + // Exit from current menu if "avbryt" is entered + if (input.equalsIgnoreCase("avbryt")) { + throw new RuntimeException("AVBRUTET"); } + return input; } + + private String appName() { + return "\n\nBIBLIOTEKSSYSTEMET"; + } + } From 7494f7b8633f834f3372545283d18a7d0584ff82 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:27:29 +0100 Subject: [PATCH 28/39] Refined exception message in UserService for consistency --- src/main/java/org/example/user/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/user/UserService.java b/src/main/java/org/example/user/UserService.java index bf9742fb..51e32178 100644 --- a/src/main/java/org/example/user/UserService.java +++ b/src/main/java/org/example/user/UserService.java @@ -22,7 +22,7 @@ public User login(String username, String password) { // Check if a user was found if (user == null) { - throw new IllegalArgumentException("Kunde inte hitta användarnamn: " + username); + throw new IllegalArgumentException("Kunde inte hitta användarnamn \"" + username + "\""); } // Check if the password is correct From 7616e3b3bbc3aee7ed21a67cb494b06501209bb1 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:53:27 +0100 Subject: [PATCH 29/39] Made EMFactory implement AutoCloseable --- src/main/java/org/example/EMFactory.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/example/EMFactory.java b/src/main/java/org/example/EMFactory.java index 8a50eb34..db8f0352 100644 --- a/src/main/java/org/example/EMFactory.java +++ b/src/main/java/org/example/EMFactory.java @@ -4,12 +4,9 @@ import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Persistence; -public class EMFactory { +public class EMFactory implements AutoCloseable { private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system"); - // To block instantiation - private EMFactory(){} - public static void init(){ // Call this once to initialize the factory att app start } @@ -21,8 +18,9 @@ public static EntityManager getEntityManager() { // Close the factory - public static void close() { - if (emf.isOpen()){ + @Override + public void close() { + if (emf != null && emf.isOpen()) { emf.close(); } } From 4eacd1ab6d6ce9dcb4d269624bc9cde70c33558c Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:56:18 +0100 Subject: [PATCH 30/39] Improved UserCLI workflows by refining menu conditions and removing unnecessary variables --- src/main/java/org/example/user/UserCLI.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/example/user/UserCLI.java b/src/main/java/org/example/user/UserCLI.java index 15985b42..e07c8389 100644 --- a/src/main/java/org/example/user/UserCLI.java +++ b/src/main/java/org/example/user/UserCLI.java @@ -18,7 +18,7 @@ public void userMenu() { if (SessionManager.isLoggedIn()) return; boolean closeMenu = false; - while (!closeMenu) { + while (!closeMenu && !SessionManager.isLoggedIn()) { System.out.println(appName()); System.out.println("Konto\n========================================================="); System.out.println("1. Logga in | 2. Skapa konto | 3. Tillbaka"); @@ -43,7 +43,7 @@ public void manageUserMenu() { if (!SessionManager.isLoggedIn()) return; boolean closeMenu = false; - while (!closeMenu) { + while (!closeMenu && SessionManager.isLoggedIn()) { System.out.println(appName()); System.out.println("Hantera användare\n========================================================="); System.out.println("1. Uppdatera användare | 2. Radera användare | 3. Logga ut | 4. Tillbaka"); @@ -63,9 +63,7 @@ public void loginMenu() { // Make sure user is not already logged in if (SessionManager.isLoggedIn()) return; - boolean success = false; - - while (!success) { + while (!SessionManager.isLoggedIn()) { try { System.out.println(appName()); System.out.println("Logga in\n========================================================="); @@ -82,7 +80,6 @@ public void loginMenu() { if (SessionManager.isLoggedIn()){ System.out.println("Inloggningen lyckades. Välkommen " + user.getFirstName() + "!"); - success = true; } else { System.out.println("Inloggningen misslyckades."); From dec4c1dc2cf5f4b7d476c1a8b1bf4353223e2280 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:32:58 +0100 Subject: [PATCH 31/39] Fixed error in test --- src/test/java/org/example/UserServiceUnitTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/example/UserServiceUnitTests.java b/src/test/java/org/example/UserServiceUnitTests.java index bc84edcb..5d82c295 100644 --- a/src/test/java/org/example/UserServiceUnitTests.java +++ b/src/test/java/org/example/UserServiceUnitTests.java @@ -31,7 +31,7 @@ void testLoginFailed_WrongUsername_ShowsErrorMessage() { assertThatThrownBy(() -> userService.login("wronguser", "a$$word")) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Kunde inte hitta användarnamn: "); + .hasMessageContaining("Kunde inte hitta användarnamn"); } @Test From 114d2610b1a9080d7d8cf90d7c66f226e4d24969 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:46:04 +0100 Subject: [PATCH 32/39] Changed error handling in JpaUserRepository --- .../java/org/example/user/repository/JpaUserRepository.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/user/repository/JpaUserRepository.java b/src/main/java/org/example/user/repository/JpaUserRepository.java index d0b17f98..b7742e8e 100644 --- a/src/main/java/org/example/user/repository/JpaUserRepository.java +++ b/src/main/java/org/example/user/repository/JpaUserRepository.java @@ -1,6 +1,7 @@ package org.example.user.repository; import jakarta.persistence.EntityManager; +import jakarta.persistence.NoResultException; import org.example.EMFactory; import org.example.User; @@ -65,7 +66,7 @@ public User findByEmail(String email) { return em.createQuery("SELECT u FROM User u WHERE u.email = :email", User.class) .setParameter("email", email) .getSingleResult(); - } catch (Exception e) { + } catch (NoResultException e) { return null; } } @@ -76,7 +77,7 @@ public User findByUsername(String username) { return em.createQuery("SELECT u FROM User u WHERE u.username = :username", User.class) .setParameter("username", username) .getSingleResult(); - } catch (Exception e) { + } catch (NoResultException e) { return null; } } From 9d144a24cab25cf07aa0750ad7b8ce2ebcf37a8a Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:48:56 +0100 Subject: [PATCH 33/39] Allow blank inputs in UserCLI to retain existing user details during update --- src/main/java/org/example/user/UserCLI.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/example/user/UserCLI.java b/src/main/java/org/example/user/UserCLI.java index e07c8389..89a10fa9 100644 --- a/src/main/java/org/example/user/UserCLI.java +++ b/src/main/java/org/example/user/UserCLI.java @@ -160,6 +160,11 @@ public void updateUserMenu() { String email = prompt("Ny email: "); String password = prompt("Nytt lösenord: "); + // If user didn't enter anything, keep current value + if (firstName.isBlank()) firstName = currentUser.getFirstName(); + if (lastName.isBlank()) lastName = currentUser.getLastName(); + if (email.isBlank()) email = currentUser.getEmail(); + User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password); SessionManager.login(updatedUser); // Update session with new user System.out.println("Användaren har uppdaterats!"); From bff2351ea25ff93b18bf29e43c1679bfc73f92fc Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 02:12:20 +0100 Subject: [PATCH 34/39] Added validation to updateUser --- .../java/org/example/user/UserService.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/example/user/UserService.java b/src/main/java/org/example/user/UserService.java index 51e32178..56de7abd 100644 --- a/src/main/java/org/example/user/UserService.java +++ b/src/main/java/org/example/user/UserService.java @@ -34,25 +34,8 @@ public User login(String username, String password) { } public User createUser(String firstName, String lastName, String email, String password) { - // Check for null or empty - if (isInvalid(firstName) || isInvalid(lastName) || isInvalid(email) || isInvalid(password)) { - throw new IllegalArgumentException("Inga fält får lämnas tomma."); - } - // Validate email with regex pattern - if (!EMAIL_PATTERN.matcher(email).matches()) { - throw new IllegalArgumentException("Ogiltigt emailformat."); - } - - // Validate password length - if (password.length() < 3) { - throw new IllegalArgumentException("Lösenordet måste vara minst 3 karaktärer långt."); - } - - // Check for existing email - if (userRepository.findByEmail(email) != null) { - throw new IllegalArgumentException("En användare med denna emailadress finns redan."); - } + validate(firstName, lastName, email, password); // Generate username String username = generateUsername(firstName, lastName); @@ -75,12 +58,15 @@ public User updateUser(Long id, String firstName, String lastName, String email, throw new IllegalArgumentException("Kunde inte hitta användare med id: " + id); } + validate(firstName, lastName, email, password); + // Update user user.setFirstName(firstName); user.setLastName(lastName); user.setEmail(email); user.setPassword(password); return userRepository.updateUser(user); + } public boolean deleteUserById(Long id) { @@ -126,6 +112,28 @@ private String createSubstrings(String name) { return name.substring(0, length); } + private void validate (String firstName, String lastName, String email, String password) { + // Check for null or empty + if (isInvalid(firstName) || isInvalid(lastName) || isInvalid(email) || isInvalid(password)) { + throw new IllegalArgumentException("Inga fält får lämnas tomma."); + } + + // Validate email with regex pattern + if (!EMAIL_PATTERN.matcher(email).matches()) { + throw new IllegalArgumentException("Ogiltigt emailformat."); + } + + // Validate password length + if (password.length() < 3) { + throw new IllegalArgumentException("Lösenordet måste vara minst 3 karaktärer långt."); + } + + // Check for existing email + if (userRepository.findByEmail(email) != null) { + throw new IllegalArgumentException("En användare med denna emailadress finns redan."); + } + } + private boolean isInvalid(String value) { return value == null || value.isBlank(); } From a24d51fa5b15b3b8ac04a5a09bf76507e877945f Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 02:30:19 +0100 Subject: [PATCH 35/39] Made EMFactory final, removed AutoCloseable implementation, and added private constructor to prevent instantiation --- src/main/java/org/example/EMFactory.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/EMFactory.java b/src/main/java/org/example/EMFactory.java index db8f0352..913ca17e 100644 --- a/src/main/java/org/example/EMFactory.java +++ b/src/main/java/org/example/EMFactory.java @@ -4,9 +4,13 @@ import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Persistence; -public class EMFactory implements AutoCloseable { +public final class EMFactory { private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("library_system"); + private EMFactory() { + // Prevent instantiation + } + public static void init(){ // Call this once to initialize the factory att app start } @@ -18,7 +22,6 @@ public static EntityManager getEntityManager() { // Close the factory - @Override public void close() { if (emf != null && emf.isOpen()) { emf.close(); From fd0c8f0b1eaae7f8f661947daa2d6850e6c69711 Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 08:59:24 +0100 Subject: [PATCH 36/39] Made `close` method static in EMFactory and fixed typos in comments --- src/main/java/org/example/EMFactory.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/example/EMFactory.java b/src/main/java/org/example/EMFactory.java index 913ca17e..7d742779 100644 --- a/src/main/java/org/example/EMFactory.java +++ b/src/main/java/org/example/EMFactory.java @@ -12,7 +12,7 @@ private EMFactory() { } public static void init(){ - // Call this once to initialize the factory att app start + // Call this once to initialize the factory at app start } // Use this in try-with @@ -22,8 +22,8 @@ public static EntityManager getEntityManager() { // Close the factory - public void close() { - if (emf != null && emf.isOpen()) { + public static void close() { + if (emf.isOpen()) { emf.close(); } } From 840f9019b310ad40557906fb93be9d9f0aeaaace Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 08:59:39 +0100 Subject: [PATCH 37/39] Improved UserCLI menus by displaying logged-in user details and refining wording for consistency. --- src/main/java/org/example/user/UserCLI.java | 24 +++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/example/user/UserCLI.java b/src/main/java/org/example/user/UserCLI.java index 89a10fa9..791e6811 100644 --- a/src/main/java/org/example/user/UserCLI.java +++ b/src/main/java/org/example/user/UserCLI.java @@ -20,8 +20,9 @@ public void userMenu() { boolean closeMenu = false; while (!closeMenu && !SessionManager.isLoggedIn()) { System.out.println(appName()); - System.out.println("Konto\n========================================================="); - System.out.println("1. Logga in | 2. Skapa konto | 3. Tillbaka"); + System.out.println("Logga in/Skapa användare Inloggad som: " + SessionManager.loggedInDisplayName()); + System.out.println("========================================================"); + System.out.println("1. Logga in | 2. Skapa användare | 3. Tillbaka"); System.out.print("Menyval: "); String choice = scanner.nextLine(); switch (choice) { @@ -45,7 +46,8 @@ public void manageUserMenu() { boolean closeMenu = false; while (!closeMenu && SessionManager.isLoggedIn()) { System.out.println(appName()); - System.out.println("Hantera användare\n========================================================="); + System.out.println("Hantera användare Inloggad som: " + SessionManager.loggedInDisplayName()); + System.out.println("========================================================"); System.out.println("1. Uppdatera användare | 2. Radera användare | 3. Logga ut | 4. Tillbaka"); System.out.print("Menyval: "); String choice = scanner.nextLine(); @@ -66,7 +68,8 @@ public void loginMenu() { while (!SessionManager.isLoggedIn()) { try { System.out.println(appName()); - System.out.println("Logga in\n========================================================="); + System.out.println("Inloggningsuppgifter Inloggad som: " + SessionManager.loggedInDisplayName()); + System.out.println("========================================================"); System.out.println("(Skriv 'avbryt' för att avbryta inloggningen)"); // Ask for credentials, prompt to enable exiting from menu String username = prompt("Användarnamn: "); @@ -114,7 +117,8 @@ public void createUserMenu() { while (!success) { try { System.out.println(appName()); - System.out.println("Skapa användare\n========================================================="); + System.out.println("Skapa användare Inloggad som: " + SessionManager.loggedInDisplayName()); + System.out.println("========================================================"); System.out.println("(Skriv 'avbryt' för att avbryta skapandet av ny användare)"); String firstName = prompt("Förnamn: "); @@ -152,7 +156,8 @@ public void updateUserMenu() { try { System.out.println(appName()); - System.out.println("UPPDATERA ANVÄNDARE\n========================================================="); + System.out.println("Uppdatera användare Inloggad som: " + SessionManager.loggedInDisplayName()); + System.out.println("========================================================"); System.out.println("(Skriv 'avbryt' för att avbryta uppdateringen av användaren)"); String firstName = prompt("Nytt förnamn: "); @@ -164,6 +169,7 @@ public void updateUserMenu() { if (firstName.isBlank()) firstName = currentUser.getFirstName(); if (lastName.isBlank()) lastName = currentUser.getLastName(); if (email.isBlank()) email = currentUser.getEmail(); + if (password.isBlank()) password = currentUser.getPassword(); User updatedUser = userService.updateUser(currentUser.getUserId(), firstName, lastName, email, password); SessionManager.login(updatedUser); // Update session with new user @@ -187,13 +193,13 @@ public void deleteUser() { // Make sure user is logged in if (!SessionManager.isLoggedIn()) return; - System.out.print("Är du säker på att du vill radera ditt konto? (ja/nej): "); + System.out.print("Är du säker på att du vill radera din användare? (ja/nej): "); if (scanner.nextLine().equalsIgnoreCase("ja")) { userService.deleteUserById(SessionManager.getCurrentUser().getUserId()); SessionManager.logout(); - System.out.println("Kontot har raderats och du har loggats ut."); + System.out.println("Användaren har raderats och du har loggats ut."); } else { - System.out.println("Du har valt att behålla kontot."); + System.out.println("Du har valt att behålla användaren."); } } From 6e5d99ac98f859af2edd36af1e341e62e05a1d6b Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 09:38:44 +0100 Subject: [PATCH 38/39] Added testUserCli class to provide a command-line interface for user management functionalities. --- .../java/org/example/user/testUserCli.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/java/org/example/user/testUserCli.java diff --git a/src/main/java/org/example/user/testUserCli.java b/src/main/java/org/example/user/testUserCli.java new file mode 100644 index 00000000..af5ee46b --- /dev/null +++ b/src/main/java/org/example/user/testUserCli.java @@ -0,0 +1,65 @@ +package org.example.user; + +import org.example.EMFactory; +import org.example.user.repository.JpaUserRepository; +import org.example.user.repository.UserRepository; + +import java.util.Scanner; + +public class testUserCli { + private static final Scanner scanner = new Scanner(System.in); + + static void main(String[] args) { + initialize(); // "Warm up" the factory + + final UserRepository userRepository = new JpaUserRepository(); + final UserService userService = new UserService(userRepository); + final UserCLI userCli = new UserCLI(userService, scanner); + + // ####################### + // Application starts here + // ####################### + boolean appRunning = true; + while (appRunning) { + System.out.println("\n\nBIBLIOTEKSSYSTEMET"); + System.out.println("Startsida Inloggad som: " + SessionManager.loggedInDisplayName()); + System.out.println("========================================================"); + + if (SessionManager.isLoggedIn()) { + System.out.println("1. Hantera användare | 2. Logga ut | 0. Avsluta"); + } else { + System.out.println("1. Logga in/Skapa konto | 0. Avsluta"); + } + + System.out.print("Menyval: "); + String choice = scanner.nextLine(); + switch (choice) { + case "1" -> { + if (SessionManager.isLoggedIn()) userCli.manageUserMenu(); + else userCli.userMenu(); + } + case "2" -> { + if (SessionManager.isLoggedIn()) userCli.logout(); + } + case "0" -> appRunning = false; + default -> System.out.println("Ogiltigt val."); + } + } + terminate(); +} + + private static void terminate() { + System.out.println("Bibliotekssystemet avslutat. Välkommen åter!"); + EMFactory.close(); + } + + public static void initialize() { + // Turn off Hibernate logging + System.setProperty("org.jboss.logging.provider", "jdk"); + java.util.logging.Logger hibernateLogger = java.util.logging.Logger.getLogger("org.hibernate"); + hibernateLogger.setLevel(java.util.logging.Level.SEVERE); + + // Initialize EntityManagerFactory + EMFactory.init(); + } +} From e6ca94a28c89c16915e2f314053c343d3cfcb75b Mon Sep 17 00:00:00 2001 From: Dennis Selden <111012436+DennSel@users.noreply.github.com> Date: Mon, 12 Jan 2026 09:39:01 +0100 Subject: [PATCH 39/39] Disabled Hibernate SQL logging --- src/main/resources/META-INF/persistence.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index b6827f13..b0a22b23 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -18,10 +18,12 @@ - - - - + + + + + +