fix: expand related routings through multi-level BOM

parent ded51bc0
package com.aps.macroplanner.data;
import com.aps.entity.RoutingHeader;
import com.aps.entity.Routingsupporting;
import com.aps.mapper.RoutingHeaderMapper;
import com.aps.mapper.RoutingsupportingMapper;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.springframework.test.util.ReflectionTestUtils;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
class MacroPlannerBomLoadingTest {
private MacroPlannerDataConverter converter;
private RoutingHeaderMapper headers;
private RoutingsupportingMapper inputs;
private Object context;
@BeforeEach
void setUp() throws Exception {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "bom-test"), RoutingHeader.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "bom-test"), Routingsupporting.class);
converter = new MacroPlannerDataConverter();
headers = mock(RoutingHeaderMapper.class);
inputs = mock(RoutingsupportingMapper.class);
ReflectionTestUtils.setField(converter, "routingHeaderMapper", headers);
ReflectionTestUtils.setField(converter, "routingsupportingMapper", inputs);
Class<?> contextType = Class.forName(MacroPlannerDataConverter.class.getName() + "$ConvertContext");
Constructor<?> constructor = contextType.getDeclaredConstructor();
constructor.setAccessible(true);
context = constructor.newInstance();
ReflectionTestUtils.setField(context, "routingHeaders", new ArrayList<>(Collections.singletonList(header(1, "A"))));
}
@Test
@Timeout(5)
void eachLevelLoadsOnlyNewRoutingIds() {
List<Set<Object>> queriedRoutes = new ArrayList<>();
when(inputs.selectList(any())).thenAnswer(call -> {
Set<Object> ids = values(call.getArgument(0));
ids.remove(0);
queriedRoutes.add(ids);
if (ids.equals(Collections.singleton(1))) return Collections.singletonList(input(1, "B"));
if (ids.equals(Collections.singleton(2))) return Collections.singletonList(input(2, "C"));
return Collections.emptyList();
});
when(headers.selectList(any())).thenAnswer(call -> values(call.getArgument(0)).contains("B")
? Collections.singletonList(header(2, "B")) : Collections.emptyList());
Set<String> materials = new HashSet<>(Collections.singleton("A"));
List<Integer> result = ReflectionTestUtils.invokeMethod(converter, "expandRoutingHeaders", context, materials);
assertEquals(Arrays.asList(1, 2), result);
assertEquals(Arrays.asList(Collections.singleton(1), Collections.singleton(2)), queriedRoutes);
assertEquals(new HashSet<>(Arrays.asList("A", "B", "C")), materials);
verify(headers, times(2)).selectList(any());
}
@Test
@Timeout(5)
void cyclicBomTerminatesWithoutDiscardingReachableHeaders() {
when(inputs.selectList(any())).thenAnswer(call -> values(call.getArgument(0)).contains(1)
? Collections.singletonList(input(1, "B")) : Collections.singletonList(input(2, "A")));
when(headers.selectList(any())).thenReturn(Collections.singletonList(header(2, "B")));
List<Integer> result = ReflectionTestUtils.invokeMethod(converter, "expandRoutingHeaders", context,
new HashSet<>(Collections.singleton("A")));
assertEquals(Arrays.asList(1, 2), result);
verify(inputs, times(2)).selectList(any());
verify(headers, times(1)).selectList(any());
}
@Test
void duplicateChildHeadersAreLoadedOnlyOnce() {
when(inputs.selectList(any())).thenReturn(Arrays.asList(input(1, "B"), input(1, "B")), Collections.emptyList());
when(headers.selectList(any())).thenReturn(Arrays.asList(header(2, "B"), header(2, "B"), header(3, "B")));
List<Integer> result = ReflectionTestUtils.invokeMethod(converter, "expandRoutingHeaders", context,
new HashSet<>(Collections.singleton("A")));
assertEquals(Arrays.asList(1, 2, 3), result);
verify(headers, times(1)).selectList(any());
verify(inputs, times(2)).selectList(any());
}
@Test
void nullInputMaterialDoesNotProduceUnboundedHeaderQuery() {
when(inputs.selectList(any())).thenReturn(Collections.singletonList(input(1, null)));
List<Integer> result = ReflectionTestUtils.invokeMethod(converter, "expandRoutingHeaders", context,
new HashSet<>(Collections.singleton("A")));
assertEquals(Collections.singletonList(1), result);
verifyNoInteractions(headers);
}
@Test
void oracleInGroupsAreBoundedDeduplicatedAndParenthesized() {
List<Integer> ids = IntStream.range(1, 2002).boxed().collect(Collectors.toList());
ids.add(1);
LambdaQueryWrapper<RoutingHeader> wrapper = MacroPlannerDataConverter.whereIn(RoutingHeader::getId, ids);
String predicate = wrapper.getSqlSegment();
assertEquals(2001, wrapper.getParamNameValuePairs().size());
assertEquals(3, predicate.split(" IN ", -1).length - 1);
assertEquals(2, predicate.split(" OR ", -1).length - 1);
java.util.regex.Matcher groups = java.util.regex.Pattern.compile(" IN [(]([^)]+)[)]").matcher(predicate);
List<Integer> sizes = new ArrayList<>();
while (groups.find()) sizes.add(groups.group(1).split(",").length);
assertEquals(Arrays.asList(1000, 1000, 1), sizes);
wrapper.eq(RoutingHeader::getIsDeleted, false);
assertTrue(wrapper.getSqlSegment().contains(") AND is_deleted ="));
assertTrue(MacroPlannerDataConverter.whereIn(RoutingHeader::getId, Collections.emptyList())
.getSqlSegment().contains("1 = 0"));
}
private Set<Object> values(LambdaQueryWrapper<?> wrapper) {
wrapper.getSqlSegment();
return new HashSet<>(wrapper.getParamNameValuePairs().values());
}
private RoutingHeader header(int id, String material) {
RoutingHeader header = new RoutingHeader();
header.setId(id);
header.setMaterialId(material);
return header;
}
private Routingsupporting input(int routingId, String material) {
Routingsupporting input = new Routingsupporting();
input.setRoutingHeaderId(routingId);
input.setMaterialId(material);
return input;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment