From 3cdc058fb571f24d101a7d8a257627934f58185e Mon Sep 17 00:00:00 2001 From: Rikitav Date: Sat, 7 Mar 2026 00:17:31 +0400 Subject: [PATCH] * Added missing summaries --- ...plicitHandlerBuilderExtensionsGenerator.cs | 437 +++++++------ docs/Telegrator.Hosting.Web.xml | 16 +- docs/Telegrator.Hosting.xml | 30 + .../GlobalSuppressions.cs | 1 + .../Hosting.Web/TelegramBotWebHost.cs | 17 +- .../Hosting.Web/TelegramBotWebHostBuilder.cs | 37 +- src/Telegrator.Hosting.Web/TypesExtensions.cs | 24 +- src/Telegrator.Hosting/GlobalSuppressions.cs | 1 + .../Hosting/TelegramBotHost.cs | 12 +- src/Telegrator.Hosting/TypesExtensions.cs | 435 +++++++------ src/Telegrator.Localized/ILocalizedHandler.cs | 9 +- .../ILocalizedMessageHandler.cs | 9 +- src/Telegrator/ContinuousAction.cs | 22 + .../Descriptors/DescribedHandlerDescriptor.cs | 16 +- src/Telegrator/Core/IUpdateReceiver.cs | 4 +- .../Mediation/UpdateHandlersPool.cs | 9 + .../Collections/CollectionTests.cs | 607 +++++++++--------- tests/Telegrator.Tests/Filters/FilterTests.cs | 323 +++++----- 18 files changed, 1045 insertions(+), 964 deletions(-) diff --git a/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs index 6bf4d0f..54af759 100644 --- a/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs +++ b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs @@ -9,83 +9,107 @@ using Telegrator.RoslynGenerators.RoslynExtensions; using System.Diagnostics; #endif -namespace Telegrator.RoslynGenerators +namespace Telegrator.RoslynGenerators; + +[Generator(LanguageNames.CSharp)] +public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator { - [Generator(LanguageNames.CSharp)] - public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator + private static readonly string[] DefaultUsings = + [ + "Telegrator.Handlers.Building", + "Telegrator.Core.Handlers.Building" + ]; + + private static readonly ParameterSyntax ExtensionMethodThisParam = SyntaxFactory.Parameter(SyntaxFactory.Identifier("builder")).WithType(SyntaxFactory.IdentifierName("TBuilder").WithLeadingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ")).WithTrailingTrivia(WhitespaceTrivia)).WithModifiers([SyntaxFactory.Token(SyntaxKind.ThisKeyword)]); + private static readonly MemberAccessExpressionSyntax BuilderAdderMethodAccessExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName("builder"), SyntaxFactory.IdentifierName("AddTargetedFilters")); + private static readonly IEqualityComparer UsingEqualityComparer = new UsingDirectiveEqualityComparer(); + + private static SyntaxTrivia WhitespaceTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); + private static SyntaxTrivia NewLineTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n"); + + public void Initialize(IncrementalGeneratorInitializationContext context) { - private static readonly string[] DefaultUsings = - [ - "Telegrator.Handlers.Building", - "Telegrator.Core.Handlers.Building" - ]; - - private static readonly ParameterSyntax ExtensionMethodThisParam = SyntaxFactory.Parameter(SyntaxFactory.Identifier("builder")).WithType(SyntaxFactory.IdentifierName("TBuilder").WithLeadingTrivia(SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ")).WithTrailingTrivia(WhitespaceTrivia)).WithModifiers([SyntaxFactory.Token(SyntaxKind.ThisKeyword)]); - private static readonly MemberAccessExpressionSyntax BuilderAdderMethodAccessExpression = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName("builder"), SyntaxFactory.IdentifierName("AddTargetedFilters")); - private static readonly IEqualityComparer UsingEqualityComparer = new UsingDirectiveEqualityComparer(); + IncrementalValueProvider> pipeline = context.SyntaxProvider + .CreateSyntaxProvider(SyntaxPredicate, SyntaxTransform) + .Where(declaration => declaration != null) + .Collect(); - private static SyntaxTrivia WhitespaceTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); - private static SyntaxTrivia NewLineTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n"); + context.RegisterImplementationSourceOutput(pipeline, GenerateSource); + } - public void Initialize(IncrementalGeneratorInitializationContext context) + private static bool SyntaxPredicate(SyntaxNode node, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return node is ClassDeclarationSyntax; + } + + private static ClassDeclarationSyntax SyntaxTransform(GeneratorSyntaxContext context, CancellationToken _) + { + ISymbol? symbol = context.SemanticModel.GetDeclaredSymbol(context.Node); + if (symbol is null) + return null!; + + if (symbol is not ITypeSymbol typeSymbol) + return null!; + + if (!typeSymbol.IsAssignableFrom("UpdateFilterAttribute")) + return null!; + + return (ClassDeclarationSyntax)context.Node; + } + + private static void GenerateSource(SourceProductionContext context, ImmutableArray declarations) + { + StringBuilder debugExport = new StringBuilder("/*"); + List usings = ParseUsings(DefaultUsings).ToList(); + + Dictionary targetters = []; + foreach (ClassDeclarationSyntax classDeclaration in declarations) { - IncrementalValueProvider> pipeline = context.SyntaxProvider - .CreateSyntaxProvider(SyntaxPredicate, SyntaxTransform) - .Where(declaration => declaration != null) - .Collect(); + try + { + string className = classDeclaration.Identifier.ToString(); + if (className == "FilterAnnotation") + continue; - context.RegisterImplementationSourceOutput(pipeline, GenerateSource); + MethodDeclarationSyntax? targeter = classDeclaration.Members.OfType().SingleOrDefault(IsTargeterMethod); + if (targeter != null) + { + try + { + MethodDeclarationSyntax genTargeter = GenerateTargetterMethod(classDeclaration, targeter); + targetters.Add(className, genTargeter); + } + catch (Exception exc) + { + string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); + debugExport.AppendLine(errorFormat); + } + } + } + catch (Exception exc) + { + string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); + debugExport.AppendLine(errorFormat); + } } - - private static bool SyntaxPredicate(SyntaxNode node, CancellationToken cancellationToken) + + List extensions = []; + foreach (ClassDeclarationSyntax classDeclaration in declarations) { - cancellationToken.ThrowIfCancellationRequested(); - return node is ClassDeclarationSyntax; - } + if (classDeclaration.Modifiers.HasModifiers("abstract")) + continue; - private static ClassDeclarationSyntax SyntaxTransform(GeneratorSyntaxContext context, CancellationToken _) - { - ISymbol? symbol = context.SemanticModel.GetDeclaredSymbol(context.Node); - if (symbol is null) - return null!; + usings.UnionAdd(classDeclaration.FindAncestor().Usings, UsingEqualityComparer); + MethodDeclarationSyntax targeter = FindTargetterMethod(targetters, classDeclaration); - if (symbol is not ITypeSymbol typeSymbol) - return null!; - - if (!typeSymbol.IsAssignableFrom("UpdateFilterAttribute")) - return null!; - - return (ClassDeclarationSyntax)context.Node; - } - - private static void GenerateSource(SourceProductionContext context, ImmutableArray declarations) - { - StringBuilder debugExport = new StringBuilder("/*"); - List usings = ParseUsings(DefaultUsings).ToList(); - - Dictionary targetters = []; - foreach (ClassDeclarationSyntax classDeclaration in declarations) + if (classDeclaration.ParameterList != null && classDeclaration.BaseList != null) { try { - string className = classDeclaration.Identifier.ToString(); - if (className == "FilterAnnotation") - continue; - - MethodDeclarationSyntax? targeter = classDeclaration.Members.OfType().SingleOrDefault(IsTargeterMethod); - if (targeter != null) - { - try - { - MethodDeclarationSyntax genTargeter = GenerateTargetterMethod(classDeclaration, targeter); - targetters.Add(className, genTargeter); - } - catch (Exception exc) - { - string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); - debugExport.AppendLine(errorFormat); - } - } + PrimaryConstructorBaseTypeSyntax primaryConstructor = (PrimaryConstructorBaseTypeSyntax)classDeclaration.BaseList.Types.ElementAt(0); + MethodDeclarationSyntax genExtension = GeneratedExtensionsMethod(classDeclaration, classDeclaration.ParameterList, primaryConstructor.ArgumentList, targeter); + extensions.Add(genExtension); } catch (Exception exc) { @@ -93,192 +117,167 @@ namespace Telegrator.RoslynGenerators debugExport.AppendLine(errorFormat); } } - - List extensions = []; - foreach (ClassDeclarationSyntax classDeclaration in declarations) + + foreach (ConstructorDeclarationSyntax ctor in GetConstructors(classDeclaration)) { - if (classDeclaration.Modifiers.HasModifiers("abstract")) - continue; - - usings.UnionAdd(classDeclaration.FindAncestor().Usings, UsingEqualityComparer); - MethodDeclarationSyntax targeter = FindTargetterMethod(targetters, classDeclaration); - - if (classDeclaration.ParameterList != null && classDeclaration.BaseList != null) + try { - try - { - PrimaryConstructorBaseTypeSyntax primaryConstructor = (PrimaryConstructorBaseTypeSyntax)classDeclaration.BaseList.Types.ElementAt(0); - MethodDeclarationSyntax genExtension = GeneratedExtensionsMethod(classDeclaration, classDeclaration.ParameterList, primaryConstructor.ArgumentList, targeter); - extensions.Add(genExtension); - } - catch (Exception exc) - { - string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); - debugExport.AppendLine(errorFormat); - } + if (ctor.Initializer == null) + continue; + + MethodDeclarationSyntax genExtension = GeneratedExtensionsMethod(classDeclaration, ctor.ParameterList, ctor.Initializer.ArgumentList, targeter); + extensions.Add(genExtension); } - - foreach (ConstructorDeclarationSyntax ctor in GetConstructors(classDeclaration)) + catch (Exception exc) { - try - { - if (ctor.Initializer == null) - continue; - - MethodDeclarationSyntax genExtension = GeneratedExtensionsMethod(classDeclaration, ctor.ParameterList, ctor.Initializer.ArgumentList, targeter); - extensions.Add(genExtension); - } - catch (Exception exc) - { - string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); - debugExport.AppendLine(errorFormat); - } + string errorFormat = string.Format("\nFailed to generate for {0} : {1}\n", classDeclaration.Identifier.ToString(), exc.ToString()); + debugExport.AppendLine(errorFormat); } } - - try - { - ClassDeclarationSyntax extensionsClass = SyntaxFactory.ClassDeclaration("HandlerBuilderExtensions") - .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PartialKeyword)) - .AddMembers([.. targetters.Values, .. extensions]) - .DecorateType(1); - - NamespaceDeclarationSyntax namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Telegrator")) - .WithMembers([extensionsClass]) - .Decorate(); - - CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit() - .WithUsings([.. usings]) - .WithMembers([namespaceDeclaration]); - - context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString()); - } - catch (Exception exc) - { - string errorFormat = string.Format("\nFailed to generate : {0}\n", exc.ToString()); - debugExport.AppendLine(errorFormat); - } - - context.AddSource("GeneratedHandlerBuilderExtensions.Debug.cs", debugExport.AppendLine("*/").ToString()); } - private static MethodDeclarationSyntax GenerateTargetterMethod(ClassDeclarationSyntax classDeclaration, MethodDeclarationSyntax targetterMethod) + try { - SyntaxToken identifier = SyntaxFactory.Identifier(classDeclaration.Identifier.ToString() + "_" + targetterMethod.Identifier.ToString()); - MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(targetterMethod.ReturnType, identifier) - .WithParameterList(targetterMethod.ParameterList) - .WithModifiers(Modifiers(SyntaxKind.PrivateKeyword, SyntaxKind.StaticKeyword)); + ClassDeclarationSyntax extensionsClass = SyntaxFactory.ClassDeclaration("HandlerBuilderExtensions") + .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PartialKeyword)) + .AddMembers([.. targetters.Values, .. extensions]) + .DecorateType(1); - if (targetterMethod.Body != null) - method = method.WithBody(targetterMethod.Body); + NamespaceDeclarationSyntax namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Telegrator")) + .WithMembers([extensionsClass]) + .Decorate(); - if (targetterMethod.ExpressionBody != null) - method = method.WithExpressionBody(targetterMethod.ExpressionBody).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit() + .WithUsings([.. usings]) + .WithMembers([namespaceDeclaration]); - return method.DecorateMember(2); + context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString()); + } + catch (Exception exc) + { + string errorFormat = string.Format("\nFailed to generate : {0}\n", exc.ToString()); + debugExport.AppendLine(errorFormat); } - private static MethodDeclarationSyntax GeneratedExtensionsMethod(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters, ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod) + context.AddSource("GeneratedHandlerBuilderExtensions.Debug.cs", debugExport.AppendLine("*/").ToString()); + } + + private static MethodDeclarationSyntax GenerateTargetterMethod(ClassDeclarationSyntax classDeclaration, MethodDeclarationSyntax targetterMethod) + { + SyntaxToken identifier = SyntaxFactory.Identifier(classDeclaration.Identifier.ToString() + "_" + targetterMethod.Identifier.ToString()); + MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(targetterMethod.ReturnType, identifier) + .WithParameterList(targetterMethod.ParameterList) + .WithModifiers(Modifiers(SyntaxKind.PrivateKeyword, SyntaxKind.StaticKeyword)); + + if (targetterMethod.Body != null) + method = method.WithBody(targetterMethod.Body); + + if (targetterMethod.ExpressionBody != null) + method = method.WithExpressionBody(targetterMethod.ExpressionBody).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + + return method.DecorateMember(2); + } + + private static MethodDeclarationSyntax GeneratedExtensionsMethod(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters, ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod) + { + ParameterListSyntax parameters = SyntaxFactory.ParameterList([ExtensionMethodThisParam, ..methodParameters.Parameters]); + TypeParameterListSyntax typeParameters = SyntaxFactory.TypeParameterList([SyntaxFactory.TypeParameter("TBuilder")]); + + InvocationExpressionSyntax invocationExpression = SyntaxFactory.InvocationExpression(BuilderAdderMethodAccessExpression, AddTargeter(invokerArguments, targetterMethod)); + BlockSyntax body = SyntaxFactory.Block(new StatementSyntax[] { - ParameterListSyntax parameters = SyntaxFactory.ParameterList([ExtensionMethodThisParam, ..methodParameters.Parameters]); - TypeParameterListSyntax typeParameters = SyntaxFactory.TypeParameterList([SyntaxFactory.TypeParameter("TBuilder")]); + SyntaxFactory.ExpressionStatement(invocationExpression), + SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName("builder").WithLeadingTrivia(WhitespaceTrivia)) + }); - InvocationExpressionSyntax invocationExpression = SyntaxFactory.InvocationExpression(BuilderAdderMethodAccessExpression, AddTargeter(invokerArguments, targetterMethod)); - BlockSyntax body = SyntaxFactory.Block(new StatementSyntax[] - { - SyntaxFactory.ExpressionStatement(invocationExpression), - SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName("builder").WithLeadingTrivia(WhitespaceTrivia)) - }); + TypeParameterConstraintClauseSyntax typeParameterConstraint = SyntaxFactory.TypeParameterConstraintClause(SyntaxFactory.IdentifierName("TBuilder").WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(WhitespaceTrivia)) + .WithConstraints([SyntaxFactory.TypeConstraint(SyntaxFactory.ParseTypeName("IHandlerBuilder").WithLeadingTrivia(WhitespaceTrivia))]) + .WithLeadingTrivia(WhitespaceTrivia); - TypeParameterConstraintClauseSyntax typeParameterConstraint = SyntaxFactory.TypeParameterConstraintClause(SyntaxFactory.IdentifierName("TBuilder").WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(WhitespaceTrivia)) - .WithConstraints([SyntaxFactory.TypeConstraint(SyntaxFactory.ParseTypeName("IHandlerBuilder").WithLeadingTrivia(WhitespaceTrivia))]) - .WithLeadingTrivia(WhitespaceTrivia); + string filterName = classDeclaration.Identifier.ToString().Replace("Attribute", string.Empty); + if (filterName == "ChatType") + filterName = "InChatType"; // Because it conflicting - string filterName = classDeclaration.Identifier.ToString().Replace("Attribute", string.Empty); - if (filterName == "ChatType") - filterName = "InChatType"; // Because it conflicting + SyntaxToken identifier = SyntaxFactory.Identifier(filterName); + TypeSyntax returnType = SyntaxFactory.ParseTypeName("TBuilder").WithTrailingTrivia(WhitespaceTrivia); + SyntaxTriviaList xmlDoc = BuildExtensionXmlDocTrivia(classDeclaration, methodParameters); - SyntaxToken identifier = SyntaxFactory.Identifier(filterName); - TypeSyntax returnType = SyntaxFactory.ParseTypeName("TBuilder").WithTrailingTrivia(WhitespaceTrivia); - SyntaxTriviaList xmlDoc = BuildExtensionXmlDocTrivia(classDeclaration, methodParameters); + MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(returnType, identifier) + .WithParameterList(parameters) + .WithBody(body.DecorateBlock(2)) + .WithTypeParameterList(typeParameters) + .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword)) + .WithConstraintClauses([typeParameterConstraint]) + .DecorateMember(2) + .WithLeadingTrivia(xmlDoc); + + return method; + } - MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(returnType, identifier) - .WithParameterList(parameters) - .WithBody(body.DecorateBlock(2)) - .WithTypeParameterList(typeParameters) - .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword)) - .WithConstraintClauses([typeParameterConstraint]) - .DecorateMember(2) - .WithLeadingTrivia(xmlDoc); - - return method; - } + private static SyntaxTokenList Modifiers(params SyntaxKind[] kinds) + => new SyntaxTokenList(kinds.Select(SyntaxFactory.Token).Select(mod => mod.WithTrailingTrivia(WhitespaceTrivia))); - private static SyntaxTokenList Modifiers(params SyntaxKind[] kinds) - => new SyntaxTokenList(kinds.Select(SyntaxFactory.Token).Select(mod => mod.WithTrailingTrivia(WhitespaceTrivia))); + private static IEnumerable ParseUsings(params string[] names) => names + .Select(name => SyntaxFactory.IdentifierName(name).WithLeadingTrivia(WhitespaceTrivia)) + .Select(name => SyntaxFactory.UsingDirective(name).WithTrailingTrivia(NewLineTrivia)); - private static IEnumerable ParseUsings(params string[] names) => names - .Select(name => SyntaxFactory.IdentifierName(name).WithLeadingTrivia(WhitespaceTrivia)) - .Select(name => SyntaxFactory.UsingDirective(name).WithTrailingTrivia(NewLineTrivia)); + private static ArgumentListSyntax AddTargeter(ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod) + => SyntaxFactory.ArgumentList([SyntaxFactory.Argument(SyntaxFactory.IdentifierName(targetterMethod.Identifier)), ..invokerArguments.Arguments]); - private static ArgumentListSyntax AddTargeter(ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod) - => SyntaxFactory.ArgumentList([SyntaxFactory.Argument(SyntaxFactory.IdentifierName(targetterMethod.Identifier)), ..invokerArguments.Arguments]); + private static bool IsTargeterMethod(MethodDeclarationSyntax method) + => method.Identifier.ToString() == "GetFilterringTarget"; - private static bool IsTargeterMethod(MethodDeclarationSyntax method) - => method.Identifier.ToString() == "GetFilterringTarget"; + private static IEnumerable GetConstructors(ClassDeclarationSyntax classDeclaration) + => classDeclaration.Members.OfType().Where(ctor => ctor.Modifiers.HasModifiers("public")); - private static IEnumerable GetConstructors(ClassDeclarationSyntax classDeclaration) - => classDeclaration.Members.OfType().Where(ctor => ctor.Modifiers.HasModifiers("public")); + private static MethodDeclarationSyntax FindTargetterMethod(Dictionary targeters, ClassDeclarationSyntax classDeclaration) + { + if (targeters.TryGetValue(classDeclaration.Identifier.ValueText, out MethodDeclarationSyntax targeter)) + return targeter; - private static MethodDeclarationSyntax FindTargetterMethod(Dictionary targeters, ClassDeclarationSyntax classDeclaration) + if (classDeclaration.BaseList != null && targeters.TryGetValue(classDeclaration.BaseList.Types.ElementAt(0).Type.ToString(), out targeter)) + return targeter; + + throw new TargteterNotFoundException(); + } + + private static SyntaxTriviaList BuildExtensionXmlDocTrivia(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters) + { + StringBuilder summaryBuilder = new StringBuilder(); + + summaryBuilder + .Append("\t\t/// \n") + .Append("\t\t/// Adds a ").Append(classDeclaration.Identifier.ToString()).Append(" target filter to the handler builder.\n") + .Append("\t\t/// \n"); + + summaryBuilder + .AppendLine("\t\t/// The builder type.") + .AppendLine("\t\t/// The handler builder."); + + foreach (ParameterSyntax param in methodParameters.Parameters) { - if (targeters.TryGetValue(classDeclaration.Identifier.ValueText, out MethodDeclarationSyntax targeter)) - return targeter; - - if (classDeclaration.BaseList != null && targeters.TryGetValue(classDeclaration.BaseList.Types.ElementAt(0).Type.ToString(), out targeter)) - return targeter; - - throw new TargteterNotFoundException(); - } - - private static SyntaxTriviaList BuildExtensionXmlDocTrivia(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters) - { - StringBuilder summaryBuilder = new StringBuilder(); - + string name = param.Identifier.ToString(); summaryBuilder - .Append("\t\t/// \n") - .Append("\t\t/// Adds a ").Append(classDeclaration.Identifier.ToString()).Append(" target filter to the handler builder.\n") - .Append("\t\t/// \n"); - - summaryBuilder - .AppendLine("\t\t/// The builder type.") - .AppendLine("\t\t/// The handler builder."); - - foreach (ParameterSyntax param in methodParameters.Parameters) - { - string name = param.Identifier.ToString(); - summaryBuilder - .Append("\t\t/// ") - .Append("The ").Append(name) - .AppendLine("."); - } - - summaryBuilder.AppendLine("\t\t/// The same builder instance."); - summaryBuilder.Append("\t\t"); - return SyntaxFactory.ParseLeadingTrivia(summaryBuilder.ToString()); + .Append("\t\t/// ") + .Append("The ").Append(name) + .AppendLine("."); } - private class UsingDirectiveEqualityComparer : IEqualityComparer - { - public bool Equals(UsingDirectiveSyntax x, UsingDirectiveSyntax y) - { - return x.ToString() == y.ToString(); - } + summaryBuilder.AppendLine("\t\t/// The same builder instance."); + summaryBuilder.Append("\t\t"); + return SyntaxFactory.ParseLeadingTrivia(summaryBuilder.ToString()); + } - public int GetHashCode(UsingDirectiveSyntax obj) - { - return obj.GetHashCode(); - } + private class UsingDirectiveEqualityComparer : IEqualityComparer + { + public bool Equals(UsingDirectiveSyntax x, UsingDirectiveSyntax y) + { + return x.ToString() == y.ToString(); + } + + public int GetHashCode(UsingDirectiveSyntax obj) + { + return obj.GetHashCode(); } } } diff --git a/docs/Telegrator.Hosting.Web.xml b/docs/Telegrator.Hosting.Web.xml index e3d47ff..9c21b40 100644 --- a/docs/Telegrator.Hosting.Web.xml +++ b/docs/Telegrator.Hosting.Web.xml @@ -34,12 +34,11 @@ This application's logger - + Initializes a new instance of the class. The proxied instance of host builder. - @@ -209,6 +208,14 @@ Provides method to configure + + + The key used to store the in the builder properties. + + + + + Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers. @@ -227,5 +234,10 @@ + + + Gets the from the builder properties. + + diff --git a/docs/Telegrator.Hosting.xml b/docs/Telegrator.Hosting.xml index 830efa0..32ffc46 100644 --- a/docs/Telegrator.Hosting.xml +++ b/docs/Telegrator.Hosting.xml @@ -396,11 +396,29 @@ The configured TelegramBotClientOptions instance. + + + Provides extension methods for to configure Telegrator. + + + + + The key used to store the in the builder properties. + + + + + Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers. + + + Gets the from the builder properties. + + Contains extensions for @@ -476,5 +494,17 @@ True if the type implements IPreBuildingRoutine; otherwise, false. + + + Provides extension methods for logging Telegrator-related information. + + + + + Logs the registered handlers to the specified logger. + + The logger to write to. + The collection of handlers to log. + diff --git a/src/Telegrator.Hosting.Web/GlobalSuppressions.cs b/src/Telegrator.Hosting.Web/GlobalSuppressions.cs index e8cd1b7..d8586fa 100644 --- a/src/Telegrator.Hosting.Web/GlobalSuppressions.cs +++ b/src/Telegrator.Hosting.Web/GlobalSuppressions.cs @@ -8,3 +8,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Style", "IDE0290")] [assembly: SuppressMessage("Style", "IDE0090")] [assembly: SuppressMessage("Usage", "CA2254")] +[assembly: SuppressMessage("Maintainability", "CA1510")] diff --git a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs index 79961fe..9853fe1 100644 --- a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs +++ b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs @@ -49,11 +49,12 @@ namespace Telegrator.Hosting.Web /// Initializes a new instance of the class. /// /// The proxied instance of host builder. - /// - public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers) + public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder) { // Registering this host in services for easy access - RegisterHostServices(webApplicationBuilder.Services, handlers); + webApplicationBuilder.Services.AddSingleton(this); + webApplicationBuilder.Services.AddSingleton(this); + webApplicationBuilder.Services.AddSingleton(this); // Building proxy application _innerApp = webApplicationBuilder.Build(); @@ -161,15 +162,5 @@ namespace Telegrator.Hosting.Web GC.SuppressFinalize(this); _disposed = true; } - - private void RegisterHostServices(IServiceCollection services, IHandlersCollection handlers) - { - //service.RemoveAll(); - //service.AddSingleton(this); - - services.AddSingleton(this); - services.AddSingleton(this); - services.AddSingleton(this); - } } } diff --git a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs index 3f93927..8beee08 100644 --- a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs +++ b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs @@ -46,6 +46,8 @@ namespace Telegrator.Hosting.Web _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _handlers = new HostHandlersCollection(Services, _settings); + + _innerBuilder.AddTelegratorWeb(settings); } /// @@ -60,7 +62,7 @@ namespace Telegrator.Hosting.Web _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _handlers = handlers ?? throw new ArgumentNullException(nameof(settings)); - _innerBuilder.AddTelegratorWeb(settings); + _innerBuilder.AddTelegratorWeb(settings, handlers); } /// @@ -69,38 +71,7 @@ namespace Telegrator.Hosting.Web /// public TelegramBotWebHost Build() { - if (_handlers is IHostHandlersCollection hostHandlers) - { - foreach (PreBuildingRoutine preBuildRoutine in hostHandlers.PreBuilderRoutines) - { - try - { - preBuildRoutine.Invoke(this); - } - catch (NotImplementedException) - { - _ = 0xBAD + 0xC0DE; - } - } - } - - if (!_settings.DisableAutoConfigure) - { - Services.Configure(Configuration.GetSection(nameof(TelegratorWebOptions))); - Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); - } - else - { - if (null == Services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions))) - throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegratorWebOptions' wasn't registered. This configuration is runtime required!"); - - if (null == Services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions))) - throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegramBotClientOptions' wasn't registered. This configuration is runtime required!"); - } - - Services.AddSingleton(Configuration); - Services.AddSingleton>(Options.Create(_settings)); - return new TelegramBotWebHost(_innerBuilder, _handlers); + return new TelegramBotWebHost(_innerBuilder); } } } diff --git a/src/Telegrator.Hosting.Web/TypesExtensions.cs b/src/Telegrator.Hosting.Web/TypesExtensions.cs index eac0306..a8b5878 100644 --- a/src/Telegrator.Hosting.Web/TypesExtensions.cs +++ b/src/Telegrator.Hosting.Web/TypesExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Diagnostics; @@ -20,6 +21,19 @@ namespace Telegrator /// public static class ServicesCollectionExtensions { + /// + /// The key used to store the in the builder properties. + /// + public const string HandlersCollectionPropertyKey = nameof(IHandlersCollection); + + extension(IHostApplicationBuilder builder) + { + /// + /// Gets the from the builder properties. + /// + public IHandlersCollection Handlers => (IHandlersCollection)builder.Properties[HandlersCollectionPropertyKey]; + } + /// /// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers. /// @@ -32,6 +46,7 @@ namespace Telegrator ConfigurationManager configuration = builder.Configuration; handlers ??= new HostHandlersCollection(services, settings); + builder.Host.Properties.Add(HandlersCollectionPropertyKey, handlers); if (handlers is IHostHandlersCollection hostHandlers) { @@ -53,6 +68,7 @@ namespace Telegrator if (!settings.DisableAutoConfigure) { services.Configure(configuration.GetSection(nameof(TelegratorWebOptions))); + services.Configure(configuration.GetSection(nameof(TelegramBotClientOptions))); } else { @@ -92,8 +108,12 @@ namespace Telegrator ILoggerFactory loggerFactory = app.Services.GetRequiredService(); ILogger logger = loggerFactory.CreateLogger("Telegrator.Hosting.Web.TelegratorHost"); - logger.LogInformation("Telegrator Bot ASP.NET WebHost started"); - logger.LogHandlers(handlers); + if (logger.IsEnabled(LogLevel.Information)) + { + logger.LogInformation("Telegrator Bot ASP.NET WebHost started"); + logger.LogInformation("Telegram Bot : {firstname}, @{usrname}, id:{id},", info.User.FirstName ?? "[NULL]", info.User.Username ?? "[NULL]", info.User.Id); + logger.LogHandlers(handlers); + } return app; } diff --git a/src/Telegrator.Hosting/GlobalSuppressions.cs b/src/Telegrator.Hosting/GlobalSuppressions.cs index e8cd1b7..d8586fa 100644 --- a/src/Telegrator.Hosting/GlobalSuppressions.cs +++ b/src/Telegrator.Hosting/GlobalSuppressions.cs @@ -8,3 +8,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Style", "IDE0290")] [assembly: SuppressMessage("Style", "IDE0090")] [assembly: SuppressMessage("Usage", "CA2254")] +[assembly: SuppressMessage("Maintainability", "CA1510")] diff --git a/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs b/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs index 4d9863f..3c2b9ac 100644 --- a/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs +++ b/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs @@ -36,7 +36,8 @@ namespace Telegrator.Hosting public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers) { // Registering this host in services for easy access - RegisterHostServices(hostApplicationBuilder.Services, handlers); + hostApplicationBuilder.Services.AddSingleton(this); + hostApplicationBuilder.Services.AddSingleton(this); // Building proxy hoster _innerHost = hostApplicationBuilder.Build(); @@ -122,14 +123,5 @@ namespace Telegrator.Hosting GC.SuppressFinalize(this); _disposed = true; } - - private void RegisterHostServices(IServiceCollection services, IHandlersCollection handlers) - { - //services.RemoveAll(); - //services.AddSingleton(this); - - services.AddSingleton(this); - services.AddSingleton(this); - } } } diff --git a/src/Telegrator.Hosting/TypesExtensions.cs b/src/Telegrator.Hosting/TypesExtensions.cs index 1dbcbba..3de27b5 100644 --- a/src/Telegrator.Hosting/TypesExtensions.cs +++ b/src/Telegrator.Hosting/TypesExtensions.cs @@ -21,228 +21,257 @@ using Telegrator.Logging; using Telegrator.Polling; using Telegrator.Providers; -namespace Telegrator +namespace Telegrator; + +/// +/// Provides extension methods for to configure Telegrator. +/// +public static class HostBuilderExtensions { - public static class HostBuilderExtensions + /// + /// The key used to store the in the builder properties. + /// + public const string HandlersCollectionPropertyKey = nameof(IHandlersCollection); + + extension (IHostApplicationBuilder builder) { /// - /// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers. + /// Gets the from the builder properties. /// - public static IHostApplicationBuilder AddTelegrator(this IHostApplicationBuilder builder, TelegramBotHostBuilderSettings settings, IHandlersCollection? handlers = null) + public IHandlersCollection Handlers => (IHandlersCollection)builder.Properties[HandlersCollectionPropertyKey]; + } + + /// + /// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers. + /// + public static IHostApplicationBuilder AddTelegrator(this IHostApplicationBuilder builder, TelegramBotHostBuilderSettings settings, IHandlersCollection? handlers = null) + { + if (settings is null) + throw new ArgumentNullException(nameof(settings)); + + IServiceCollection services = builder.Services; + IConfigurationManager configuration = builder.Configuration; + + handlers ??= new HostHandlersCollection(services, settings); + builder.Properties.Add(HandlersCollectionPropertyKey, handlers); + + if (handlers is IHostHandlersCollection hostHandlers) { - if (settings is null) - throw new ArgumentNullException(nameof(settings)); - - IServiceCollection services = builder.Services; - IConfigurationManager configuration = builder.Configuration; - - handlers ??= new HostHandlersCollection(services, settings); - - if (handlers is IHostHandlersCollection hostHandlers) + foreach (PreBuildingRoutine preBuildRoutine in hostHandlers.PreBuilderRoutines) { - foreach (PreBuildingRoutine preBuildRoutine in hostHandlers.PreBuilderRoutines) + try { - try - { - // TODO: fix - //preBuildRoutine.Invoke(builder); - Debug.WriteLine("Pre-Building routine was not executed"); - } - catch (NotImplementedException) - { - _ = 0xBAD + 0xC0DE; - } + // TODO: fix + //preBuildRoutine.Invoke(builder); + Debug.WriteLine("Pre-Building routine was not executed"); + } + catch (NotImplementedException) + { + _ = 0xBAD + 0xC0DE; } } - - if (!settings.DisableAutoConfigure) - { - services.Configure(configuration.GetSection(nameof(ReceiverOptions))); - services.Configure(configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); - } - else - { - /* - if (null == Services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions))) - throw new MissingMemberException("Auto configuration disabled, yet no options of type 'ReceiverOptions' wasn't registered. This configuration is runtime required!"); - */ - - if (null == services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions))) - throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegramBotClientOptions' wasn't registered. This configuration is runtime required!"); - } - - IOptions options = Options.Create(settings); - services.AddSingleton((IOptions)options); - services.AddTelegramBotHostDefaults(); - services.AddSingleton(options); - services.AddSingleton(handlers); - - if (handlers is IHandlersManager manager) - { - ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IHandlersProvider), manager); - services.Replace(descriptor); - services.AddSingleton(manager); - } - - return builder; } - } - /// - /// Contains extensions for - /// Provides method to configure - /// - public static class ServicesCollectionExtensions - { - /// - /// Registers a configuration instance that strongly-typed will bind against using . - /// - /// - /// - /// - /// - /// - public static IServiceCollection Configure(this IServiceCollection services, IConfiguration configuration, ConfigureOptionsProxy optionsProxy) where TOptions : class + if (!settings.DisableAutoConfigure) { - optionsProxy.Configure(services, configuration); - return services; + services.Configure(configuration.GetSection(nameof(ReceiverOptions))); + services.Configure(configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); } - - /// - /// Registers default services - /// - /// - /// - public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services) + else { - services.AddLogging(builder => builder.AddConsole().AddDebug()); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + if (null == services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions))) + throw new MissingMemberException("Auto configuration disabled, yet no options of type 'ReceiverOptions' wasn't registered. This configuration is runtime required!"); - return services; + if (null == services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions))) + throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegramBotClientOptions' wasn't registered. This configuration is runtime required!"); } - /// - /// Registers service with to receive updates using long polling - /// - /// - /// - public static IServiceCollection AddTelegramReceiver(this IServiceCollection services) + IOptions options = Options.Create(settings); + services.AddSingleton((IOptions)options); + services.AddTelegramBotHostDefaults(); + services.AddSingleton(options); + services.AddSingleton(handlers); + + if (handlers is IHandlersManager manager) { - services.AddHttpClient("tgreceiver").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory); - services.AddHostedService(); - return services; + ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IHandlersProvider), manager); + services.Replace(descriptor); + services.AddSingleton(manager); } - /// - /// factory method - /// - /// - /// - /// - private static ITelegramBotClient TypedTelegramBotClientFactory(HttpClient httpClient, IServiceProvider provider) - => new TelegramBotClient(provider.GetRequiredService>().Value, httpClient); - } - - /// - /// Provides useful methods to adjust - /// - public static class TelegramBotHostExtensions - { - /// - /// Replaces the initialization logic from TelegramBotWebHost constructor. - /// Initializes the bot and logs handlers on application startup. - /// - public static IHost UseTelegrator(this IHost botHost) - { - ITelegramBotInfo info = botHost.Services.GetRequiredService(); - IHandlersCollection handlers = botHost.Services.GetRequiredService(); - ILoggerFactory loggerFactory = botHost.Services.GetRequiredService(); - ILogger logger = loggerFactory.CreateLogger("Telegrator.Hosting.Web.TelegratorHost"); - - logger.LogInformation("Telegrator Bot .NET Host started"); - logger.LogHandlers(handlers); - - return botHost; - } - - /// - /// Configures bots available commands depending on what handlers was registered - /// - /// - /// - public static IHost SetBotCommands(this IHost botHost) - { - ITelegramBotClient client = botHost.Services.GetRequiredService(); - IUpdateRouter router = botHost.Services.GetRequiredService(); - - IEnumerable aliases = router.HandlersProvider.GetBotCommands(); - client.SetMyCommands(aliases).Wait(); - return botHost; - } - - /// - /// Adds a Microsoft.Extensions.Logging adapter to Alligator using a logger factory. - /// - /// - public static IHost AddLoggingAdapter(this IHost host) - { - ILoggerFactory loggerFactory = host.Services.GetRequiredService(); - ILogger logger = loggerFactory.CreateLogger("Telegrator"); - - MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger); - TelegratorLogging.AddAdapter(adapter); - return host; - } - } - - /// - /// Provides extension methods for reflection and type inspection. - /// - public static class ReflectionExtensions - { - /// - /// Checks if a type implements the interface. - /// - /// The type to check. - /// - /// True if the type implements IPreBuildingRoutine; otherwise, false. - public static bool IsPreBuildingRoutine(this Type handlerType, [NotNullWhen(true)] out MethodInfo? routineMethod) - { - routineMethod = null; - if (handlerType.GetInterface(nameof(IPreBuildingRoutine)) == null) - return false; - - routineMethod = handlerType.GetMethod(nameof(IPreBuildingRoutine.PreBuildingRoutine), BindingFlags.Static | BindingFlags.Public); - return routineMethod != null; - } - } - - public static class LoggerExtensions - { - public static void LogHandlers(this ILogger logger, IHandlersCollection handlers) - { - StringBuilder logBuilder = new StringBuilder("Registered handlers : "); - if (!handlers.Keys.Any()) - throw new Exception(); - - foreach (UpdateType updateType in handlers.Keys) - { - HandlerDescriptorList descriptors = handlers[updateType]; - logBuilder.Append("\n\tUpdateType." + updateType + " :"); - - foreach (HandlerDescriptor descriptor in descriptors.Reverse()) - { - logBuilder.AppendFormat("\n\t* {0} - {1}", - descriptor.Indexer.ToString(), - descriptor.ToString()); - } - } - - logger.LogInformation(logBuilder.ToString()); - } + return builder; + } +} + +/// +/// Contains extensions for +/// Provides method to configure +/// +public static class ServicesCollectionExtensions +{ + /// + /// Registers a configuration instance that strongly-typed will bind against using . + /// + /// + /// + /// + /// + /// + public static IServiceCollection Configure(this IServiceCollection services, IConfiguration configuration, ConfigureOptionsProxy optionsProxy) where TOptions : class + { + optionsProxy.Configure(services, configuration); + return services; + } + + /// + /// Registers default services + /// + /// + /// + public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services) + { + services.AddLogging(builder => builder.AddConsole().AddDebug()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + /// + /// Registers service with to receive updates using long polling + /// + /// + /// + public static IServiceCollection AddTelegramReceiver(this IServiceCollection services) + { + services.AddHttpClient("tgreceiver").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory); + services.AddHostedService(); + return services; + } + + /// + /// factory method + /// + /// + /// + /// + private static ITelegramBotClient TypedTelegramBotClientFactory(HttpClient httpClient, IServiceProvider provider) + => new TelegramBotClient(provider.GetRequiredService>().Value, httpClient); +} + +/// +/// Provides useful methods to adjust +/// +public static class TelegramBotHostExtensions +{ + /// + /// Replaces the initialization logic from TelegramBotWebHost constructor. + /// Initializes the bot and logs handlers on application startup. + /// + public static IHost UseTelegrator(this IHost botHost) + { + ITelegramBotInfo info = botHost.Services.GetRequiredService(); + IHandlersCollection handlers = botHost.Services.GetRequiredService(); + ILoggerFactory loggerFactory = botHost.Services.GetRequiredService(); + ILogger logger = loggerFactory.CreateLogger("Telegrator.Hosting.Web.TelegratorHost"); + + if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information)) + { + logger.LogInformation("Telegrator Bot .NET Host started"); + logger.LogInformation("Telegram Bot : {firstname}, @{usrname}, id:{id},", info.User.FirstName ?? "[NULL]", info.User.Username ?? "[NULL]", info.User.Id); + logger.LogHandlers(handlers); + } + + return botHost; + } + + /// + /// Configures bots available commands depending on what handlers was registered + /// + /// + /// + public static IHost SetBotCommands(this IHost botHost) + { + ITelegramBotClient client = botHost.Services.GetRequiredService(); + IUpdateRouter router = botHost.Services.GetRequiredService(); + + IEnumerable aliases = router.HandlersProvider.GetBotCommands(); + client.SetMyCommands(aliases).Wait(); + return botHost; + } + + /// + /// Adds a Microsoft.Extensions.Logging adapter to Alligator using a logger factory. + /// + /// + public static IHost AddLoggingAdapter(this IHost host) + { + ILoggerFactory loggerFactory = host.Services.GetRequiredService(); + ILogger logger = loggerFactory.CreateLogger("Telegrator"); + + MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger); + TelegratorLogging.AddAdapter(adapter); + return host; + } +} + +/// +/// Provides extension methods for reflection and type inspection. +/// +public static class ReflectionExtensions +{ + /// + /// Checks if a type implements the interface. + /// + /// The type to check. + /// + /// True if the type implements IPreBuildingRoutine; otherwise, false. + public static bool IsPreBuildingRoutine(this Type handlerType, [NotNullWhen(true)] out MethodInfo? routineMethod) + { + routineMethod = null; + if (handlerType.GetInterface(nameof(IPreBuildingRoutine)) == null) + return false; + + routineMethod = handlerType.GetMethod(nameof(IPreBuildingRoutine.PreBuildingRoutine), BindingFlags.Static | BindingFlags.Public); + return routineMethod != null; + } +} + +/// +/// Provides extension methods for logging Telegrator-related information. +/// +public static class LoggerExtensions +{ + /// + /// Logs the registered handlers to the specified logger. + /// + /// The logger to write to. + /// The collection of handlers to log. + public static void LogHandlers(this ILogger logger, IHandlersCollection handlers) + { + if (!logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information)) + return; + + StringBuilder logBuilder = new StringBuilder("Registered handlers : "); + if (!handlers.Keys.Any()) + throw new Exception(); + + foreach (UpdateType updateType in handlers.Keys) + { + HandlerDescriptorList descriptors = handlers[updateType]; + logBuilder.Append("\n\tUpdateType." + updateType + " :"); + + foreach (HandlerDescriptor descriptor in descriptors.Reverse()) + { + logBuilder.AppendFormat("\n\t* {0} - {1}", + descriptor.Indexer.ToString(), + descriptor.ToString()); + } + } + + logger.LogInformation(logBuilder.ToString()); } } diff --git a/src/Telegrator.Localized/ILocalizedHandler.cs b/src/Telegrator.Localized/ILocalizedHandler.cs index 7a2ba25..11f60c5 100644 --- a/src/Telegrator.Localized/ILocalizedHandler.cs +++ b/src/Telegrator.Localized/ILocalizedHandler.cs @@ -2,10 +2,9 @@ using Telegram.Bot.Types; using Telegrator.Core.Handlers; -namespace Telegrator.Localized +namespace Telegrator.Localized; + +public interface ILocalizedHandler : IAbstractUpdateHandler where T : class { - public interface ILocalizedHandler : IAbstractUpdateHandler where T : class - { - public IStringLocalizer LocalizationProvider { get; } - } + public IStringLocalizer LocalizationProvider { get; } } diff --git a/src/Telegrator.Localized/ILocalizedMessageHandler.cs b/src/Telegrator.Localized/ILocalizedMessageHandler.cs index beafbee..32bb1b7 100644 --- a/src/Telegrator.Localized/ILocalizedMessageHandler.cs +++ b/src/Telegrator.Localized/ILocalizedMessageHandler.cs @@ -1,9 +1,8 @@ using Telegram.Bot.Types; -namespace Telegrator.Localized -{ - public interface ILocalizedMessageHandler : ILocalizedHandler - { +namespace Telegrator.Localized; + +public interface ILocalizedMessageHandler : ILocalizedHandler +{ - } } diff --git a/src/Telegrator/ContinuousAction.cs b/src/Telegrator/ContinuousAction.cs index c14a195..230bad6 100644 --- a/src/Telegrator/ContinuousAction.cs +++ b/src/Telegrator/ContinuousAction.cs @@ -5,6 +5,9 @@ using Telegrator.Logging; namespace Telegrator; +/// +/// Represents a continuous chat action that runs in the background until cancelled or disposed. +/// public class ContinuousAction : IDisposable { private readonly ITelegramBotClient _client; @@ -17,6 +20,14 @@ public class ContinuousAction : IDisposable private int _disposed; + /// + /// Initializes a new instance of the class. + /// + /// The Telegram bot client. + /// The target chat. + /// The action to perform continuously. + /// The delay between actions. Defaults to 4 seconds. + /// The cancellation token. public ContinuousAction(ITelegramBotClient client, ChatId chat, ChatAction action, TimeSpan? delay = null, CancellationToken cancellationToken = default) { _client = client; @@ -52,6 +63,9 @@ public class ContinuousAction : IDisposable } } + /// + /// Cancels the continuous action. + /// public void Cancel() { if (Interlocked.CompareExchange(ref _disposed, 0, 0) == 0) @@ -67,11 +81,17 @@ public class ContinuousAction : IDisposable } } + /// + /// Waits for the background worker task to complete. + /// public async Task WaitAsync() { await _workerTask.ConfigureAwait(false); } + /// + /// Disposes the instance and stops the continuous action. + /// public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 1) @@ -85,5 +105,7 @@ public class ContinuousAction : IDisposable { _linkedCts.Dispose(); } + + GC.SuppressFinalize(this); } } diff --git a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs index d05660b..3f85684 100644 --- a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs +++ b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs @@ -14,7 +14,7 @@ namespace Telegrator.Core.Descriptors private readonly ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false); /// - /// descriptor from that handler was described from + /// Descriptor from that handler was described from. /// public HandlerDescriptor From { get; } @@ -31,7 +31,7 @@ namespace Telegrator.Core.Descriptors /// /// The Telegram bot client used for this handler. /// - public ITelegramBotClient Client { get; } + public ITelegramBotClient Client { get; } /// /// The handler instance being described. @@ -71,9 +71,9 @@ namespace Telegrator.Core.Descriptors /// /// Initializes a new instance of the class. /// - /// descriptor from that handler was described from - /// + /// The descriptor from which this handler was described. /// The update router. + /// The awaiting provider. /// The Telegram bot client. /// The handler instance. /// The filter execution context. @@ -98,6 +98,10 @@ namespace Telegrator.Core.Descriptors DisplayString = displayString ?? fromDescriptor.HandlerType.Name; } + /// + /// Waits for the handler execution result. + /// + /// The cancellation token. public async Task AwaitResult(CancellationToken cancellationToken) { await Task.Yield(); @@ -105,6 +109,10 @@ namespace Telegrator.Core.Descriptors ResetEvent.Wait(cancellationToken); } + /// + /// Reports the execution result and signals completion. + /// + /// The execution result. public void ReportResult(Result? result) { if (result != null) diff --git a/src/Telegrator/Core/IUpdateReceiver.cs b/src/Telegrator/Core/IUpdateReceiver.cs index 2a3a2d0..c96e4be 100644 --- a/src/Telegrator/Core/IUpdateReceiver.cs +++ b/src/Telegrator/Core/IUpdateReceiver.cs @@ -4,8 +4,8 @@ using Telegram.Bot.Types; namespace Telegrator.Core; /// -/// Requests new s and processes them using provided instance< -/// /summary> +/// Requests new s and processes them using provided instance. +/// public interface IUpdateReceiver { /// diff --git a/src/Telegrator/Mediation/UpdateHandlersPool.cs b/src/Telegrator/Mediation/UpdateHandlersPool.cs index baa4160..dfab263 100644 --- a/src/Telegrator/Mediation/UpdateHandlersPool.cs +++ b/src/Telegrator/Mediation/UpdateHandlersPool.cs @@ -18,8 +18,14 @@ namespace Telegrator.Mediation /// protected readonly object SyncObj = new object(); + /// + /// The task responsible for reading and processing handlers from the channel. + /// protected readonly Task ChannelReaderTask; + /// + /// The channel used to queue handlers for execution. + /// protected readonly Channel ExecutionChannel; /// @@ -27,6 +33,9 @@ namespace Telegrator.Mediation /// protected readonly SemaphoreSlim? ExecutionLimiter; + /// + /// The update router associated with this pool. + /// protected readonly IUpdateRouter UpdateRouter; /// diff --git a/tests/Telegrator.Tests/Collections/CollectionTests.cs b/tests/Telegrator.Tests/Collections/CollectionTests.cs index dc49321..d28cec9 100644 --- a/tests/Telegrator.Tests/Collections/CollectionTests.cs +++ b/tests/Telegrator.Tests/Collections/CollectionTests.cs @@ -4,314 +4,313 @@ using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; using Xunit; -namespace Telegrator.Tests.Collections +namespace Telegrator.Tests.Collections; + +/// +/// Тесты для коллекций. +/// +/// ПАРАДИГМЫ ТЕСТИРОВАНИЯ: +/// 1. Collection Testing - тестирование коллекций и их операций +/// 2. List Testing - тестирование списков +/// 3. Indexing Testing - тестирование индексации +/// 4. Enumeration Testing - тестирование перечисления +/// 5. Capacity Testing - тестирование емкости коллекций +/// +public class CollectionTests { /// - /// Тесты для коллекций. + /// Тест для HandlerDescriptorList - создание списка. /// - /// ПАРАДИГМЫ ТЕСТИРОВАНИЯ: - /// 1. Collection Testing - тестирование коллекций и их операций - /// 2. List Testing - тестирование списков - /// 3. Indexing Testing - тестирование индексации - /// 4. Enumeration Testing - тестирование перечисления - /// 5. Capacity Testing - тестирование емкости коллекций + /// ПРИНЦИП: Тестируем создание коллекций /// - public class CollectionTests + [Fact] + public void HandlerDescriptorList_ShouldBeCreated() { - /// - /// Тест для HandlerDescriptorList - создание списка. - /// - /// ПРИНЦИП: Тестируем создание коллекций - /// - [Fact] - public void HandlerDescriptorList_ShouldBeCreated() - { - // Arrange & Act - var list = new HandlerDescriptorList(); + // Arrange & Act + var list = new HandlerDescriptorList(); - // Assert - list.Should().NotBeNull(); - list.Should().BeEmpty(); - } - - /// - /// Тест для HandlerDescriptorList - добавление дескриптора. - /// - /// ПРИНЦИП: Тестируем добавление элементов в коллекцию - /// - [Fact] - public void HandlerDescriptorList_Add_ShouldAddDescriptor() - { - // Arrange - var list = new HandlerDescriptorList(); - var descriptor = CreateTestDescriptor(UpdateType.Message); - - // Act - list.Add(descriptor); - - // Assert - list.Should().HaveCount(1); - list.Should().Contain(descriptor); - } - - /// - /// Тест для HandlerDescriptorList - добавление нескольких дескрипторов. - /// - /// ПРИНЦИП: Тестируем множественные операции - /// - [Fact] - public void HandlerDescriptorList_AddMultiple_ShouldAddAllDescriptors() - { - // Arrange - var list = new HandlerDescriptorList(); - var descriptor1 = CreateTestDescriptor(UpdateType.Message); - var descriptor2 = CreateTestDescriptor(UpdateType.CallbackQuery); - var descriptor3 = CreateTestDescriptor(UpdateType.InlineQuery); - - // Act - list.Add(descriptor1); - list.Add(descriptor2); - list.Add(descriptor3); - - // Assert - list.Should().HaveCount(3); - list.Should().Contain(descriptor1); - list.Should().Contain(descriptor2); - list.Should().Contain(descriptor3); - } - - /// - /// Тест для HandlerDescriptorList - получение по индексу. - /// - /// ПРИНЦИП: Тестируем индексацию коллекций - /// - [Fact] - public void HandlerDescriptorList_Indexer_ShouldReturnDescriptorAtIndex() - { - // Arrange - var descriptor = CreateTestDescriptor(UpdateType.Message); - var list = new HandlerDescriptorList - { - descriptor - }; - - // Act - var result = list[0]; - - // Assert - result.Should().Be(descriptor); - } - - /// - /// Тест для HandlerDescriptorList - получение по неверному индексу. - /// - /// ПРИНЦИП: Тестируем исключения при некорректном доступе - /// - [Theory] - [InlineData(-1)] - [InlineData(1)] - [InlineData(100)] - public void HandlerDescriptorList_IndexerWithInvalidIndex_ShouldThrowArgumentOutOfRangeException(int invalidIndex) - { - // Arrange - var list = new HandlerDescriptorList - { - CreateTestDescriptor(UpdateType.Message) - }; - - // Act & Assert - list.Invoking(l => _ = l[invalidIndex]) - .Should().Throw(); - } - - /// - /// Тест для HandlerDescriptorList - перечисление элементов. - /// - /// ПРИНЦИП: Тестируем перечисление коллекций - /// - [Fact] - public void HandlerDescriptorList_ShouldBeEnumerable() - { - // Arrange - var descriptor1 = CreateTestDescriptor(UpdateType.Message); - var descriptor2 = CreateTestDescriptor(UpdateType.CallbackQuery); - var list = new HandlerDescriptorList - { - descriptor1, - descriptor2 - }; - - // Act - var enumeratedItems = list.ToList(); - - // Assert - enumeratedItems.Should().HaveCount(2); - enumeratedItems.Should().Contain(descriptor1); - enumeratedItems.Should().Contain(descriptor2); - } - - /// - /// Тест для HandlerDescriptorList - очистка списка. - /// - /// ПРИНЦИП: Тестируем очистку коллекций - /// - [Fact] - public void HandlerDescriptorList_Clear_ShouldRemoveAllDescriptors() - { - // Arrange - var list = new HandlerDescriptorList - { - CreateTestDescriptor(UpdateType.Message), - CreateTestDescriptor(UpdateType.CallbackQuery) - }; - - // Act - list.Clear(); - - // Assert - list.Should().BeEmpty(); - list.Should().HaveCount(0); - } - - /// - /// Тест для HandlerDescriptorList - проверка содержания элемента. - /// - /// ПРИНЦИП: Тестируем поиск в коллекциях - /// - [Fact] - public void HandlerDescriptorList_Contains_ShouldReturnCorrectResult() - { - // Arrange - var list = new HandlerDescriptorList(); - var descriptor = CreateTestDescriptor(UpdateType.Message); - var nonExistentDescriptor = CreateTestDescriptor(UpdateType.CallbackQuery); - - // Act - list.Add(descriptor); - var containsExisting = list.Contains(descriptor); - var containsNonExistent = list.Contains(nonExistentDescriptor); - - // Assert - containsExisting.Should().BeTrue(); - containsNonExistent.Should().BeFalse(); - } - - /// - /// Тест для HandlerDescriptorList - удаление элемента. - /// - /// ПРИНЦИП: Тестируем удаление элементов из коллекций - /// - [Fact] - public void HandlerDescriptorList_Remove_ShouldRemoveDescriptor() - { - // Arrange - var list = new HandlerDescriptorList(); - var descriptor = CreateTestDescriptor(UpdateType.Message); - list.Add(descriptor); - - // Act - var removed = list.Remove(descriptor); - - // Assert - removed.Should().BeTrue(); - list.Should().BeEmpty(); - list.Should().NotContain(descriptor); - } - - /// - /// Тест для HandlerDescriptorList - удаление несуществующего элемента. - /// - /// ПРИНЦИП: Тестируем удаление несуществующих элементов - /// - [Fact] - public void HandlerDescriptorList_RemoveNonExistent_ShouldReturnFalse() - { - // Arrange - var list = new HandlerDescriptorList(); - var nonExistentDescriptor = CreateTestDescriptor(UpdateType.CallbackQuery); - - // Act - var removed = list.Remove(nonExistentDescriptor); - - // Assert - removed.Should().BeFalse(); - list.Should().BeEmpty(); - } - - /// - /// Тест для CompletedFiltersList - создание списка. - /// - /// ПРИНЦИП: Тестируем создание специализированных коллекций - /// - [Fact] - public void CompletedFiltersList_ShouldBeCreated() - { - // Arrange & Act - var list = new CompletedFiltersList(); - - // Assert - list.Should().NotBeNull(); - list.Should().BeEmpty(); - } - - /// - /// Тест для проверки производительности коллекций. - /// - /// ПРИНЦИП: Тестируем производительность при большом количестве элементов - /// - [Fact] - public void HandlerDescriptorList_ShouldHandleLargeNumberOfItems() - { - // Arrange - var list = new HandlerDescriptorList(); - var itemsCount = 1000; - - // Act - for (int i = 0; i < itemsCount; i++) - { - list.Add(CreateTestDescriptor(UpdateType.Message)); - } - - // Assert - list.Should().HaveCount(itemsCount); - } - - /// - /// Тест для проверки потокобезопасности (базовый тест). - /// - /// ПРИНЦИП: Тестируем базовую потокобезопасность - /// - [Fact] - public async void HandlerDescriptorList_ShouldHandleConcurrentAccess() - { - // Arrange - var list = new HandlerDescriptorList(); - var tasks = new List(); - - // Act - for (int i = 0; i < 10; i++) - { - tasks.Add(Task.Run(() => - { - for (int j = 0; j < 10; j++) - { - list.Add(CreateTestDescriptor(UpdateType.Message)); - } - })); - } - - await Task.WhenAll(tasks.ToArray()); - - // Assert - list.Should().HaveCount(100); - } - - /// - /// Вспомогательный метод для создания тестового дескриптора. - /// - private static HandlerDescriptor CreateTestDescriptor(UpdateType updateType) - { - return new HandlerDescriptor(DescriptorType.General, typeof(TestUpdateHandler)); - } + // Assert + list.Should().NotBeNull(); + list.Should().BeEmpty(); } -} \ No newline at end of file + + /// + /// Тест для HandlerDescriptorList - добавление дескриптора. + /// + /// ПРИНЦИП: Тестируем добавление элементов в коллекцию + /// + [Fact] + public void HandlerDescriptorList_Add_ShouldAddDescriptor() + { + // Arrange + var list = new HandlerDescriptorList(); + var descriptor = CreateTestDescriptor(UpdateType.Message); + + // Act + list.Add(descriptor); + + // Assert + list.Should().HaveCount(1); + list.Should().Contain(descriptor); + } + + /// + /// Тест для HandlerDescriptorList - добавление нескольких дескрипторов. + /// + /// ПРИНЦИП: Тестируем множественные операции + /// + [Fact] + public void HandlerDescriptorList_AddMultiple_ShouldAddAllDescriptors() + { + // Arrange + var list = new HandlerDescriptorList(); + var descriptor1 = CreateTestDescriptor(UpdateType.Message); + var descriptor2 = CreateTestDescriptor(UpdateType.CallbackQuery); + var descriptor3 = CreateTestDescriptor(UpdateType.InlineQuery); + + // Act + list.Add(descriptor1); + list.Add(descriptor2); + list.Add(descriptor3); + + // Assert + list.Should().HaveCount(3); + list.Should().Contain(descriptor1); + list.Should().Contain(descriptor2); + list.Should().Contain(descriptor3); + } + + /// + /// Тест для HandlerDescriptorList - получение по индексу. + /// + /// ПРИНЦИП: Тестируем индексацию коллекций + /// + [Fact] + public void HandlerDescriptorList_Indexer_ShouldReturnDescriptorAtIndex() + { + // Arrange + var descriptor = CreateTestDescriptor(UpdateType.Message); + var list = new HandlerDescriptorList + { + descriptor + }; + + // Act + var result = list[0]; + + // Assert + result.Should().Be(descriptor); + } + + /// + /// Тест для HandlerDescriptorList - получение по неверному индексу. + /// + /// ПРИНЦИП: Тестируем исключения при некорректном доступе + /// + [Theory] + [InlineData(-1)] + [InlineData(1)] + [InlineData(100)] + public void HandlerDescriptorList_IndexerWithInvalidIndex_ShouldThrowArgumentOutOfRangeException(int invalidIndex) + { + // Arrange + var list = new HandlerDescriptorList + { + CreateTestDescriptor(UpdateType.Message) + }; + + // Act & Assert + list.Invoking(l => _ = l[invalidIndex]) + .Should().Throw(); + } + + /// + /// Тест для HandlerDescriptorList - перечисление элементов. + /// + /// ПРИНЦИП: Тестируем перечисление коллекций + /// + [Fact] + public void HandlerDescriptorList_ShouldBeEnumerable() + { + // Arrange + var descriptor1 = CreateTestDescriptor(UpdateType.Message); + var descriptor2 = CreateTestDescriptor(UpdateType.CallbackQuery); + var list = new HandlerDescriptorList + { + descriptor1, + descriptor2 + }; + + // Act + var enumeratedItems = list.ToList(); + + // Assert + enumeratedItems.Should().HaveCount(2); + enumeratedItems.Should().Contain(descriptor1); + enumeratedItems.Should().Contain(descriptor2); + } + + /// + /// Тест для HandlerDescriptorList - очистка списка. + /// + /// ПРИНЦИП: Тестируем очистку коллекций + /// + [Fact] + public void HandlerDescriptorList_Clear_ShouldRemoveAllDescriptors() + { + // Arrange + var list = new HandlerDescriptorList + { + CreateTestDescriptor(UpdateType.Message), + CreateTestDescriptor(UpdateType.CallbackQuery) + }; + + // Act + list.Clear(); + + // Assert + list.Should().BeEmpty(); + list.Should().HaveCount(0); + } + + /// + /// Тест для HandlerDescriptorList - проверка содержания элемента. + /// + /// ПРИНЦИП: Тестируем поиск в коллекциях + /// + [Fact] + public void HandlerDescriptorList_Contains_ShouldReturnCorrectResult() + { + // Arrange + var list = new HandlerDescriptorList(); + var descriptor = CreateTestDescriptor(UpdateType.Message); + var nonExistentDescriptor = CreateTestDescriptor(UpdateType.CallbackQuery); + + // Act + list.Add(descriptor); + var containsExisting = list.Contains(descriptor); + var containsNonExistent = list.Contains(nonExistentDescriptor); + + // Assert + containsExisting.Should().BeTrue(); + containsNonExistent.Should().BeFalse(); + } + + /// + /// Тест для HandlerDescriptorList - удаление элемента. + /// + /// ПРИНЦИП: Тестируем удаление элементов из коллекций + /// + [Fact] + public void HandlerDescriptorList_Remove_ShouldRemoveDescriptor() + { + // Arrange + var list = new HandlerDescriptorList(); + var descriptor = CreateTestDescriptor(UpdateType.Message); + list.Add(descriptor); + + // Act + var removed = list.Remove(descriptor); + + // Assert + removed.Should().BeTrue(); + list.Should().BeEmpty(); + list.Should().NotContain(descriptor); + } + + /// + /// Тест для HandlerDescriptorList - удаление несуществующего элемента. + /// + /// ПРИНЦИП: Тестируем удаление несуществующих элементов + /// + [Fact] + public void HandlerDescriptorList_RemoveNonExistent_ShouldReturnFalse() + { + // Arrange + var list = new HandlerDescriptorList(); + var nonExistentDescriptor = CreateTestDescriptor(UpdateType.CallbackQuery); + + // Act + var removed = list.Remove(nonExistentDescriptor); + + // Assert + removed.Should().BeFalse(); + list.Should().BeEmpty(); + } + + /// + /// Тест для CompletedFiltersList - создание списка. + /// + /// ПРИНЦИП: Тестируем создание специализированных коллекций + /// + [Fact] + public void CompletedFiltersList_ShouldBeCreated() + { + // Arrange & Act + var list = new CompletedFiltersList(); + + // Assert + list.Should().NotBeNull(); + list.Should().BeEmpty(); + } + + /// + /// Тест для проверки производительности коллекций. + /// + /// ПРИНЦИП: Тестируем производительность при большом количестве элементов + /// + [Fact] + public void HandlerDescriptorList_ShouldHandleLargeNumberOfItems() + { + // Arrange + var list = new HandlerDescriptorList(); + var itemsCount = 1000; + + // Act + for (int i = 0; i < itemsCount; i++) + { + list.Add(CreateTestDescriptor(UpdateType.Message)); + } + + // Assert + list.Should().HaveCount(itemsCount); + } + + /// + /// Тест для проверки потокобезопасности (базовый тест). + /// + /// ПРИНЦИП: Тестируем базовую потокобезопасность + /// + [Fact] + public async void HandlerDescriptorList_ShouldHandleConcurrentAccess() + { + // Arrange + var list = new HandlerDescriptorList(); + var tasks = new List(); + + // Act + for (int i = 0; i < 10; i++) + { + tasks.Add(Task.Run(() => + { + for (int j = 0; j < 10; j++) + { + list.Add(CreateTestDescriptor(UpdateType.Message)); + } + })); + } + + await Task.WhenAll(tasks.ToArray()); + + // Assert + list.Should().HaveCount(100); + } + + /// + /// Вспомогательный метод для создания тестового дескриптора. + /// + private static HandlerDescriptor CreateTestDescriptor(UpdateType updateType) + { + return new HandlerDescriptor(DescriptorType.General, typeof(TestUpdateHandler)); + } +} diff --git a/tests/Telegrator.Tests/Filters/FilterTests.cs b/tests/Telegrator.Tests/Filters/FilterTests.cs index fcf3b93..860386b 100644 --- a/tests/Telegrator.Tests/Filters/FilterTests.cs +++ b/tests/Telegrator.Tests/Filters/FilterTests.cs @@ -5,173 +5,172 @@ using Telegrator.Filters; using Xunit; #pragma warning disable CS8625 -namespace Telegrator.Tests.Filters +namespace Telegrator.Tests.Filters; + +/// +/// Тесты для базовых фильтров. +/// +/// ПАРАДИГМЫ ТЕСТИРОВАНИЯ: +/// 1. AAA (Arrange-Act-Assert) - структура теста: подготовка, действие, проверка +/// 2. Given-When-Then - альтернативная формулировка AAA для лучшей читаемости +/// 3. Тестирование граничных случаев и исключений +/// 4. Использование моков для изоляции тестируемого кода +/// 5. Тестирование как позитивных, так и негативных сценариев +/// +public class FilterTests { /// - /// Тесты для базовых фильтров. + /// Тест для AnyFilter - фильтр, который всегда проходит. /// - /// ПАРАДИГМЫ ТЕСТИРОВАНИЯ: - /// 1. AAA (Arrange-Act-Assert) - структура теста: подготовка, действие, проверка - /// 2. Given-When-Then - альтернативная формулировка AAA для лучшей читаемости - /// 3. Тестирование граничных случаев и исключений - /// 4. Использование моков для изоляции тестируемого кода - /// 5. Тестирование как позитивных, так и негативных сценариев + /// ПРИНЦИП: Тестируем базовое поведение - фильтр должен всегда возвращать true /// - public class FilterTests + [Fact] + public void AnyFilter_ShouldAlwaysPass() { - /// - /// Тест для AnyFilter - фильтр, который всегда проходит. - /// - /// ПРИНЦИП: Тестируем базовое поведение - фильтр должен всегда возвращать true - /// - [Fact] - public void AnyFilter_ShouldAlwaysPass() - { - // Arrange (Given) - подготовка тестовых данных - var anyFilter = Filter.Any(); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + // Arrange (Given) - подготовка тестовых данных + var anyFilter = Filter.Any(); + var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); - // Act (When) - выполнение тестируемого действия - var result = anyFilter.CanPass(context); + // Act (When) - выполнение тестируемого действия + var result = anyFilter.CanPass(context); - // Assert (Then) - проверка результата - result.Should().BeTrue(); - } - - /// - /// Тест для ReverseFilter - инвертирование результата фильтра. - /// - /// ПРИНЦИП: Тестируем композицию фильтров и логику инверсии - /// - [Fact] - public void ReverseFilter_ShouldInvertResult() - { - // Arrange - var alwaysTrueFilter = Filter.Any(); - var reverseFilter = alwaysTrueFilter.Not(); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); - - // Act - var result = reverseFilter.CanPass(context); - - // Assert - result.Should().BeFalse(); - } - - /// - /// Тест для AndFilter - логическое И между фильтрами. - /// - /// ПРИНЦИП: Тестируем комбинирование фильтров и логику И - /// - [Theory] - [InlineData(true, true, true)] // Оба фильтра проходят - [InlineData(true, false, false)] // Первый проходит, второй нет - [InlineData(false, true, false)] // Первый не проходит, второй проходит - [InlineData(false, false, false)] // Оба фильтра не проходят - public void AndFilter_ShouldCombineFiltersWithAndLogic(bool firstResult, bool secondResult, bool expectedResult) - { - // Arrange - var firstFilter = Filter.If(_ => firstResult); - var secondFilter = Filter.If(_ => secondResult); - var andFilter = firstFilter.And(secondFilter); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); - - // Act - var result = andFilter.CanPass(context); - - // Assert - result.Should().Be(expectedResult); - } - - /// - /// Тест для OrFilter - логическое ИЛИ между фильтрами. - /// - /// ПРИНЦИП: Тестируем комбинирование фильтров и логику ИЛИ - /// - [Theory] - [InlineData(true, true, true)] // Оба фильтра проходят - [InlineData(true, false, true)] // Первый проходит, второй нет - [InlineData(false, true, true)] // Первый не проходит, второй проходит - [InlineData(false, false, false)] // Оба фильтра не проходят - public void OrFilter_ShouldCombineFiltersWithOrLogic(bool firstResult, bool secondResult, bool expectedResult) - { - // Arrange - var firstFilter = Filter.If(_ => firstResult); - var secondFilter = Filter.If(_ => secondResult); - var orFilter = firstFilter.Or(secondFilter); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); - - // Act - var result = orFilter.CanPass(context); - - // Assert - result.Should().Be(expectedResult); - } - - /// - /// Тест для CompiledFilter - компиляция нескольких фильтров. - /// - /// ПРИНЦИП: Тестируем сложную композицию фильтров - /// - [Fact] - public void CompiledFilter_ShouldPassOnlyWhenAllFiltersPass() - { - // Arrange - var filter1 = Filter.If(_ => true); - var filter2 = Filter.If(_ => true); - var filter3 = Filter.If(_ => false); - - var compiledFilter = new CompiledFilter(filter1, filter2, filter3); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); - - // Act - var result = compiledFilter.CanPass(context); - - // Assert - result.Should().BeFalse(); // Должен вернуть false, так как filter3 возвращает false - } - - /// - /// Тест для проверки IsCollectible свойства. - /// - /// ПРИНЦИП: Тестируем свойства объектов - /// - [Fact] - public void Filter_IsCollectible_ShouldBeTrueForAnyFilter() - { - // Arrange - var anyFilter = Filter.Any(); - - // Act - var isCollectible = anyFilter.IsCollectible; - - // Assert - isCollectible.Should().BeFalse(); - } - - /// - /// Тест для FunctionFilter - фильтр на основе функции. - /// - /// ПРИНЦИП: Тестируем создание фильтров из функций - /// - [Fact] - public void FunctionFilter_ShouldUseProvidedFunction() - { - // Arrange - var wasCalled = false; - var functionFilter = Filter.If(_ => - { - wasCalled = true; - return true; - }); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); - - // Act - var result = functionFilter.CanPass(context); - - // Assert - result.Should().BeTrue(); - wasCalled.Should().BeTrue(); - } + // Assert (Then) - проверка результата + result.Should().BeTrue(); } -} \ No newline at end of file + + /// + /// Тест для ReverseFilter - инвертирование результата фильтра. + /// + /// ПРИНЦИП: Тестируем композицию фильтров и логику инверсии + /// + [Fact] + public void ReverseFilter_ShouldInvertResult() + { + // Arrange + var alwaysTrueFilter = Filter.Any(); + var reverseFilter = alwaysTrueFilter.Not(); + var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + + // Act + var result = reverseFilter.CanPass(context); + + // Assert + result.Should().BeFalse(); + } + + /// + /// Тест для AndFilter - логическое И между фильтрами. + /// + /// ПРИНЦИП: Тестируем комбинирование фильтров и логику И + /// + [Theory] + [InlineData(true, true, true)] // Оба фильтра проходят + [InlineData(true, false, false)] // Первый проходит, второй нет + [InlineData(false, true, false)] // Первый не проходит, второй проходит + [InlineData(false, false, false)] // Оба фильтра не проходят + public void AndFilter_ShouldCombineFiltersWithAndLogic(bool firstResult, bool secondResult, bool expectedResult) + { + // Arrange + var firstFilter = Filter.If(_ => firstResult); + var secondFilter = Filter.If(_ => secondResult); + var andFilter = firstFilter.And(secondFilter); + var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + + // Act + var result = andFilter.CanPass(context); + + // Assert + result.Should().Be(expectedResult); + } + + /// + /// Тест для OrFilter - логическое ИЛИ между фильтрами. + /// + /// ПРИНЦИП: Тестируем комбинирование фильтров и логику ИЛИ + /// + [Theory] + [InlineData(true, true, true)] // Оба фильтра проходят + [InlineData(true, false, true)] // Первый проходит, второй нет + [InlineData(false, true, true)] // Первый не проходит, второй проходит + [InlineData(false, false, false)] // Оба фильтра не проходят + public void OrFilter_ShouldCombineFiltersWithOrLogic(bool firstResult, bool secondResult, bool expectedResult) + { + // Arrange + var firstFilter = Filter.If(_ => firstResult); + var secondFilter = Filter.If(_ => secondResult); + var orFilter = firstFilter.Or(secondFilter); + var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + + // Act + var result = orFilter.CanPass(context); + + // Assert + result.Should().Be(expectedResult); + } + + /// + /// Тест для CompiledFilter - компиляция нескольких фильтров. + /// + /// ПРИНЦИП: Тестируем сложную композицию фильтров + /// + [Fact] + public void CompiledFilter_ShouldPassOnlyWhenAllFiltersPass() + { + // Arrange + var filter1 = Filter.If(_ => true); + var filter2 = Filter.If(_ => true); + var filter3 = Filter.If(_ => false); + + var compiledFilter = new CompiledFilter(filter1, filter2, filter3); + var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + + // Act + var result = compiledFilter.CanPass(context); + + // Assert + result.Should().BeFalse(); // Должен вернуть false, так как filter3 возвращает false + } + + /// + /// Тест для проверки IsCollectible свойства. + /// + /// ПРИНЦИП: Тестируем свойства объектов + /// + [Fact] + public void Filter_IsCollectible_ShouldBeTrueForAnyFilter() + { + // Arrange + var anyFilter = Filter.Any(); + + // Act + var isCollectible = anyFilter.IsCollectible; + + // Assert + isCollectible.Should().BeFalse(); + } + + /// + /// Тест для FunctionFilter - фильтр на основе функции. + /// + /// ПРИНЦИП: Тестируем создание фильтров из функций + /// + [Fact] + public void FunctionFilter_ShouldUseProvidedFunction() + { + // Arrange + var wasCalled = false; + var functionFilter = Filter.If(_ => + { + wasCalled = true; + return true; + }); + var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + + // Act + var result = functionFilter.CanPass(context); + + // Assert + result.Should().BeTrue(); + wasCalled.Should().BeTrue(); + } +}