Вчера я весь день пробовал гуглить и использовать второй пилот, и мне ходим по кругу. Я не понимаю, почему grpc отличается при обработке запроса, представляющего собой двунаправленный поток, от унарного вызова и почему установка статуса контекста работает, когда он не находится в потоке...
Дополнительно В контексте серверная часть «launchApplicationService» имеет обработчики событий, которые сообщают о ходе выполнения, которые прослушивает вызов RPC, поэтому прослушивание обновлений не происходит напрямую в цикле while. Также стоит отметить, что сервер и внутренняя логика, похоже, реагируют соответствующим образом на отмену вызова через Postman, но мои модульные тесты не работают. Мне также нужно будет реализовать фактическую отмену из клиента, написанного на flutter/dart.
Некоторые советы или ссылки на ресурсы были бы полезны, чтобы помочь мне разобраться в этом. Спасибо, удачи!
Вот метод RPC:
Код: Выделить всё
public override async Task StartApplication(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context)
{
CancellationToken cancelToken = context.CancellationToken;
while (!cancelToken.IsCancellationRequested && await requestStream.MoveNext(cancelToken))
{
StartApplicationRequest request = requestStream.Current;
try
{
SubscribeHandlers();
startupStream = responseStream;
ConfigurationDto? configurationDto = configurationService.Get(request.ConfigurationId);
if (configurationDto == null)
{
context.Status = new Status(StatusCode.NotFound, $"{Languages.GetTranslatedText(ApplicationCoreConstants.ErrorApiStatusKeys.FailStartApplicationNotFound)} {request.ConfigurationId}.");
await WriteToStartupStream(running: false, progress: 0);
break;
}
configurationsHelper.DisableConfigIfInvalid(configurationDto);
if (!configurationDto.ConfigEnabled || configurationDto.Location == null)
{
context.Status = new Status(StatusCode.FailedPrecondition, $"{Languages.GetTranslatedText(SimulatorCoreConstants.ErrorApiStatusKeys.ConfigurationNotValid)} {request.ConfigurationId}.");
await WriteToStartupStream(running: false, progress: 0);
break;
}
await Task.Run(() => launchApplicationService.StartApplication(configurationDto, context.CancellationToken));
await WriteToStartupStream(running: true, progress: 100);
}
catch (Exception ex)
{
logger.Error(ex.Message, ex);
context.Status = new Status(StatusCode.Internal, $"{Languages.GetTranslatedText(ApplicationCoreConstants.ErrorApiStatusKeys.FailStartApplication)} Error: {ex.Message}");
await WriteToStartupStream(running: false);
break;
}
}
ClearHandlers();
if (cancelToken.IsCancellationRequested)
{
context.Status = new Status(StatusCode.Cancelled, "Test canceled");
CancelStartup();
await WriteToStartupStream(running: false);
}
}
Код: Выделить всё
[TestMethod]
[DataRow(StatusCode.OK, DisplayName = "Start Application success")]
[DataRow(StatusCode.Internal, DisplayName = "Start Application error")]
[DataRow(StatusCode.NotFound, DisplayName = "Start Application doesn't find configuration")]
[DataRow(StatusCode.FailedPrecondition, DisplayName = "Start Application config not enabled")]
[DataRow(StatusCode.Cancelled, DisplayName = "Start Application canceled")]
public async Task TestStartApplication(StatusCode expectedCode)
{
MockConfigurationService mockConfigurationService = new();
MockUserPreferencesService mockUserPreferencesService = new();
MockStartSimulationService mockStartApplicationService = new();
MockStopAllService mockStopAllService = new();
MockConfigurationsHelper mockConfigurationsHelper = new();
GrpcServicesImpl.grpcService grpcService = new GrpcServicesImpl.grpcService(
mockConfigurationService.Mock.Object, mockStartApplicationService.Mock.Object, mockStopAllService.Mock.Object, mockConfigurationsHelper.Mock.Object);
CancellationTokenSource cancelToken = new();
TestServerCallContext mockContext = new(cancelToken.Token);
if (expectedCode == StatusCode.NotFound)
{
mockConfigurationService.Mock.Setup(x => x.Get(It.IsAny())).Returns(value: null);
}
else if (expectedCode == StatusCode.Internal)
{
mockConfigurationService.Mock.Setup(x => x.Get(It.IsAny())).Throws(new Exception());
}
else if (expectedCode == StatusCode.FailedPrecondition)
{
ConfigurationDto configuration = TestHelpers.ConfigurationDto(1, configEnabled: false);
mockConfigurationService.Mock.Setup(x => x.Get(It.IsAny())).Returns(configuration);
}
// This else runs for an expected OK or CANCELLED response.
else
{
var config = TestHelpers.ConfigurationDto(1);
config.Brand!.BrandShortName = "company";
config.VehicleConfiguration ??= new();
config.VehicleConfiguration.Platform = ApplicationCoreConstants.Platforms.Vehicle;
mockConfigurationService.Mock.Setup(x => x.Get(It.IsAny())).Returns(config);
}
mockUserPreferencesService.Mock.Setup(x => x.Get(It.IsAny())).Returns(TestHelpers.UserPreferencesDto(1));
mockStartApplicationService.Mock.Setup(x => x.StartApplication(It.IsAny(), It.IsAny())).Callback(() => { });
Mock mockRequestStream = new();
Mock mockResponseStream = new();
Queue requestQueue = new([new StartApplicationRequest() { ConfigurationId = 1 }]);
mockRequestStream.Setup(x => x.MoveNext(It.IsAny())).ReturnsAsync(() => requestQueue.Count > 0);
mockRequestStream.Setup(x => x.Current).Returns(requestQueue.Peek());
mockResponseStream.Setup(x => x.WriteAsync(It.IsAny())).Returns(Task.CompletedTask);
Action startup = async () =>
{
Thread.Sleep(100);
await grpcService.StartApplication(mockRequestStream.Object, mockResponseStream.Object, mockContext);
};
await Task.Run(startup);
if (expectedCode == StatusCode.Cancelled)
{
cancelToken.Cancel();
mockResponseStream.Verify(x => x.WriteAsync(It.IsAny()), Times.Never());
}
else if (expectedCode == StatusCode.OK)
{
mockResponseStream.Verify(x => x.WriteAsync(It.IsAny()), Times.Once());
}
Assert.AreEqual(expectedCode, mockContext.Status.StatusCode);
}
Подробнее здесь: https://stackoverflow.com/questions/793 ... -that-retu