Add rebus (#1)

Reviewed-on: #1
Co-authored-by: Sergey Nazarov <insight.appdev@gmail.com>
Co-committed-by: Sergey Nazarov <insight.appdev@gmail.com>
This commit is contained in:
Sergey Nazarov 2024-03-21 18:57:29 +00:00 committed by nazarovsa
parent 98f56ed212
commit 2692f549f4
31 changed files with 630 additions and 18 deletions

View File

@ -1,16 +1,34 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<MicrosoftVersion>8.0.0</MicrosoftVersion>
</PropertyGroup>
<ItemGroup Label="Serilog">
<PackageVersion Include="Serilog" Version="3.1.1"/>
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1"/>
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0"/>
</ItemGroup>
<ItemGroup Label="Microsoft">
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftVersion)"/>
</ItemGroup>
</Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<MicrosoftVersion>8.0.0</MicrosoftVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup Label="Serilog">
<PackageVersion Include="Nocr.TL.2024.03.21.001.rc1" Version="0.0.1" />
<PackageVersion Include="RestEase" Version="1.6.4" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
</ItemGroup>
<ItemGroup Label="Rebus">
<PackageVersion Include="Rebus" Version="8.2.2" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.1.0" />
<PackageVersion Include="Rebus.RabbitMq" Version="9.0.1" />
<PackageVersion Include="Rebus.Serilog" Version="8.0.0" />
</ItemGroup>
<ItemGroup Label="Microsoft">
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(MicrosoftVersion)" />
</ItemGroup>
<ItemGroup Label="Tests">
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageVersion Include="xunit" Version="2.4.2"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5"/>
<PackageVersion Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup>
</Project>

View File

@ -14,6 +14,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.AppService
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.Host", "src\Nocr.TextMatcher.Host\Nocr.TextMatcher.Host.csproj", "{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.Async.Api.Contracts", "src\Nocr.TextMatcher.Async.Api.Contracts\Nocr.TextMatcher.Async.Api.Contracts.csproj", "{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.Api.Contracts", "src\Nocr.TextMatcher.Api.Contracts\Nocr.TextMatcher.Api.Contracts.csproj", "{A6332064-40EE-498A-826D-638BC295A185}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.AppServices.Contracts", "src\Nocr.TextMatcher.AppServices.Contracts\Nocr.TextMatcher.AppServices.Contracts.csproj", "{01BF99EE-D635-4931-9CAB-B51C54F0760B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Tests", "_Tests", "{6E4D9F75-861F-4C00-A5C8-00D1BEE5A659}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.AppServices.UnitTests", "tests\Nocr.TextMatcher.AppServices.UnitTests\Nocr.TextMatcher.AppServices.UnitTests.csproj", "{B721E055-84AF-44C6-973D-33241FD2EA7C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -32,5 +42,24 @@ Global
{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Release|Any CPU.Build.0 = Release|Any CPU
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Release|Any CPU.Build.0 = Release|Any CPU
{A6332064-40EE-498A-826D-638BC295A185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6332064-40EE-498A-826D-638BC295A185}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6332064-40EE-498A-826D-638BC295A185}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6332064-40EE-498A-826D-638BC295A185}.Release|Any CPU.Build.0 = Release|Any CPU
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Release|Any CPU.Build.0 = Release|Any CPU
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B721E055-84AF-44C6-973D-33241FD2EA7C} = {6E4D9F75-861F-4C00-A5C8-00D1BEE5A659}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nocr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Nocr.TextMatcher.AppServices.Contracts\Nocr.TextMatcher.AppServices.Contracts.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RestEase" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,21 @@
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
using RestEase;
namespace Nocr.TextMatcher.Api.Contracts.TextMatches.Dto;
public class CreateTextMatchRequest
{
public long UserId { get; set; }
public long ChatId { get; set; }
public string Template { get; set; }
public TextMatchRule Rule { get; set; }
}
[BasePath(WebRoutes.TextMatches.Path)]
public interface ITextMatcherController
{
Task<long> Create([Body] CreateTextMatchRequest request, CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,11 @@
namespace Nocr.TextMatcher.Api.Contracts;
public static class WebRoutes
{
public const string BasePath = "/api";
public static class TextMatches
{
public const string Path = BasePath + "/" + "text-matches";
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,13 @@
namespace Nocr.TextMatcher.AppServices.Contracts.TextMatches;
public enum TextMatchRule
{
// Substring
Full = 1,
// Any word from list splited by space
AnyWord,
// All words from list splited by space
AllWords
}

View File

@ -2,9 +2,14 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Nocr.TL.2024.03.21.001.rc1" />
<PackageReference Include="Rebus" />
<PackageReference Include="Rebus.ServiceProvider" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Nocr.TextMatcher.AppServices.Contracts\Nocr.TextMatcher.AppServices.Contracts.csproj" />
<ProjectReference Include="..\Nocr.TextMatcher.Async.Api.Contracts\Nocr.TextMatcher.Async.Api.Contracts.csproj" />
<ProjectReference Include="..\Nocr.TextMatcher.Core\Nocr.TextMatcher.Core.csproj" />
</ItemGroup>

View File

@ -1,4 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Nocr.TextMatcher.AppServices.TextMatchers;
using Nocr.TextMatcher.AppServices.TextMatches.Repositories;
using Nocr.TextMatcher.AppServices.TextMatches.Services;
using Rebus.Config;
namespace Nocr.TextMatcher.AppServices;
@ -10,6 +14,9 @@ public static class ServiceCollectionExtensions
throw new ArgumentNullException(nameof(services));
// Add registrations here
services.AddRebusHandler<MessageReceivedHandler>();
services.AddSingleton<ITextMatchService, TextMatchService>();
services.AddSingleton<ITextMatchRepository, InMemoryTextMatchRepository>();
return services;
}

View File

@ -0,0 +1,53 @@
using Microsoft.Extensions.Logging;
using Nocr.TelegramListener.Async.Api.Contracts.Events;
using Nocr.TextMatcher.AppServices.TextMatches.Services;
using Nocr.TextMatcher.Async.Api.Contracts;
using Nocr.TextMatcher.Core.Dates;
using Rebus.Bus;
using Rebus.Handlers;
namespace Nocr.TextMatcher.AppServices.TextMatchers;
public sealed class MessageReceivedHandler : IHandleMessages<MessageReceived>
{
private readonly ILogger<MessageReceivedHandler> _logger;
private readonly IBus _bus;
private readonly ITextMatchService _textMatchService;
private readonly ICurrentDateProvider _dateProvider;
public MessageReceivedHandler(ILogger<MessageReceivedHandler> logger,
IBus bus,
ITextMatchService textMatchService,
ICurrentDateProvider dateProvider)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_bus = bus ?? throw new ArgumentNullException(nameof(bus));
_textMatchService = textMatchService ?? throw new ArgumentNullException(nameof(textMatchService));
_dateProvider = dateProvider ?? throw new ArgumentNullException(nameof(dateProvider));
}
public async Task Handle(MessageReceived message)
{
_logger.LogInformation("Received message: {@Message}", message);
var matches = await _textMatchService.Get();
foreach (var match in matches)
{
if (match.IsMatches(message.ChatId, message.Text))
{
_logger.LogInformation("Message {@Message} matched {@Match}", message, match);
var @event = new TextMatchMatched
{
ChatId = message.ChatId,
Text = message.Text,
UserId = message.From,
OccuredDateTime = message.OccuredDateTime,
PublishedDateTime = _dateProvider.UtcNow
};
await _bus.Advanced.Topics.Publish("nocr.text.matcher", @event);
}
}
}
}

View File

@ -0,0 +1,12 @@
using Nocr.TextMatcher.AppServices.TextMatches.Services;
namespace Nocr.TextMatcher.AppServices.TextMatches.Repositories;
public interface ITextMatchRepository
{
Task<long> Create(TextMatch textMatch, CancellationToken cancellationToken = default);
Task Delete(long id, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,30 @@
namespace Nocr.TextMatcher.AppServices.TextMatches.Repositories;
public sealed class InMemoryTextMatchRepository : ITextMatchRepository
{
private long _id = 0;
private List<TextMatch> _textMatches = new List<TextMatch>();
public Task<long> Create(TextMatch textMatch, CancellationToken cancellationToken = default)
{
var id = Interlocked.Increment(ref _id);
textMatch.Id = id;
_textMatches.Add(textMatch);
return Task.FromResult(id);
}
public Task Delete(long id, CancellationToken cancellationToken = default)
{
var index = _textMatches.FindIndex(x => x.Id == id);
_textMatches.RemoveAt(index);
return Task.CompletedTask;
}
public Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default)
{
return Task.FromResult<IReadOnlyCollection<TextMatch>>(_textMatches);
}
}

View File

@ -0,0 +1,12 @@
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
namespace Nocr.TextMatcher.AppServices.TextMatches.Services;
public interface ITextMatchService
{
Task<long> Create(long userId, long chatId, string template, TextMatchRule rule, CancellationToken cancellationToken = default);
Task Delete(long id, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,48 @@
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
using Nocr.TextMatcher.AppServices.TextMatches.Repositories;
using Nocr.TextMatcher.Async.Api.Contracts;
using Nocr.TextMatcher.Core.Dates;
using Rebus.Bus;
namespace Nocr.TextMatcher.AppServices.TextMatches.Services;
public sealed class TextMatchService : ITextMatchService
{
private readonly IBus _bus;
private readonly ITextMatchRepository _repository;
private readonly ICurrentDateProvider _dateProvider;
public TextMatchService(IBus bus, ITextMatchRepository repository,
ICurrentDateProvider dateProvider)
{
_bus = bus ?? throw new ArgumentNullException(nameof(bus));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_dateProvider = dateProvider ?? throw new ArgumentNullException(nameof(dateProvider));
}
public async Task<long> Create(long userId, long chatId, string template, TextMatchRule rule,
CancellationToken cancellationToken = default)
{
var textMatch = TextMatch.Initialize(userId, chatId, template, rule, _dateProvider.UtcNow);
await _repository.Create(textMatch, cancellationToken);
var @event = new TextMatchCreated
{
ChatId = chatId
};
await _bus.Advanced.Topics.Publish("nocr.text.matcher", @event);
return textMatch.Id;
}
public Task Delete(long id, CancellationToken cancellationToken = default)
{
return _repository.Delete(id, cancellationToken);
}
public Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default)
{
return _repository.Get(cancellationToken);
}
}

View File

@ -0,0 +1,70 @@
using System.Text.RegularExpressions;
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
namespace Nocr.TextMatcher.AppServices.TextMatches;
public sealed class TextMatch
{
public long Id { get; set; }
public long UserId { get; private set; }
public long ChatId { get; private set; }
public string Template { get; private set; }
public TextMatchRule Rule { get; private set; }
public DateTimeOffset CreatedDateTime { get; private set; }
private TextMatch(long userId,
long chatId,
string template,
TextMatchRule rule,
DateTimeOffset createdDateTime)
{
UserId = userId;
ChatId = chatId;
Template = template;
Rule = rule;
CreatedDateTime = createdDateTime;
}
public static TextMatch Initialize(long userId,
long chatId,
string template,
TextMatchRule rule,
DateTimeOffset createdDateTime)
{
if (userId <= 0)
throw new ArgumentException("User id should be greater tha 0", nameof(userId));
if (chatId <= 0)
throw new ArgumentException("Chat id should be greater tha 0", nameof(chatId));
if (string.IsNullOrWhiteSpace(template))
throw new ArgumentException("Template should not be empty", nameof(template));
return new TextMatch(userId, chatId, template, rule, createdDateTime);
}
public bool IsMatches(long chatId, string text)
{
if (ChatId != chatId)
return false;
switch (Rule)
{
case TextMatchRule.Full:
return text.Contains(Template, StringComparison.OrdinalIgnoreCase);
case TextMatchRule.AnyWord:
var anyWords = Regex.Split(Template, @"\s+");
return anyWords.Any(word => text.Contains(word, StringComparison.OrdinalIgnoreCase));
case TextMatchRule.AllWords:
var allWords = Regex.Split(Template, @"\s+");
return allWords.All(word => text.Contains(word, StringComparison.OrdinalIgnoreCase));
default:
throw new ArgumentOutOfRangeException(nameof(Rule));
}
}
}

View File

@ -0,0 +1,6 @@
namespace Nocr.TextMatcher.Async.Api.Contracts;
public interface IEvent
{
public Guid Id { get; }
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,8 @@
namespace Nocr.TextMatcher.Async.Api.Contracts;
public class TextMatchCreated : IEvent
{
public Guid Id => Guid.NewGuid();
public long ChatId { get; set; }
}

View File

@ -0,0 +1,16 @@
namespace Nocr.TextMatcher.Async.Api.Contracts;
public class TextMatchMatched : IEvent
{
public Guid Id => Guid.NewGuid();
public long UserId { get; set; }
public long ChatId { get; set; }
public string Text { get; set; }
public DateTimeOffset OccuredDateTime { get; set; }
public DateTimeOffset PublishedDateTime { get; set; }
}

View File

@ -0,0 +1,12 @@
namespace Nocr.TextMatcher.Core.Options;
public sealed class RebusRabbitMqOptions
{
public string ConnectionString { get; set; }
public string InputQueueName { get; set; }
public string DirectExchangeName { get; set; }
public string TopicsExchangeName { get; set; }
}

View File

@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Nocr.TextMatcher.Api.Contracts;
using Nocr.TextMatcher.Api.Contracts.TextMatches;
using Nocr.TextMatcher.Api.Contracts.TextMatches.Dto;
using Nocr.TextMatcher.AppServices.TextMatches;
using Nocr.TextMatcher.AppServices.TextMatches.Services;
namespace Nocr.TextMatcher.Host.Controllers;
[ApiController]
[Route(WebRoutes.TextMatches.Path)]
public class TextMatchController : ControllerBase
{
private readonly ITextMatchService _textMatchService;
public TextMatchController(ITextMatchService textMatchService)
{
_textMatchService = textMatchService ?? throw new ArgumentNullException(nameof(textMatchService));
}
[HttpPost]
public Task<long> Create([FromBody] CreateTextMatchRequest request, CancellationToken cancellationToken = default)
{
return _textMatchService.Create(request.UserId, request.ChatId, request.Template, request.Rule,
cancellationToken);
}
}

View File

@ -12,7 +12,7 @@ public class HostBuilderFactory<TStartup> where TStartup : class
if (!string.IsNullOrWhiteSpace(baseDirectory))
configurationBuilder.SetBasePath(baseDirectory);
configurationBuilder.AddJsonFile("appsettings.protected.json", false);
configurationBuilder.AddJsonFile("appsettings.protected.json", true);
})
.ConfigureWebHostDefaults(host => { host.UseStartup<TStartup>(); })
.UseSerilog((ctx, logBuilder) =>

View File

@ -1,5 +1,11 @@
using Microsoft.Extensions.Options;
using Nocr.TextMatcher.AppServices;
using Nocr.TextMatcher.Core.Dates;
using Nocr.TextMatcher.Core.Options;
using Rebus.Bus;
using Rebus.Config;
using Rebus.Routing.TypeBased;
using Rebus.Serialization.Json;
namespace Nocr.TextMatcher.Host.Infrastructure;
@ -16,10 +22,39 @@ public class Startup
{
services.AddSingleton<ICurrentDateProvider, DefaultCurrentDateProvider>();
services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.AddAppServices();
services.Configure<RebusRabbitMqOptions>(Configuration.GetSection(nameof(RebusRabbitMqOptions)));
services.AddRebus((builder, ctx) =>
builder.Transport(t =>
{
var rebusOptions = ctx.GetRequiredService<IOptions<RebusRabbitMqOptions>>().Value;
t.UseRabbitMq(rebusOptions.ConnectionString, rebusOptions.InputQueueName)
.DefaultQueueOptions(queue => queue.SetDurable(true))
.ExchangeNames(rebusOptions.DirectExchangeName, rebusOptions.TopicsExchangeName);
})
.Serialization(s => s.UseSystemTextJson())
.Logging(l => l.Serilog())
.Routing(r => r.TypeBased()));
}
public void Configure(IApplicationBuilder app)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (!env.IsProduction())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting();
app.UseEndpoints(builder => builder.MapControllers());
var bus = app.ApplicationServices.GetRequiredService<IBus>();
bus.Advanced.Topics.Subscribe("nocr.telegram.listener").GetAwaiter().GetResult();
}
}

View File

@ -6,9 +6,18 @@
<ItemGroup>
<PackageReference Include="Serilog.AspNetCore" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Swashbuckle.AspNetCore" />
</ItemGroup>
<ItemGroup Label="Rebus">
<PackageReference Include="Rebus.ServiceProvider" />
<PackageReference Include="Rebus.RabbitMq" />
<PackageReference Include="Rebus.Serilog" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Nocr.TextMatcher.Api.Contracts\Nocr.TextMatcher.Api.Contracts.csproj" />
<ProjectReference Include="..\Nocr.TextMatcher.AppServices\Nocr.TextMatcher.AppServices.csproj" />
</ItemGroup>

View File

@ -8,5 +8,8 @@
}
}
]
},
"RebusRabbitMqOptions": {
"ConnectionString": "amqp://admin:admin@localhost:5672/"
}
}

View File

@ -0,0 +1,28 @@
{
"Serilog": {
"MinimumLevel": {
"Default": "Information"
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Level:u3}] {Timestamp:MM-dd HH:mm:ss} {TraceId} {SourceContext:l} {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "/var/log/nocr/text-matcher/nocr-text-matcher-.log",
"outputTemplate": "[{Level:u3}] {Timestamp:dd-MM-yyyy HH:mm:ss} {TraceId} {SourceContext:l} {Message:lj}{NewLine}{Exception}",
"fileSizeLimitBytes": 104857600,
"rollingInterval": "Day",
"rollOnFileSizeLimit": true
}
}
]
},
"RebusRabbitMqOptions": {
"ConnectionString": "amqp://admin:admin@nocr-rabbitmq:5672/"
}
}

View File

@ -21,5 +21,8 @@
}
}
]
},
"RebusRabbitMqOptions": {
"ConnectionString": "amqp://admin:admin@nocr-rabbitmq:5672/"
}
}

View File

@ -5,8 +5,14 @@
"Override": {
"Microsoft": "Information",
"Microsoft.AspNetCore": "Error",
"System.Net.Http.HttpClient": "Warning"
"System.Net.Http.HttpClient": "Warning",
"Rebus": "Warning"
}
}
},
"RebusRabbitMqOptions": {
"InputQueueName": "nocr.text.matcher.queue",
"DirectExchangeName": "nocr.direct",
"TopicsExchangeName": "nocr.topics"
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Nocr.TextMatcher.AppServices\Nocr.TextMatcher.AppServices.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,68 @@
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
using Nocr.TextMatcher.AppServices.TextMatches;
using Xunit;
namespace Nocr.TextMatcher.AppServices.UnitTests;
public class TextMatchTests
{
private const long UserId = 1;
private const long ChatId = 1;
private static DateTimeOffset CreatedDateTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
[Fact]
public void IsMatches_SameChatId_FullRule_MatchesText()
{
// Arrange
var match = TextMatch.Initialize(UserId, ChatId, "велосипед", TextMatchRule.Full, CreatedDateTime);
var text = "Продам снежный велосипед 100 лари. Гудаури.";
// Act
var result = match.IsMatches(ChatId, text);
// Assert
Assert.True(result);
}
[Fact]
public void IsMatches_SameChatId_AnyWord_MatchesText()
{
// Arrange
var match = TextMatch.Initialize(UserId, ChatId, "iphone айфон", TextMatchRule.AnyWord, CreatedDateTime);
var text = "Продам айфон велосипед 100 лари. Гудаури.";
// Act
var result = match.IsMatches(ChatId, text);
// Assert
Assert.True(result);
}
[Fact]
public void IsMatches_SameChatId_AllWords_MatchesText()
{
// Arrange
var match = TextMatch.Initialize(UserId, ChatId, "iphone айфон", TextMatchRule.AnyWord, CreatedDateTime);
var text = "Гомарджоба. Продам iphone (айфон) 1000 лари. Гудаури.";
// Act
var result = match.IsMatches(ChatId, text);
// Assert
Assert.True(result);
}
[Fact]
public void IsMatches_DifferentChatId_NotMatchesText()
{
// Arrange
var match = TextMatch.Initialize(UserId, ChatId, "iphone", TextMatchRule.Full, CreatedDateTime);
// Act
var result = match.IsMatches(ChatId, string.Empty);
// Assert
Assert.False(result);
}
}