diff --git a/src/LinkTracker.AiAgent.Application/Services/LinkUpdateProcessingService.cs b/src/LinkTracker.AiAgent.Application/Services/LinkUpdateProcessingService.cs index 2804f5a..4e88064 100644 --- a/src/LinkTracker.AiAgent.Application/Services/LinkUpdateProcessingService.cs +++ b/src/LinkTracker.AiAgent.Application/Services/LinkUpdateProcessingService.cs @@ -24,7 +24,7 @@ public async Task ProcessAsync(LinkUpdate update, IMessageAck ack, CancellationT if (filter.ShouldFilter(update)) { logger.LogDebug( - "Обновление отфильтровано. UpdateId={UpdateId}, Author={Author}", + "Update filtered out. UpdateId={UpdateId}, Author={Author}", update.Id, update.Author); return; } @@ -48,7 +48,7 @@ public async Task ProcessAsync(LinkUpdate update, IMessageAck ack, CancellationT } logger.LogDebug( - "Обновление добавлено в буфер. UpdateId={UpdateId}, Priority={Priority}", + "Update added to the buffer. UpdateId={UpdateId}, Priority={Priority}", update.Id, priority); } @@ -69,6 +69,6 @@ await publisher.PublishAsync( ct); } - logger.LogDebug("Служебный отчёт опубликован без обработки. UpdateId={UpdateId}", update.Id); + logger.LogDebug("System report published without processing. UpdateId={UpdateId}", update.Id); } } diff --git a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/ProcessedUpdatesKafkaPublisher.cs b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/ProcessedUpdatesKafkaPublisher.cs index 02e467b..8fd2fb4 100644 --- a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/ProcessedUpdatesKafkaPublisher.cs +++ b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/ProcessedUpdatesKafkaPublisher.cs @@ -24,7 +24,7 @@ public async Task PublishAsync(ProcessedLinkUpdate update, CancellationToken ct) var result = await producer.ProduceAsync(topic, message, ct); logger.LogInformation( - "Kafka: обновление опубликовано. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", + "Kafka: update published. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", result.Topic, result.Partition.Value, result.Offset.Value, update.Id); } } \ No newline at end of file diff --git a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumer.cs b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumer.cs index fae6ebe..97a0422 100644 --- a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumer.cs +++ b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumer.cs @@ -35,7 +35,7 @@ private async Task ConsumeLoopAsync(CancellationToken stoppingToken) consumer.Subscribe(topic); logger.LogInformation( - "Kafka consumer запущен. Topic={Topic}, GroupId={GroupId}", + "Kafka consumer started. Topic={Topic}, GroupId={GroupId}", topic, kafkaOptions.Value.GroupId); @@ -51,7 +51,7 @@ private async Task ConsumeLoopAsync(CancellationToken stoppingToken) } catch (ConsumeException ex) { - logger.LogWarning(ex, "Kafka consume завершился ошибкой. Пауза перед повтором."); + logger.LogWarning(ex, "Kafka consume failed, backing off before retry."); await Task.Delay(ConsumeErrorBackoff, stoppingToken); continue; } @@ -66,7 +66,7 @@ private async Task ConsumeLoopAsync(CancellationToken stoppingToken) } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { - logger.LogInformation("Kafka consumer остановлен."); + logger.LogInformation("Kafka consumer stopped."); } finally { @@ -108,7 +108,7 @@ private async Task ProcessMessageAsync(ConsumeResult result, Can logger.LogError( ex, - "Ошибка обработки Kafka сообщения. Offset не будет подтвержден. Topic={Topic}, Partition={Partition}, Offset={Offset}", + "Failed to process Kafka message. Offset will not be committed. Topic={Topic}, Partition={Partition}, Offset={Offset}", result.Topic, result.Partition.Value, result.Offset.Value); @@ -132,7 +132,7 @@ private void CommitCompleted() { logger.LogError( ex, - "Не удалось подтвердить Kafka offsets. Offsets={Offsets}", + "Failed to commit Kafka offsets. Offsets={Offsets}", string.Join(", ", offsets)); } } diff --git a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaDeadLetterPublisher.cs b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaDeadLetterPublisher.cs index 63ffce2..1805608 100644 --- a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaDeadLetterPublisher.cs +++ b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaDeadLetterPublisher.cs @@ -39,7 +39,7 @@ public async Task PublishAsync( ct); logger.LogWarning( - "Kafka сообщение отправлено в DLQ. Topic={Topic}, Partition={Partition}, Offset={Offset}, Reason={Reason}", + "Kafka message sent to DLQ. Topic={Topic}, Partition={Partition}, Offset={Offset}, Reason={Reason}", result.Topic, result.Partition.Value, result.Offset.Value, diff --git a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandler.cs b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandler.cs index 3e29f68..1e1a5b2 100644 --- a/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandler.cs +++ b/src/LinkTracker.AiAgent.Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandler.cs @@ -36,7 +36,7 @@ public async Task HandleAsync( { return await TryPublishToDeadLetterAsync( result, - $"Kafka сообщение не удалось десериализовать: {ex.Message}", + $"Failed to deserialize Kafka message: {ex.Message}", ex, ct); } @@ -45,7 +45,7 @@ public async Task HandleAsync( { return await TryPublishToDeadLetterAsync( result, - "Kafka сообщение десериализовалось в null.", + "Kafka message deserialized to null.", null, ct); } @@ -56,13 +56,13 @@ public async Task HandleAsync( { return await TryPublishToDeadLetterAsync( result, - "Исчерпаны попытки обработки Kafka сообщения.", + "Kafka message processing retries exhausted.", processingError, ct); } logger.LogInformation( - "Kafka сообщение обработано. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", + "Kafka message processed. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", result.Topic, result.Partition.Value, result.Offset.Value, @@ -94,7 +94,7 @@ public async Task HandleAsync( { logger.LogWarning( ex, - "Ошибка обработки Kafka сообщения. Будет повторная попытка. Attempt={Attempt}, MaxAttempts={MaxAttempts}, UpdateId={UpdateId}", + "Failed to process Kafka message, retrying. Attempt={Attempt}, MaxAttempts={MaxAttempts}, UpdateId={UpdateId}", attempt, attempts, update.Id); @@ -108,7 +108,7 @@ public async Task HandleAsync( { logger.LogWarning( ex, - "Ошибка обработки Kafka сообщения. Повторные попытки закончились. Attempts={Attempts}, UpdateId={UpdateId}", + "Failed to process Kafka message, no retries left. Attempts={Attempts}, UpdateId={UpdateId}", attempts, update.Id); @@ -143,7 +143,7 @@ private async Task TryPublishToDeadLetterAsync( logger.LogError( ex, - "Не удалось отправить Kafka сообщение в DLQ. Offset не будет подтвержден, сообщение будет переигрываться. Topic={Topic}, Partition={Partition}, Offset={Offset}, DeadLetterTopic={DeadLetterTopic}", + "Failed to send Kafka message to DLQ. Offset will not be committed, the message will be replayed. Topic={Topic}, Partition={Partition}, Offset={Offset}, DeadLetterTopic={DeadLetterTopic}", result.Topic, result.Partition.Value, result.Offset.Value, diff --git a/src/LinkTracker.AiAgent.Infrastructure/Clients/YandexAi/YandexAiHttpClient.cs b/src/LinkTracker.AiAgent.Infrastructure/Clients/YandexAi/YandexAiHttpClient.cs index 6eb4248..7bb97c8 100644 --- a/src/LinkTracker.AiAgent.Infrastructure/Clients/YandexAi/YandexAiHttpClient.cs +++ b/src/LinkTracker.AiAgent.Infrastructure/Clients/YandexAi/YandexAiHttpClient.cs @@ -17,7 +17,7 @@ internal sealed class YandexAiHttpClient( IAiAgentMetrics metrics, ILogger logger) : ILinkUpdateSummarizer { - private const string Instructions = "You are a concise summarizer. Summarize the given update in 2-3 sentences."; + private const string Instructions = "You are a concise summarizer. Summarize the given update in 2-3 sentences. Always answer in Russian."; public async Task SummarizeAsync(string text, CancellationToken ct) { @@ -34,7 +34,7 @@ public async Task SummarizeAsync(string text, CancellationToken ct) if (string.IsNullOrWhiteSpace(summary)) { - logger.LogWarning("Yandex AI вернул пустой ответ. Используется обрезка текста."); + logger.LogWarning("Yandex AI returned an empty response, falling back to text truncation."); metrics.IncrementSummarizationFallback("empty_response"); return FallbackTruncate(text, threshold); @@ -49,7 +49,7 @@ public async Task SummarizeAsync(string text, CancellationToken ct) } catch (Exception ex) { - logger.LogWarning(ex, "Yandex AI суммаризация завершилась ошибкой ({Type}). Используется обрезка текста.", ex.GetType().Name); + logger.LogWarning(ex, "Yandex AI summarization failed ({Type}), falling back to text truncation.", ex.GetType().Name); metrics.IncrementSummarizationFallback(ex.GetType().Name); return FallbackTruncate(text, threshold); diff --git a/src/LinkTracker.AiAgent.Infrastructure/Services/GroupingFlushJob.cs b/src/LinkTracker.AiAgent.Infrastructure/Services/GroupingFlushJob.cs index c3516a1..fd73193 100644 --- a/src/LinkTracker.AiAgent.Infrastructure/Services/GroupingFlushJob.cs +++ b/src/LinkTracker.AiAgent.Infrastructure/Services/GroupingFlushJob.cs @@ -33,8 +33,6 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } } - // Окна, не успевшие закрыться, публикуются на остановке: иначе они умрут вместе - // с процессом, а сообщения будут переигрываться с последнего подтвержденного оффсета. public override async Task StopAsync(CancellationToken cancellationToken) { await base.StopAsync(cancellationToken); @@ -78,7 +76,7 @@ private async Task TryPublishAsync( await publisher.PublishAsync(group, ct); logger.LogInformation( - "Группа опубликована. ChatId={ChatId}, UpdateId={UpdateId}, Priority={Priority}", + "Group published. ChatId={ChatId}, UpdateId={UpdateId}, Priority={Priority}", bucket.ChatId, group.Id, group.Priority); } } @@ -86,13 +84,12 @@ private async Task TryPublishAsync( { logger.LogError( ex, - "Ошибка публикации сгруппированного обновления, окно возвращено в буфер. ChatId={ChatId}", + "Failed to publish the grouped update, the window was returned to the buffer. ChatId={ChatId}", bucket.ChatId); return false; } - // Оффсеты исходных сообщений подтверждаются только после успешной публикации. foreach (var buffered in bucket.Updates) { buffered.Ack.Release(); diff --git a/src/LinkTracker.Bot.Application/Dialogs/Implementations/Track/Nodes/AskTagsNode.cs b/src/LinkTracker.Bot.Application/Dialogs/Implementations/Track/Nodes/AskTagsNode.cs index 2191295..97fc3b3 100644 --- a/src/LinkTracker.Bot.Application/Dialogs/Implementations/Track/Nodes/AskTagsNode.cs +++ b/src/LinkTracker.Bot.Application/Dialogs/Implementations/Track/Nodes/AskTagsNode.cs @@ -76,7 +76,7 @@ private static string BuildAcceptedTagsReply( private static string BuildConfirmText(DialogContext ctx) { - var url = ctx.GetPendingUrl() ?? "(unknown)"; + var url = ctx.GetPendingUrl() ?? "(неизвестно)"; var tagsCsv = ctx.GetTagsCsv(); var tagsText = string.IsNullOrWhiteSpace(tagsCsv) ? "—" : string.Join(", ", tagsCsv.Split(',')); diff --git a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateDeadLetterPublisher.cs b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateDeadLetterPublisher.cs index d886a5d..ea7bb07 100644 --- a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateDeadLetterPublisher.cs +++ b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateDeadLetterPublisher.cs @@ -39,7 +39,7 @@ public async Task PublishAsync( ct); logger.LogWarning( - "Kafka сообщение отправлено в DLQ. Topic={Topic}, Partition={Partition}, Offset={Offset}, Reason={Reason}", + "Kafka message sent to DLQ. Topic={Topic}, Partition={Partition}, Offset={Offset}, Reason={Reason}", result.Topic, result.Partition.Value, result.Offset.Value, diff --git a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParser.cs b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParser.cs index 74089fa..39d4132 100644 --- a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParser.cs +++ b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParser.cs @@ -10,19 +10,19 @@ public bool TryValidate(LinkUpdate? update, out string? error) if (update is null) { - error = "Сообщение не удалось десериализовать."; + error = "Failed to deserialize the message."; return false; } if (update.Id < 0) { - error = "Поле 'id' не может быть отрицательным."; + error = "Field 'id' must not be negative."; return false; } if (update.Url is null || !update.Url.IsAbsoluteUri) { - error = "Поле 'url' должно содержать абсолютный URI."; + error = "Field 'url' must contain an absolute URI."; return false; } @@ -31,7 +31,7 @@ public bool TryValidate(LinkUpdate? update, out string? error) return true; } - error = "Поле 'tgChatIds' должно содержать хотя бы один chat id."; + error = "Field 'tgChatIds' must contain at least one chat id."; return false; } } \ No newline at end of file diff --git a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumer.cs b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumer.cs index 407207b..dbe9ea6 100644 --- a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumer.cs +++ b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumer.cs @@ -35,7 +35,7 @@ private async Task ConsumeLoopAsync(CancellationToken stoppingToken) consumer.Subscribe(topic); logger.LogInformation( - "Kafka consumer запущен. Topic={Topic}, GroupId={GroupId}", + "Kafka consumer started. Topic={Topic}, GroupId={GroupId}", topic, kafkaOptions.Value.GroupId); @@ -51,7 +51,7 @@ private async Task ConsumeLoopAsync(CancellationToken stoppingToken) } catch (ConsumeException ex) { - logger.LogWarning(ex, "Kafka consume завершился ошибкой. Пауза перед повтором."); + logger.LogWarning(ex, "Kafka consume failed, backing off before retry."); await Task.Delay(ConsumeErrorBackoff, stoppingToken); continue; } @@ -66,7 +66,7 @@ private async Task ConsumeLoopAsync(CancellationToken stoppingToken) } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { - logger.LogInformation("Kafka consumer остановлен."); + logger.LogInformation("Kafka consumer stopped."); } finally { @@ -106,7 +106,7 @@ private async Task ProcessMessageAsync(ConsumeResult result, Can logger.LogError( ex, - "Ошибка обработки Kafka сообщения. Offset не будет подтвержден. Topic={Topic}, Partition={Partition}, Offset={Offset}", + "Failed to process Kafka message. Offset will not be committed. Topic={Topic}, Partition={Partition}, Offset={Offset}", result.Topic, result.Partition.Value, result.Offset.Value); @@ -124,7 +124,7 @@ private bool TryCommit(ConsumeResult result) { logger.LogError( ex, - "Не удалось подтвердить Kafka offset. Topic={Topic}, Partition={Partition}, Offset={Offset}", + "Failed to commit Kafka offset. Topic={Topic}, Partition={Partition}, Offset={Offset}", result.Topic, result.Partition.Value, result.Offset.Value); diff --git a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandler.cs b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandler.cs index f25d997..715e882 100644 --- a/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandler.cs +++ b/src/LinkTracker.Bot.Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandler.cs @@ -35,7 +35,7 @@ public async Task HandleAsync(ConsumeResult result, Cancel { return await TryPublishToDeadLetterAsync( result, - $"Kafka сообщение не удалось десериализовать: {ex.Message}", + $"Failed to deserialize Kafka message: {ex.Message}", ex, ct); } @@ -44,7 +44,7 @@ public async Task HandleAsync(ConsumeResult result, Cancel { return await TryPublishToDeadLetterAsync( result, - error ?? "Kafka сообщение не прошло валидацию.", + error ?? "Kafka message failed validation.", null, ct); } @@ -55,13 +55,13 @@ public async Task HandleAsync(ConsumeResult result, Cancel { return await TryPublishToDeadLetterAsync( result, - "Исчерпаны попытки обработки Kafka сообщения.", + "Kafka message processing retries exhausted.", notificationError, ct); } logger.LogInformation( - "Kafka сообщение обработано. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", + "Kafka message processed. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", result.Topic, result.Partition.Value, result.Offset.Value, @@ -90,7 +90,7 @@ public async Task HandleAsync(ConsumeResult result, Cancel { logger.LogWarning( ex, - "Ошибка обработки Kafka сообщения. Будет повторная попытка. Attempt={Attempt}, MaxAttempts={MaxAttempts}, UpdateId={UpdateId}", + "Failed to process Kafka message, retrying. Attempt={Attempt}, MaxAttempts={MaxAttempts}, UpdateId={UpdateId}", attempt, attempts, update.Id); @@ -104,7 +104,7 @@ public async Task HandleAsync(ConsumeResult result, Cancel { logger.LogWarning( ex, - "Ошибка обработки Kafka сообщения. Повторные попытки закончились. Attempts={Attempts}, UpdateId={UpdateId}", + "Failed to process Kafka message, no retries left. Attempts={Attempts}, UpdateId={UpdateId}", attempts, update.Id); @@ -140,7 +140,7 @@ private async Task TryPublishToDeadLetterAsync( logger.LogError( ex, - "Не удалось отправить Kafka сообщение в DLQ. Offset не будет подтвержден, сообщение будет переигрываться. Topic={Topic}, Partition={Partition}, Offset={Offset}, DeadLetterTopic={DeadLetterTopic}", + "Failed to send Kafka message to DLQ. Offset will not be committed, the message will be replayed. Topic={Topic}, Partition={Partition}, Offset={Offset}, DeadLetterTopic={DeadLetterTopic}", result.Topic, result.Partition.Value, result.Offset.Value, diff --git a/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperGrpcClient.cs b/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperGrpcClient.cs index f85a4aa..68e1497 100644 --- a/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperGrpcClient.cs +++ b/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperGrpcClient.cs @@ -23,16 +23,16 @@ public async Task RegisterChatAsync(long chatId, CancellationToken ct = default) try { - logger.LogInformation("Клиент gRPC Scrapper: вызов RegisterChat. ChatId={ChatId}", chatId); + logger.LogInformation("Scrapper gRPC client: RegisterChat called. ChatId={ChatId}", chatId); try { await client.RegisterChatAsync(new ChatRequest { ChatId = chatId }, cancellationToken: ct); - logger.LogInformation("Клиент gRPC Scrapper: RegisterChat успешно выполнен. ChatId={ChatId}", chatId); + logger.LogInformation("Scrapper gRPC client: RegisterChat succeeded. ChatId={ChatId}", chatId); } catch (RpcException ex) { - logger.LogWarning(ex, "Клиент gRPC Scrapper: RegisterChat завершился с ошибкой. ChatId={ChatId}", chatId); + logger.LogWarning(ex, "Scrapper gRPC client: RegisterChat failed. ChatId={ChatId}", chatId); throw ToClientException(ex); } } @@ -48,16 +48,16 @@ public async Task DeleteChatAsync(long chatId, CancellationToken ct = default) try { - logger.LogInformation("Клиент gRPC Scrapper: вызов DeleteChat. ChatId={ChatId}", chatId); + logger.LogInformation("Scrapper gRPC client: DeleteChat called. ChatId={ChatId}", chatId); try { await client.DeleteChatAsync(new ChatRequest { ChatId = chatId }, cancellationToken: ct); - logger.LogInformation("Клиент gRPC Scrapper: DeleteChat успешно выполнен. ChatId={ChatId}", chatId); + logger.LogInformation("Scrapper gRPC client: DeleteChat succeeded. ChatId={ChatId}", chatId); } catch (RpcException ex) { - logger.LogWarning(ex, "Клиент gRPC Scrapper: DeleteChat завершился с ошибкой. ChatId={ChatId}", chatId); + logger.LogWarning(ex, "Scrapper gRPC client: DeleteChat failed. ChatId={ChatId}", chatId); throw ToClientException(ex); } } @@ -73,7 +73,7 @@ public async Task GetLinksAsync(long chatId, CancellationToke try { - logger.LogInformation("Клиент gRPC Scrapper: вызов GetLinks. ChatId={ChatId}", chatId); + logger.LogInformation("Scrapper gRPC client: GetLinks called. ChatId={ChatId}", chatId); try { @@ -82,7 +82,7 @@ public async Task GetLinksAsync(long chatId, CancellationToke cancellationToken: ct); logger.LogInformation( - "Клиент gRPC Scrapper: GetLinks успешно выполнен. ChatId={ChatId}, КоличествоСсылок={Count}", + "Scrapper gRPC client: GetLinks succeeded. ChatId={ChatId}, LinksCount={Count}", chatId, response.Size); @@ -90,7 +90,7 @@ public async Task GetLinksAsync(long chatId, CancellationToke } catch (RpcException ex) { - logger.LogWarning(ex, "Клиент gRPC Scrapper: GetLinks завершился с ошибкой. ChatId={ChatId}", chatId); + logger.LogWarning(ex, "Scrapper gRPC client: GetLinks failed. ChatId={ChatId}", chatId); throw ToClientException(ex); } } @@ -111,7 +111,7 @@ public async Task AddLinkAsync( try { logger.LogInformation( - "Клиент gRPC Scrapper: вызов AddLink. ChatId={ChatId}, Ссылка={Link}, КоличествоТегов={TagsCount}", + "Scrapper gRPC client: AddLink called. ChatId={ChatId}, Link={Link}, TagsCount={TagsCount}", chatId, link, tags.Count); @@ -125,7 +125,7 @@ public async Task AddLinkAsync( var response = await client.AddLinkAsync(request, cancellationToken: ct); logger.LogInformation( - "Клиент gRPC Scrapper: AddLink успешно выполнен. ChatId={ChatId}, LinkId={LinkId}", + "Scrapper gRPC client: AddLink succeeded. ChatId={ChatId}, LinkId={LinkId}", chatId, response.Id); @@ -133,7 +133,7 @@ public async Task AddLinkAsync( } catch (RpcException ex) { - logger.LogWarning(ex, "Клиент gRPC Scrapper: AddLink завершился с ошибкой. ChatId={ChatId}, Ссылка={Link}", chatId, link); + logger.LogWarning(ex, "Scrapper gRPC client: AddLink failed. ChatId={ChatId}, Link={Link}", chatId, link); throw ToClientException(ex); } } @@ -149,7 +149,7 @@ public async Task RemoveLinkAsync(long chatId, Uri link, Cancellat try { - logger.LogInformation("Клиент gRPC Scrapper: вызов RemoveLink. ChatId={ChatId}, Ссылка={Link}", chatId, link); + logger.LogInformation("Scrapper gRPC client: RemoveLink called. ChatId={ChatId}, Link={Link}", chatId, link); try { @@ -158,7 +158,7 @@ public async Task RemoveLinkAsync(long chatId, Uri link, Cancellat cancellationToken: ct); logger.LogInformation( - "Клиент gRPC Scrapper: RemoveLink успешно выполнен. ChatId={ChatId}, LinkId={LinkId}", + "Scrapper gRPC client: RemoveLink succeeded. ChatId={ChatId}, LinkId={LinkId}", chatId, response.Id); @@ -166,7 +166,7 @@ public async Task RemoveLinkAsync(long chatId, Uri link, Cancellat } catch (RpcException ex) { - logger.LogWarning(ex, "Клиент gRPC Scrapper: RemoveLink завершился с ошибкой. ChatId={ChatId}, Ссылка={Link}", chatId, link); + logger.LogWarning(ex, "Scrapper gRPC client: RemoveLink failed. ChatId={ChatId}, Link={Link}", chatId, link); throw ToClientException(ex); } } diff --git a/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperHttpClient.cs b/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperHttpClient.cs index e558b37..1852e52 100644 --- a/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperHttpClient.cs +++ b/src/LinkTracker.Bot.Infrastructure/Clients/Scrapper/ScrapperHttpClient.cs @@ -96,7 +96,7 @@ public Task AddLinkAsync( var body = await response.Content.ReadFromJsonAsync(token); - return body ?? throw new InvalidOperationException("Scrapper вернул пустое тело ответа."); + return body ?? throw new InvalidOperationException("Scrapper returned an empty response body."); }, ct); } @@ -124,7 +124,7 @@ public Task RemoveLinkAsync(long chatId, Uri link, CancellationTok var body = await response.Content.ReadFromJsonAsync(token); - return body ?? throw new InvalidOperationException("Scrapper вернул пустое тело ответа."); + return body ?? throw new InvalidOperationException("Scrapper returned an empty response body."); }, ct); } @@ -202,7 +202,6 @@ private static async Task EnsureSuccessStatusCodeAsync(HttpResponseMessage respo } catch { - // ignored } var message = error?.Description ?? $"Scrapper request failed with status code {(int)response.StatusCode}."; @@ -223,7 +222,7 @@ private static ScrapperClientException CreateServiceUnavailableException(Excepti { return new ScrapperClientException( HttpStatusCode.ServiceUnavailable, - "Scrapper сейчас недоступен.", + "Scrapper is currently unavailable.", innerException: innerException) { FallbackCode = ScrapperErrorCodes.ScrapperServiceUnavailable }; } diff --git a/src/LinkTracker.Bot.Infrastructure/Storage/Valkey/ValkeyDialogStateStore.cs b/src/LinkTracker.Bot.Infrastructure/Storage/Valkey/ValkeyDialogStateStore.cs index 795f24e..c0941f3 100644 --- a/src/LinkTracker.Bot.Infrastructure/Storage/Valkey/ValkeyDialogStateStore.cs +++ b/src/LinkTracker.Bot.Infrastructure/Storage/Valkey/ValkeyDialogStateStore.cs @@ -39,7 +39,7 @@ public async Task GetOrCreateAsync(long chatId, CancellationToken { logger.LogError( ex, - "Не удалось прочитать состояние диалога. Диалог начнётся заново. ChatId={ChatId}, Key={Key}", + "Failed to read dialog state, the dialog will restart. ChatId={ChatId}, Key={Key}", chatId, key); @@ -65,7 +65,7 @@ await connection.GetDatabase().StringSetAsync( { logger.LogError( ex, - "Не удалось сохранить состояние диалога. ChatId={ChatId}, Key={Key}", + "Failed to save dialog state. ChatId={ChatId}, Key={Key}", ctx.ChatId, key); } @@ -85,7 +85,7 @@ public async Task ResetAsync(long chatId, CancellationToken ct) { logger.LogError( ex, - "Не удалось сбросить состояние диалога. ChatId={ChatId}, Key={Key}", + "Failed to reset dialog state. ChatId={ChatId}, Key={Key}", chatId, key); } diff --git a/src/LinkTracker.Bot.Presentation/BotApi/Endpoints/LinkUpdateEndpoints.cs b/src/LinkTracker.Bot.Presentation/BotApi/Endpoints/LinkUpdateEndpoints.cs index f027fa8..f86187e 100644 --- a/src/LinkTracker.Bot.Presentation/BotApi/Endpoints/LinkUpdateEndpoints.cs +++ b/src/LinkTracker.Bot.Presentation/BotApi/Endpoints/LinkUpdateEndpoints.cs @@ -16,8 +16,8 @@ public static RouteGroupBuilder MapBotApi(this IEndpointRouteBuilder builder) app.MapPost("/updates", HandleUpdateAsync) .WithName("HandleUpdate") - .WithSummary("Отправить обновление") - .WithDescription("Получает обновление ссылки от Scrapper и отправляет его в телеграм") + .WithSummary("Send update") + .WithDescription("Receives a link update from Scrapper and sends it to Telegram.") .Accepts("application/json") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest); @@ -32,12 +32,12 @@ private static async Task HandleUpdateAsync( { if (update is null) { - return Results.BadRequest(new ApiErrorResponse { Description = "Тело запроса обязательно.", Code = "invalid_request" }); + return Results.BadRequest(new ApiErrorResponse { Description = "Request body is required.", Code = "invalid_request" }); } if (update.TgChatIds.Count == 0) { - return Results.BadRequest(new ApiErrorResponse { Description = "Поле 'tgChatIds' должно содержать хотя бы один chat id.", Code = "invalid_request" }); + return Results.BadRequest(new ApiErrorResponse { Description = "Field 'tgChatIds' must contain at least one chat id.", Code = "invalid_request" }); } await notifier.NotifyAsync(update, ct); diff --git a/src/LinkTracker.Bot.Presentation/Grpc/BotUpdatesGrpcService.cs b/src/LinkTracker.Bot.Presentation/Grpc/BotUpdatesGrpcService.cs index 91715fb..acf9f55 100644 --- a/src/LinkTracker.Bot.Presentation/Grpc/BotUpdatesGrpcService.cs +++ b/src/LinkTracker.Bot.Presentation/Grpc/BotUpdatesGrpcService.cs @@ -15,7 +15,7 @@ public sealed class BotUpdatesGrpcService( public override async Task SendUpdate(LinkUpdateGrpcRequest request, ServerCallContext context) { logger.LogInformation( - "gRPC вызов SendUpdate. UpdateId={UpdateId}, Ссылка={Url}, КоличествоЧатов={ChatsCount}", + "gRPC SendUpdate called. UpdateId={UpdateId}, Url={Url}, ChatsCount={ChatsCount}", request.Id, request.Url, request.TgChatIds.Count); @@ -23,11 +23,11 @@ public override async Task SendUpdate(LinkUpdateGrpcRequest request, Serv if (request.TgChatIds.Count == 0) { logger.LogWarning( - "gRPC SendUpdate отклонён: список tg_chat_ids пуст. UpdateId={UpdateId}", + "gRPC SendUpdate rejected: tg_chat_ids is empty. UpdateId={UpdateId}", request.Id); throw new RpcException( - new Status(StatusCode.InvalidArgument, "Список tg_chat_ids не должен быть пустым")); + new Status(StatusCode.InvalidArgument, "tg_chat_ids must not be empty")); } try @@ -37,7 +37,7 @@ await notifier.NotifyAsync( context.CancellationToken); logger.LogInformation( - "gRPC SendUpdate успешно выполнен. UpdateId={UpdateId}, КоличествоЧатов={ChatsCount}", + "gRPC SendUpdate succeeded. UpdateId={UpdateId}, ChatsCount={ChatsCount}", request.Id, request.TgChatIds.Count); @@ -47,12 +47,12 @@ await notifier.NotifyAsync( { logger.LogWarning( ex, - "gRPC SendUpdate отклонён: некорректный формат ссылки. UpdateId={UpdateId}, Url={Url}", + "gRPC SendUpdate rejected: invalid link format. UpdateId={UpdateId}, Url={Url}", request.Id, request.Url); throw new RpcException( - new Status(StatusCode.InvalidArgument, "Некорректный формат ссылки")); + new Status(StatusCode.InvalidArgument, "Invalid link format")); } } } \ No newline at end of file diff --git a/src/LinkTracker.Bot.Presentation/Telegram/Hosting/TelegramPollingHostedService.cs b/src/LinkTracker.Bot.Presentation/Telegram/Hosting/TelegramPollingHostedService.cs index 7e2059a..cdc3056 100644 --- a/src/LinkTracker.Bot.Presentation/Telegram/Hosting/TelegramPollingHostedService.cs +++ b/src/LinkTracker.Bot.Presentation/Telegram/Hosting/TelegramPollingHostedService.cs @@ -17,7 +17,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) var receiverOptions = new ReceiverOptions { AllowedUpdates = Array.Empty() }; var me = await bot.GetMe(stoppingToken); - logger.LogInformation("Бот {Username} успешно запущен, начат приём обновлений.", me.Username); + logger.LogInformation("Bot {Username} started, receiving updates.", me.Username); try { @@ -25,7 +25,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { - logger.LogInformation("Polling остановлен."); + logger.LogInformation("Polling stopped."); } } } \ No newline at end of file diff --git a/src/LinkTracker.Bot.Presentation/Telegram/Updates/UpdateMapper.cs b/src/LinkTracker.Bot.Presentation/Telegram/Updates/UpdateMapper.cs index 3f1ba12..e37feb2 100644 --- a/src/LinkTracker.Bot.Presentation/Telegram/Updates/UpdateMapper.cs +++ b/src/LinkTracker.Bot.Presentation/Telegram/Updates/UpdateMapper.cs @@ -28,7 +28,7 @@ await botClient.SendMessage( public Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken ct) { - logger.LogError(exception, "Ошибка при Polling"); + logger.LogError(exception, "Polling failed."); return Task.CompletedTask; } diff --git a/src/LinkTracker.EnvReader/DotEnv.cs b/src/LinkTracker.EnvReader/DotEnv.cs index 9c47669..b907048 100644 --- a/src/LinkTracker.EnvReader/DotEnv.cs +++ b/src/LinkTracker.EnvReader/DotEnv.cs @@ -14,7 +14,7 @@ public static class DotEnv return new Dictionary(); } - throw new FileNotFoundException($"'.env' не найден: {path}"); + throw new FileNotFoundException($"'.env' was not found: {path}"); } var result = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/LinkTracker.Scrapper.Application/Errors/ScrapperErrors.cs b/src/LinkTracker.Scrapper.Application/Errors/ScrapperErrors.cs index e379b5c..71dbef3 100644 --- a/src/LinkTracker.Scrapper.Application/Errors/ScrapperErrors.cs +++ b/src/LinkTracker.Scrapper.Application/Errors/ScrapperErrors.cs @@ -10,7 +10,7 @@ public static ApiException MissingChatIdHeader() return new ApiException( HttpStatusCode.BadRequest, ScrapperErrorCodes.MissingHeader, - "Отсутствует обязательный заголовок 'Tg-Chat-Id'."); + "Required header 'Tg-Chat-Id' is missing."); } public static ApiException RequestLinkIsRequired() @@ -18,7 +18,7 @@ public static ApiException RequestLinkIsRequired() return new ApiException( HttpStatusCode.BadRequest, ScrapperErrorCodes.InvalidRequest, - "Поле 'link' обязательно."); + "Field 'link' is required."); } public static ApiException ChatAlreadyExists(long chatId) @@ -26,7 +26,7 @@ public static ApiException ChatAlreadyExists(long chatId) return new ApiException( HttpStatusCode.Conflict, ScrapperErrorCodes.ChatAlreadyExists, - $"Чат с id={chatId} уже зарегистрирован."); + $"Chat with id={chatId} is already registered."); } public static ApiException ChatNotFound(long chatId) @@ -34,7 +34,7 @@ public static ApiException ChatNotFound(long chatId) return new ApiException( HttpStatusCode.NotFound, ScrapperErrorCodes.ChatNotFound, - $"Чат с id={chatId} не существует."); + $"Chat with id={chatId} does not exist."); } public static ApiException LinkAlreadyExists(Uri link) @@ -42,7 +42,7 @@ public static ApiException LinkAlreadyExists(Uri link) return new ApiException( HttpStatusCode.Conflict, ScrapperErrorCodes.LinkAlreadyExists, - $"Ссылка '{link}' уже отслеживается."); + $"Link '{link}' is already tracked."); } public static ApiException LinkNotFound(Uri link) @@ -50,7 +50,7 @@ public static ApiException LinkNotFound(Uri link) return new ApiException( HttpStatusCode.NotFound, ScrapperErrorCodes.LinkNotFound, - $"Ссылка '{link}' не найдена."); + $"Link '{link}' was not found."); } public static ApiException InvalidChatId() @@ -58,7 +58,7 @@ public static ApiException InvalidChatId() return new ApiException( HttpStatusCode.BadRequest, ScrapperErrorCodes.InvalidChatId, - "Идентификатор чата должен быть положительным числом."); + "Chat id must be a positive number."); } public static ApiException InvalidLink() @@ -66,7 +66,7 @@ public static ApiException InvalidLink() return new ApiException( HttpStatusCode.BadRequest, ScrapperErrorCodes.InvalidLink, - "Ссылка должна быть абсолютным URI."); + "Link must be an absolute URI."); } public static ApiException InvalidLinkScheme() @@ -74,7 +74,7 @@ public static ApiException InvalidLinkScheme() return new ApiException( HttpStatusCode.BadRequest, ScrapperErrorCodes.InvalidLinkScheme, - "Поддерживаются только ссылки с http/https."); + "Only http/https links are supported."); } public static ApiException UnsupportedLink(Uri link) @@ -82,6 +82,6 @@ public static ApiException UnsupportedLink(Uri link) return new ApiException( HttpStatusCode.BadRequest, ScrapperErrorCodes.UnsupportedLink, - $"Ссылка '{link}' не поддерживается. Сейчас поддерживаются только GitHub repository, StackOverflow question и Reddit subreddit."); + $"Link '{link}' is not supported. Only GitHub repositories, StackOverflow questions and Reddit subreddits are supported."); } } \ No newline at end of file diff --git a/src/LinkTracker.Scrapper.Application/Services/Updates/Clients/LinkUpdateHandlerBase.cs b/src/LinkTracker.Scrapper.Application/Services/Updates/Clients/LinkUpdateHandlerBase.cs index 09b036c..6b122d2 100644 --- a/src/LinkTracker.Scrapper.Application/Services/Updates/Clients/LinkUpdateHandlerBase.cs +++ b/src/LinkTracker.Scrapper.Application/Services/Updates/Clients/LinkUpdateHandlerBase.cs @@ -18,7 +18,7 @@ public async Task CheckAsync( { if (!CanHandle(subscription.Url)) { - Logger.LogDebug("Пропускаю неподдерживаемую ссылку {Url}", subscription.Url); + Logger.LogDebug("Skipping unsupported link {Url}", subscription.Url); return LinkUpdateResultBuilder.NoChanges(); } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ClientSideLinksResponseCache.cs b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ClientSideLinksResponseCache.cs index 551c46c..f81e740 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ClientSideLinksResponseCache.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ClientSideLinksResponseCache.cs @@ -25,7 +25,7 @@ internal sealed class ClientSideLinksResponseCache( if (localCache.TryGet(key, out var localResponse)) { logger.LogDebug( - "Client-side кэш HIT. ChatId={ChatId}, Key={Key}", + "Client-side cache HIT. ChatId={ChatId}, Key={Key}", chatId, key); @@ -33,7 +33,7 @@ internal sealed class ClientSideLinksResponseCache( } logger.LogDebug( - "Client-side кэш MISS. ChatId={ChatId}, Key={Key}", + "Client-side cache MISS. ChatId={ChatId}, Key={Key}", chatId, key); @@ -101,7 +101,7 @@ private async Task SetCoreAsync(long chatId, ListLinksResponse response, Cancell localCache.Set(key, response, ttl); logger.LogDebug( - "Client-side кэш SET. ChatId={ChatId}, Key={Key}, TtlSeconds={TtlSeconds}", + "Client-side cache SET. ChatId={ChatId}, Key={Key}, TtlSeconds={TtlSeconds}", chatId, key, ttl.TotalSeconds); @@ -115,7 +115,7 @@ private async Task InvalidateCoreAsync(long chatId, CancellationToken ct) await distributedCache.InvalidateAsync(chatId, ct); logger.LogDebug( - "Client-side кэш INVALIDATE. ChatId={ChatId}, Key={Key}", + "Client-side cache INVALIDATE. ChatId={ChatId}, Key={Key}", chatId, key); } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyConnectionProvider.cs b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyConnectionProvider.cs index 187046c..3c4988b 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyConnectionProvider.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyConnectionProvider.cs @@ -45,13 +45,13 @@ public async Task GetConnectionAsync(CancellationToken c var configuration = ValkeyConfiguration.Parse(options.Value.ConnectionString); logger.LogInformation( - "Подключение к Valkey. Endpoints={Endpoints}", + "Connecting to Valkey. Endpoints={Endpoints}", string.Join(", ", configuration.EndPoints)); _connection = await ConnectionMultiplexer.ConnectAsync(configuration); logger.LogInformation( - "Подключение к Valkey установлено. IsConnected={IsConnected}", + "Connected to Valkey. IsConnected={IsConnected}", _connection.IsConnected); return _connection; diff --git a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyKeyValueCache.cs b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyKeyValueCache.cs index f8cc22c..90481fa 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyKeyValueCache.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyKeyValueCache.cs @@ -22,7 +22,7 @@ public sealed class ValkeyKeyValueCache( { logger.LogWarning( ex, - "Ошибка при чтении значения из Valkey. Key={Key}", + "Failed to read value from Valkey. Key={Key}", key); return null; @@ -44,7 +44,7 @@ public async Task SetStringAsync( { logger.LogWarning( ex, - "Ошибка при записи значения в Valkey. Key={Key}", + "Failed to write value to Valkey. Key={Key}", key); } } @@ -60,7 +60,7 @@ public async Task DeleteAsync(string key, CancellationToken ct = default) { logger.LogWarning( ex, - "Ошибка при удалении значения из Valkey. Key={Key}", + "Failed to delete value from Valkey. Key={Key}", key); } } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyLinksResponseCache.cs b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyLinksResponseCache.cs index f07aa61..e0a0160 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyLinksResponseCache.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Cache/Implementation/ValkeyLinksResponseCache.cs @@ -27,7 +27,7 @@ internal sealed class ValkeyLinksResponseCache( if (string.IsNullOrWhiteSpace(value)) { logger.LogDebug( - "Valkey кэш MISS. ChatId={ChatId}, Key={Key}", + "Valkey cache MISS. ChatId={ChatId}, Key={Key}", chatId, key); @@ -35,7 +35,7 @@ internal sealed class ValkeyLinksResponseCache( } logger.LogDebug( - "Valkey кэш HIT. ChatId={ChatId}, Key={Key}", + "Valkey cache HIT. ChatId={ChatId}, Key={Key}", chatId, key); @@ -47,7 +47,7 @@ internal sealed class ValkeyLinksResponseCache( { logger.LogWarning( ex, - "Ошибка десериализации ответа links из Valkey кэша для чата {ChatId}. Key={Key}", + "Failed to deserialize links response from Valkey cache. ChatId={ChatId}, Key={Key}", chatId, key); @@ -86,7 +86,7 @@ public async Task SetAsync(long chatId, ListLinksResponse response, Cancellation await keyValueCache.SetStringAsync(key, value, ttl, ct); logger.LogDebug( - "Valkey кэш SET. ChatId={ChatId}, Key={Key}, TtlSeconds={TtlSeconds}", + "Valkey cache SET. ChatId={ChatId}, Key={Key}, TtlSeconds={TtlSeconds}", chatId, key, ttl.TotalSeconds); @@ -99,7 +99,7 @@ public async Task InvalidateAsync(long chatId, CancellationToken ct = default) await keyValueCache.DeleteAsync(key, ct); logger.LogDebug( - "Valkey кэш INVALIDATE. ChatId={ChatId}, Key={Key}", + "Valkey cache INVALIDATE. ChatId={ChatId}, Key={Key}", chatId, key); } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotGrpcClient.cs b/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotGrpcClient.cs index a27f0d6..d78b9d3 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotGrpcClient.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotGrpcClient.cs @@ -16,7 +16,7 @@ internal sealed class BotGrpcClient( public async Task SendUpdateAsync(LinkUpdate update, CancellationToken ct = default) { logger.LogInformation( - "Клиент gRPC Bot: вызов SendUpdate. UpdateId={UpdateId}, Ссылка={Url}, КоличествоЧатов={ChatsCount}", + "Bot gRPC client: SendUpdate called. UpdateId={UpdateId}, Url={Url}, ChatsCount={ChatsCount}", update.Id, update.Url, update.TgChatIds.Count); @@ -30,13 +30,13 @@ public async Task SendUpdateAsync(LinkUpdate update, CancellationToken ct = defa await client.SendUpdateAsync(request, cancellationToken: ct); logger.LogInformation( - "Клиент gRPC Bot: SendUpdate успешно выполнен. UpdateId={UpdateId}, КоличествоЧатов={ChatsCount}", + "Bot gRPC client: SendUpdate succeeded. UpdateId={UpdateId}, ChatsCount={ChatsCount}", update.Id, update.TgChatIds.Count); } catch (RpcException ex) { - logger.LogWarning(ex, "Клиент gRPC Bot: SendUpdate завершился с ошибкой. UpdateId={UpdateId}", update.Id); + logger.LogWarning(ex, "Bot gRPC client: SendUpdate failed. UpdateId={UpdateId}", update.Id); throw ToClientException(ex); } } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotHttpClient.cs b/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotHttpClient.cs index 3b4adaa..29f2142 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotHttpClient.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotHttpClient.cs @@ -60,7 +60,6 @@ private static async Task EnsureSuccessStatusCodeAsync(HttpResponseMessage respo } catch { - // ignored } var message = error?.Description ?? $"Bot request failed with status code {(int)response.StatusCode}."; @@ -72,7 +71,7 @@ private static BotClientException CreateServiceUnavailableException(Exception in { return new BotClientException( HttpStatusCode.ServiceUnavailable, - "Bot сейчас недоступен по HTTP.", + "Bot is currently unavailable over HTTP.", innerException: innerException); } } \ No newline at end of file diff --git a/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotKafkaClient.cs b/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotKafkaClient.cs index 6c12ef0..6435428 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotKafkaClient.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Clients/Bot/BotKafkaClient.cs @@ -42,7 +42,7 @@ public async Task SendUpdateAsync(LinkUpdate update, CancellationToken ct = defa new KeyValuePair("topic", options.Topic)); logger.LogInformation( - "Kafka: уведомление для Bot опубликовано. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", + "Kafka: Bot notification published. Topic={Topic}, Partition={Partition}, Offset={Offset}, UpdateId={UpdateId}", result.Topic, result.Partition.Value, result.Offset.Value, @@ -64,13 +64,13 @@ public async Task SendUpdateAsync(LinkUpdate update, CancellationToken ct = defa logger.LogWarning( ex, - "Kafka: ошибка публикации уведомления для Bot. Topic={Topic}, UpdateId={UpdateId}", + "Kafka: failed to publish Bot notification. Topic={Topic}, UpdateId={UpdateId}", options.Topic, update.Id); throw new BotClientException( HttpStatusCode.InternalServerError, - $"Kafka produce завершился с ошибкой: {ex.Error.Reason}"); + $"Kafka produce failed: {ex.Error.Reason}"); } } } \ No newline at end of file diff --git a/src/LinkTracker.Scrapper.Infrastructure/Clients/RateLimiting/ExternalApiRateLimitingHandler.cs b/src/LinkTracker.Scrapper.Infrastructure/Clients/RateLimiting/ExternalApiRateLimitingHandler.cs index 7deac76..2689a90 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Clients/RateLimiting/ExternalApiRateLimitingHandler.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Clients/RateLimiting/ExternalApiRateLimitingHandler.cs @@ -67,7 +67,7 @@ private async Task WaitForCooldownAsync(CancellationToken ct) } logger.LogWarning( - "Ожидание сброса лимита внешнего API. Api={Api}, DelaySeconds={DelaySeconds}", + "Waiting for external API rate limit reset. Api={Api}, DelaySeconds={DelaySeconds}", rateLimiter.ApiName, remaining.TotalSeconds); @@ -92,7 +92,7 @@ private void ApplyThrottlingHints(HttpResponseMessage response) new KeyValuePair("reason", "rate_limited")); logger.LogWarning( - "Внешний API сообщил об исчерпании лимита. Api={Api}, Status={Status}, CooldownUntil={CooldownUntil}", + "External API reported rate limit exhaustion. Api={Api}, Status={Status}, CooldownUntil={CooldownUntil}", rateLimiter.ApiName, (int)response.StatusCode, cooldownUntil.Value); diff --git a/src/LinkTracker.Scrapper.Infrastructure/Database/Migrations/DbUpMigrator.cs b/src/LinkTracker.Scrapper.Infrastructure/Database/Migrations/DbUpMigrator.cs index 608a6fa..eccd4fc 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Database/Migrations/DbUpMigrator.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Database/Migrations/DbUpMigrator.cs @@ -18,7 +18,7 @@ public Task MigrateAsync(CancellationToken cancellationToken = default) { if (ShouldSkipMigrations()) { - logger.LogInformation("Пропускаю миграции базы данных."); + logger.LogInformation("Skipping database migrations."); return Task.CompletedTask; } @@ -30,7 +30,7 @@ public Task MigrateAsync(CancellationToken cancellationToken = default) if (Directory.Exists(migrationsPath) is false) { throw new DirectoryNotFoundException( - $"Директория миграций не была найдена: {migrationsPath}"); + $"Migrations directory was not found: {migrationsPath}"); } EnsureDatabase.For.PostgresqlDatabase(connectionString); @@ -47,11 +47,11 @@ public Task MigrateAsync(CancellationToken cancellationToken = default) if (result.Successful is false) { - logger.LogError(result.Error, "Ошибка применения миграций"); + logger.LogError(result.Error, "Failed to apply migrations."); throw result.Error ?? new InvalidOperationException("DbUp migration failed"); } - logger.LogInformation("Миграции успешно применены."); + logger.LogInformation("Migrations applied successfully."); return Task.CompletedTask; } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Errors/ApiExceptionHandler.cs b/src/LinkTracker.Scrapper.Infrastructure/Errors/ApiExceptionHandler.cs index 16346b2..c62a3e8 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Errors/ApiExceptionHandler.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Errors/ApiExceptionHandler.cs @@ -31,7 +31,7 @@ public static async Task HandleAsync(HttpContext context) _ => ( (int)HttpStatusCode.InternalServerError, ApiErrorResponseFactory.Create( - "Внутренняя ошибка сервера.", + "Internal server error.", "internal_error", exception, includeExceptionDetails)) @@ -44,7 +44,7 @@ public static async Task HandleAsync(HttpContext context) .CreateLogger(LoggerName) .LogError( exception, - "Необработанная ошибка при обработке запроса {Method} {Path}.", + "Unhandled error while processing request {Method} {Path}.", context.Request.Method, context.Request.Path); } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Outbox/Jobs/OutboxDispatchJob.cs b/src/LinkTracker.Scrapper.Infrastructure/Outbox/Jobs/OutboxDispatchJob.cs index 6089b28..c71faa3 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Outbox/Jobs/OutboxDispatchJob.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Outbox/Jobs/OutboxDispatchJob.cs @@ -32,7 +32,7 @@ public async Task Execute(IJobExecutionContext context) return; } - logger.LogInformation("Начата отправка outbox сообщений. Count={Count}", messages.Count); + logger.LogInformation("Started dispatching outbox messages. Count={Count}", messages.Count); foreach (var message in messages) { @@ -44,7 +44,7 @@ public async Task Execute(IJobExecutionContext context) metrics.SentUpdates.Add(1); logger.LogDebug( - "Outbox сообщение отправлено. OutboxMessageId={OutboxMessageId}", + "Outbox message dispatched. OutboxMessageId={OutboxMessageId}", message.Id); } catch (OperationCanceledException) when (ct.IsCancellationRequested) @@ -57,7 +57,7 @@ public async Task Execute(IJobExecutionContext context) logger.LogWarning( ex, - "Не удалось отправить outbox сообщение. OutboxMessageId={OutboxMessageId}, RetryCount={RetryCount}", + "Failed to dispatch outbox message. OutboxMessageId={OutboxMessageId}, RetryCount={RetryCount}", message.Id, message.RetryCount + 1); } diff --git a/src/LinkTracker.Scrapper.Infrastructure/Quartz/Jobs/LinkUpdatesJob.cs b/src/LinkTracker.Scrapper.Infrastructure/Quartz/Jobs/LinkUpdatesJob.cs index ac2a532..908f7c1 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Quartz/Jobs/LinkUpdatesJob.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Quartz/Jobs/LinkUpdatesJob.cs @@ -53,7 +53,7 @@ public async Task Execute(IJobExecutionContext context) } logger.LogDebug( - "Начата обработка батча ссылок. CheckedBefore={CheckedBefore}, BatchSize={BatchSize}, ActualCount={ActualCount}, MaxDegreeOfParallelism={MaxDegreeOfParallelism}", + "Started processing link batch. CheckedBefore={CheckedBefore}, BatchSize={BatchSize}, ActualCount={ActualCount}, MaxDegreeOfParallelism={MaxDegreeOfParallelism}", runStartedAt, _batchSize, batch.Count, @@ -76,7 +76,7 @@ await Parallel.ForEachAsync( logger.LogError( ex, - "Не удалось обработать ссылку. LinkId={LinkId}, Url={Url}", + "Failed to process link. LinkId={LinkId}, Url={Url}", subscription.Id, subscription.Url); } @@ -96,7 +96,7 @@ await trackingStore.MarkCheckedAsync( .ToArray(); logger.LogWarning( - "Батч обработан с ошибками. FailedCount={FailedCount}, FailedLinkIds={FailedLinkIds}, FailedUrls={FailedUrls}", + "Batch processed with errors. FailedCount={FailedCount}, FailedLinkIds={FailedLinkIds}, FailedUrls={FailedUrls}", failed.Length, failed.Select(x => x.Id).ToArray(), failed.Select(x => x.Url.ToString()).ToArray()); @@ -120,7 +120,7 @@ private async Task ProcessSubscriptionAsync( if (handler is null) { logger.LogDebug( - "Не найден обработчик для ссылки. LinkId={LinkId}, Url={Url}", + "No handler found for link. LinkId={LinkId}, Url={Url}", subscription.Id, subscription.Url); @@ -150,7 +150,7 @@ private async Task ProcessSubscriptionAsync( await botClient.SendUpdateAsync(update, ct); logger.LogDebug( - "Отправлено обновление. LinkId={LinkId}, Url={Url}, ChatCount={ChatCount}", + "Update sent. LinkId={LinkId}, Url={Url}, ChatCount={ChatCount}", subscription.Id, subscription.Url, subscription.TgChatIds.Count); @@ -220,7 +220,7 @@ await botClient.SendUpdateAsync( { logger.LogError( ex, - "Не удалось отправить отчет о проблемных ссылках. ChatId={ChatId}, UrlCount={UrlCount}", + "Failed to send the failed-links report. ChatId={ChatId}, UrlCount={UrlCount}", report.ChatId, report.Urls.Length); } @@ -253,7 +253,7 @@ private async Task SaveUpdatesToOutboxAsync( if (checkResult.NewLastUpdatedAt is null) { throw new InvalidOperationException( - $"Невозможно сохранить обновление ссылки в outbox без курсора. LinkId={subscription.Id}"); + $"Cannot save a link update to the outbox without a cursor. LinkId={subscription.Id}"); } await outboxStore.AddRangeAndSetCursorAsync( @@ -266,7 +266,7 @@ await outboxStore.AddRangeAndSetCursorAsync( metrics.OutboxEnqueuedUpdates.Add(updates.Count); logger.LogDebug( - "Обновления сохранены в transactional outbox. LinkId={LinkId}, Url={Url}, UpdateCount={UpdateCount}, ChatCount={ChatCount}", + "Updates saved to the transactional outbox. LinkId={LinkId}, Url={Url}, UpdateCount={UpdateCount}, ChatCount={ChatCount}", subscription.Id, subscription.Url, updates.Count, diff --git a/src/LinkTracker.Scrapper.Infrastructure/Storage/Registration/StorageModule.cs b/src/LinkTracker.Scrapper.Infrastructure/Storage/Registration/StorageModule.cs index 11e24be..3b4d5a9 100644 --- a/src/LinkTracker.Scrapper.Infrastructure/Storage/Registration/StorageModule.cs +++ b/src/LinkTracker.Scrapper.Infrastructure/Storage/Registration/StorageModule.cs @@ -17,13 +17,13 @@ public static IServiceCollection AddStorage( if (!databaseSection.Exists()) { - throw new InvalidOperationException("Секция 'Database' не найдена."); + throw new InvalidOperationException("Section 'Database' was not found."); } services.Configure(databaseSection); var databaseOptions = databaseSection.Get() - ?? throw new InvalidOperationException("Не удалось прочитать настройки базы данных."); + ?? throw new InvalidOperationException("Failed to read database settings."); switch (databaseOptions.AccessType) { @@ -35,7 +35,7 @@ public static IServiceCollection AddStorage( break; default: throw new InvalidOperationException( - $"Неподдерживаемый тип доступа к БД: '{databaseOptions.AccessType}'."); + $"Unsupported database access type: '{databaseOptions.AccessType}'."); } return services; diff --git a/src/LinkTracker.Scrapper.Presentation/Grpc/GrpcExceptionInterceptor.cs b/src/LinkTracker.Scrapper.Presentation/Grpc/GrpcExceptionInterceptor.cs index a06ec36..47fcae2 100644 --- a/src/LinkTracker.Scrapper.Presentation/Grpc/GrpcExceptionInterceptor.cs +++ b/src/LinkTracker.Scrapper.Presentation/Grpc/GrpcExceptionInterceptor.cs @@ -21,7 +21,7 @@ public override async Task UnaryServerHandler( { logger.LogWarning( ex, - "gRPC {Method} завершился с ошибкой. Code={Code}", + "gRPC {Method} failed. Code={Code}", context.Method, ex.Code); @@ -29,8 +29,8 @@ public override async Task UnaryServerHandler( } catch (UriFormatException ex) { - logger.LogWarning(ex, "gRPC {Method}: некорректный формат ссылки.", context.Method); - throw new RpcException(new Status(StatusCode.InvalidArgument, "Некорректный формат ссылки")); + logger.LogWarning(ex, "gRPC {Method}: invalid link format.", context.Method); + throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid link format")); } catch (RpcException) { @@ -38,8 +38,8 @@ public override async Task UnaryServerHandler( } catch (Exception ex) { - logger.LogError(ex, "gRPC {Method}: неизвестная ошибка", context.Method); - throw new RpcException(new Status(StatusCode.Internal, "Внутренняя ошибка scrapper")); + logger.LogError(ex, "gRPC {Method}: unknown error.", context.Method); + throw new RpcException(new Status(StatusCode.Internal, "Internal scrapper error")); } } diff --git a/src/LinkTracker.Shared/Contracts/Bot/LinkUpdate.cs b/src/LinkTracker.Shared/Contracts/Bot/LinkUpdate.cs index 82ea1a8..7bc7be1 100644 --- a/src/LinkTracker.Shared/Contracts/Bot/LinkUpdate.cs +++ b/src/LinkTracker.Shared/Contracts/Bot/LinkUpdate.cs @@ -14,10 +14,6 @@ public sealed class LinkUpdate public IReadOnlyList TgChatIds { get; init; } = []; - /// - /// Проставляется AI-агентом. На сыром пути Scrapper -> Bot остаётся Medium: - /// приоритет там просто ещё не вычислен. - /// public LinkUpdatePriority Priority { get; init; } = LinkUpdatePriority.Medium; public LinkUpdateKind Kind { get; init; } = LinkUpdateKind.Content; diff --git a/src/LinkTracker.Shared/Infrastructure/Authentication/ServiceTokenAuthenticationHandler.cs b/src/LinkTracker.Shared/Infrastructure/Authentication/ServiceTokenAuthenticationHandler.cs index a586f09..c0a1cba 100644 --- a/src/LinkTracker.Shared/Infrastructure/Authentication/ServiceTokenAuthenticationHandler.cs +++ b/src/LinkTracker.Shared/Infrastructure/Authentication/ServiceTokenAuthenticationHandler.cs @@ -33,7 +33,7 @@ protected override Task HandleAuthenticateAsync() if (!IsKnownSecret(values.ToString())) { - return Task.FromResult(AuthenticateResult.Fail("Передан неизвестный сервисный токен.")); + return Task.FromResult(AuthenticateResult.Fail("Unknown service token.")); } var identity = new ClaimsIdentity( diff --git a/src/LinkTracker.Tests/AiAgent/Integration/Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumerIntegrationTests.cs b/src/LinkTracker.Tests/AiAgent/Integration/Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumerIntegrationTests.cs index 64cbeba..ddf2a8b 100644 --- a/src/LinkTracker.Tests/AiAgent/Integration/Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumerIntegrationTests.cs +++ b/src/LinkTracker.Tests/AiAgent/Integration/Infrastructure/Clients/Kafka/RawUpdatesKafkaConsumerIntegrationTests.cs @@ -162,7 +162,7 @@ public async Task Consumer_WhenMalformedMessagePublished_PublishesMessageToDeadL var root = document.RootElement; Assert.Equal(topic, root.GetProperty("sourceTopic").GetString()); - Assert.Contains("десериализовать", root.GetProperty("reason").GetString()); + Assert.Contains("deserialize", root.GetProperty("reason").GetString()); Assert.Equal( "{ this is not valid json !!!", Encoding.UTF8.GetString(Convert.FromBase64String(root.GetProperty("payload").GetString()!))); diff --git a/src/LinkTracker.Tests/AiAgent/Unit/Application/Services/LinkUpdateProcessingServiceTests.cs b/src/LinkTracker.Tests/AiAgent/Unit/Application/Services/LinkUpdateProcessingServiceTests.cs index c58b227..e2a222d 100644 --- a/src/LinkTracker.Tests/AiAgent/Unit/Application/Services/LinkUpdateProcessingServiceTests.cs +++ b/src/LinkTracker.Tests/AiAgent/Unit/Application/Services/LinkUpdateProcessingServiceTests.cs @@ -163,7 +163,6 @@ public async Task ProcessAsync_WhenSystemReport_PublishesDirectlyWithoutFilterSu Kind = LinkUpdateKind.SystemReport }; - // Стоп-слово в тексте отчета не должно приводить к его отбрасыванию. _filter.ShouldFilter(Arg.Any()).Returns(true); await CreateService().ProcessAsync(update, _ack, CancellationToken.None); diff --git a/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/KafkaOffsetTrackerTests.cs b/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/KafkaOffsetTrackerTests.cs index 891a4ed..b8b0e40 100644 --- a/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/KafkaOffsetTrackerTests.cs +++ b/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/KafkaOffsetTrackerTests.cs @@ -38,7 +38,6 @@ public void TakeCommittableOffsets_WhenBufferStillHoldsMessage_ReturnsNothing() var tracker = new KafkaOffsetTracker(); var ack = tracker.Track(CreateResult(0)); - // Копия обновления лежит в буфере группировки. ack.Retain(); ack.Release(); diff --git a/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandlerTests.cs b/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandlerTests.cs index 7ee6915..a360408 100644 --- a/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandlerTests.cs +++ b/src/LinkTracker.Tests/AiAgent/Unit/Infrastructure/Clients/Kafka/RawUpdatesKafkaMessageHandlerTests.cs @@ -92,7 +92,7 @@ await processingService.Received(3).ProcessAsync( await deadLetterPublisher.Received(1).PublishAsync( message, - "Исчерпаны попытки обработки Kafka сообщения.", + "Kafka message processing retries exhausted.", Arg.Is(ex => ex.Message == "YandexAi failed"), Arg.Any()); } diff --git a/src/LinkTracker.Tests/Bot/Integration/Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumerIntegrationTests.cs b/src/LinkTracker.Tests/Bot/Integration/Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumerIntegrationTests.cs index 2de4d65..ec3ece6 100644 --- a/src/LinkTracker.Tests/Bot/Integration/Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumerIntegrationTests.cs +++ b/src/LinkTracker.Tests/Bot/Integration/Infrastructure/Clients/Kafka/LinkUpdatesKafkaConsumerIntegrationTests.cs @@ -210,7 +210,7 @@ public async Task Consumer_WhenMessageIsInvalid_PublishesMessageToDeadLetterTopi Convert.FromBase64String(deadLetterMessage.Payload)); Assert.Equal(invalidPayload, originalPayload); - Assert.StartsWith("Kafka сообщение не удалось десериализовать:", deadLetterMessage.Reason); + Assert.StartsWith("Failed to deserialize Kafka message:", deadLetterMessage.Reason); Assert.Equal(topic, deadLetterMessage.SourceTopic); await notifier.DidNotReceive().NotifyAsync( diff --git a/src/LinkTracker.Tests/Bot/Integration/Kafka/KafkaTestContainerFixture.cs b/src/LinkTracker.Tests/Bot/Integration/Kafka/KafkaTestContainerFixture.cs index 0f04b4b..fc7e691 100644 --- a/src/LinkTracker.Tests/Bot/Integration/Kafka/KafkaTestContainerFixture.cs +++ b/src/LinkTracker.Tests/Bot/Integration/Kafka/KafkaTestContainerFixture.cs @@ -74,7 +74,6 @@ await adminClient.CreateTopicsAsync( catch (CreateTopicsException ex) when (ex.Results.Any(result => result.Error.Code == ErrorCode.TopicAlreadyExists)) { - // Topic already exists. Nothing to do. } } diff --git a/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParserTests.cs b/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParserTests.cs index be8a98e..853c70c 100644 --- a/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParserTests.cs +++ b/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/KafkaLinkUpdateMessageParserTests.cs @@ -37,7 +37,7 @@ public void TryValidate_WhenUpdateIsNull_ReturnsError() var result = _sut.TryValidate(null, out var error); Assert.False(result); - Assert.Equal("Сообщение не удалось десериализовать.", error); + Assert.Equal("Failed to deserialize the message.", error); } [Fact] @@ -48,7 +48,7 @@ public void TryValidate_WhenIdIsNegative_ReturnsError() var result = _sut.TryValidate(update, out var error); Assert.False(result); - Assert.Equal("Поле 'id' не может быть отрицательным.", error); + Assert.Equal("Field 'id' must not be negative.", error); } [Fact] @@ -59,7 +59,7 @@ public void TryValidate_WhenUrlIsRelative_ReturnsError() var result = _sut.TryValidate(update, out var error); Assert.False(result); - Assert.Equal("Поле 'url' должно содержать абсолютный URI.", error); + Assert.Equal("Field 'url' must contain an absolute URI.", error); } [Fact] @@ -70,7 +70,7 @@ public void TryValidate_WhenTgChatIdsIsEmpty_ReturnsError() var result = _sut.TryValidate(update, out var error); Assert.False(result); - Assert.Equal("Поле 'tgChatIds' должно содержать хотя бы один chat id.", error); + Assert.Equal("Field 'tgChatIds' must contain at least one chat id.", error); } private static LinkUpdate CreateValidUpdate( diff --git a/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandlerTests.cs b/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandlerTests.cs index a0c0298..95a715b 100644 --- a/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandlerTests.cs +++ b/src/LinkTracker.Tests/Bot/Unit/Infrastructure/Clients/Kafka/LinkUpdatesKafkaMessageHandlerTests.cs @@ -80,7 +80,7 @@ await notifier.DidNotReceive().NotifyAsync( await deadLetterPublisher.Received(1).PublishAsync( message, - Arg.Is(reason => reason.StartsWith("Kafka сообщение не удалось десериализовать:")), + Arg.Is(reason => reason.StartsWith("Failed to deserialize Kafka message:")), Arg.Any(), Arg.Any()); } @@ -154,7 +154,7 @@ await notifier.Received(3).NotifyAsync( await deadLetterPublisher.Received(1).PublishAsync( message, - "Исчерпаны попытки обработки Kafka сообщения.", + "Kafka message processing retries exhausted.", Arg.Is(ex => ex.Message == "Telegram failed"), Arg.Any()); } diff --git a/src/LinkTracker.Tests/Bot/Unit/Presentation/Telegram/Notifications/LinkUpdateNotifierTests.cs b/src/LinkTracker.Tests/Bot/Unit/Presentation/Telegram/Notifications/LinkUpdateNotifierTests.cs index 88be82a..1b5c50f 100644 --- a/src/LinkTracker.Tests/Bot/Unit/Presentation/Telegram/Notifications/LinkUpdateNotifierTests.cs +++ b/src/LinkTracker.Tests/Bot/Unit/Presentation/Telegram/Notifications/LinkUpdateNotifierTests.cs @@ -42,7 +42,6 @@ public async Task NotifyAsync_WhenPriorityIsNotSet_UsesNeutralHeader() var (botClient, sentTexts) = CreateBotClient(); var sut = new LinkUpdateNotifier(botClient, Substitute.For()); - // Сырой путь Scrapper -> Bot приоритет не проставляет. await sut.NotifyAsync( new LinkUpdate { Id = 1, Url = Url, Description = "Новый issue", TgChatIds = [42] }, CancellationToken.None); diff --git a/src/LinkTracker.Tests/Scrapper/Integration/Storage/DatabaseMigrationTests.cs b/src/LinkTracker.Tests/Scrapper/Integration/Storage/DatabaseMigrationTests.cs index f4cf0fb..e85e5d8 100644 --- a/src/LinkTracker.Tests/Scrapper/Integration/Storage/DatabaseMigrationTests.cs +++ b/src/LinkTracker.Tests/Scrapper/Integration/Storage/DatabaseMigrationTests.cs @@ -23,7 +23,6 @@ public async Task Migrations_CreateExpectedSchema() Assert.Contains("dbup_schema_versions", tables); Assert.Contains("outbox_messages", tables); - // 005_drop_filters.sql: фича фильтров удалена, таблицы не должны возвращаться. Assert.DoesNotContain("filters", tables); Assert.DoesNotContain("subscription_filters", tables); diff --git a/src/LinkTracker.Tests/Shared/Unit/Contracts/LinkUpdateAvroSchemaTests.cs b/src/LinkTracker.Tests/Shared/Unit/Contracts/LinkUpdateAvroSchemaTests.cs index 7904f5f..6d36ffe 100644 --- a/src/LinkTracker.Tests/Shared/Unit/Contracts/LinkUpdateAvroSchemaTests.cs +++ b/src/LinkTracker.Tests/Shared/Unit/Contracts/LinkUpdateAvroSchemaTests.cs @@ -7,8 +7,6 @@ namespace LinkTracker.Tests.Shared.Unit.Contracts; [Trait("Category", "Unit")] public sealed class LinkUpdateAvroSchemaTests { - // Avro-ветка обязана переносить тот же контракт, что и JSON, иначе выбор - // сериализации молча меняет данные (так терялся author). [Fact] public void Schema_CoversEveryLinkUpdateProperty() { diff --git a/src/LinkTracker.Tests/Shared/Unit/Contracts/ProcessedUpdateWireCompatibilityTests.cs b/src/LinkTracker.Tests/Shared/Unit/Contracts/ProcessedUpdateWireCompatibilityTests.cs index 2b1e36d..0b37538 100644 --- a/src/LinkTracker.Tests/Shared/Unit/Contracts/ProcessedUpdateWireCompatibilityTests.cs +++ b/src/LinkTracker.Tests/Shared/Unit/Contracts/ProcessedUpdateWireCompatibilityTests.cs @@ -5,10 +5,6 @@ namespace LinkTracker.Tests.Shared.Unit.Contracts; -/// -/// AI-агент публикует ProcessedLinkUpdate, а Bot читает то же сообщение как LinkUpdate. -/// Контракты разные, связь между ними — только формат на проводе, поэтому она проверяется явно. -/// [Trait("Module", "Shared")] [Trait("Category", "Unit")] public sealed class ProcessedUpdateWireCompatibilityTests