Added integration tests for RouteRepository and RouteNodeRepository for MySQL and MSSQL; Marked DestinationRepository as @Repository.

This commit is contained in:
Jan 2026-01-28 12:18:52 +01:00
parent a381ca7ef8
commit 8d08fedbc4
7 changed files with 2182 additions and 3 deletions

View file

@ -9,6 +9,7 @@ import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -19,7 +20,7 @@ import java.sql.Statement;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Repository
public class DestinationRepository {
private final JdbcTemplate jdbcTemplate;

View file

@ -28,8 +28,7 @@ import javax.sql.DataSource;
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = {
de.avatic.lcc.repositories.error.DumpRepository.class,
de.avatic.lcc.repositories.premise.DestinationRepository.class
de.avatic.lcc.repositories.error.DumpRepository.class
}
)
)

View file

@ -0,0 +1,527 @@
package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.model.db.premises.route.Destination;
import de.avatic.lcc.repositories.AbstractRepositoryIntegrationTest;
import de.avatic.lcc.util.exception.base.ForbiddenException;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
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.util.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* Integration tests for DestinationRepository.
* <p>
* Tests critical functionality across both MySQL and MSSQL:
* - Dynamic IN clauses
* - Named parameters
* - JOIN queries
* - NULL handling
* - Authorization checks
* - BigDecimal field operations
* <p>
* Run with:
* <pre>
* mvn test -Dspring.profiles.active=test,mysql -Dtest=DestinationRepositoryIntegrationTest
* mvn test -Dspring.profiles.active=test,mssql -Dtest=DestinationRepositoryIntegrationTest
* </pre>
*/
class DestinationRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest {
@Autowired
private DestinationRepository destinationRepository;
private Integer testUserId1;
private Integer testUserId2;
private Integer testCountryId;
private Integer testNodeId1;
private Integer testNodeId2;
private Integer testMaterialId;
private Integer testPremiseId1;
private Integer testPremiseId2;
@BeforeEach
void setupTestData() {
// Clean up in correct order (respecting foreign key constraints)
jdbcTemplate.update("DELETE FROM premise_route_section");
jdbcTemplate.update("DELETE FROM premise_route");
jdbcTemplate.update("DELETE FROM premise_destination");
jdbcTemplate.update("DELETE FROM premise");
jdbcTemplate.update("DELETE FROM material");
// Clean up node-referencing tables before deleting nodes
jdbcTemplate.update("DELETE FROM container_rate");
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 distance_matrix");
jdbcTemplate.update("DELETE FROM node");
jdbcTemplate.update("DELETE FROM sys_user");
// Create test users
testUserId1 = createUser("WD001", "user1@example.com");
testUserId2 = createUser("WD002", "user2@example.com");
// Get test country
testCountryId = getCountryId("DE");
// Create test nodes
testNodeId1 = createNode("Node 1", "NODE-001", testCountryId);
testNodeId2 = createNode("Node 2", "NODE-002", testCountryId);
// Create test material
testMaterialId = createMaterial("Test Material", "MAT-001");
// Create test premises
testPremiseId1 = createPremise(testUserId1, testNodeId1, testMaterialId, testCountryId, PremiseState.DRAFT);
testPremiseId2 = createPremise(testUserId2, testNodeId1, testMaterialId, testCountryId, PremiseState.DRAFT);
// Create some test destinations
createDestination(testPremiseId1, testNodeId1, 1000);
createDestination(testPremiseId1, testNodeId2, 2000);
createDestination(testPremiseId2, testNodeId1, 3000);
}
@Test
void testGetById() {
// Given: Create destination
Integer destinationId = createDestination(testPremiseId1, testNodeId1, 500);
// When: Get by ID
Optional<Destination> destination = destinationRepository.getById(destinationId);
// Then: Should retrieve
assertTrue(destination.isPresent());
assertEquals(destinationId, destination.get().getId());
assertEquals(testPremiseId1, destination.get().getPremiseId());
assertEquals(testNodeId1, destination.get().getDestinationNodeId());
assertEquals(500, destination.get().getAnnualAmount());
}
@Test
void testGetByIdNotFound() {
// When: Get non-existent ID
Optional<Destination> destination = destinationRepository.getById(99999);
// Then: Should return empty
assertFalse(destination.isPresent());
}
@Test
void testGetByPremiseId() {
// When: Get destinations for premise1
List<Destination> destinations = destinationRepository.getByPremiseId(testPremiseId1);
// Then: Should return all destinations for premise1
assertNotNull(destinations);
assertEquals(2, destinations.size());
assertTrue(destinations.stream().allMatch(d -> d.getPremiseId().equals(testPremiseId1)));
}
@Test
void testGetByPremiseIdAndUserId() {
// When: Get destinations for premise1 with correct user
List<Destination> destinations = destinationRepository.getByPremiseIdAndUserId(testPremiseId1, testUserId1);
// Then: Should return destinations
assertNotNull(destinations);
assertEquals(2, destinations.size());
}
@Test
void testGetByPremiseIdAndUserIdWrongUser() {
// When: Get destinations for premise1 with wrong user
List<Destination> destinations = destinationRepository.getByPremiseIdAndUserId(testPremiseId1, testUserId2);
// Then: Should return empty
assertTrue(destinations.isEmpty());
}
@Test
void testGetByPremiseIdAndUserIdNonExistent() {
// When: Get destinations for non-existent premise
List<Destination> destinations = destinationRepository.getByPremiseIdAndUserId(99999, testUserId1);
// Then: Should return empty
assertTrue(destinations.isEmpty());
}
@Test
void testUpdate() {
// Given: Create destination
Integer destinationId = createDestination(testPremiseId1, testNodeId1, 500);
// When: Update
destinationRepository.update(
destinationId,
1500, // annualAmount
new BigDecimal("10.50"), // repackingCost
new BigDecimal("5.25"), // disposalCost
new BigDecimal("8.75"), // handlingCost
true, // isD2d
new BigDecimal("100.00"), // d2dRate
new BigDecimal("48.00"), // d2dLeadTime
new BigDecimal("150.5") // distanceD2d
);
// Then: Should be updated
Optional<Destination> updated = destinationRepository.getById(destinationId);
assertTrue(updated.isPresent());
assertEquals(1500, updated.get().getAnnualAmount());
assertEquals(0, new BigDecimal("10.50").compareTo(updated.get().getRepackingCost()));
assertEquals(0, new BigDecimal("5.25").compareTo(updated.get().getDisposalCost()));
assertEquals(0, new BigDecimal("8.75").compareTo(updated.get().getHandlingCost()));
assertTrue(updated.get().getD2d());
assertEquals(0, new BigDecimal("100.00").compareTo(updated.get().getRateD2d()));
}
@Test
void testUpdateWithNulls() {
// Given: Create destination
Integer destinationId = createDestination(testPremiseId1, testNodeId1, 500);
// When: Update with null values
destinationRepository.update(
destinationId,
null, // annualAmount
null, // repackingCost
null, // disposalCost
null, // handlingCost
false, // isD2d
null, // d2dRate (should be null when isD2d is false)
null, // d2dLeadTime
null // distanceD2d
);
// Then: Should be updated with nulls
Optional<Destination> updated = destinationRepository.getById(destinationId);
assertTrue(updated.isPresent());
assertFalse(updated.get().getD2d());
assertNull(updated.get().getRateD2d());
}
@Test
void testUpdateNonExistent() {
// When/Then: Update non-existent destination should throw
assertThrows(DatabaseException.class, () ->
destinationRepository.update(99999, 100, null, null, null, false, null, null, null));
}
@Test
void testDeleteById() {
// Given: Create destination
Integer destinationId = createDestination(testPremiseId1, testNodeId1, 500);
// When: Delete
destinationRepository.deleteById(destinationId);
// Then: Should be deleted
Optional<Destination> deleted = destinationRepository.getById(destinationId);
assertFalse(deleted.isPresent());
}
@Test
void testDeleteByIdNull() {
// When: Delete with null (should not throw)
destinationRepository.deleteById(null);
// Then: No error
assertTrue(true);
}
@Test
void testGetOwnerIdById() {
// Given: Create destination for user1
Integer destinationId = createDestination(testPremiseId1, testNodeId1, 500);
// When: Get owner ID
Optional<Integer> ownerId = destinationRepository.getOwnerIdById(destinationId);
// Then: Should return user1's ID
assertTrue(ownerId.isPresent());
assertEquals(testUserId1, ownerId.get());
}
@Test
void testGetOwnerIdByIdNotFound() {
// When: Get owner ID for non-existent destination
Optional<Integer> ownerId = destinationRepository.getOwnerIdById(99999);
// Then: Should return empty
assertFalse(ownerId.isPresent());
}
@Test
void testGetOwnerIdsByIds() {
// Given: Create destinations for different users
Integer dest1 = createDestination(testPremiseId1, testNodeId1, 500);
Integer dest2 = createDestination(testPremiseId1, testNodeId2, 600);
Integer dest3 = createDestination(testPremiseId2, testNodeId1, 700);
// When: Get owner IDs
Map<Integer, Integer> ownerMap = destinationRepository.getOwnerIdsByIds(List.of(dest1, dest2, dest3));
// Then: Should return correct mappings
assertEquals(3, ownerMap.size());
assertEquals(testUserId1, ownerMap.get(dest1));
assertEquals(testUserId1, ownerMap.get(dest2));
assertEquals(testUserId2, ownerMap.get(dest3));
}
@Test
void testGetOwnerIdsByIdsEmpty() {
// When: Get owner IDs for empty list
Map<Integer, Integer> ownerMap = destinationRepository.getOwnerIdsByIds(List.of());
// Then: Should return empty map
assertTrue(ownerMap.isEmpty());
}
@Test
void testGetOwnerIdsByIdsNull() {
// When: Get owner IDs for null
Map<Integer, Integer> ownerMap = destinationRepository.getOwnerIdsByIds(null);
// Then: Should return empty map
assertTrue(ownerMap.isEmpty());
}
@Test
void testGetByPremiseIdsAndNodeIdsMap() {
// Given: Map of premise IDs to node IDs
Map<Integer, List<Integer>> premiseToNodes = new HashMap<>();
premiseToNodes.put(testPremiseId1, List.of(testNodeId1, testNodeId2));
premiseToNodes.put(testPremiseId2, List.of(testNodeId1));
// When: Get destinations
Map<Integer, List<Destination>> result =
destinationRepository.getByPremiseIdsAndNodeIds(premiseToNodes, testUserId1);
// Then: Should return destinations for user1's premises only
assertNotNull(result);
assertTrue(result.containsKey(testPremiseId1));
assertEquals(2, result.get(testPremiseId1).size());
}
@Test
void testGetByPremiseIdsAndNodeIdsMapEmpty() {
// When: Get with empty map
Map<Integer, List<Destination>> result =
destinationRepository.getByPremiseIdsAndNodeIds(Map.of(), testUserId1);
// Then: Should return empty map
assertTrue(result.isEmpty());
}
@Test
void testGetByPremiseIdsAndNodeIdsLists() {
// When: Get destinations by lists
Map<Integer, List<Destination>> result = destinationRepository.getByPremiseIdsAndNodeIds(
List.of(testPremiseId1, testPremiseId2),
List.of(testNodeId1, testNodeId2),
testUserId1
);
// Then: Should return only user1's destinations
// Note: The method queries for all premises in the list, then filters by userId in the JOIN
// So premise2 (owned by user2) should NOT be returned
assertNotNull(result);
assertTrue(result.containsKey(testPremiseId1));
// The method returns empty list for premises not owned by the user
if (result.containsKey(testPremiseId2)) {
assertTrue(result.get(testPremiseId2).isEmpty(), "User2's premise should return empty list");
}
}
@Test
void testInsert() {
// Given: New destination
Destination newDest = new Destination();
newDest.setPremiseId(testPremiseId1);
newDest.setDestinationNodeId(testNodeId1);
newDest.setCountryId(testCountryId);
newDest.setAnnualAmount(999);
newDest.setRepackingCost(new BigDecimal("12.50"));
newDest.setHandlingCost(new BigDecimal("8.25"));
newDest.setDisposalCost(new BigDecimal("3.75"));
newDest.setD2d(true);
newDest.setRateD2d(new BigDecimal("50.00"));
newDest.setLeadTimeD2d(24);
newDest.setGeoLat(new BigDecimal("51.5"));
newDest.setGeoLng(new BigDecimal("7.5"));
newDest.setDistanceD2d(new BigDecimal("100.0"));
// When: Insert
Integer id = destinationRepository.insert(newDest);
// Then: Should be inserted
assertNotNull(id);
assertTrue(id > 0);
Optional<Destination> inserted = destinationRepository.getById(id);
assertTrue(inserted.isPresent());
assertEquals(999, inserted.get().getAnnualAmount());
assertEquals(0, new BigDecimal("12.50").compareTo(inserted.get().getRepackingCost()));
assertTrue(inserted.get().getD2d());
}
@Test
void testInsertWithNulls() {
// Given: New destination with nullable fields as null
Destination newDest = new Destination();
newDest.setPremiseId(testPremiseId1);
newDest.setDestinationNodeId(testNodeId1);
newDest.setCountryId(testCountryId);
newDest.setAnnualAmount(null); // nullable
newDest.setRepackingCost(null);
newDest.setHandlingCost(null);
newDest.setDisposalCost(null);
newDest.setD2d(false);
newDest.setRateD2d(null);
newDest.setLeadTimeD2d(null); // nullable
newDest.setGeoLat(new BigDecimal("51.5"));
newDest.setGeoLng(new BigDecimal("7.5"));
newDest.setDistanceD2d(null);
// When: Insert
Integer id = destinationRepository.insert(newDest);
// Then: Should be inserted successfully
assertNotNull(id);
Optional<Destination> inserted = destinationRepository.getById(id);
assertTrue(inserted.isPresent());
// Note: annualAmount might be null or 0 depending on database behavior with nullable INT
// Just verify the record was inserted with the non-null fields
assertEquals(testPremiseId1, inserted.get().getPremiseId());
assertEquals(testNodeId1, inserted.get().getDestinationNodeId());
assertFalse(inserted.get().getD2d());
}
@Test
void testCheckOwnerSuccess() {
// Given: Destination owned by user1
Integer destId = createDestination(testPremiseId1, testNodeId1, 500);
// When/Then: Check with correct owner should not throw
assertDoesNotThrow(() -> destinationRepository.checkOwner(destId, testUserId1));
}
@Test
void testCheckOwnerWrongUser() {
// Given: Destination owned by user1
Integer destId = createDestination(testPremiseId1, testNodeId1, 500);
// When/Then: Check with wrong user should throw
assertThrows(ForbiddenException.class, () ->
destinationRepository.checkOwner(destId, testUserId2));
}
@Test
void testCheckOwnerNonExistent() {
// When/Then: Check non-existent destination should throw
assertThrows(ForbiddenException.class, () ->
destinationRepository.checkOwner(99999, testUserId1));
}
@Test
void testCheckOwnerListSuccess() {
// Given: Destinations owned by user1
Integer dest1 = createDestination(testPremiseId1, testNodeId1, 500);
Integer dest2 = createDestination(testPremiseId1, testNodeId2, 600);
// When/Then: Check with correct owner should not throw
assertDoesNotThrow(() -> destinationRepository.checkOwner(List.of(dest1, dest2), testUserId1));
}
@Test
void testCheckOwnerListMixedOwners() {
// Given: Destinations with different owners
Integer dest1 = createDestination(testPremiseId1, testNodeId1, 500);
Integer dest2 = createDestination(testPremiseId2, testNodeId1, 600);
// When/Then: Check with user1 should throw (dest2 is owned by user2)
assertThrows(ForbiddenException.class, () ->
destinationRepository.checkOwner(List.of(dest1, dest2), testUserId1));
}
@Test
void testCheckOwnerListEmpty() {
// When/Then: Check with empty list should not throw
assertDoesNotThrow(() -> destinationRepository.checkOwner(List.of(), testUserId1));
}
@Test
void testCheckOwnerListNull() {
// When/Then: Check with null should not throw
assertDoesNotThrow(() -> destinationRepository.checkOwner((List<Integer>) null, testUserId1));
}
// ========== Helper Methods ==========
private Integer createUser(String workdayId, String email) {
String sql = String.format(
"INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active) VALUES (?, ?, ?, ?, %s)",
dialectProvider.getBooleanTrue());
executeRawSql(sql, workdayId, email, "Test", "User");
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer getCountryId(String isoCode) {
return jdbcTemplate.queryForObject("SELECT id FROM country WHERE iso_code = ?", Integer.class, isoCode);
}
private Integer createNode(String name, String externalId, Integer countryId) {
String sql = String.format(
"INSERT INTO node (name, external_mapping_id, country_id, is_deprecated, is_source, is_destination, is_intermediate, address) " +
"VALUES (?, ?, ?, %s, %s, %s, %s, 'Test Address')",
dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue());
executeRawSql(sql, name, externalId, countryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createMaterial(String name, String partNumber) {
String sql = String.format(
"INSERT INTO material (name, part_number, normalized_part_number, hs_code, is_deprecated) VALUES (?, ?, ?, '123456', %s)",
dialectProvider.getBooleanFalse());
executeRawSql(sql, name, partNumber, partNumber);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createPremise(Integer userId, Integer nodeId, Integer materialId, Integer countryId, PremiseState state) {
String sql = String.format(
"INSERT INTO premise (user_id, supplier_node_id, material_id, country_id, state, geo_lat, geo_lng, created_at, updated_at) " +
"VALUES (?, ?, ?, ?, ?, 51.5, 7.5, %s, %s)",
isMysql() ? "NOW()" : "GETDATE()",
isMysql() ? "NOW()" : "GETDATE()");
executeRawSql(sql, userId, nodeId, materialId, countryId, state.name());
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createDestination(Integer premiseId, Integer nodeId, Integer annualAmount) {
String sql = "INSERT INTO premise_destination (premise_id, destination_node_id, annual_amount, country_id, geo_lat, geo_lng) " +
"VALUES (?, ?, ?, ?, 51.5, 7.5)";
executeRawSql(sql, premiseId, nodeId, annualAmount, testCountryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
}

View file

@ -0,0 +1,440 @@
package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.db.premises.Premise;
import de.avatic.lcc.model.db.premises.PremiseListEntry;
import de.avatic.lcc.model.db.premises.PremiseState;
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.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
/**
* Integration tests for PremiseRepository.
* <p>
* Tests critical functionality across both MySQL and MSSQL:
* - Pagination (LIMIT/OFFSET vs OFFSET/FETCH)
* - CURRENT_TIMESTAMP functions (NOW() vs GETDATE())
* - Boolean literals (TRUE/FALSE vs 1/0)
* - Dynamic IN clauses
* - Complex JOIN queries with filtering
* <p>
* Run with:
* <pre>
* mvn test -Dspring.profiles.active=test,mysql -Dtest=PremiseRepositoryIntegrationTest
* mvn test -Dspring.profiles.active=test,mssql -Dtest=PremiseRepositoryIntegrationTest
* </pre>
*/
class PremiseRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest {
@Autowired
private PremiseRepository premiseRepository;
private Integer testUserId;
private Integer testCountryId;
private Integer testNodeId;
private Integer testMaterialId;
@BeforeEach
void setupTestData() {
// Clean up in correct order (respecting foreign key constraints)
jdbcTemplate.update("DELETE FROM premise_destination");
jdbcTemplate.update("DELETE FROM premise");
jdbcTemplate.update("DELETE FROM material");
// Clean up node-referencing tables before deleting nodes
jdbcTemplate.update("DELETE FROM container_rate");
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 distance_matrix");
jdbcTemplate.update("DELETE FROM node");
jdbcTemplate.update("DELETE FROM sys_user");
// Create test user
testUserId = createUser("WD001", "test@example.com");
// Get test country
testCountryId = getCountryId("DE");
// Create test node
testNodeId = createNode("Test Supplier", "SUP-001", testCountryId);
// Create test material
testMaterialId = createMaterial("Test Material", "MAT-001");
// Create some test premises
createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.COMPLETED);
createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.ARCHIVED);
}
@Test
void testListPremises() {
// Given: Pagination
SearchQueryPagination pagination = new SearchQueryPagination(1, 10);
// When: List premises
SearchQueryResult<PremiseListEntry> result = premiseRepository.listPremises(
null, pagination, testUserId, null, null, null);
// Then: Should return all premises
assertNotNull(result);
assertEquals(3, result.getTotalElements());
assertFalse(result.toList().isEmpty());
}
@Test
void testListPremisesPagination() {
// Given: Create more premises and use pagination
for (int i = 0; i < 10; i++) {
createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
}
SearchQueryPagination pagination = new SearchQueryPagination(1, 5);
// When: List premises
SearchQueryResult<PremiseListEntry> result = premiseRepository.listPremises(
null, pagination, testUserId, null, null, null);
// Then: Should respect limit
assertNotNull(result);
assertEquals(5, result.toList().size());
assertTrue(result.getTotalElements() >= 13);
}
@Test
void testListPremisesWithFilter() {
// Given: Pagination and filter
SearchQueryPagination pagination = new SearchQueryPagination(1, 10);
// When: List with filter
SearchQueryResult<PremiseListEntry> result = premiseRepository.listPremises(
"Test Supplier", pagination, testUserId, null, null, null);
// Then: Should filter results
assertNotNull(result);
assertTrue(result.getTotalElements() >= 3);
assertTrue(result.toList().stream()
.allMatch(p -> p.getSupplierName().contains("Test Supplier")));
}
@Test
void testListPremisesWithDraftFilter() {
// Given: Pagination with draft filter
SearchQueryPagination pagination = new SearchQueryPagination(1, 10);
// When: List only drafts
SearchQueryResult<PremiseListEntry> result = premiseRepository.listPremises(
null, pagination, testUserId, true, false, false);
// Then: Should return only DRAFT premises
assertNotNull(result);
assertTrue(result.toList().stream()
.allMatch(p -> p.getState() == de.avatic.lcc.dto.calculation.PremiseState.DRAFT));
}
@Test
void testInsert() {
// When: Insert new premise
Integer premiseId = premiseRepository.insert(
testMaterialId, testNodeId, null,
new BigDecimal("51.5"), new BigDecimal("7.5"),
testCountryId, testUserId);
// Then: Should be inserted
assertNotNull(premiseId);
assertTrue(premiseId > 0);
Optional<Premise> inserted = premiseRepository.getPremiseById(premiseId);
assertTrue(inserted.isPresent());
assertEquals(testMaterialId, inserted.get().getMaterialId());
assertEquals(testNodeId, inserted.get().getSupplierNodeId());
assertEquals(PremiseState.DRAFT, inserted.get().getState());
}
@Test
void testGetPremiseById() {
// Given: Create premise
Integer premiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Get by ID
Optional<Premise> premise = premiseRepository.getPremiseById(premiseId);
// Then: Should retrieve
assertTrue(premise.isPresent());
assertEquals(premiseId, premise.get().getId());
assertEquals(testMaterialId, premise.get().getMaterialId());
}
@Test
void testGetPremiseByIdNotFound() {
// When: Get non-existent ID
Optional<Premise> premise = premiseRepository.getPremiseById(99999);
// Then: Should return empty
assertFalse(premise.isPresent());
}
@Test
void testGetPremisesById() {
// Given: Create multiple premises
Integer id1 = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
Integer id2 = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Get by IDs
List<Premise> premises = premiseRepository.getPremisesById(List.of(id1, id2));
// Then: Should retrieve both
assertNotNull(premises);
assertEquals(2, premises.size());
assertTrue(premises.stream().anyMatch(p -> p.getId().equals(id1)));
assertTrue(premises.stream().anyMatch(p -> p.getId().equals(id2)));
}
@Test
void testGetPremisesByIdEmpty() {
// When: Get with empty list
List<Premise> premises = premiseRepository.getPremisesById(List.of());
// Then: Should return empty
assertNotNull(premises);
assertTrue(premises.isEmpty());
}
@Test
void testResetPrice() {
// Given: Create premise with price
Integer premiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// Set initial price
premiseRepository.updatePrice(List.of(premiseId), new BigDecimal("100.50"), true, new BigDecimal("0.5"));
// When: Reset price
premiseRepository.resetPrice(List.of(premiseId));
// Then: Price should be null
Optional<Premise> premise = premiseRepository.getPremiseById(premiseId);
assertTrue(premise.isPresent());
assertNull(premise.get().getMaterialCost());
assertFalse(premise.get().getFcaEnabled());
}
@Test
void testUpdatePrice() {
// Given: Create premise
Integer premiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Update price
premiseRepository.updatePrice(List.of(premiseId), new BigDecimal("200.75"), true, new BigDecimal("0.6"));
// Then: Price should be updated
Optional<Premise> premise = premiseRepository.getPremiseById(premiseId);
assertTrue(premise.isPresent());
assertEquals(0, new BigDecimal("200.75").compareTo(premise.get().getMaterialCost()));
assertTrue(premise.get().getFcaEnabled());
assertEquals(0, new BigDecimal("0.6").compareTo(premise.get().getOverseaShare()));
}
@Test
void testUpdateMaterial() {
// Given: Create premise
Integer premiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Update material properties
premiseRepository.updateMaterial(List.of(premiseId), "12345678", new BigDecimal("0.05"), true);
// Then: Material properties should be updated
Optional<Premise> premise = premiseRepository.getPremiseById(premiseId);
assertTrue(premise.isPresent());
assertEquals("12345678", premise.get().getHsCode());
assertEquals(0, new BigDecimal("0.05").compareTo(premise.get().getTariffRate()));
assertTrue(premise.get().getTariffUnlocked());
}
@Test
void testSetMaterialId() {
// Given: Create premise and new material
Integer premiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
Integer newMaterialId = createMaterial("New Material", "MAT-002");
// When: Set new material ID
premiseRepository.setMaterialId(List.of(premiseId), newMaterialId);
// Then: Material should be changed
Optional<Premise> premise = premiseRepository.getPremiseById(premiseId);
assertTrue(premise.isPresent());
assertEquals(newMaterialId, premise.get().getMaterialId());
}
@Test
void testDeletePremisesById() {
// Given: Create DRAFT premises
Integer draftId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
Integer completedId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.COMPLETED);
// When: Delete (should only delete DRAFT)
premiseRepository.deletePremisesById(List.of(draftId, completedId));
// Then: DRAFT should be deleted, COMPLETED should remain
assertFalse(premiseRepository.getPremiseById(draftId).isPresent());
assertTrue(premiseRepository.getPremiseById(completedId).isPresent());
}
@Test
void testSetStatus() {
// Given: Create DRAFT premise
Integer premiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Set status to COMPLETED
premiseRepository.setStatus(List.of(premiseId), PremiseState.COMPLETED);
// Then: Status should be updated
Optional<Premise> premise = premiseRepository.getPremiseById(premiseId);
assertTrue(premise.isPresent());
assertEquals(PremiseState.COMPLETED, premise.get().getState());
}
@Test
void testFindByMaterialIdAndSupplierId() {
// Given: Create premise
createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Find by material and supplier
List<Premise> premises = premiseRepository.findByMaterialIdAndSupplierId(
testMaterialId, testNodeId, null, testUserId);
// Then: Should find premise
assertNotNull(premises);
assertFalse(premises.isEmpty());
assertTrue(premises.stream().anyMatch(p ->
p.getMaterialId().equals(testMaterialId) && p.getSupplierNodeId().equals(testNodeId)));
}
@Test
void testGetPremisesByMaterialIdsAndSupplierIds() {
// Given: Create premises
createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Get by IDs
List<Premise> premises = premiseRepository.getPremisesByMaterialIdsAndSupplierIds(
List.of(testMaterialId), List.of(testNodeId), null, testUserId, true);
// Then: Should find premises
assertNotNull(premises);
assertFalse(premises.isEmpty());
assertTrue(premises.stream().allMatch(p -> p.getState() == PremiseState.DRAFT));
}
@Test
void testFindAssociatedSuppliers() {
// Given: Premises with suppliers
createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// When: Find associated suppliers
List<Integer> supplierIds = premiseRepository.findAssociatedSuppliers(List.of(testMaterialId));
// Then: Should find supplier
assertNotNull(supplierIds);
assertFalse(supplierIds.isEmpty());
assertTrue(supplierIds.contains(testNodeId));
}
@Test
void testGetIdsWithUnlockedTariffs() {
// Given: Create premises with different tariff states
Integer unlockedId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
Integer lockedId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// Set one as unlocked
premiseRepository.updateMaterial(List.of(unlockedId), null, null, true);
// When: Get unlocked IDs
List<Integer> unlockedIds = premiseRepository.getIdsWithUnlockedTariffs(List.of(unlockedId, lockedId));
// Then: Should return only unlocked
assertNotNull(unlockedIds);
assertTrue(unlockedIds.contains(unlockedId));
assertFalse(unlockedIds.contains(lockedId));
}
@Test
void testGetPremiseCompletedCountByUserId() {
// When: Get count
Integer count = premiseRepository.getPremiseCompletedCountByUserId(testUserId);
// Then: Should count COMPLETED premises
assertNotNull(count);
assertTrue(count >= 1, "Should have at least 1 COMPLETED premise from setup");
}
@Test
void testGetPremiseDraftCountByUserId() {
// When: Get count
Integer count = premiseRepository.getPremiseDraftCountByUserId(testUserId);
// Then: Should count DRAFT premises
assertNotNull(count);
assertTrue(count >= 1, "Should have at least 1 DRAFT premise from setup");
}
// ========== Helper Methods ==========
private Integer createUser(String workdayId, String email) {
String sql = String.format(
"INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active) VALUES (?, ?, ?, ?, %s)",
dialectProvider.getBooleanTrue());
executeRawSql(sql, workdayId, email, "Test", "User");
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer getCountryId(String isoCode) {
return jdbcTemplate.queryForObject("SELECT id FROM country WHERE iso_code = ?", Integer.class, isoCode);
}
private Integer createNode(String name, String externalId, Integer countryId) {
String sql = String.format(
"INSERT INTO node (name, external_mapping_id, country_id, is_deprecated, is_source, is_destination, is_intermediate, address) " +
"VALUES (?, ?, ?, %s, %s, %s, %s, 'Test Address')",
dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue());
executeRawSql(sql, name, externalId, countryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createMaterial(String name, String partNumber) {
String sql = String.format(
"INSERT INTO material (name, part_number, normalized_part_number, hs_code, is_deprecated) VALUES (?, ?, ?, '123456', %s)",
dialectProvider.getBooleanFalse());
executeRawSql(sql, name, partNumber, partNumber);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createPremise(Integer userId, Integer nodeId, Integer materialId, Integer countryId, PremiseState state) {
String sql = String.format(
"INSERT INTO premise (user_id, supplier_node_id, material_id, country_id, state, geo_lat, geo_lng, created_at, updated_at) " +
"VALUES (?, ?, ?, ?, ?, 51.5, 7.5, %s, %s)",
isMysql() ? "NOW()" : "GETDATE()",
isMysql() ? "NOW()" : "GETDATE()");
executeRawSql(sql, userId, nodeId, materialId, countryId, state.name());
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
}

View file

@ -0,0 +1,444 @@
package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.model.db.premises.route.RouteNode;
import de.avatic.lcc.repositories.AbstractRepositoryIntegrationTest;
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.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
/**
* Integration tests for RouteNodeRepository.
* <p>
* Tests critical functionality across both MySQL and MSSQL:
* - Boolean literals (TRUE/FALSE vs 1/0)
* - Dynamic IN clauses
* - JOIN queries
* - NULL handling for optional foreign keys
* - BigDecimal geo coordinates
* <p>
* Run with:
* <pre>
* mvn test -Dspring.profiles.active=test,mysql -Dtest=RouteNodeRepositoryIntegrationTest
* mvn test -Dspring.profiles.active=test,mssql -Dtest=RouteNodeRepositoryIntegrationTest
* </pre>
*/
class RouteNodeRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest {
@Autowired
private RouteNodeRepository routeNodeRepository;
private Integer testUserId;
private Integer testCountryId;
private Integer testNodeId;
private Integer testMaterialId;
private Integer testPremiseId;
private Integer testDestinationId;
private Integer testRouteId;
@BeforeEach
void setupTestData() {
// Clean up in correct order (respecting foreign key constraints)
jdbcTemplate.update("DELETE FROM premise_route_section");
jdbcTemplate.update("DELETE FROM premise_route_node");
jdbcTemplate.update("DELETE FROM premise_route");
jdbcTemplate.update("DELETE FROM premise_destination");
jdbcTemplate.update("DELETE FROM premise");
jdbcTemplate.update("DELETE FROM material");
// Clean up node-referencing tables before deleting nodes
jdbcTemplate.update("DELETE FROM container_rate");
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 distance_matrix");
jdbcTemplate.update("DELETE FROM node");
jdbcTemplate.update("DELETE FROM sys_user");
// Create test user
testUserId = createUser("WD001", "test@example.com");
// Get test country
testCountryId = getCountryId("DE");
// Create test node
testNodeId = createNode("Test Node", "NODE-001", testCountryId);
// Create test material
testMaterialId = createMaterial("Test Material", "MAT-001");
// Create test premise
testPremiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// Create test destination
testDestinationId = createDestination(testPremiseId, testNodeId);
// Create test route
testRouteId = createRoute(testDestinationId);
// Create some test route nodes
createRouteNode("Node A", testNodeId, testCountryId, true, false, false);
createRouteNode("Node B", testNodeId, testCountryId, false, true, false);
createRouteNode("Node C", testNodeId, testCountryId, false, false, true);
}
@Test
void testGetById() {
// Given: Create route node
Integer nodeId = createRouteNode("Test Node", testNodeId, testCountryId, true, true, true);
// When: Get by ID
Optional<RouteNode> node = routeNodeRepository.getById(nodeId);
// Then: Should retrieve
assertTrue(node.isPresent());
assertEquals(nodeId, node.get().getId());
assertEquals("Test Node", node.get().getName());
assertTrue(node.get().getDestination());
assertTrue(node.get().getIntermediate());
assertTrue(node.get().getSource());
}
@Test
void testGetByIdNotFound() {
// When: Get non-existent ID
Optional<RouteNode> node = routeNodeRepository.getById(99999);
// Then: Should return empty
assertFalse(node.isPresent());
}
@Test
void testInsert() {
// Given: New route node
RouteNode newNode = new RouteNode();
newNode.setName("New Route Node");
newNode.setAddress("123 Test Street");
newNode.setGeoLat(new BigDecimal("51.5074"));
newNode.setGeoLng(new BigDecimal("0.1278"));
newNode.setDestination(true);
newNode.setIntermediate(false);
newNode.setSource(true);
newNode.setNodeId(testNodeId);
newNode.setUserNodeId(null);
newNode.setOutdated(false);
newNode.setCountryId(testCountryId);
newNode.setExternalMappingId("EXT-001");
// When: Insert
Integer id = routeNodeRepository.insert(newNode);
// Then: Should be inserted
assertNotNull(id);
assertTrue(id > 0);
Optional<RouteNode> inserted = routeNodeRepository.getById(id);
assertTrue(inserted.isPresent());
assertEquals("New Route Node", inserted.get().getName());
assertTrue(inserted.get().getDestination());
assertFalse(inserted.get().getIntermediate());
assertTrue(inserted.get().getSource());
assertFalse(inserted.get().getOutdated());
assertEquals("EXT-001", inserted.get().getExternalMappingId());
}
@Test
void testInsertWithNulls() {
// Given: Route node with nullable fields as null
RouteNode newNode = new RouteNode();
newNode.setName("Minimal Node");
newNode.setAddress("Address");
newNode.setGeoLat(new BigDecimal("50.0"));
newNode.setGeoLng(new BigDecimal("8.0"));
newNode.setDestination(false);
newNode.setIntermediate(false);
newNode.setSource(false);
newNode.setNodeId(null); // nullable FK
newNode.setUserNodeId(null); // nullable FK
newNode.setOutdated(false);
newNode.setCountryId(testCountryId);
newNode.setExternalMappingId("MIN-EXT"); // NOT NULL in schema
// When: Insert
Integer id = routeNodeRepository.insert(newNode);
// Then: Should be inserted with nullable FKs as null
assertNotNull(id);
Optional<RouteNode> inserted = routeNodeRepository.getById(id);
assertTrue(inserted.isPresent());
assertEquals("Minimal Node", inserted.get().getName());
assertEquals("MIN-EXT", inserted.get().getExternalMappingId());
}
@Test
void testDeleteAllById() {
// Given: Multiple route nodes
Integer node1 = createRouteNode("Node 1", testNodeId, testCountryId, true, false, false);
Integer node2 = createRouteNode("Node 2", testNodeId, testCountryId, false, true, false);
Integer node3 = createRouteNode("Node 3", testNodeId, testCountryId, false, false, true);
// When: Delete first two
routeNodeRepository.deleteAllById(List.of(node1, node2));
// Then: Should delete specified nodes
assertFalse(routeNodeRepository.getById(node1).isPresent());
assertFalse(routeNodeRepository.getById(node2).isPresent());
assertTrue(routeNodeRepository.getById(node3).isPresent());
}
@Test
void testDeleteAllByIdEmpty() {
// Given: Some route nodes
Integer nodeId = createRouteNode("Node", testNodeId, testCountryId, true, false, false);
// When: Delete with empty list
routeNodeRepository.deleteAllById(List.of());
// Then: Should not delete anything
assertTrue(routeNodeRepository.getById(nodeId).isPresent());
}
@Test
void testDeleteAllByIdNull() {
// Given: Some route nodes
Integer nodeId = createRouteNode("Node", testNodeId, testCountryId, true, false, false);
// When: Delete with null
routeNodeRepository.deleteAllById(null);
// Then: Should not delete anything
assertTrue(routeNodeRepository.getById(nodeId).isPresent());
}
@Test
void testGetFromNodeBySectionId() {
// Given: Create route section with from and to nodes
Integer fromNodeId = createRouteNode("From Node", testNodeId, testCountryId, false, false, true);
Integer toNodeId = createRouteNode("To Node", testNodeId, testCountryId, true, false, false);
Integer sectionId = createRouteSection(testRouteId, fromNodeId, toNodeId);
// When: Get from node by section ID
Optional<RouteNode> fromNode = routeNodeRepository.getFromNodeBySectionId(sectionId);
// Then: Should retrieve from node (verify by name and properties, not ID due to JOIN)
assertTrue(fromNode.isPresent());
assertEquals("From Node", fromNode.get().getName());
assertTrue(fromNode.get().getSource());
}
@Test
void testGetFromNodeBySectionIdNotFound() {
// When: Get from node for non-existent section
Optional<RouteNode> fromNode = routeNodeRepository.getFromNodeBySectionId(99999);
// Then: Should return empty
assertFalse(fromNode.isPresent());
}
@Test
void testGetToNodeBySectionId() {
// Given: Create route section with from and to nodes
Integer fromNodeId = createRouteNode("From Node", testNodeId, testCountryId, false, false, true);
Integer toNodeId = createRouteNode("To Node", testNodeId, testCountryId, true, false, false);
Integer sectionId = createRouteSection(testRouteId, fromNodeId, toNodeId);
// When: Get to node by section ID
Optional<RouteNode> toNode = routeNodeRepository.getToNodeBySectionId(sectionId);
// Then: Should retrieve to node (verify by name and properties, not ID due to JOIN)
assertTrue(toNode.isPresent());
assertEquals("To Node", toNode.get().getName());
assertTrue(toNode.get().getDestination());
}
@Test
void testGetToNodeBySectionIdNotFound() {
// When: Get to node for non-existent section
Optional<RouteNode> toNode = routeNodeRepository.getToNodeBySectionId(99999);
// Then: Should return empty
assertFalse(toNode.isPresent());
}
@Test
void testBooleanFields() {
// Given: Create nodes with different boolean combinations
RouteNode node1 = new RouteNode();
node1.setName("All True");
node1.setAddress("Address 1");
node1.setGeoLat(new BigDecimal("50.0"));
node1.setGeoLng(new BigDecimal("8.0"));
node1.setDestination(true);
node1.setIntermediate(true);
node1.setSource(true);
node1.setOutdated(true);
node1.setCountryId(testCountryId);
node1.setExternalMappingId("EXT1");
RouteNode node2 = new RouteNode();
node2.setName("All False");
node2.setAddress("Address 2");
node2.setGeoLat(new BigDecimal("51.0"));
node2.setGeoLng(new BigDecimal("9.0"));
node2.setDestination(false);
node2.setIntermediate(false);
node2.setSource(false);
node2.setOutdated(false);
node2.setCountryId(testCountryId);
node2.setExternalMappingId("EXT2");
// When: Insert
Integer id1 = routeNodeRepository.insert(node1);
Integer id2 = routeNodeRepository.insert(node2);
// Then: Boolean values should be stored and retrieved correctly
Optional<RouteNode> retrieved1 = routeNodeRepository.getById(id1);
assertTrue(retrieved1.isPresent());
assertTrue(retrieved1.get().getDestination());
assertTrue(retrieved1.get().getIntermediate());
assertTrue(retrieved1.get().getSource());
assertTrue(retrieved1.get().getOutdated());
Optional<RouteNode> retrieved2 = routeNodeRepository.getById(id2);
assertTrue(retrieved2.isPresent());
assertFalse(retrieved2.get().getDestination());
assertFalse(retrieved2.get().getIntermediate());
assertFalse(retrieved2.get().getSource());
assertFalse(retrieved2.get().getOutdated());
}
@Test
void testGeoCoordinates() {
// Given: Node with specific coordinates
RouteNode node = new RouteNode();
node.setName("Geo Node");
node.setAddress("Geo Address");
node.setGeoLat(new BigDecimal("52.5200"));
node.setGeoLng(new BigDecimal("13.4050"));
node.setDestination(true);
node.setIntermediate(false);
node.setSource(false);
node.setCountryId(testCountryId);
node.setExternalMappingId("GEO1");
// When: Insert
Integer id = routeNodeRepository.insert(node);
// Then: Coordinates should be stored correctly
Optional<RouteNode> retrieved = routeNodeRepository.getById(id);
assertTrue(retrieved.isPresent());
assertEquals(0, new BigDecimal("52.5200").compareTo(retrieved.get().getGeoLat()));
assertEquals(0, new BigDecimal("13.4050").compareTo(retrieved.get().getGeoLng()));
}
// ========== Helper Methods ==========
private Integer createUser(String workdayId, String email) {
String sql = String.format(
"INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active) VALUES (?, ?, ?, ?, %s)",
dialectProvider.getBooleanTrue());
executeRawSql(sql, workdayId, email, "Test", "User");
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer getCountryId(String isoCode) {
return jdbcTemplate.queryForObject("SELECT id FROM country WHERE iso_code = ?", Integer.class, isoCode);
}
private Integer createNode(String name, String externalId, Integer countryId) {
String sql = String.format(
"INSERT INTO node (name, external_mapping_id, country_id, is_deprecated, is_source, is_destination, is_intermediate, address) " +
"VALUES (?, ?, ?, %s, %s, %s, %s, 'Test Address')",
dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue());
executeRawSql(sql, name, externalId, countryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createMaterial(String name, String partNumber) {
String sql = String.format(
"INSERT INTO material (name, part_number, normalized_part_number, hs_code, is_deprecated) VALUES (?, ?, ?, '123456', %s)",
dialectProvider.getBooleanFalse());
executeRawSql(sql, name, partNumber, partNumber);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createPremise(Integer userId, Integer nodeId, Integer materialId, Integer countryId, PremiseState state) {
String sql = String.format(
"INSERT INTO premise (user_id, supplier_node_id, material_id, country_id, state, geo_lat, geo_lng, created_at, updated_at) " +
"VALUES (?, ?, ?, ?, ?, 51.5, 7.5, %s, %s)",
isMysql() ? "NOW()" : "GETDATE()",
isMysql() ? "NOW()" : "GETDATE()");
executeRawSql(sql, userId, nodeId, materialId, countryId, state.name());
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createDestination(Integer premiseId, Integer nodeId) {
String sql = "INSERT INTO premise_destination (premise_id, destination_node_id, annual_amount, country_id, geo_lat, geo_lng) " +
"VALUES (?, ?, 1000, ?, 51.5, 7.5)";
executeRawSql(sql, premiseId, nodeId, testCountryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createRoute(Integer destinationId) {
String sql = String.format(
"INSERT INTO premise_route (premise_destination_id, is_cheapest, is_fastest, is_selected) VALUES (?, %s, %s, %s)",
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue());
executeRawSql(sql, destinationId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createRouteNode(String name, Integer nodeId, Integer countryId, boolean isDestination, boolean isIntermediate, boolean isSource) {
String sql = String.format(
"INSERT INTO premise_route_node (name, address, geo_lat, geo_lng, is_destination, is_intermediate, is_source, " +
"node_id, country_id, is_outdated, external_mapping_id) " +
"VALUES (?, 'Address', 51.5, 7.5, %s, %s, %s, ?, ?, %s, 'EXT')",
isDestination ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
isIntermediate ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
isSource ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanFalse());
executeRawSql(sql, name, nodeId, countryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createRouteSection(Integer routeId, Integer fromNodeId, Integer toNodeId) {
String sql = String.format(
"INSERT INTO premise_route_section (premise_route_id, from_route_node_id, to_route_node_id, list_position, " +
"transport_type, rate_type, is_pre_run, is_main_run, is_post_run, is_outdated) " +
"VALUES (?, ?, ?, 1, 'SEA', 'CONTAINER', %s, %s, %s, %s)",
dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanFalse());
executeRawSql(sql, routeId, fromNodeId, toNodeId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
}

View file

@ -0,0 +1,341 @@
package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.model.db.premises.route.Route;
import de.avatic.lcc.repositories.AbstractRepositoryIntegrationTest;
import de.avatic.lcc.util.exception.internalerror.DatabaseException;
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 RouteRepository.
* <p>
* Tests critical functionality across both MySQL and MSSQL:
* - Boolean literals (TRUE/FALSE vs 1/0)
* - Dynamic IN clauses
* - Auto-generated keys
* - CRUD operations
* <p>
* Run with:
* <pre>
* mvn test -Dspring.profiles.active=test,mysql -Dtest=RouteRepositoryIntegrationTest
* mvn test -Dspring.profiles.active=test,mssql -Dtest=RouteRepositoryIntegrationTest
* </pre>
*/
class RouteRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest {
@Autowired
private RouteRepository routeRepository;
private Integer testUserId;
private Integer testCountryId;
private Integer testNodeId;
private Integer testMaterialId;
private Integer testPremiseId;
private Integer testDestinationId;
@BeforeEach
void setupTestData() {
// Clean up in correct order (respecting foreign key constraints)
jdbcTemplate.update("DELETE FROM premise_route_section");
jdbcTemplate.update("DELETE FROM premise_route");
jdbcTemplate.update("DELETE FROM premise_destination");
jdbcTemplate.update("DELETE FROM premise");
jdbcTemplate.update("DELETE FROM material");
// Clean up node-referencing tables before deleting nodes
jdbcTemplate.update("DELETE FROM container_rate");
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 distance_matrix");
jdbcTemplate.update("DELETE FROM node");
jdbcTemplate.update("DELETE FROM sys_user");
// Create test user
testUserId = createUser("WD001", "test@example.com");
// Get test country
testCountryId = getCountryId("DE");
// Create test node
testNodeId = createNode("Test Supplier", "SUP-001", testCountryId);
// Create test material
testMaterialId = createMaterial("Test Material", "MAT-001");
// Create test premise
testPremiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// Create test destination
testDestinationId = createDestination(testPremiseId, testNodeId);
// Create some test routes
createRoute(testDestinationId, true, true, true); // cheapest, fastest, selected
createRoute(testDestinationId, false, false, false); // not cheapest, not fastest, not selected
createRoute(testDestinationId, false, false, false); // not cheapest, not fastest, not selected
}
@Test
void testGetByDestinationId() {
// When: Get routes by destination
List<Route> routes = routeRepository.getByDestinationId(testDestinationId);
// Then: Should return all routes for destination
assertNotNull(routes);
assertEquals(3, routes.size());
assertTrue(routes.stream().allMatch(r -> r.getDestinationId().equals(testDestinationId)));
}
@Test
void testGetByDestinationIdEmpty() {
// When: Get routes for non-existent destination
List<Route> routes = routeRepository.getByDestinationId(99999);
// Then: Should return empty list
assertNotNull(routes);
assertTrue(routes.isEmpty());
}
@Test
void testGetSelectedByDestinationId() {
// When: Get selected route
Optional<Route> selected = routeRepository.getSelectedByDestinationId(testDestinationId);
// Then: Should return the selected route
assertTrue(selected.isPresent());
assertEquals(testDestinationId, selected.get().getDestinationId());
assertTrue(selected.get().getSelected());
assertTrue(selected.get().getCheapest());
assertTrue(selected.get().getFastest());
}
@Test
void testGetSelectedByDestinationIdNotFound() {
// Given: Destination with no selected routes
Integer destinationId2 = createDestination(testPremiseId, testNodeId);
createRoute(destinationId2, false, false, false);
// When: Get selected route
Optional<Route> selected = routeRepository.getSelectedByDestinationId(destinationId2);
// Then: Should return empty
assertFalse(selected.isPresent());
}
@Test
void testGetSelectedByDestinationIdMultipleThrows() {
// Given: Destination with multiple selected routes (invalid state)
Integer destinationId2 = createDestination(testPremiseId, testNodeId);
createRoute(destinationId2, false, false, true);
createRoute(destinationId2, false, false, true);
// When/Then: Should throw DatabaseException
assertThrows(DatabaseException.class, () ->
routeRepository.getSelectedByDestinationId(destinationId2));
}
@Test
void testInsert() {
// Given: New route
Route newRoute = new Route();
newRoute.setDestinationId(testDestinationId);
newRoute.setCheapest(false);
newRoute.setFastest(true);
newRoute.setSelected(false);
// When: Insert
Integer id = routeRepository.insert(newRoute);
// Then: Should be inserted
assertNotNull(id);
assertTrue(id > 0);
// Verify insertion
List<Route> routes = routeRepository.getByDestinationId(testDestinationId);
assertTrue(routes.stream().anyMatch(r -> r.getId().equals(id) && r.getFastest()));
}
@Test
void testDeleteAllById() {
// Given: Multiple routes
List<Route> routes = routeRepository.getByDestinationId(testDestinationId);
assertEquals(3, routes.size());
// Get first two route IDs
List<Integer> idsToDelete = routes.stream()
.limit(2)
.map(Route::getId)
.toList();
// When: Delete by IDs
routeRepository.deleteAllById(idsToDelete);
// Then: Should delete specified routes
List<Route> remaining = routeRepository.getByDestinationId(testDestinationId);
assertEquals(1, remaining.size());
assertFalse(idsToDelete.contains(remaining.getFirst().getId()));
}
@Test
void testDeleteAllByIdEmpty() {
// When: Delete with empty list
routeRepository.deleteAllById(List.of());
// Then: Should not throw error, routes remain
List<Route> routes = routeRepository.getByDestinationId(testDestinationId);
assertEquals(3, routes.size());
}
@Test
void testDeleteAllByIdNull() {
// When: Delete with null
routeRepository.deleteAllById(null);
// Then: Should not throw error, routes remain
List<Route> routes = routeRepository.getByDestinationId(testDestinationId);
assertEquals(3, routes.size());
}
@Test
void testUpdateSelectedByDestinationId() {
// Given: Get non-selected route
List<Route> routes = routeRepository.getByDestinationId(testDestinationId);
Route nonSelectedRoute = routes.stream()
.filter(r -> !r.getSelected())
.findFirst()
.orElseThrow();
// When: Update selected route
routeRepository.updateSelectedByDestinationId(testDestinationId, nonSelectedRoute.getId());
// Then: New route should be selected, old route should be deselected
Optional<Route> newSelected = routeRepository.getSelectedByDestinationId(testDestinationId);
assertTrue(newSelected.isPresent());
assertEquals(nonSelectedRoute.getId(), newSelected.get().getId());
assertTrue(newSelected.get().getSelected());
// Verify only one route is selected
List<Route> allRoutes = routeRepository.getByDestinationId(testDestinationId);
long selectedCount = allRoutes.stream().filter(Route::getSelected).count();
assertEquals(1, selectedCount, "Only one route should be selected");
}
@Test
void testUpdateSelectedByDestinationIdInvalidRoute() {
// When/Then: Update with non-existent route ID should throw
assertThrows(DatabaseException.class, () ->
routeRepository.updateSelectedByDestinationId(testDestinationId, 99999));
}
@Test
void testBooleanLiterals() {
// Given: Create routes with different boolean values
Route route1 = new Route();
route1.setDestinationId(testDestinationId);
route1.setCheapest(true);
route1.setFastest(false);
route1.setSelected(true);
Route route2 = new Route();
route2.setDestinationId(testDestinationId);
route2.setCheapest(false);
route2.setFastest(true);
route2.setSelected(false);
// When: Insert
Integer id1 = routeRepository.insert(route1);
Integer id2 = routeRepository.insert(route2);
// Then: Boolean values should be stored and retrieved correctly
List<Route> routes = routeRepository.getByDestinationId(testDestinationId);
Route retrieved1 = routes.stream().filter(r -> r.getId().equals(id1)).findFirst().orElseThrow();
assertTrue(retrieved1.getCheapest());
assertFalse(retrieved1.getFastest());
Route retrieved2 = routes.stream().filter(r -> r.getId().equals(id2)).findFirst().orElseThrow();
assertFalse(retrieved2.getCheapest());
assertTrue(retrieved2.getFastest());
}
// ========== Helper Methods ==========
private Integer createUser(String workdayId, String email) {
String sql = String.format(
"INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active) VALUES (?, ?, ?, ?, %s)",
dialectProvider.getBooleanTrue());
executeRawSql(sql, workdayId, email, "Test", "User");
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer getCountryId(String isoCode) {
return jdbcTemplate.queryForObject("SELECT id FROM country WHERE iso_code = ?", Integer.class, isoCode);
}
private Integer createNode(String name, String externalId, Integer countryId) {
String sql = String.format(
"INSERT INTO node (name, external_mapping_id, country_id, is_deprecated, is_source, is_destination, is_intermediate, address) " +
"VALUES (?, ?, ?, %s, %s, %s, %s, 'Test Address')",
dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue());
executeRawSql(sql, name, externalId, countryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createMaterial(String name, String partNumber) {
String sql = String.format(
"INSERT INTO material (name, part_number, normalized_part_number, hs_code, is_deprecated) VALUES (?, ?, ?, '123456', %s)",
dialectProvider.getBooleanFalse());
executeRawSql(sql, name, partNumber, partNumber);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createPremise(Integer userId, Integer nodeId, Integer materialId, Integer countryId, PremiseState state) {
String sql = String.format(
"INSERT INTO premise (user_id, supplier_node_id, material_id, country_id, state, geo_lat, geo_lng, created_at, updated_at) " +
"VALUES (?, ?, ?, ?, ?, 51.5, 7.5, %s, %s)",
isMysql() ? "NOW()" : "GETDATE()",
isMysql() ? "NOW()" : "GETDATE()");
executeRawSql(sql, userId, nodeId, materialId, countryId, state.name());
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createDestination(Integer premiseId, Integer nodeId) {
String sql = "INSERT INTO premise_destination (premise_id, destination_node_id, annual_amount, country_id, geo_lat, geo_lng) " +
"VALUES (?, ?, 1000, ?, 51.5, 7.5)";
executeRawSql(sql, premiseId, nodeId, testCountryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createRoute(Integer destinationId, boolean isCheapest, boolean isFastest, boolean isSelected) {
String sql = String.format(
"INSERT INTO premise_route (premise_destination_id, is_cheapest, is_fastest, is_selected) VALUES (?, %s, %s, %s)",
isCheapest ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
isFastest ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
isSelected ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse());
executeRawSql(sql, destinationId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
}

View file

@ -0,0 +1,427 @@
package de.avatic.lcc.repositories.premise;
import de.avatic.lcc.dto.generic.RateType;
import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.db.premises.PremiseState;
import de.avatic.lcc.model.db.premises.route.RouteSection;
import de.avatic.lcc.repositories.AbstractRepositoryIntegrationTest;
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 RouteSectionRepository.
* <p>
* Tests critical functionality across both MySQL and MSSQL:
* - Boolean literals (TRUE/FALSE vs 1/0)
* - Enum handling (transport_type, rate_type)
* - Dynamic IN clauses
* - NULL handling for optional fields
* - BigDecimal to Double conversion
* <p>
* Run with:
* <pre>
* mvn test -Dspring.profiles.active=test,mysql -Dtest=RouteSectionRepositoryIntegrationTest
* mvn test -Dspring.profiles.active=test,mssql -Dtest=RouteSectionRepositoryIntegrationTest
* </pre>
*/
class RouteSectionRepositoryIntegrationTest extends AbstractRepositoryIntegrationTest {
@Autowired
private RouteSectionRepository routeSectionRepository;
private Integer testUserId;
private Integer testCountryId;
private Integer testNodeId;
private Integer testMaterialId;
private Integer testPremiseId;
private Integer testDestinationId;
private Integer testRouteId;
private Integer testFromNodeId;
private Integer testToNodeId;
@BeforeEach
void setupTestData() {
// Clean up in correct order (respecting foreign key constraints)
jdbcTemplate.update("DELETE FROM premise_route_section");
jdbcTemplate.update("DELETE FROM premise_route_node");
jdbcTemplate.update("DELETE FROM premise_route");
jdbcTemplate.update("DELETE FROM premise_destination");
jdbcTemplate.update("DELETE FROM premise");
jdbcTemplate.update("DELETE FROM material");
// Clean up node-referencing tables before deleting nodes
jdbcTemplate.update("DELETE FROM container_rate");
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 distance_matrix");
jdbcTemplate.update("DELETE FROM node");
jdbcTemplate.update("DELETE FROM sys_user");
// Create test user
testUserId = createUser("WD001", "test@example.com");
// Get test country
testCountryId = getCountryId("DE");
// Create test node
testNodeId = createNode("Test Node", "NODE-001", testCountryId);
// Create test material
testMaterialId = createMaterial("Test Material", "MAT-001");
// Create test premise
testPremiseId = createPremise(testUserId, testNodeId, testMaterialId, testCountryId, PremiseState.DRAFT);
// Create test destination
testDestinationId = createDestination(testPremiseId, testNodeId);
// Create test route
testRouteId = createRoute(testDestinationId);
// Create test route nodes
testFromNodeId = createRouteNode("From Node", testNodeId, testCountryId);
testToNodeId = createRouteNode("To Node", testNodeId, testCountryId);
// Create some test sections (respecting constraint: is_main_run must be TRUE unless transport_type is ROAD/POST_RUN)
createRouteSection(testRouteId, testFromNodeId, testToNodeId, 1, TransportType.SEA, RateType.CONTAINER, true, true, false);
createRouteSection(testRouteId, testFromNodeId, testToNodeId, 2, TransportType.ROAD, RateType.MATRIX, false, false, false); // ROAD allows is_main_run=FALSE
createRouteSection(testRouteId, testFromNodeId, testToNodeId, 3, TransportType.RAIL, RateType.NEAR_BY, false, true, true);
}
@Test
void testGetByRouteId() {
// When: Get sections by route ID
List<RouteSection> sections = routeSectionRepository.getByRouteId(testRouteId);
// Then: Should return all sections for route
assertNotNull(sections);
assertEquals(3, sections.size());
assertTrue(sections.stream().allMatch(s -> s.getRouteId().equals(testRouteId)));
}
@Test
void testGetByRouteIdEmpty() {
// When: Get sections for non-existent route
List<RouteSection> sections = routeSectionRepository.getByRouteId(99999);
// Then: Should return empty list
assertNotNull(sections);
assertTrue(sections.isEmpty());
}
@Test
void testGetById() {
// Given: Create route section
Integer sectionId = createRouteSection(testRouteId, testFromNodeId, testToNodeId, 10,
TransportType.SEA, RateType.CONTAINER, true, true, false);
// When: Get by ID
Optional<RouteSection> section = routeSectionRepository.getById(sectionId);
// Then: Should retrieve
assertTrue(section.isPresent());
assertEquals(sectionId, section.get().getId());
assertEquals(testRouteId, section.get().getRouteId());
assertEquals(10, section.get().getListPosition());
assertEquals(TransportType.SEA, section.get().getTransportType());
assertEquals(RateType.CONTAINER, section.get().getRateType());
assertTrue(section.get().getPreRun());
assertTrue(section.get().getMainRun());
assertFalse(section.get().getPostRun());
}
@Test
void testGetByIdNotFound() {
// When: Get non-existent ID
Optional<RouteSection> section = routeSectionRepository.getById(99999);
// Then: Should return empty
assertFalse(section.isPresent());
}
@Test
void testInsert() {
// Given: New route section
RouteSection newSection = new RouteSection();
newSection.setRouteId(testRouteId);
newSection.setFromRouteNodeId(testFromNodeId);
newSection.setToRouteNodeId(testToNodeId);
newSection.setListPosition(99);
newSection.setTransportType(TransportType.POST_RUN);
newSection.setRateType(RateType.MATRIX);
newSection.setPreRun(false);
newSection.setMainRun(true);
newSection.setPostRun(true);
newSection.setOutdated(false);
newSection.setDistance(250.5);
// When: Insert
Integer id = routeSectionRepository.insert(newSection);
// Then: Should be inserted
assertNotNull(id);
assertTrue(id > 0);
Optional<RouteSection> inserted = routeSectionRepository.getById(id);
assertTrue(inserted.isPresent());
assertEquals(99, inserted.get().getListPosition());
assertEquals(TransportType.POST_RUN, inserted.get().getTransportType());
assertEquals(RateType.MATRIX, inserted.get().getRateType());
assertFalse(inserted.get().getPreRun());
assertTrue(inserted.get().getMainRun());
assertTrue(inserted.get().getPostRun());
assertNotNull(inserted.get().getDistance());
assertEquals(250.5, inserted.get().getDistance(), 0.01);
}
@Test
void testInsertWithNullDistance() {
// Given: Route section with null distance
RouteSection newSection = new RouteSection();
newSection.setRouteId(testRouteId);
newSection.setFromRouteNodeId(testFromNodeId);
newSection.setToRouteNodeId(testToNodeId);
newSection.setListPosition(50);
newSection.setTransportType(TransportType.SEA);
newSection.setRateType(RateType.CONTAINER);
newSection.setPreRun(true);
newSection.setMainRun(true); // Must be TRUE for SEA (constraint)
newSection.setPostRun(false);
newSection.setOutdated(false);
newSection.setDistance(null); // nullable
// When: Insert
Integer id = routeSectionRepository.insert(newSection);
// Then: Should be inserted with null distance
assertNotNull(id);
Optional<RouteSection> inserted = routeSectionRepository.getById(id);
assertTrue(inserted.isPresent());
assertNull(inserted.get().getDistance());
}
@Test
void testDeleteAllById() {
// Given: Multiple route sections
List<RouteSection> sections = routeSectionRepository.getByRouteId(testRouteId);
assertEquals(3, sections.size());
// Get first two section IDs
List<Integer> idsToDelete = sections.stream()
.limit(2)
.map(RouteSection::getId)
.toList();
// When: Delete by IDs
routeSectionRepository.deleteAllById(idsToDelete);
// Then: Should delete specified sections
List<RouteSection> remaining = routeSectionRepository.getByRouteId(testRouteId);
assertEquals(1, remaining.size());
assertFalse(idsToDelete.contains(remaining.getFirst().getId()));
}
@Test
void testDeleteAllByIdEmpty() {
// When: Delete with empty list
routeSectionRepository.deleteAllById(List.of());
// Then: Should not throw error, sections remain
List<RouteSection> sections = routeSectionRepository.getByRouteId(testRouteId);
assertEquals(3, sections.size());
}
@Test
void testDeleteAllByIdNull() {
// When: Delete with null
routeSectionRepository.deleteAllById(null);
// Then: Should not throw error, sections remain
List<RouteSection> sections = routeSectionRepository.getByRouteId(testRouteId);
assertEquals(3, sections.size());
}
@Test
void testTransportTypeEnum() {
// Given: Sections with different transport types
RouteSection section1 = new RouteSection();
section1.setRouteId(testRouteId);
section1.setFromRouteNodeId(testFromNodeId);
section1.setToRouteNodeId(testToNodeId);
section1.setListPosition(101);
section1.setTransportType(TransportType.RAIL);
section1.setRateType(RateType.CONTAINER);
section1.setPreRun(false);
section1.setMainRun(true); // Must be TRUE for RAIL (constraint)
section1.setPostRun(false);
section1.setOutdated(false);
RouteSection section2 = new RouteSection();
section2.setRouteId(testRouteId);
section2.setFromRouteNodeId(testFromNodeId);
section2.setToRouteNodeId(testToNodeId);
section2.setListPosition(102);
section2.setTransportType(TransportType.POST_RUN); // POST_RUN allows is_main_run=FALSE
section2.setRateType(RateType.NEAR_BY);
section2.setPreRun(false);
section2.setMainRun(false);
section2.setPostRun(true);
section2.setOutdated(false);
// When: Insert
Integer id1 = routeSectionRepository.insert(section1);
Integer id2 = routeSectionRepository.insert(section2);
// Then: Enum values should be stored and retrieved correctly
Optional<RouteSection> retrieved1 = routeSectionRepository.getById(id1);
assertTrue(retrieved1.isPresent());
assertEquals(TransportType.RAIL, retrieved1.get().getTransportType());
assertEquals(RateType.CONTAINER, retrieved1.get().getRateType());
Optional<RouteSection> retrieved2 = routeSectionRepository.getById(id2);
assertTrue(retrieved2.isPresent());
assertEquals(TransportType.POST_RUN, retrieved2.get().getTransportType());
assertEquals(RateType.NEAR_BY, retrieved2.get().getRateType());
}
@Test
void testBooleanFlags() {
// Given: Section with different boolean flags (respecting constraint)
RouteSection section = new RouteSection();
section.setRouteId(testRouteId);
section.setFromRouteNodeId(testFromNodeId);
section.setToRouteNodeId(testToNodeId);
section.setListPosition(200);
section.setTransportType(TransportType.ROAD); // ROAD allows is_main_run=FALSE
section.setRateType(RateType.CONTAINER);
section.setPreRun(true);
section.setMainRun(false);
section.setPostRun(true);
section.setOutdated(true);
// When: Insert
Integer id = routeSectionRepository.insert(section);
// Then: Boolean flags should be stored correctly
Optional<RouteSection> retrieved = routeSectionRepository.getById(id);
assertTrue(retrieved.isPresent());
assertTrue(retrieved.get().getPreRun());
assertFalse(retrieved.get().getMainRun());
assertTrue(retrieved.get().getPostRun());
assertTrue(retrieved.get().getOutdated());
}
// ========== Helper Methods ==========
private Integer createUser(String workdayId, String email) {
String sql = String.format(
"INSERT INTO sys_user (workday_id, email, firstname, lastname, is_active) VALUES (?, ?, ?, ?, %s)",
dialectProvider.getBooleanTrue());
executeRawSql(sql, workdayId, email, "Test", "User");
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer getCountryId(String isoCode) {
return jdbcTemplate.queryForObject("SELECT id FROM country WHERE iso_code = ?", Integer.class, isoCode);
}
private Integer createNode(String name, String externalId, Integer countryId) {
String sql = String.format(
"INSERT INTO node (name, external_mapping_id, country_id, is_deprecated, is_source, is_destination, is_intermediate, address) " +
"VALUES (?, ?, ?, %s, %s, %s, %s, 'Test Address')",
dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue());
executeRawSql(sql, name, externalId, countryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createMaterial(String name, String partNumber) {
String sql = String.format(
"INSERT INTO material (name, part_number, normalized_part_number, hs_code, is_deprecated) VALUES (?, ?, ?, '123456', %s)",
dialectProvider.getBooleanFalse());
executeRawSql(sql, name, partNumber, partNumber);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createPremise(Integer userId, Integer nodeId, Integer materialId, Integer countryId, PremiseState state) {
String sql = String.format(
"INSERT INTO premise (user_id, supplier_node_id, material_id, country_id, state, geo_lat, geo_lng, created_at, updated_at) " +
"VALUES (?, ?, ?, ?, ?, 51.5, 7.5, %s, %s)",
isMysql() ? "NOW()" : "GETDATE()",
isMysql() ? "NOW()" : "GETDATE()");
executeRawSql(sql, userId, nodeId, materialId, countryId, state.name());
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createDestination(Integer premiseId, Integer nodeId) {
String sql = "INSERT INTO premise_destination (premise_id, destination_node_id, annual_amount, country_id, geo_lat, geo_lng) " +
"VALUES (?, ?, 1000, ?, 51.5, 7.5)";
executeRawSql(sql, premiseId, nodeId, testCountryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createRoute(Integer destinationId) {
String sql = String.format(
"INSERT INTO premise_route (premise_destination_id, is_cheapest, is_fastest, is_selected) VALUES (?, %s, %s, %s)",
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue());
executeRawSql(sql, destinationId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createRouteNode(String name, Integer nodeId, Integer countryId) {
String sql = String.format(
"INSERT INTO premise_route_node (name, address, geo_lat, geo_lng, is_destination, is_intermediate, is_source, " +
"node_id, country_id, is_outdated, external_mapping_id) " +
"VALUES (?, 'Address', 51.5, 7.5, %s, %s, %s, ?, ?, %s, 'EXT')",
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanTrue(),
dialectProvider.getBooleanFalse());
executeRawSql(sql, name, nodeId, countryId);
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
private Integer createRouteSection(Integer routeId, Integer fromNodeId, Integer toNodeId, int listPosition,
TransportType transportType, RateType rateType,
boolean isPreRun, boolean isMainRun, boolean isPostRun) {
String sql = String.format(
"INSERT INTO premise_route_section (premise_route_id, from_route_node_id, to_route_node_id, list_position, " +
"transport_type, rate_type, is_pre_run, is_main_run, is_post_run, is_outdated) " +
"VALUES (?, ?, ?, ?, ?, ?, %s, %s, %s, %s)",
isPreRun ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
isMainRun ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
isPostRun ? dialectProvider.getBooleanTrue() : dialectProvider.getBooleanFalse(),
dialectProvider.getBooleanFalse());
executeRawSql(sql, routeId, fromNodeId, toNodeId, listPosition, transportType.name(), rateType.name());
String selectSql = isMysql() ? "SELECT LAST_INSERT_ID()" : "SELECT CAST(@@IDENTITY AS INT)";
return jdbcTemplate.queryForObject(selectSql, Integer.class);
}
}