From 52116be1c36e072fe25d746cd2640031221718e7 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 28 Jan 2026 10:36:24 +0100 Subject: [PATCH] Added integration tests for `ContainerRateRepository`, `GroupRepository`, and `UserRepository` for MySQL and MSSQL. Improved test coverage for pagination, filtering, UPSERT operations, and data cleanup methods. --- ...ontainerRateRepositoryIntegrationTest.java | 358 ++++++++++++++++++ .../users/GroupRepositoryIntegrationTest.java | 215 +++++++++++ .../users/UserRepositoryIntegrationTest.java | 350 +++++++++++++++++ 3 files changed, 923 insertions(+) create mode 100644 src/test/java/de/avatic/lcc/repositories/rates/ContainerRateRepositoryIntegrationTest.java create mode 100644 src/test/java/de/avatic/lcc/repositories/users/GroupRepositoryIntegrationTest.java create mode 100644 src/test/java/de/avatic/lcc/repositories/users/UserRepositoryIntegrationTest.java diff --git a/src/test/java/de/avatic/lcc/repositories/rates/ContainerRateRepositoryIntegrationTest.java b/src/test/java/de/avatic/lcc/repositories/rates/ContainerRateRepositoryIntegrationTest.java new file mode 100644 index 0000000..d23b354 --- /dev/null +++ b/src/test/java/de/avatic/lcc/repositories/rates/ContainerRateRepositoryIntegrationTest.java @@ -0,0 +1,358 @@ +package de.avatic.lcc.repositories.rates; + +import de.avatic.lcc.dto.generic.TransportType; +import de.avatic.lcc.model.db.rates.ContainerRate; +import de.avatic.lcc.model.db.rates.ValidityPeriodState; +import de.avatic.lcc.repositories.AbstractRepositoryIntegrationTest; +import de.avatic.lcc.repositories.pagination.SearchQueryPagination; +import de.avatic.lcc.repositories.pagination.SearchQueryResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests for ContainerRateRepository. + *

+ * Tests critical functionality across both MySQL and MSSQL: + * - Pagination (LIMIT/OFFSET vs OFFSET/FETCH) + * - UPSERT operations (ON DUPLICATE KEY UPDATE vs MERGE) + * - Complex JOIN queries with filtering + * - Transport type filtering (SEA, RAIL, POST_RUN, ROAD) + * - Boolean literals (TRUE/FALSE vs 1/0) + *

+ * Run with: + *

+ * mvn test -Dspring.profiles.active=test,mysql -Dtest=ContainerRateRepositoryIntegrationTest
+ * mvn test -Dspring.profiles.active=test,mssql -Dtest=ContainerRateRepositoryIntegrationTest
+ * 
+ */ +class ContainerRateRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest { + + @Autowired + private ContainerRateRepository containerRateRepository; + + private Integer testValidPeriodId; + private Integer testCountryDeId; + private Integer testCountryUsId; + private Integer testNodeHamburgId; + private Integer testNodeBremenId; + private Integer testNodeNewYorkId; + + @BeforeEach + void setupTestData() { + // Clean up in correct order (foreign key constraints) + jdbcTemplate.update("DELETE FROM container_rate"); + jdbcTemplate.update("DELETE FROM country_property"); + jdbcTemplate.update("DELETE FROM country_matrix_rate"); + jdbcTemplate.update("DELETE FROM node_predecessor_entry"); + jdbcTemplate.update("DELETE FROM node_predecessor_chain"); + jdbcTemplate.update("DELETE FROM node"); + jdbcTemplate.update("DELETE FROM validity_period"); + + // Use existing countries from migrations + testCountryDeId = jdbcTemplate.queryForObject( + "SELECT id FROM country WHERE iso_code = 'DE'", Integer.class); + testCountryUsId = jdbcTemplate.queryForObject( + "SELECT id FROM country WHERE iso_code = 'US'", Integer.class); + + // Create test validity period + testValidPeriodId = createTestValidityPeriod(ValidityPeriodState.VALID, + LocalDateTime.now().minusDays(1), null); + + // Create test nodes + testNodeHamburgId = createTestNode("Hamburg Port", "HAM", testCountryDeId, false, 53.5, 10.0); + testNodeBremenId = createTestNode("Bremen Port", "BRE", testCountryDeId, false, 53.1, 8.8); + testNodeNewYorkId = createTestNode("New York Port", "NYC", testCountryUsId, false, 40.7, -74.0); + + // Create test container rates + createTestContainerRate(testNodeHamburgId, testNodeNewYorkId, TransportType.SEA, + new BigDecimal("2000"), new BigDecimal("1000"), new BigDecimal("2200"), 14, testValidPeriodId); + createTestContainerRate(testNodeBremenId, testNodeNewYorkId, TransportType.SEA, + new BigDecimal("2100"), new BigDecimal("1050"), new BigDecimal("2300"), 15, testValidPeriodId); + createTestContainerRate(testNodeHamburgId, testNodeBremenId, TransportType.RAIL, + new BigDecimal("300"), new BigDecimal("150"), new BigDecimal("350"), 1, testValidPeriodId); + } + + @Test + void testListRatesByPeriodId() { + // Given: Valid period ID + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + + // When: List rates by period + SearchQueryResult result = containerRateRepository.listRatesByPeriodId(null, pagination, testValidPeriodId); + + // Then: Should return all 3 rates + assertNotNull(result); + assertEquals(3, result.getTotalElements()); + assertTrue(result.toList().stream() + .allMatch(rate -> rate.getValidityPeriodId().equals(testValidPeriodId))); + } + + @Test + void testListRatesByPeriodIdWithFilter() { + // Given: Filter for "Hamburg" + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + + // When: List rates with filter + SearchQueryResult result = containerRateRepository.listRatesByPeriodId("Hamburg", pagination, testValidPeriodId); + + // Then: Should return rates involving Hamburg + assertNotNull(result); + assertTrue(result.getTotalElements() >= 2, "Should find at least 2 rates with Hamburg"); + } + + @Test + void testListRatesByPeriodIdWithExternalMappingIdFilter() { + // Given: Filter for "HAM" + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + + // When: List rates with external mapping ID filter + SearchQueryResult result = containerRateRepository.listRatesByPeriodId("HAM", pagination, testValidPeriodId); + + // Then: Should return rates involving Hamburg + assertNotNull(result); + assertTrue(result.getTotalElements() >= 2, "Should find at least 2 rates with HAM"); + } + + @Test + void testListRatesByPeriodIdPagination() { + // Given: Pagination with limit 2 + SearchQueryPagination pagination = new SearchQueryPagination(1, 2); + + // When: List rates + SearchQueryResult result = containerRateRepository.listRatesByPeriodId(null, pagination, testValidPeriodId); + + // Then: Should respect limit + assertNotNull(result); + assertEquals(2, result.toList().size()); + assertEquals(3, result.getTotalElements()); + } + + @Test + void testGetById() { + // Given: Get first rate ID + SearchQueryPagination pagination = new SearchQueryPagination(1, 1); + SearchQueryResult result = containerRateRepository.listRatesByPeriodId(null, pagination, testValidPeriodId); + Integer rateId = result.toList().getFirst().getId(); + + // When: Get by ID + Optional rate = containerRateRepository.getById(rateId); + + // Then: Should retrieve correct rate + assertTrue(rate.isPresent()); + assertEquals(rateId, rate.get().getId()); + assertNotNull(rate.get().getRateFeu()); + assertNotNull(rate.get().getRateTeu()); + assertNotNull(rate.get().getFromNodeId()); + assertNotNull(rate.get().getToNodeId()); + } + + @Test + void testGetByIdNotFound() { + // When: Get non-existent ID + Optional rate = containerRateRepository.getById(99999); + + // Then: Should not find + assertFalse(rate.isPresent()); + } + + @Test + void testListAllRatesByPeriodId() { + // When: List all rates for valid period + List rates = containerRateRepository.listAllRatesByPeriodId(testValidPeriodId); + + // Then: Should return all 3 rates + assertNotNull(rates); + assertEquals(3, rates.size()); + assertTrue(rates.stream().allMatch(rate -> rate.getValidityPeriodId().equals(testValidPeriodId))); + } + + @Test + void testFindRoutesByStartNodeIdAndDestinationCountryId() { + // When: Find routes from Hamburg to US + List routes = containerRateRepository.findRoutesByStartNodeIdAndDestinationCountryId( + testNodeHamburgId, List.of(testCountryUsId)); + + // Then: Should find Hamburg -> New York route + assertNotNull(routes); + assertEquals(1, routes.size()); + assertEquals(testNodeHamburgId, routes.getFirst().getFromNodeId()); + assertEquals(testNodeNewYorkId, routes.getFirst().getToNodeId()); + assertEquals(TransportType.SEA, routes.getFirst().getType()); + } + + @Test + void testFindRoutesByStartNodeIdAndDestinationCountryIdMultiple() { + // When: Find routes from Hamburg to DE or US + List routes = containerRateRepository.findRoutesByStartNodeIdAndDestinationCountryId( + testNodeHamburgId, List.of(testCountryDeId, testCountryUsId)); + + // Then: Should find both routes (Hamburg -> Bremen and Hamburg -> New York) + assertNotNull(routes); + assertEquals(2, routes.size()); + assertTrue(routes.stream().allMatch(r -> r.getFromNodeId().equals(testNodeHamburgId))); + } + + @Test + void testFindRoutesByStartNodeIdAndDestinationCountryIdEmpty() { + // When: Find routes with empty destination list + List routes = containerRateRepository.findRoutesByStartNodeIdAndDestinationCountryId( + testNodeHamburgId, List.of()); + + // Then: Should return empty list + assertNotNull(routes); + assertTrue(routes.isEmpty()); + } + + @Test + void testGetPostRunsFor() { + // Given: Create a main run and post-run + Integer testNodeWarehouseId = createTestNode("Warehouse", "WH1", testCountryUsId, false, 40.8, -74.1); + createTestContainerRate(testNodeNewYorkId, testNodeWarehouseId, TransportType.POST_RUN, + new BigDecimal("100"), new BigDecimal("50"), new BigDecimal("120"), 1, testValidPeriodId); + + ContainerRate mainRun = new ContainerRate(); + mainRun.setToNodeId(testNodeNewYorkId); + + // When: Get post runs + List postRuns = containerRateRepository.getPostRunsFor(mainRun); + + // Then: Should find the post-run + assertNotNull(postRuns); + assertEquals(1, postRuns.size()); + assertEquals(testNodeNewYorkId, postRuns.getFirst().getFromNodeId()); + assertEquals(TransportType.POST_RUN, postRuns.getFirst().getType()); + } + + @Test + void testFindRouteWithPeriodId() { + // When: Find route Hamburg -> New York SEA in valid period + Optional route = containerRateRepository.findRoute( + testNodeHamburgId, testNodeNewYorkId, testValidPeriodId, TransportType.SEA); + + // Then: Should find route + assertTrue(route.isPresent()); + assertEquals(testNodeHamburgId, route.get().getFromNodeId()); + assertEquals(testNodeNewYorkId, route.get().getToNodeId()); + assertEquals(TransportType.SEA, route.get().getType()); + assertEquals(0, new BigDecimal("2000").compareTo(route.get().getRateFeu())); + } + + @Test + void testFindRouteWithPeriodIdNotFound() { + // When: Find route with wrong transport type + Optional route = containerRateRepository.findRoute( + testNodeHamburgId, testNodeNewYorkId, testValidPeriodId, TransportType.ROAD); + + // Then: Should not find + assertFalse(route.isPresent()); + } + + @Test + void testFindRouteWithoutPeriodId() { + // When: Find route Hamburg -> New York SEA (uses VALID period) + Optional route = containerRateRepository.findRoute( + testNodeHamburgId, testNodeNewYorkId, TransportType.SEA); + + // Then: Should find route + assertTrue(route.isPresent()); + assertEquals(testNodeHamburgId, route.get().getFromNodeId()); + assertEquals(testNodeNewYorkId, route.get().getToNodeId()); + assertEquals(TransportType.SEA, route.get().getType()); + } + + @Test + void testInsertNewRate() { + // Given: New container rate + ContainerRate newRate = new ContainerRate(); + newRate.setFromNodeId(testNodeBremenId); + newRate.setToNodeId(testNodeHamburgId); + newRate.setType(TransportType.ROAD); + newRate.setRateFeu(new BigDecimal("200")); + newRate.setRateTeu(new BigDecimal("100")); + newRate.setRateHc(new BigDecimal("220")); + newRate.setLeadTime(1); + newRate.setValidityPeriodId(testValidPeriodId); + + // When: Insert + containerRateRepository.insert(newRate); + + // Then: Should be inserted + Optional inserted = containerRateRepository.findRoute( + testNodeBremenId, testNodeHamburgId, testValidPeriodId, TransportType.ROAD); + assertTrue(inserted.isPresent()); + assertEquals(0, new BigDecimal("200").compareTo(inserted.get().getRateFeu())); + } + + @Test + void testInsertUpsertExisting() { + // Given: Existing rate Hamburg -> New York + ContainerRate updateRate = new ContainerRate(); + updateRate.setFromNodeId(testNodeHamburgId); + updateRate.setToNodeId(testNodeNewYorkId); + updateRate.setType(TransportType.SEA); + updateRate.setRateFeu(new BigDecimal("2500")); // Different rate + updateRate.setRateTeu(new BigDecimal("1250")); + updateRate.setRateHc(new BigDecimal("2700")); + updateRate.setLeadTime(12); + updateRate.setValidityPeriodId(testValidPeriodId); + + // When: Insert (should upsert) + containerRateRepository.insert(updateRate); + + // Then: Rate should be updated + Optional updated = containerRateRepository.findRoute( + testNodeHamburgId, testNodeNewYorkId, testValidPeriodId, TransportType.SEA); + assertTrue(updated.isPresent()); + assertEquals(0, new BigDecimal("2500").compareTo(updated.get().getRateFeu())); + assertEquals(12, updated.get().getLeadTime()); + + // Should still have only 3 rates total + List allRates = containerRateRepository.listAllRatesByPeriodId(testValidPeriodId); + assertEquals(3, allRates.size()); + } + + // ========== Helper Methods ========== + + private Integer createTestValidityPeriod(ValidityPeriodState state, LocalDateTime startDate, LocalDateTime endDate) { + String sql = "INSERT INTO validity_period (state, start_date, end_date) VALUES (?, ?, ?)"; + Timestamp startTs = Timestamp.valueOf(startDate); + Timestamp endTs = endDate != null ? Timestamp.valueOf(endDate) : null; + executeRawSql(sql, state.name(), startTs, endTs); + + String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)"; + return jdbcTemplate.queryForObject(selectSql, Integer.class); + } + + private Integer createTestNode(String name, String externalMappingId, Integer countryId, boolean isDeprecated, + double geoLat, double geoLng) { + String isDeprecatedValue = isDeprecated ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(); + String sql = String.format( + "INSERT INTO node (name, external_mapping_id, country_id, is_deprecated, is_source, is_destination, is_intermediate, address, geo_lat, geo_lng) " + + "VALUES (?, ?, ?, %s, %s, %s, %s, 'Test Address', ?, ?)", + isDeprecatedValue, + dialectProvider.getBooleanTrue(), + dialectProvider.getBooleanTrue(), + dialectProvider.getBooleanTrue()); + executeRawSql(sql, name, externalMappingId, countryId, geoLat, geoLng); + + String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)"; + return jdbcTemplate.queryForObject(selectSql, Integer.class); + } + + private void createTestContainerRate(Integer fromNodeId, Integer toNodeId, TransportType type, + BigDecimal rateFeu, BigDecimal rateTeu, BigDecimal rateHc, + int leadTime, Integer validityPeriodId) { + String sql = "INSERT INTO container_rate (from_node_id, to_node_id, container_rate_type, rate_feu, rate_teu, rate_hc, lead_time, validity_period_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + executeRawSql(sql, fromNodeId, toNodeId, type.name(), rateFeu, rateTeu, rateHc, leadTime, validityPeriodId); + } +} diff --git a/src/test/java/de/avatic/lcc/repositories/users/GroupRepositoryIntegrationTest.java b/src/test/java/de/avatic/lcc/repositories/users/GroupRepositoryIntegrationTest.java new file mode 100644 index 0000000..1744cca --- /dev/null +++ b/src/test/java/de/avatic/lcc/repositories/users/GroupRepositoryIntegrationTest.java @@ -0,0 +1,215 @@ +package de.avatic.lcc.repositories.users; + +import de.avatic.lcc.model.db.users.Group; +import de.avatic.lcc.repositories.AbstractRepositoryIntegrationTest; +import de.avatic.lcc.repositories.pagination.SearchQueryPagination; +import de.avatic.lcc.repositories.pagination.SearchQueryResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests for GroupRepository. + *

+ * Tests critical functionality across both MySQL and MSSQL: + * - Pagination (LIMIT/OFFSET vs OFFSET/FETCH) + * - UPSERT operations (ON DUPLICATE KEY UPDATE vs MERGE) + * - IN clause with dynamic parameters + *

+ * Run with: + *

+ * mvn test -Dspring.profiles.active=test,mysql -Dtest=GroupRepositoryIntegrationTest
+ * mvn test -Dspring.profiles.active=test,mssql -Dtest=GroupRepositoryIntegrationTest
+ * 
+ */ +class GroupRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest { + + @Autowired + private GroupRepository groupRepository; + + @BeforeEach + void setupTestData() { + // Clean up groups + jdbcTemplate.update("DELETE FROM sys_user_group_mapping"); + jdbcTemplate.update("DELETE FROM sys_group"); + + // Create test groups + createTestGroup("Administrators", "Admin users with full access"); + createTestGroup("Developers", "Software developers"); + createTestGroup("Analysts", "Data analysts"); + createTestGroup("Viewers", "Read-only users"); + } + + @Test + void testListGroups() { + // Given: Pagination + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + + // When: List groups + SearchQueryResult result = groupRepository.listGroups(pagination); + + // Then: Should return all groups + assertNotNull(result); + assertEquals(4, result.getTotalElements()); + assertFalse(result.toList().isEmpty()); + } + + @Test + void testListGroupsPagination() { + // Given: Pagination with limit 2 + SearchQueryPagination pagination = new SearchQueryPagination(1, 2); + + // When: List groups + SearchQueryResult result = groupRepository.listGroups(pagination); + + // Then: Should respect limit + assertNotNull(result); + assertEquals(2, result.toList().size()); + assertEquals(4, result.getTotalElements()); + } + + @Test + void testListGroupsOrdering() { + // Given: Pagination + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + + // When: List groups + SearchQueryResult result = groupRepository.listGroups(pagination); + + // Then: Should be ordered by group_name + assertNotNull(result); + List groups = result.toList(); + for (int i = 1; i < groups.size(); i++) { + assertTrue(groups.get(i - 1).getName().compareTo(groups.get(i).getName()) <= 0, + "Groups should be ordered alphabetically by name"); + } + } + + @Test + void testFindGroupIds() { + // When: Find group IDs by names + List ids = groupRepository.findGroupIds(List.of("Administrators", "Developers")); + + // Then: Should find 2 groups + assertNotNull(ids); + assertEquals(2, ids.size()); + } + + @Test + void testFindGroupIdsSingle() { + // When: Find single group ID + List ids = groupRepository.findGroupIds(List.of("Administrators")); + + // Then: Should find 1 group + assertNotNull(ids); + assertEquals(1, ids.size()); + } + + @Test + void testFindGroupIdsNotFound() { + // When: Find non-existent group + List ids = groupRepository.findGroupIds(List.of("NonExistent")); + + // Then: Should return empty list + assertNotNull(ids); + assertTrue(ids.isEmpty()); + } + + @Test + void testFindGroupIdsEmptyList() { + // When: Find with empty list + List ids = groupRepository.findGroupIds(List.of()); + + // Then: Should return empty list + assertNotNull(ids); + assertTrue(ids.isEmpty()); + } + + @Test + void testFindGroupIdsNull() { + // When: Find with null + List ids = groupRepository.findGroupIds(null); + + // Then: Should return empty list + assertNotNull(ids); + assertTrue(ids.isEmpty()); + } + + @Test + void testUpdateGroupInsert() { + // Given: New group + Group newGroup = new Group(); + newGroup.setName("Testers"); + newGroup.setDescription("QA testers"); + + // When: Update (insert) + groupRepository.updateGroup(newGroup); + + // Then: Should be inserted + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + SearchQueryResult result = groupRepository.listGroups(pagination); + assertEquals(5, result.getTotalElements()); + + // Verify the new group exists + List ids = groupRepository.findGroupIds(List.of("Testers")); + assertEquals(1, ids.size()); + } + + @Test + void testUpdateGroupUpsert() { + // Given: Existing group name + Group updateGroup = new Group(); + updateGroup.setName("Administrators"); + updateGroup.setDescription("Updated admin description"); + + // When: Update (upsert) + groupRepository.updateGroup(updateGroup); + + // Then: Should update description + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + SearchQueryResult result = groupRepository.listGroups(pagination); + + // Should still have 4 groups + assertEquals(4, result.getTotalElements()); + + // Find the updated group + Group updated = result.toList().stream() + .filter(g -> "Administrators".equals(g.getName())) + .findFirst() + .orElseThrow(); + assertEquals("Updated admin description", updated.getDescription()); + } + + @Test + void testFindGroupIdsMultiple() { + // When: Find multiple group IDs + List ids = groupRepository.findGroupIds( + List.of("Administrators", "Developers", "Analysts")); + + // Then: Should find 3 groups + assertNotNull(ids); + assertEquals(3, ids.size()); + } + + @Test + void testFindGroupIdsPartialMatch() { + // When: Find mix of existing and non-existing groups + List ids = groupRepository.findGroupIds( + List.of("Administrators", "NonExistent", "Developers")); + + // Then: Should find only existing groups + assertNotNull(ids); + assertEquals(2, ids.size()); + } + + // ========== Helper Methods ========== + + private void createTestGroup(String name, String description) { + String sql = "INSERT INTO sys_group (group_name, group_description) VALUES (?, ?)"; + executeRawSql(sql, name, description); + } +} diff --git a/src/test/java/de/avatic/lcc/repositories/users/UserRepositoryIntegrationTest.java b/src/test/java/de/avatic/lcc/repositories/users/UserRepositoryIntegrationTest.java new file mode 100644 index 0000000..297e1bd --- /dev/null +++ b/src/test/java/de/avatic/lcc/repositories/users/UserRepositoryIntegrationTest.java @@ -0,0 +1,350 @@ +package de.avatic.lcc.repositories.users; + +import de.avatic.lcc.model.db.users.Group; +import de.avatic.lcc.model.db.users.User; +import de.avatic.lcc.repositories.AbstractRepositoryIntegrationTest; +import de.avatic.lcc.repositories.pagination.SearchQueryPagination; +import de.avatic.lcc.repositories.pagination.SearchQueryResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests for UserRepository. + *

+ * Tests critical functionality across both MySQL and MSSQL: + * - Pagination (LIMIT/OFFSET vs OFFSET/FETCH) + * - INSERT IGNORE (MySQL) vs MERGE (MSSQL) + * - Complex group mapping operations + * - User lookup by various fields + *

+ * Run with: + *

+ * mvn test -Dspring.profiles.active=test,mysql -Dtest=UserRepositoryIntegrationTest
+ * mvn test -Dspring.profiles.active=test,mssql -Dtest=UserRepositoryIntegrationTest
+ * 
+ */ +class UserRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private GroupRepository groupRepository; + + private Integer testGroupAdminId; + private Integer testGroupDevId; + + @BeforeEach + void setupTestData() { + // Clean up in correct order + jdbcTemplate.update("DELETE FROM sys_user_group_mapping"); + jdbcTemplate.update("DELETE FROM sys_user"); + jdbcTemplate.update("DELETE FROM sys_group"); + + // Create test groups + createTestGroup("Administrators", "Admin users"); + createTestGroup("Developers", "Dev users"); + createTestGroup("Viewers", "Read-only users"); + + // Get group IDs + testGroupAdminId = groupRepository.findGroupIds(List.of("Administrators")).getFirst(); + testGroupDevId = groupRepository.findGroupIds(List.of("Developers")).getFirst(); + + // Create test users + createTestUser("WD001", "john.doe@example.com", "John", "Doe", true); + createTestUser("WD002", "jane.smith@example.com", "Jane", "Smith", true); + createTestUser("WD003", "bob.inactive@example.com", "Bob", "Inactive", false); + + // Create group mappings + createUserGroupMapping(getUserIdByWorkday("WD001"), testGroupAdminId); + createUserGroupMapping(getUserIdByWorkday("WD002"), testGroupDevId); + } + + @Test + void testListUsers() { + // Given: Pagination + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + + // When: List users + SearchQueryResult result = userRepository.listUsers(pagination); + + // Then: Should return all users + assertNotNull(result); + assertEquals(3, result.getTotalElements()); + assertFalse(result.toList().isEmpty()); + } + + @Test + void testListUsersPagination() { + // Given: Pagination with limit 2 + SearchQueryPagination pagination = new SearchQueryPagination(1, 2); + + // When: List users + SearchQueryResult result = userRepository.listUsers(pagination); + + // Then: Should respect limit + assertNotNull(result); + assertEquals(2, result.toList().size()); + assertEquals(3, result.getTotalElements()); + } + + @Test + void testListUsersOrdering() { + // Given: Pagination + SearchQueryPagination pagination = new SearchQueryPagination(1, 10); + + // When: List users + SearchQueryResult result = userRepository.listUsers(pagination); + + // Then: Should be ordered by workday_id + assertNotNull(result); + List users = result.toList(); + for (int i = 1; i < users.size(); i++) { + assertTrue(users.get(i - 1).getWorkdayId().compareTo(users.get(i).getWorkdayId()) <= 0, + "Users should be ordered by workday_id"); + } + } + + @Test + void testUpdateInsertNewUser() { + // Given: New user + User newUser = new User(); + newUser.setWorkdayId("WD004"); + newUser.setEmail("new.user@example.com"); + newUser.setFirstName("New"); + newUser.setLastName("User"); + newUser.setActive(true); + newUser.setGroups(List.of()); + + // When: Update (insert) + Integer userId = userRepository.update(newUser); + + // Then: Should be inserted + assertNotNull(userId); + assertTrue(userId > 0); + + User inserted = userRepository.getById(userId); + assertNotNull(inserted); + assertEquals("WD004", inserted.getWorkdayId()); + assertEquals("new.user@example.com", inserted.getEmail()); + } + + @Test + void testUpdateExistingUser() { + // Given: Existing user + User user = userRepository.getByWorkdayId("WD001").orElseThrow(); + user.setEmail("john.updated@example.com"); + user.setFirstName("Johnny"); + + // When: Update + Integer userId = userRepository.update(user); + + // Then: Should be updated + assertNotNull(userId); + + User updated = userRepository.getById(userId); + assertEquals("john.updated@example.com", updated.getEmail()); + assertEquals("Johnny", updated.getFirstName()); + assertEquals("Doe", updated.getLastName()); // Unchanged + } + + @Test + void testUpdateUserWithGroups() { + // Given: New user with groups + User newUser = new User(); + newUser.setWorkdayId("WD005"); + newUser.setEmail("grouped.user@example.com"); + newUser.setFirstName("Grouped"); + newUser.setLastName("User"); + newUser.setActive(true); + + Group adminGroup = new Group(); + adminGroup.setName("Administrators"); + Group devGroup = new Group(); + devGroup.setName("Developers"); + newUser.setGroups(List.of(adminGroup, devGroup)); + + // When: Update (insert) + Integer userId = userRepository.update(newUser); + + // Then: Should have groups + User inserted = userRepository.getById(userId); + assertNotNull(inserted.getGroups()); + assertEquals(2, inserted.getGroups().size()); + } + + @Test + void testUpdateUserRemoveGroups() { + // Given: User with groups + User user = userRepository.getByWorkdayId("WD001").orElseThrow(); + assertEquals(1, user.getGroups().size()); + + // When: Update with empty groups + user.setGroups(List.of()); + userRepository.update(user); + + // Then: Groups should be removed + User updated = userRepository.getById(user.getId()); + assertTrue(updated.getGroups().isEmpty()); + } + + @Test + void testUpdateUserChangeGroups() { + // Given: User with Admin group + User user = userRepository.getByWorkdayId("WD001").orElseThrow(); + assertEquals("Administrators", user.getGroups().getFirst().getName()); + + // When: Change to Dev group + Group devGroup = new Group(); + devGroup.setName("Developers"); + user.setGroups(List.of(devGroup)); + userRepository.update(user); + + // Then: Should have Dev group + User updated = userRepository.getById(user.getId()); + assertEquals(1, updated.getGroups().size()); + assertEquals("Developers", updated.getGroups().getFirst().getName()); + } + + @Test + void testCount() { + // When: Count users + Integer count = userRepository.count(); + + // Then: Should return 3 + assertEquals(3, count); + } + + @Test + void testGetUserIdByWorkdayId() { + // When: Get user ID by workday ID + Integer userId = userRepository.getUserIdByWorkdayId("WD001"); + + // Then: Should find user + assertNotNull(userId); + assertTrue(userId > 0); + } + + @Test + void testGetUserIdByWorkdayIdNotFound() { + // When: Get non-existent user + Integer userId = userRepository.getUserIdByWorkdayId("NONEXISTENT"); + + // Then: Should return null + assertNull(userId); + } + + @Test + void testGetByWorkdayId() { + // When: Get user by workday ID + Optional user = userRepository.getByWorkdayId("WD001"); + + // Then: Should find user + assertTrue(user.isPresent()); + assertEquals("WD001", user.get().getWorkdayId()); + assertEquals("john.doe@example.com", user.get().getEmail()); + assertEquals("John", user.get().getFirstName()); + } + + @Test + void testGetByWorkdayIdNotFound() { + // When: Get non-existent user + Optional user = userRepository.getByWorkdayId("NONEXISTENT"); + + // Then: Should not find + assertFalse(user.isPresent()); + } + + @Test + void testGetById() { + // Given: User ID + Integer userId = userRepository.getUserIdByWorkdayId("WD001"); + + // When: Get by ID + User user = userRepository.getById(userId); + + // Then: Should find user + assertNotNull(user); + assertEquals("WD001", user.getWorkdayId()); + } + + @Test + void testGetByIdNotFound() { + // When: Get non-existent ID + User user = userRepository.getById(99999); + + // Then: Should return null + assertNull(user); + } + + @Test + void testGetByEmail() { + // When: Get user by email + User user = userRepository.getByEmail("john.doe@example.com"); + + // Then: Should find user + assertNotNull(user); + assertEquals("WD001", user.getWorkdayId()); + assertEquals("john.doe@example.com", user.getEmail()); + } + + @Test + void testGetByEmailNotFound() { + // When: Get non-existent email + User user = userRepository.getByEmail("nonexistent@example.com"); + + // Then: Should return null + assertNull(user); + } + + @Test + void testUserWithGroupMemberships() { + // When: Get user with groups + User user = userRepository.getByWorkdayId("WD001").orElseThrow(); + + // Then: Should have group memberships + assertNotNull(user.getGroups()); + assertEquals(1, user.getGroups().size()); + assertEquals("Administrators", user.getGroups().getFirst().getName()); + } + + @Test + void testUserWithoutGroupMemberships() { + // When: Get user without groups + User user = userRepository.getByWorkdayId("WD003").orElseThrow(); + + // Then: Should have empty groups + assertNotNull(user.getGroups()); + assertTrue(user.getGroups().isEmpty()); + } + + // ========== Helper Methods ========== + + private void createTestGroup(String name, String description) { + String sql = "INSERT INTO sys_group (group_name, group_description) VALUES (?, ?)"; + executeRawSql(sql, name, description); + } + + private void createTestUser(String workdayId, String email, String firstName, String lastName, boolean isActive) { + String isActiveValue = isActive ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(); + String sql = String.format( + "INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active) VALUES (?, ?, ?, ?, %s)", + isActiveValue); + executeRawSql(sql, workdayId, email, firstName, lastName); + } + + private Integer getUserIdByWorkday(String workdayId) { + return jdbcTemplate.queryForObject("SELECT id FROM sys_user WHERE workday_id = ?", Integer.class, workdayId); + } + + private void createUserGroupMapping(Integer userId, Integer groupId) { + String sql = "INSERT INTO sys_user_group_mapping (user_id, group_id) VALUES (?, ?)"; + executeRawSql(sql, userId, groupId); + } +}