| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- package com.ruoyi.system.service;
- import static org.junit.jupiter.api.Assertions.assertEquals;
- import static org.junit.jupiter.api.Assertions.assertNull;
- import static org.junit.jupiter.api.Assertions.assertThrows;
- import static org.junit.jupiter.api.Assertions.assertTrue;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.Mockito.doAnswer;
- import static org.mockito.Mockito.lenient;
- import static org.mockito.Mockito.never;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.verifyNoInteractions;
- import static org.mockito.Mockito.when;
- import java.math.BigDecimal;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Locale;
- import java.util.Set;
- import java.util.concurrent.atomic.AtomicLong;
- import org.junit.jupiter.api.AfterAll;
- import org.junit.jupiter.api.BeforeAll;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.junit.jupiter.api.extension.ExtendWith;
- import org.mockito.ArgumentCaptor;
- import org.mockito.InjectMocks;
- import org.mockito.Mock;
- import org.mockito.junit.jupiter.MockitoExtension;
- import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
- import org.springframework.beans.factory.support.DefaultListableBeanFactory;
- import org.springframework.context.support.StaticMessageSource;
- import org.springframework.test.util.ReflectionTestUtils;
- import com.ruoyi.common.exception.ServiceException;
- import com.ruoyi.common.utils.spring.SpringUtils;
- import com.ruoyi.system.domain.FoodSpecRelation;
- import com.ruoyi.system.domain.FoodSpecs;
- import com.ruoyi.system.domain.FoodSpecsValue;
- import com.ruoyi.system.domain.PosFenlei;
- import com.ruoyi.system.domain.PosFood;
- import com.ruoyi.system.dto.menu.StoreMenuCopyExecuteRequest;
- import com.ruoyi.system.dto.menu.StoreMenuCopyPreviewRequest;
- import com.ruoyi.system.dto.menu.StoreMenuCopyPreviewResponse;
- import com.ruoyi.system.dto.menu.StoreMenuCopyResult;
- import com.ruoyi.system.mapper.FoodSpecRelationMapper;
- import com.ruoyi.system.mapper.FoodSpecsMapper;
- import com.ruoyi.system.mapper.FoodSpecsValueMapper;
- import com.ruoyi.system.mapper.PosFenleiMapper;
- import com.ruoyi.system.mapper.PosFoodMapper;
- import com.ruoyi.system.service.impl.StoreMenuCopyServiceImpl;
- @ExtendWith(MockitoExtension.class)
- class StoreMenuCopyServiceTest
- {
- private static final Long USER_ID = 9L;
- private static final Long SOURCE_STORE_ID = 1L;
- private static final Long TARGET_STORE_ID = 2L;
- private static ConfigurableListableBeanFactory originalBeanFactory;
- @BeforeAll
- static void initializeMessages()
- {
- originalBeanFactory = (ConfigurableListableBeanFactory)
- ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
- DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- StaticMessageSource messageSource = new StaticMessageSource();
- messageSource.setUseCodeAsDefaultMessage(true);
- messageSource.addMessage("menu.copy.store.required", Locale.getDefault(), "门店不能为空");
- messageSource.addMessage("menu.copy.store.same", Locale.getDefault(), "源门店与目标门店不能相同");
- messageSource.addMessage("menu.copy.strategy.invalid", Locale.getDefault(), "冲突处理方式无效");
- messageSource.addMessage("merchant.store.access.denied", Locale.getDefault(), "无门店权限");
- beanFactory.registerSingleton("messageSource", messageSource);
- new SpringUtils().postProcessBeanFactory(beanFactory);
- }
- @AfterAll
- static void restoreMessages()
- {
- new SpringUtils().postProcessBeanFactory(originalBeanFactory);
- }
- @Mock
- private MerchantStoreAccessService accessService;
- @Mock
- private PosFenleiMapper categoryMapper;
- @Mock
- private PosFoodMapper foodMapper;
- @Mock
- private FoodSpecsMapper specsMapper;
- @Mock
- private FoodSpecsValueMapper specsValueMapper;
- @Mock
- private FoodSpecRelationMapper relationMapper;
- @InjectMocks
- private StoreMenuCopyServiceImpl service;
- @BeforeEach
- void setUp()
- {
- lenient().when(accessService.getAccessibleStoreIds(USER_ID))
- .thenReturn(Set.of(SOURCE_STORE_ID, TARGET_STORE_ID));
- lenient().when(categoryMapper.selectList(any())).thenReturn(CollectionsFixture.emptyCategories());
- lenient().when(foodMapper.selectList(any())).thenReturn(CollectionsFixture.emptyFoods());
- lenient().when(specsMapper.selectList(any())).thenReturn(CollectionsFixture.emptySpecs());
- lenient().when(specsValueMapper.selectList(any())).thenReturn(CollectionsFixture.emptySpecValues());
- lenient().when(relationMapper.selectList(any())).thenReturn(CollectionsFixture.emptyRelations());
- }
- @Test
- void preview_shouldReportAddedMergedIdenticalAndDifferentItems()
- {
- PosFenlei sourceDrinks = category(11L, SOURCE_STORE_ID, "饮品", "2");
- PosFenlei sourceMeals = category(12L, SOURCE_STORE_ID, "主食", "2");
- PosFenlei targetDrinks = category(21L, TARGET_STORE_ID, "饮品", "2");
- when(categoryMapper.selectList(any())).thenReturn(List.of(sourceDrinks, sourceMeals, targetDrinks));
- PosFood sourceCoffee = food(101L, SOURCE_STORE_ID, 11L, "咖啡", "2", "100");
- PosFood targetCoffee = food(201L, TARGET_STORE_ID, 21L, "咖啡", "2", "100");
- PosFood sourceTea = food(102L, SOURCE_STORE_ID, 11L, "红茶", "2", "50");
- PosFood targetTea = food(202L, TARGET_STORE_ID, 21L, "红茶", "2", "45");
- PosFood sourceBurger = food(103L, SOURCE_STORE_ID, 12L, "汉堡", "2", "80");
- when(foodMapper.selectList(any())).thenReturn(
- List.of(sourceCoffee, sourceTea, sourceBurger, targetCoffee, targetTea));
- StoreMenuCopyPreviewResponse response = service.preview(USER_ID, previewRequest());
- assertEquals(1, response.getAddedCategoryCount());
- assertEquals(1, response.getMergedCategoryCount());
- assertEquals(1, response.getAddedProductCount());
- assertEquals(1, response.getIdenticalProductCount());
- assertEquals(1, response.getDifferentProductCount());
- assertEquals("红茶", response.getDifferences().get(0).getName());
- assertEquals(List.of("price"), response.getDifferences().get(0).getFields().stream()
- .map(StoreMenuCopyPreviewResponse.FieldDifference::getField)
- .toList());
- assertEquals("45", response.getDifferences().get(0).getFields().get(0).getTargetValue());
- assertEquals("50", response.getDifferences().get(0).getFields().get(0).getSourceValue());
- }
- @Test
- void preview_shouldTreatSameNameInDifferentLanguageAsNewProduct()
- {
- when(categoryMapper.selectList(any())).thenReturn(List.of(
- category(11L, SOURCE_STORE_ID, "饮品", "2"),
- category(21L, TARGET_STORE_ID, "饮品", "2")));
- when(foodMapper.selectList(any())).thenReturn(List.of(
- food(101L, SOURCE_STORE_ID, 11L, "Cola", "1", "40"),
- food(201L, TARGET_STORE_ID, 21L, "Cola", "2", "40")));
- StoreMenuCopyPreviewResponse response = service.preview(USER_ID, previewRequest());
- assertEquals(1, response.getAddedProductCount());
- assertEquals(0, response.getIdenticalProductCount());
- assertEquals(0, response.getDifferentProductCount());
- }
- @Test
- void preview_shouldCountDuplicateSourceCategoriesOnlyOnce()
- {
- when(categoryMapper.selectList(any())).thenReturn(List.of(
- category(11L, SOURCE_STORE_ID, "饮品", "2"),
- category(12L, SOURCE_STORE_ID, "饮品", "2")));
- StoreMenuCopyPreviewResponse response = service.preview(USER_ID, previewRequest());
- assertEquals(1, response.getAddedCategoryCount());
- assertEquals(0, response.getMergedCategoryCount());
- }
- @Test
- void preview_shouldRejectMissingStoreBeforeReadingMenu()
- {
- StoreMenuCopyPreviewRequest request = new StoreMenuCopyPreviewRequest();
- request.setTargetStoreId(TARGET_STORE_ID);
- assertThrows(ServiceException.class, () -> service.preview(USER_ID, request));
- verifyNoInteractions(accessService, categoryMapper, foodMapper, specsMapper, specsValueMapper, relationMapper);
- }
- @Test
- void preview_shouldRejectSameStoreBeforeReadingMenu()
- {
- StoreMenuCopyPreviewRequest request = new StoreMenuCopyPreviewRequest();
- request.setSourceStoreId(SOURCE_STORE_ID);
- request.setTargetStoreId(SOURCE_STORE_ID);
- assertThrows(ServiceException.class, () -> service.preview(USER_ID, request));
- verifyNoInteractions(categoryMapper, foodMapper, specsMapper, specsValueMapper, relationMapper);
- }
- @Test
- void preview_shouldRejectWhenEitherStoreIsOutsideCurrentAccessScope()
- {
- when(accessService.getAccessibleStoreIds(USER_ID)).thenReturn(Set.of(SOURCE_STORE_ID));
- assertThrows(ServiceException.class, () -> service.preview(USER_ID, previewRequest()));
- verifyNoInteractions(categoryMapper, foodMapper, specsMapper, specsValueMapper, relationMapper);
- }
- @Test
- void execute_shouldCopyEmptyStoreMenuWithStatusesAndSpecs()
- {
- PosFenlei sourceCategory = category(11L, SOURCE_STORE_ID, "饮品", "2");
- PosFood sourceFood = food(101L, SOURCE_STORE_ID, 11L, "咖啡", "2", "100");
- sourceFood.setStackingUp("1");
- sourceFood.setToExamine("0");
- FoodSpecs sourceSpecs = specs(301L, SOURCE_STORE_ID, "大小", "2");
- FoodSpecsValue sourceValue = specValue(401L, 301L, "大杯", "10");
- FoodSpecRelation sourceRelation = relation(501L, 101L, 301L);
- when(categoryMapper.selectList(any())).thenReturn(List.of(sourceCategory));
- when(foodMapper.selectList(any())).thenReturn(List.of(sourceFood));
- when(specsMapper.selectList(any())).thenReturn(List.of(sourceSpecs));
- when(specsValueMapper.selectList(any())).thenReturn(List.of(sourceValue));
- when(relationMapper.selectList(any())).thenReturn(List.of(sourceRelation));
- AtomicLong ids = new AtomicLong(1000L);
- doAnswer(invocation -> assignId(invocation.getArgument(0), ids.incrementAndGet()))
- .when(categoryMapper).insert(any(PosFenlei.class));
- doAnswer(invocation -> assignId(invocation.getArgument(0), ids.incrementAndGet()))
- .when(specsMapper).insert(any(FoodSpecs.class));
- doAnswer(invocation -> assignId(invocation.getArgument(0), ids.incrementAndGet()))
- .when(specsValueMapper).insert(any(FoodSpecsValue.class));
- doAnswer(invocation -> assignId(invocation.getArgument(0), ids.incrementAndGet()))
- .when(foodMapper).insert(any(PosFood.class));
- when(relationMapper.insert(any(FoodSpecRelation.class))).thenReturn(1);
- StoreMenuCopyResult result = service.execute(USER_ID, executeRequest("KEEP_TARGET"));
- assertEquals(1, result.getAddedCategoryCount());
- assertEquals(1, result.getAddedProductCount());
- assertEquals(0, result.getSkippedProductCount());
- assertEquals(0, result.getOverwrittenProductCount());
- ArgumentCaptor<PosFood> foodCaptor = ArgumentCaptor.forClass(PosFood.class);
- verify(foodMapper).insert(foodCaptor.capture());
- PosFood insertedFood = foodCaptor.getValue();
- assertEquals(TARGET_STORE_ID, insertedFood.getMdid());
- assertEquals("1", insertedFood.getStackingUp());
- assertEquals("0", insertedFood.getToExamine());
- ArgumentCaptor<FoodSpecs> specsCaptor = ArgumentCaptor.forClass(FoodSpecs.class);
- verify(specsMapper).insert(specsCaptor.capture());
- assertEquals(TARGET_STORE_ID, specsCaptor.getValue().getMdId());
- ArgumentCaptor<FoodSpecsValue> valueCaptor = ArgumentCaptor.forClass(FoodSpecsValue.class);
- verify(specsValueMapper).insert(valueCaptor.capture());
- assertEquals(specsCaptor.getValue().getId(), valueCaptor.getValue().getParentId());
- ArgumentCaptor<FoodSpecRelation> relationCaptor = ArgumentCaptor.forClass(FoodSpecRelation.class);
- verify(relationMapper).insert(relationCaptor.capture());
- assertEquals(insertedFood.getId(), relationCaptor.getValue().getFoodId());
- assertEquals(specsCaptor.getValue().getId(), relationCaptor.getValue().getSpecsId());
- }
- @Test
- void execute_shouldKeepDifferentTargetProductByDefault()
- {
- stubOneDifferentProductWithoutSpecs();
- StoreMenuCopyResult result = service.execute(USER_ID, executeRequest(null));
- assertEquals(1, result.getSkippedProductCount());
- assertEquals(0, result.getOverwrittenProductCount());
- verify(foodMapper, never()).updateMenuCopyContent(any(PosFood.class));
- }
- @Test
- void execute_shouldOverwriteContentAndRelationsWithoutChangingTargetStatus()
- {
- PosFenlei sourceCategory = category(11L, SOURCE_STORE_ID, "饮品", "2");
- PosFenlei targetCategory = category(21L, TARGET_STORE_ID, "饮品", "2");
- PosFood sourceFood = food(101L, SOURCE_STORE_ID, 11L, "红茶", "2", "50");
- sourceFood.setImage("source.png");
- sourceFood.setStackingUp("0");
- sourceFood.setToExamine("1");
- PosFood targetFood = food(201L, TARGET_STORE_ID, 21L, "红茶", "2", "45");
- targetFood.setImage("target.png");
- targetFood.setStackingUp("1");
- targetFood.setToExamine("0");
- FoodSpecs sourceSpecs = specs(301L, SOURCE_STORE_ID, "大小", "2");
- FoodSpecs targetSpecs = specs(302L, TARGET_STORE_ID, "大小", "2");
- when(categoryMapper.selectList(any())).thenReturn(List.of(sourceCategory, targetCategory));
- when(foodMapper.selectList(any())).thenReturn(List.of(sourceFood, targetFood));
- when(specsMapper.selectList(any())).thenReturn(List.of(sourceSpecs, targetSpecs));
- when(relationMapper.selectList(any())).thenReturn(List.of(relation(501L, 101L, 301L)));
- when(foodMapper.updateMenuCopyContent(any(PosFood.class))).thenReturn(1);
- when(relationMapper.delete(any())).thenReturn(1);
- when(relationMapper.insert(any(FoodSpecRelation.class))).thenReturn(1);
- StoreMenuCopyResult result = service.execute(USER_ID, executeRequest("OVERWRITE_TARGET"));
- assertEquals(1, result.getOverwrittenProductCount());
- ArgumentCaptor<PosFood> updateCaptor = ArgumentCaptor.forClass(PosFood.class);
- verify(foodMapper).updateMenuCopyContent(updateCaptor.capture());
- PosFood update = updateCaptor.getValue();
- assertEquals(201L, update.getId());
- assertEquals(new BigDecimal("50"), update.getPrice());
- assertEquals("source.png", update.getImage());
- assertNull(update.getStackingUp());
- assertNull(update.getToExamine());
- verify(specsMapper, never()).insert(any(FoodSpecs.class));
- verify(specsValueMapper, never()).insert(any(FoodSpecsValue.class));
- ArgumentCaptor<FoodSpecRelation> relationCaptor = ArgumentCaptor.forClass(FoodSpecRelation.class);
- verify(relationMapper).insert(relationCaptor.capture());
- assertEquals(201L, relationCaptor.getValue().getFoodId());
- assertEquals(302L, relationCaptor.getValue().getSpecsId());
- }
- @Test
- void execute_shouldBeIdempotentWhenTargetAlreadyMatches()
- {
- PosFenlei sourceCategory = category(11L, SOURCE_STORE_ID, "饮品", "2");
- PosFenlei targetCategory = category(21L, TARGET_STORE_ID, "饮品", "2");
- PosFood sourceFood = food(101L, SOURCE_STORE_ID, 11L, "咖啡", "2", "100");
- PosFood targetFood = food(201L, TARGET_STORE_ID, 21L, "咖啡", "2", "100");
- when(categoryMapper.selectList(any())).thenReturn(List.of(sourceCategory, targetCategory));
- when(foodMapper.selectList(any())).thenReturn(List.of(sourceFood, targetFood));
- StoreMenuCopyResult result = service.execute(USER_ID, executeRequest("OVERWRITE_TARGET"));
- assertEquals(0, result.getAddedCategoryCount());
- assertEquals(0, result.getAddedProductCount());
- assertEquals(1, result.getSkippedProductCount());
- assertEquals(0, result.getOverwrittenProductCount());
- verify(categoryMapper, never()).insert(any(PosFenlei.class));
- verify(foodMapper, never()).insert(any(PosFood.class));
- verify(foodMapper, never()).updateMenuCopyContent(any(PosFood.class));
- }
- @Test
- void execute_shouldRejectUnknownConflictStrategyBeforeReadingMenu()
- {
- assertThrows(ServiceException.class,
- () -> service.execute(USER_ID, executeRequest("OVERWRITE_EVERYTHING")));
- verifyNoInteractions(categoryMapper, foodMapper, specsMapper, specsValueMapper, relationMapper);
- }
- private void stubOneDifferentProductWithoutSpecs()
- {
- when(categoryMapper.selectList(any())).thenReturn(List.of(
- category(11L, SOURCE_STORE_ID, "饮品", "2"),
- category(21L, TARGET_STORE_ID, "饮品", "2")));
- when(foodMapper.selectList(any())).thenReturn(List.of(
- food(101L, SOURCE_STORE_ID, 11L, "红茶", "2", "50"),
- food(201L, TARGET_STORE_ID, 21L, "红茶", "2", "45")));
- }
- private StoreMenuCopyPreviewRequest previewRequest()
- {
- StoreMenuCopyPreviewRequest request = new StoreMenuCopyPreviewRequest();
- request.setSourceStoreId(SOURCE_STORE_ID);
- request.setTargetStoreId(TARGET_STORE_ID);
- return request;
- }
- private StoreMenuCopyExecuteRequest executeRequest(String strategy)
- {
- StoreMenuCopyExecuteRequest request = new StoreMenuCopyExecuteRequest();
- request.setSourceStoreId(SOURCE_STORE_ID);
- request.setTargetStoreId(TARGET_STORE_ID);
- request.setConflictStrategy(strategy);
- return request;
- }
- private PosFenlei category(Long id, Long storeId, String name, String language)
- {
- PosFenlei category = new PosFenlei();
- category.setId(id);
- category.setMendid(storeId);
- category.setName(name);
- category.setLanguage(language);
- category.setImg("category.png");
- category.setSort("1");
- return category;
- }
- private PosFood food(Long id, Long storeId, Long categoryId, String name, String language, String price)
- {
- PosFood food = new PosFood();
- food.setId(id);
- food.setMdid(storeId);
- food.setFlId(categoryId);
- food.setName(name);
- food.setLanguage(language);
- food.setPrice(new BigDecimal(price));
- food.setImage("food.png");
- food.setIntroduce("介绍");
- food.setFoodSku("[]");
- food.setSort(1L);
- food.setRecommend(0L);
- food.setStackingUp("0");
- food.setToExamine("1");
- return food;
- }
- private FoodSpecs specs(Long id, Long storeId, String title, String language)
- {
- FoodSpecs specs = new FoodSpecs();
- specs.setId(id);
- specs.setMdId(storeId);
- specs.setTitle(title);
- specs.setLanguage(language);
- specs.setType("1");
- specs.setState("1");
- specs.setSort(1);
- specs.setIsOpen(true);
- specs.setIsDelete(false);
- return specs;
- }
- private FoodSpecsValue specValue(Long id, Long parentId, String name, String price)
- {
- FoodSpecsValue value = new FoodSpecsValue();
- value.setId(id);
- value.setParentId(parentId);
- value.setName(name);
- value.setPrice(new BigDecimal(price));
- value.setState("1");
- value.setIsOpen(true);
- return value;
- }
- private FoodSpecRelation relation(Long id, Long foodId, Long specsId)
- {
- FoodSpecRelation relation = new FoodSpecRelation();
- relation.setId(id);
- relation.setFoodId(foodId);
- relation.setSpecsId(specsId);
- return relation;
- }
- private int assignId(Object entity, long id)
- {
- if (entity instanceof PosFenlei category)
- {
- category.setId(id);
- }
- else if (entity instanceof PosFood food)
- {
- food.setId(id);
- }
- else if (entity instanceof FoodSpecs specs)
- {
- specs.setId(id);
- }
- else if (entity instanceof FoodSpecsValue value)
- {
- value.setId(id);
- }
- return 1;
- }
- private static final class CollectionsFixture
- {
- private CollectionsFixture()
- {
- }
- private static List<PosFenlei> emptyCategories()
- {
- return new ArrayList<>();
- }
- private static List<PosFood> emptyFoods()
- {
- return new ArrayList<>();
- }
- private static List<FoodSpecs> emptySpecs()
- {
- return new ArrayList<>();
- }
- private static List<FoodSpecsValue> emptySpecValues()
- {
- return new ArrayList<>();
- }
- private static List<FoodSpecRelation> emptyRelations()
- {
- return new ArrayList<>();
- }
- }
- }
|