package com.ruoyi.system.service.impl;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.mockito.Mockito.mock;
|
|
import java.io.IOException;
|
import java.time.Duration;
|
import java.time.Instant;
|
import java.util.List;
|
import org.awaitility.Awaitility;
|
import org.junit.jupiter.api.Test;
|
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import com.ruoyi.device.bts.BtsDevelopmentSimulator;
|
import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore;
|
import com.ruoyi.system.service.ITTestService;
|
import com.ruoyi.system.service.ITestLiveService.LiveState;
|
|
/** 实时流客户端断连时应结束连接而不触发全局异常处理。 */
|
class TTestLiveServiceImplTest
|
{
|
@Test
|
void initialWriteFailureShouldCompleteStreamWithoutErrorDispatch()
|
{
|
RecordingEmitter emitter = new RecordingEmitter(0);
|
TTestLiveServiceImpl service = new TestableLiveService(emitter);
|
|
try
|
{
|
service.stream(4L);
|
}
|
finally
|
{
|
service.shutdown();
|
}
|
|
assertTrue(emitter.completed);
|
assertFalse(emitter.completedWithError);
|
}
|
|
@Test
|
void scheduledWriteFailureShouldCompleteStreamWithoutErrorDispatch()
|
{
|
RecordingEmitter emitter = new RecordingEmitter(6);
|
TTestLiveServiceImpl service = new TestableLiveService(emitter);
|
|
try
|
{
|
service.stream(4L);
|
Awaitility.await().atMost(Duration.ofSeconds(5)).until(() -> emitter.completed);
|
}
|
finally
|
{
|
service.shutdown();
|
}
|
|
assertFalse(emitter.completedWithError);
|
}
|
|
private static final class TestableLiveService extends TTestLiveServiceImpl
|
{
|
private final RecordingEmitter emitter;
|
|
private TestableLiveService(RecordingEmitter emitter)
|
{
|
super(mock(ITTestService.class), mock(DeviceDiscoveryCandidateStore.class),
|
mock(BtsDevelopmentSimulator.class));
|
this.emitter = emitter;
|
}
|
|
@Override
|
protected SseEmitter createEmitter()
|
{
|
return emitter;
|
}
|
|
@Override
|
public LiveState current(Long testId)
|
{
|
return new LiveState(testId, "RUNNING", "RECEIVING", Instant.now(), List.of(), List.of());
|
}
|
}
|
|
private static final class RecordingEmitter extends SseEmitter
|
{
|
private final int successfulWrites;
|
private int writes;
|
private volatile boolean completed;
|
private volatile boolean completedWithError;
|
|
private RecordingEmitter(int successfulWrites)
|
{
|
this.successfulWrites = successfulWrites;
|
}
|
|
@Override
|
public void send(SseEventBuilder builder) throws IOException
|
{
|
if (writes++ >= successfulWrites)
|
throw new AsyncRequestNotUsableException("client disconnected", new IOException("connection closed"));
|
}
|
|
@Override
|
public synchronized void complete()
|
{
|
completed = true;
|
}
|
|
@Override
|
public synchronized void completeWithError(Throwable error)
|
{
|
completedWithError = true;
|
}
|
}
|
}
|