puzhibing
3 小时以前 976ff3fe092ee2f9515013dbfab77d25b2944b75
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package com.ruoyi.order.service.impl;
 
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.order.api.model.TChargingOrder;
import com.ruoyi.order.api.vo.SecurityDetectionVO;
import com.ruoyi.order.dto.PreChargeCheck;
import com.ruoyi.order.mapper.TChargingOrderMapper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;
 
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
 
/**
 * 启动等待链路单测:安全检测上报缓存自愈重建、启动超时订单兜底取消
 */
class TChargingOrderStartupTimeoutTest {
 
    private static final Integer GUN_ID = 1967;
    private static final String ORDER_CODE = "CD3720261014104949469";
 
    /**
     * 纯单测无 Spring 容器,MyBatis-Plus 的 lambda 列缓存需手工初始化
     */
    @BeforeAll
    static void initMybatisPlusTableInfo() {
        MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
        TableInfoHelper.initTableInfo(assistant, TChargingOrder.class);
    }
 
    private TChargingOrderServiceImpl service;
    private TChargingOrderMapper mapper;
    private RedisService redisService;
    private ChargingGunClient chargingGunClient;
 
    @BeforeEach
    void setUp() {
        service = spy(new TChargingOrderServiceImpl());
        mapper = mock(TChargingOrderMapper.class);
        redisService = mock(RedisService.class);
        chargingGunClient = mock(ChargingGunClient.class);
        ReflectionTestUtils.setField(service, "baseMapper", mapper);
        ReflectionTestUtils.setField(service, "redisService", redisService);
        ReflectionTestUtils.setField(service, "chargingGunClient", chargingGunClient);
    }
 
    /**
     * 缓存缺失时按待启动订单重建,保证设备安全检测上报仍能推进启动
     */
    @Test
    void securityDetectionRebuildsCacheWhenCacheMissing() {
        when(chargingGunClient.getChargingGunByCode(any())).thenReturn(R.ok(chargingGun()));
        when(redisService.getCacheObject("AQJC_" + GUN_ID)).thenReturn(null);
        when(mapper.selectOne(any())).thenReturn(pendingOrder());
 
        // 绝缘检测未通过(1=不安全)时仅更新缓存,不进入启动下发
        service.securityDetection(securityDetectionVO(1, 1, 1));
 
        ArgumentCaptor<PreChargeCheck> captor = ArgumentCaptor.forClass(PreChargeCheck.class);
        verify(redisService).setCacheObject(eq("AQJC_" + GUN_ID), captor.capture(), eq(24L), eq(TimeUnit.HOURS));
        PreChargeCheck saved = captor.getValue();
        assertEquals(Integer.valueOf(1), saved.getStartupSuccess(), "重建后应回到等待启动状态");
        assertEquals(Boolean.TRUE, saved.getElectronicLockLock());
        assertEquals(Boolean.FALSE, saved.getInsulationTesting());
        assertNotNull(saved.getAddTime(), "重建后应刷新等待基准时间");
    }
 
    /**
     * 缓存缺失且无待启动订单时忽略上报,不产生脏缓存
     */
    @Test
    void securityDetectionIgnoresWhenNoPendingOrder() {
        when(chargingGunClient.getChargingGunByCode(any())).thenReturn(R.ok(chargingGun()));
        when(redisService.getCacheObject("AQJC_" + GUN_ID)).thenReturn(null);
        when(mapper.selectOne(any())).thenReturn(null);
 
        service.securityDetection(securityDetectionVO(1, 0, 1));
 
        verify(redisService, never()).setCacheObject(anyString(), any(), anyLong(), any(TimeUnit.class));
    }
 
    /**
     * 等待安全检测超时的订单自动取消:退款、推进为已结束并清理缓存
     */
    @Test
    void startupTimeoutOrderIsCancelled() {
        when(mapper.selectList(any())).thenReturn(Collections.singletonList(pendingOrder()));
        when(redisService.getCacheObject("AQJC_" + GUN_ID)).thenReturn(null);
        doNothing().when(service).refund(ORDER_CODE);
 
        service.cancelStartupTimeoutOrders();
 
        ArgumentCaptor<TChargingOrder> captor = ArgumentCaptor.forClass(TChargingOrder.class);
        verify(mapper).updateById(captor.capture());
        TChargingOrder finished = captor.getValue();
        assertEquals(Integer.valueOf(5), finished.getStatus());
        assertEquals(Integer.valueOf(0), finished.getEndMode());
        assertNotNull(finished.getEndTime());
        verify(service).refund(ORDER_CODE);
        verify(redisService).deleteObject("AQJC_" + GUN_ID);
    }
 
    /**
     * 已下发启动指令的订单交给启动结果检测处理,兜底任务不得误取消
     */
    @Test
    void startupTimeoutOrderWithCommandSentIsSkipped() {
        when(mapper.selectList(any())).thenReturn(Collections.singletonList(pendingOrder()));
        PreChargeCheck check = new PreChargeCheck();
        check.setStartCommandSent(true);
        when(redisService.getCacheObject("AQJC_" + GUN_ID)).thenReturn(check);
 
        service.cancelStartupTimeoutOrders();
 
        verify(service, never()).refund(anyString());
        verify(mapper, never()).updateById(any());
        verify(redisService, never()).deleteObject(anyString());
    }
 
    /**
     * 初始化安全检测等待对象:预约订单以预约时间为等待基准
     */
    @Test
    void newPreChargeCheckUsesReservationTimeAsWaitBase() {
        LocalDateTime reservationTime = LocalDateTime.now().plusMinutes(5);
        TChargingOrder order = pendingOrder();
        order.setReservationTime(reservationTime);
 
        PreChargeCheck check = ReflectionTestUtils.invokeMethod(service, "newPreChargeCheck", order);
 
        assertNotNull(check);
        assertEquals(reservationTime, check.getAddTime());
        assertEquals(Integer.valueOf(1), check.getStartupSuccess());
        assertEquals(Boolean.FALSE, check.getStartCommandSent());
    }
 
    private static TChargingGun chargingGun() {
        TChargingGun gun = new TChargingGun();
        gun.setId(GUN_ID);
        return gun;
    }
 
    private static TChargingOrder pendingOrder() {
        TChargingOrder order = new TChargingOrder();
        order.setId(2110201381176229890L);
        order.setCode(ORDER_CODE);
        order.setChargingGunId(GUN_ID);
        order.setStatus(2);
        order.setRechargePaymentStatus(2);
        order.setDelFlag(false);
        order.setPayTime(LocalDateTime.now().minusMinutes(10));
        return order;
    }
 
    private static SecurityDetectionVO securityDetectionVO(Integer electronicLock, Integer insulation, Integer secureConnection) {
        SecurityDetectionVO vo = new SecurityDetectionVO();
        vo.setCharging_pile_code("8832010600495288");
        vo.setCharging_gun_code("02");
        vo.setElectronic_lock_lock(electronicLock);
        vo.setNsulation_detection(insulation);
        vo.setSecure_connection(secureConnection);
        return vo;
    }
}