diff --git a/src/Custom.Build.props b/src/Custom.Build.props index 5b885869..48ebfd96 100644 --- a/src/Custom.Build.props +++ b/src/Custom.Build.props @@ -5,7 +5,7 @@ - 5.0 + 6.0 minor diff --git a/src/IntegrationTests.HostV4/When_starting_the_function_host.cs b/src/IntegrationTests.HostV4/When_starting_the_function_host.cs index 9fb7a972..4615e882 100644 --- a/src/IntegrationTests.HostV4/When_starting_the_function_host.cs +++ b/src/IntegrationTests.HostV4/When_starting_the_function_host.cs @@ -1,204 +1,203 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus.Administration; +using Microsoft.Extensions.Configuration; +using NUnit.Framework; + +[TestFixture] +public class When_starting_the_function_host { - using System; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus.Administration; - using Microsoft.Extensions.Configuration; - using NUnit.Framework; - - [TestFixture] - public class When_starting_the_function_host + [Test] + public async Task Should_not_blow_up() { - [Test] - public async Task Should_not_blow_up() - { - var configBuilder = new ConfigurationBuilder(); - configBuilder.SetBasePath(Directory.GetCurrentDirectory()); - configBuilder.AddEnvironmentVariables(); - configBuilder.AddJsonFile("local.settings.json", true); + var configBuilder = new ConfigurationBuilder(); + configBuilder.SetBasePath(Directory.GetCurrentDirectory()); + configBuilder.AddEnvironmentVariables(); + configBuilder.AddJsonFile("local.settings.json", true); - var config = configBuilder.Build(); + var config = configBuilder.Build(); - var pathToFuncExe = config.GetValue("PathToFuncExe"); + var pathToFuncExe = config.GetValue("PathToFuncExe"); - if (pathToFuncExe == null) - { - Console.WriteLine("Environment variable 'PathToFuncExe' not defined. Going to try to find the latest version."); + if (pathToFuncExe == null) + { + Console.WriteLine("Environment variable 'PathToFuncExe' not defined. Going to try to find the latest version."); - var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - var sdkPath = Path.Combine(userProfile, "AppData", "Local", "AzureFunctionsTools", "Releases"); - if (Directory.Exists(sdkPath)) + var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var sdkPath = Path.Combine(userProfile, "AppData", "Local", "AzureFunctionsTools", "Releases"); + if (Directory.Exists(sdkPath)) + { + var mostRecent = Directory.GetDirectories(sdkPath) + .Select(path => + { + var name = Path.GetFileName(path); + Version.TryParse(name, out var version); + return new { Name = name, Version = version }; + }) + .Where(x => x.Version is not null) + .OrderByDescending(x => x.Version) + .FirstOrDefault() + ?.Name; + + if (mostRecent is not null) { - var mostRecent = Directory.GetDirectories(sdkPath) - .Select(path => - { - var name = Path.GetFileName(path); - Version.TryParse(name, out var version); - return new { Name = name, Version = version }; - }) - .Where(x => x.Version is not null) - .OrderByDescending(x => x.Version) - .FirstOrDefault() - ?.Name; - - if (mostRecent is not null) + var exePath = Path.Combine(sdkPath, mostRecent, "cli_x64", "func.exe"); + if (File.Exists(exePath)) { - var exePath = Path.Combine(sdkPath, mostRecent, "cli_x64", "func.exe"); - if (File.Exists(exePath)) - { - Console.WriteLine("Found " + exePath); - pathToFuncExe = exePath; - } + Console.WriteLine("Found " + exePath); + pathToFuncExe = exePath; } } } + } + + Assert.That(pathToFuncExe, Is.Not.Null, "Environment variable 'PathToFuncExe' should be defined to run tests. When running locally this is usually 'C:\\Users\\\\AppData\\Local\\AzureFunctionsTools\\Releases\\\\cli_x64\\func.exe'"); + + var connectionString = config.GetValue("AzureWebJobsServiceBus") ?? config.GetValue("Values:AzureWebJobsServiceBus"); + Assert.That(connectionString, Is.Not.Null, "Environment variable 'AzureWebJobsServiceBus' should be defined to run tests."); - Assert.That(pathToFuncExe, Is.Not.Null, "Environment variable 'PathToFuncExe' should be defined to run tests. When running locally this is usually 'C:\\Users\\\\AppData\\Local\\AzureFunctionsTools\\Releases\\\\cli_x64\\func.exe'"); + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)); + var client = new ServiceBusAdministrationClient(connectionString); - var connectionString = config.GetValue("AzureWebJobsServiceBus") ?? config.GetValue("Values:AzureWebJobsServiceBus"); - Assert.That(connectionString, Is.Not.Null, "Environment variable 'AzureWebJobsServiceBus' should be defined to run tests."); + const string queueName = "inprocess-hostv4"; + const string topicName = "bundle-1"; - var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)); - var client = new ServiceBusAdministrationClient(connectionString); + if (!await client.QueueExistsAsync(queueName, cancellationTokenSource.Token)) + { + await client.CreateQueueAsync(queueName, cancellationTokenSource.Token); + } - const string queueName = "inprocess-hostv4"; - const string topicName = "bundle-1"; + if (!await client.TopicExistsAsync(topicName, cancellationTokenSource.Token)) + { + await client.CreateTopicAsync(topicName, cancellationTokenSource.Token); + } - if (!await client.QueueExistsAsync(queueName, cancellationTokenSource.Token)) + if (!await client.SubscriptionExistsAsync(topicName, queueName, cancellationTokenSource.Token)) + { + var subscription = new CreateSubscriptionOptions(topicName, queueName) { - await client.CreateQueueAsync(queueName, cancellationTokenSource.Token); - } + LockDuration = TimeSpan.FromMinutes(5), + ForwardTo = queueName, + EnableDeadLetteringOnFilterEvaluationExceptions = false, + MaxDeliveryCount = int.MaxValue, + EnableBatchedOperations = true, + UserMetadata = queueName + }; + await client.CreateSubscriptionAsync(subscription, cancellationTokenSource.Token); + } - if (!await client.TopicExistsAsync(topicName, cancellationTokenSource.Token)) + var functionRootDir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + var port = 7076; //Use non-standard port to avoid clashing when debugging locally + var funcProcess = new Process(); + var httpClient = new HttpClient(); + var hasResult = false; + var hostFailed = false; + var eventHandlerCalled = false; + var commandHandlerCalled = false; + var someEventTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var someOtherMessageTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + cancellationTokenSource.Token.Register(state => ((TaskCompletionSource)state).TrySetResult(false), someEventTaskCompletionSource); + cancellationTokenSource.Token.Register(state => ((TaskCompletionSource)state).TrySetResult(false), someOtherMessageTaskCompletionSource); + + funcProcess.StartInfo.WorkingDirectory = functionRootDir.FullName; + funcProcess.StartInfo.Arguments = $"start --port {port} --no-build --verbose"; + funcProcess.StartInfo.FileName = pathToFuncExe; + + funcProcess.StartInfo.UseShellExecute = false; + funcProcess.StartInfo.RedirectStandardOutput = true; + funcProcess.StartInfo.RedirectStandardError = true; + funcProcess.StartInfo.CreateNoWindow = true; + funcProcess.ErrorDataReceived += (_, e) => + { + if (e.Data == null) { - await client.CreateTopicAsync(topicName, cancellationTokenSource.Token); + return; } - if (!await client.SubscriptionExistsAsync(topicName, queueName, cancellationTokenSource.Token)) + hostFailed = true; + TestContext.Out.WriteLine(e.Data); + + cancellationTokenSource.Cancel(); + }; + funcProcess.OutputDataReceived += (_, e) => + { + if (e.Data == null) { - var subscription = new CreateSubscriptionOptions(topicName, queueName) - { - LockDuration = TimeSpan.FromMinutes(5), - ForwardTo = queueName, - EnableDeadLetteringOnFilterEvaluationExceptions = false, - MaxDeliveryCount = int.MaxValue, - EnableBatchedOperations = true, - UserMetadata = queueName - }; - await client.CreateSubscriptionAsync(subscription, cancellationTokenSource.Token); + return; } - var functionRootDir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); - var port = 7076; //Use non-standard port to avoid clashing when debugging locally - var funcProcess = new Process(); - var httpClient = new HttpClient(); - var hasResult = false; - var hostFailed = false; - var eventHandlerCalled = false; - var commandHandlerCalled = false; - var someEventTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var someOtherMessageTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - cancellationTokenSource.Token.Register(state => ((TaskCompletionSource)state).TrySetResult(false), someEventTaskCompletionSource); - cancellationTokenSource.Token.Register(state => ((TaskCompletionSource)state).TrySetResult(false), someOtherMessageTaskCompletionSource); - - funcProcess.StartInfo.WorkingDirectory = functionRootDir.FullName; - funcProcess.StartInfo.Arguments = $"start --port {port} --no-build --verbose"; - funcProcess.StartInfo.FileName = pathToFuncExe; - - funcProcess.StartInfo.UseShellExecute = false; - funcProcess.StartInfo.RedirectStandardOutput = true; - funcProcess.StartInfo.RedirectStandardError = true; - funcProcess.StartInfo.CreateNoWindow = true; - funcProcess.ErrorDataReceived += (_, e) => + TestContext.Out.WriteLine(e.Data); + + if (e.Data.Contains($"Handling {nameof(SomeOtherMessage)}")) { - if (e.Data == null) - { - return; - } + someOtherMessageTaskCompletionSource.SetResult(true); + } - hostFailed = true; - TestContext.Out.WriteLine(e.Data); + if (e.Data.Contains($"Handling {nameof(SomeEvent)}")) + { + someEventTaskCompletionSource.SetResult(true); + } + }; + funcProcess.EnableRaisingEvents = true; + funcProcess.Start(); + funcProcess.BeginOutputReadLine(); + funcProcess.BeginErrorReadLine(); - cancellationTokenSource.Cancel(); - }; - funcProcess.OutputDataReceived += (_, e) => + try + { + while (!cancellationTokenSource.IsCancellationRequested && !hasResult) { - if (e.Data == null) + try { - return; - } + var result = await httpClient.GetAsync($"http://localhost:{port}/api/InProcessHttpSenderV4", cancellationTokenSource.Token); - TestContext.Out.WriteLine(e.Data); + result.EnsureSuccessStatusCode(); - if (e.Data.Contains($"Handling {nameof(SomeOtherMessage)}")) - { - someOtherMessageTaskCompletionSource.SetResult(true); + hasResult = true; } - - if (e.Data.Contains($"Handling {nameof(SomeEvent)}")) + catch (OperationCanceledException) when (cancellationTokenSource.Token.IsCancellationRequested) { - someEventTaskCompletionSource.SetResult(true); } - }; - funcProcess.EnableRaisingEvents = true; - funcProcess.Start(); - funcProcess.BeginOutputReadLine(); - funcProcess.BeginErrorReadLine(); - - try - { - while (!cancellationTokenSource.IsCancellationRequested && !hasResult) + catch (Exception ex) { - try - { - var result = await httpClient.GetAsync($"http://localhost:{port}/api/InProcessHttpSenderV4", cancellationTokenSource.Token); - - result.EnsureSuccessStatusCode(); - - hasResult = true; - } - catch (OperationCanceledException) when (cancellationTokenSource.Token.IsCancellationRequested) - { - } - catch (Exception ex) - { - await TestContext.Out.WriteLineAsync(ex.Message); - await Task.Delay(TimeSpan.FromSeconds(1), cancellationTokenSource.Token); - } + await TestContext.Out.WriteLineAsync(ex.Message); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationTokenSource.Token); } + } - await Task.WhenAll(someEventTaskCompletionSource.Task, someOtherMessageTaskCompletionSource.Task); + await Task.WhenAll(someEventTaskCompletionSource.Task, someOtherMessageTaskCompletionSource.Task); - eventHandlerCalled = await someEventTaskCompletionSource.Task; - commandHandlerCalled = await someOtherMessageTaskCompletionSource.Task; + eventHandlerCalled = await someEventTaskCompletionSource.Task; + commandHandlerCalled = await someOtherMessageTaskCompletionSource.Task; - funcProcess.Kill(); - } - finally + funcProcess.Kill(); + } + finally + { + try { - try - { - await funcProcess.WaitForExitAsync(cancellationTokenSource.Token); - } - catch (OperationCanceledException) when (cancellationTokenSource.Token.IsCancellationRequested) - { - funcProcess.Kill(); - } + await funcProcess.WaitForExitAsync(cancellationTokenSource.Token); } - - Assert.Multiple(() => + catch (OperationCanceledException) when (cancellationTokenSource.Token.IsCancellationRequested) { - Assert.That(hostFailed, Is.False, "Host should startup without errors"); - Assert.That(hasResult, Is.True, "Http trigger should respond successfully"); - Assert.That(commandHandlerCalled, Is.True, $"{nameof(SomeOtherMessageHandler)} should have been called"); - Assert.That(eventHandlerCalled, Is.True, $"{nameof(SomeEventMessageHandler)} should have been called"); - }); + funcProcess.Kill(); + } } + + Assert.Multiple(() => + { + Assert.That(hostFailed, Is.False, "Host should startup without errors"); + Assert.That(hasResult, Is.True, "Http trigger should respond successfully"); + Assert.That(commandHandlerCalled, Is.True, $"{nameof(SomeOtherMessageHandler)} should have been called"); + Assert.That(eventHandlerCalled, Is.True, $"{nameof(SomeEventMessageHandler)} should have been called"); + }); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs index 61a7dc87..9fa69454 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/AnalyzerTestFixture.cs @@ -1,216 +1,215 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; + +public class AnalyzerTestFixture where TAnalyzer : DiagnosticAnalyzer, new() { - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Reflection; - using System.Text; - using System.Text.RegularExpressions; - using System.Threading; - using System.Threading.Tasks; - using Azure.Core; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.Diagnostics; - using Microsoft.CodeAnalysis.Text; - using NUnit.Framework; - - public class AnalyzerTestFixture where TAnalyzer : DiagnosticAnalyzer, new() - { - protected virtual LanguageVersion AnalyzerLanguageVersion => LanguageVersion.CSharp7; + protected virtual LanguageVersion AnalyzerLanguageVersion => LanguageVersion.CSharp7; - protected Task Assert(string markupCode, CancellationToken cancellationToken = default) => - Assert(Array.Empty(), markupCode, Array.Empty(), cancellationToken); + protected Task Assert(string markupCode, CancellationToken cancellationToken = default) => + Assert([], markupCode, [], cancellationToken); - protected Task Assert(string expectedDiagnosticId, string markupCode, CancellationToken cancellationToken = default) => - Assert(new[] { expectedDiagnosticId }, markupCode, Array.Empty(), cancellationToken); + protected Task Assert(string expectedDiagnosticId, string markupCode, CancellationToken cancellationToken = default) => + Assert([expectedDiagnosticId], markupCode, [], cancellationToken); - protected async Task Assert(string[] expectedDiagnosticIds, string markupCode, string[] ignoreDiagnosticIds, CancellationToken cancellationToken = default) - { - var (code, markupSpans) = Parse(markupCode); + protected async Task Assert(string[] expectedDiagnosticIds, string markupCode, string[] ignoreDiagnosticIds, CancellationToken cancellationToken = default) + { + var (code, markupSpans) = Parse(markupCode); - var project = CreateProject(code); - await WriteCode(project, cancellationToken); + var project = CreateProject(code); + await WriteCode(project, cancellationToken); - var compilerDiagnostics = (await Task.WhenAll(project.Documents + var compilerDiagnostics = (await Task.WhenAll(project.Documents .Select(doc => doc.GetCompilerDiagnostics(cancellationToken)))) - .SelectMany(diagnostics => diagnostics); + .SelectMany(diagnostics => diagnostics); - WriteCompilerDiagnostics(compilerDiagnostics); + WriteCompilerDiagnostics(compilerDiagnostics); - var compilation = await project.GetCompilationAsync(cancellationToken); - compilation.Compile(); + var compilation = await project.GetCompilationAsync(cancellationToken); + compilation.Compile(); - var analyzerDiagnostics = (await compilation.GetAnalyzerDiagnostics(new TAnalyzer(), cancellationToken)) - .Where(d => !ignoreDiagnosticIds.Contains(d.Id)) - .ToList(); - WriteAnalyzerDiagnostics(analyzerDiagnostics); + var analyzerDiagnostics = (await compilation.GetAnalyzerDiagnostics(new TAnalyzer(), cancellationToken)) + .Where(d => !ignoreDiagnosticIds.Contains(d.Id)) + .ToList(); + WriteAnalyzerDiagnostics(analyzerDiagnostics); - var expectedSpansAndIds = expectedDiagnosticIds - .SelectMany(id => markupSpans.Select(span => (span.file, span.span, id))) - .OrderBy(item => item.span) - .ThenBy(item => item.id) - .ToList(); + var expectedSpansAndIds = expectedDiagnosticIds + .SelectMany(id => markupSpans.Select(span => (span.file, span.span, id))) + .OrderBy(item => item.span) + .ThenBy(item => item.id) + .ToList(); - var actualSpansAndIds = analyzerDiagnostics - .Select(diagnostic => (diagnostic.Location.SourceTree.FilePath, diagnostic.Location.SourceSpan, diagnostic.Id)) - .ToList(); + var actualSpansAndIds = analyzerDiagnostics + .Select(diagnostic => (diagnostic.Location.SourceTree.FilePath, diagnostic.Location.SourceSpan, diagnostic.Id)) + .ToList(); - NUnit.Framework.Assert.That(actualSpansAndIds, Is.EqualTo(expectedSpansAndIds).AsCollection); - } + NUnit.Framework.Assert.That(actualSpansAndIds, Is.EqualTo(expectedSpansAndIds).AsCollection); + } - protected static async Task WriteCode(Project project, CancellationToken cancellationToken = default) + protected static async Task WriteCode(Project project, CancellationToken cancellationToken = default) + { + if (!VerboseLogging) { - if (!VerboseLogging) - { - return; - } + return; + } - foreach (var document in project.Documents) + foreach (var document in project.Documents) + { + Console.WriteLine(document.Name); + var code = await document.GetCode(cancellationToken); + foreach (var (line, index) in code.Replace("\r\n", "\n").Split('\n') + .Select((line, index) => (line, index))) { - Console.WriteLine(document.Name); - var code = await document.GetCode(cancellationToken); - foreach (var (line, index) in code.Replace("\r\n", "\n").Split('\n') - .Select((line, index) => (line, index))) - { - Console.WriteLine($" {index + 1,3}: {line}"); - } + Console.WriteLine($" {index + 1,3}: {line}"); } - } - static readonly ImmutableDictionary DiagnosticOptions = new Dictionary + } + + static readonly ImmutableDictionary DiagnosticOptions = new Dictionary { { "CS1701", ReportDiagnostic.Hidden } } .ToImmutableDictionary(); - protected Project CreateProject(string[] code) + protected Project CreateProject(string[] code) + { + var workspace = new AdhocWorkspace(); + var project = workspace.AddProject("TestProject", LanguageNames.CSharp) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithSpecificDiagnosticOptions(DiagnosticOptions)) + .WithParseOptions(new CSharpParseOptions(AnalyzerLanguageVersion)) + .AddMetadataReferences(ProjectReferences); + + for (int i = 0; i < code.Length; i++) { - var workspace = new AdhocWorkspace(); - var project = workspace.AddProject("TestProject", LanguageNames.CSharp) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - .WithSpecificDiagnosticOptions(DiagnosticOptions)) - .WithParseOptions(new CSharpParseOptions(AnalyzerLanguageVersion)) - .AddMetadataReferences(ProjectReferences); - - for (int i = 0; i < code.Length; i++) - { - project = project.AddDocument($"TestDocument{i}", code[i]).Project; - } - - return project; + project = project.AddDocument($"TestDocument{i}", code[i]).Project; } - static AnalyzerTestFixture() - { - ProjectReferences = - [ - MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.Expression).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), - MetadataReference.CreateFromFile(typeof(IFunctionEndpoint).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(TokenCredential).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(EndpointConfiguration).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(AzureServiceBusTransport).GetTypeInfo().Assembly.Location), - ]; - } + return project; + } - static readonly ImmutableList ProjectReferences; + static AnalyzerTestFixture() + { + ProjectReferences = + [ + MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.Expression).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), + MetadataReference.CreateFromFile(typeof(IFunctionEndpoint).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(TokenCredential).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(EndpointConfiguration).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(AzureServiceBusTransport).GetTypeInfo().Assembly.Location), + ]; + } - static readonly Regex DocumentSplittingRegex = new Regex("^-{5,}.*", RegexOptions.Compiled | RegexOptions.Multiline); + static readonly ImmutableList ProjectReferences; - protected static void WriteCompilerDiagnostics(IEnumerable diagnostics) + static readonly Regex DocumentSplittingRegex = new Regex("^-{5,}.*", RegexOptions.Compiled | RegexOptions.Multiline); + + protected static void WriteCompilerDiagnostics(IEnumerable diagnostics) + { + if (!VerboseLogging) { - if (!VerboseLogging) - { - return; - } + return; + } - Console.WriteLine("Compiler diagnostics:"); + Console.WriteLine("Compiler diagnostics:"); - foreach (var diagnostic in diagnostics) - { - Console.WriteLine($" {diagnostic}"); - } + foreach (var diagnostic in diagnostics) + { + Console.WriteLine($" {diagnostic}"); } + } - protected static void WriteAnalyzerDiagnostics(IEnumerable diagnostics) + protected static void WriteAnalyzerDiagnostics(IEnumerable diagnostics) + { + if (!VerboseLogging) { - if (!VerboseLogging) - { - return; - } + return; + } - Console.WriteLine("Analyzer diagnostics:"); + Console.WriteLine("Analyzer diagnostics:"); - foreach (var diagnostic in diagnostics) - { - Console.WriteLine($" {diagnostic}"); - } + foreach (var diagnostic in diagnostics) + { + Console.WriteLine($" {diagnostic}"); } + } + + protected static string[] SplitMarkupCodeIntoFiles(string markupCode) + { + return DocumentSplittingRegex.Split(markupCode) + .Where(docCode => !string.IsNullOrWhiteSpace(docCode)) + .ToArray(); + } - protected static string[] SplitMarkupCodeIntoFiles(string markupCode) + static (string[] code, List<(string file, TextSpan span)>) Parse(string markupCode) + { + if (markupCode == null) { - return DocumentSplittingRegex.Split(markupCode) - .Where(docCode => !string.IsNullOrWhiteSpace(docCode)) - .ToArray(); + return ([], []); } - static (string[] code, List<(string file, TextSpan span)>) Parse(string markupCode) - { - if (markupCode == null) - { - return (Array.Empty(), new List<(string, TextSpan)>()); - } + var documents = SplitMarkupCodeIntoFiles(markupCode); - var documents = SplitMarkupCodeIntoFiles(markupCode); + var markupSpans = new List<(string, TextSpan)>(); - var markupSpans = new List<(string, TextSpan)>(); + for (var i = 0; i < documents.Length; i++) + { + var code = new StringBuilder(); + var name = $"TestDocument{i}"; - for (var i = 0; i < documents.Length; i++) - { - var code = new StringBuilder(); - var name = $"TestDocument{i}"; + var remainingCode = documents[i]; + var remainingCodeStart = 0; - var remainingCode = documents[i]; - var remainingCodeStart = 0; + while (remainingCode.Length > 0) + { + var beforeAndAfterOpening = remainingCode.Split(["[|"], 2, StringSplitOptions.None); - while (remainingCode.Length > 0) + if (beforeAndAfterOpening.Length == 1) { - var beforeAndAfterOpening = remainingCode.Split(new[] { "[|" }, 2, StringSplitOptions.None); - - if (beforeAndAfterOpening.Length == 1) - { - _ = code.Append(beforeAndAfterOpening[0]); - break; - } - - var midAndAfterClosing = beforeAndAfterOpening[1].Split(new[] { "|]" }, 2, StringSplitOptions.None); + _ = code.Append(beforeAndAfterOpening[0]); + break; + } - if (midAndAfterClosing.Length == 1) - { - throw new Exception("The markup code does not contain a closing '|]'"); - } + var midAndAfterClosing = beforeAndAfterOpening[1].Split(["|]"], 2, StringSplitOptions.None); - var markupSpan = new TextSpan(remainingCodeStart + beforeAndAfterOpening[0].Length, midAndAfterClosing[0].Length); + if (midAndAfterClosing.Length == 1) + { + throw new Exception("The markup code does not contain a closing '|]'"); + } - _ = code.Append(beforeAndAfterOpening[0]).Append(midAndAfterClosing[0]); - markupSpans.Add((name, markupSpan)); + var markupSpan = new TextSpan(remainingCodeStart + beforeAndAfterOpening[0].Length, midAndAfterClosing[0].Length); - remainingCode = midAndAfterClosing[1]; - remainingCodeStart += beforeAndAfterOpening[0].Length + markupSpan.Length; - } + _ = code.Append(beforeAndAfterOpening[0]).Append(midAndAfterClosing[0]); + markupSpans.Add((name, markupSpan)); - documents[i] = code.ToString(); + remainingCode = midAndAfterClosing[1]; + remainingCodeStart += beforeAndAfterOpening[0].Length + markupSpan.Length; } - return (documents, markupSpans); + documents[i] = code.ToString(); } - protected static readonly bool VerboseLogging = Environment.GetEnvironmentVariable("CI") != "true" - || Environment.GetEnvironmentVariable("VERBOSE_TEST_LOGGING")?.ToLower() == "true"; + return (documents, markupSpans); } -} + + protected static readonly bool VerboseLogging = Environment.GetEnvironmentVariable("CI") != "true" + || Environment.GetEnvironmentVariable("VERBOSE_TEST_LOGGING")?.ToLower() == "true"; +} \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs index 3e1938d5..df32e28f 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTests.cs @@ -1,23 +1,23 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests -{ - using System.Threading.Tasks; - using NUnit.Framework; - using static AzureFunctionsDiagnostics; +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System.Threading.Tasks; +using NUnit.Framework; +using static AzureFunctionsDiagnostics; - [TestFixture] - public class ConfigurationAnalyzerTests : AnalyzerTestFixture +[TestFixture] +public class ConfigurationAnalyzerTests : AnalyzerTestFixture +{ + [TestCase("DefineCriticalErrorAction((errorContext, cancellationToken) => Task.CompletedTask)", DefineCriticalErrorActionNotAllowedId)] + [TestCase("LimitMessageProcessingConcurrencyTo(5)", LimitMessageProcessingToNotAllowedId)] + [TestCase("MakeInstanceUniquelyAddressable(null)", MakeInstanceUniquelyAddressableNotAllowedId)] + [TestCase("OverrideLocalAddress(null)", OverrideLocalAddressNotAllowedId)] + [TestCase("PurgeOnStartup(true)", PurgeOnStartupNotAllowedId)] + [TestCase("SetDiagnosticsPath(null)", SetDiagnosticsPathNotAllowedId)] + [TestCase("UseTransport(new AzureServiceBusTransport(null, default(TopicTopology)))", UseTransportNotAllowedId)] + public Task DiagnosticIsReportedForEndpointConfiguration(string configuration, string diagnosticId) { - [TestCase("DefineCriticalErrorAction((errorContext, cancellationToken) => Task.CompletedTask)", DefineCriticalErrorActionNotAllowedId)] - [TestCase("LimitMessageProcessingConcurrencyTo(5)", LimitMessageProcessingToNotAllowedId)] - [TestCase("MakeInstanceUniquelyAddressable(null)", MakeInstanceUniquelyAddressableNotAllowedId)] - [TestCase("OverrideLocalAddress(null)", OverrideLocalAddressNotAllowedId)] - [TestCase("PurgeOnStartup(true)", PurgeOnStartupNotAllowedId)] - [TestCase("SetDiagnosticsPath(null)", SetDiagnosticsPathNotAllowedId)] - [TestCase("UseTransport(new AzureServiceBusTransport(null, default(TopicTopology)))", UseTransportNotAllowedId)] - public Task DiagnosticIsReportedForEndpointConfiguration(string configuration, string diagnosticId) - { - var source = - $@"using NServiceBus; + var source = + $@"using NServiceBus; using System; using System.Threading.Tasks; class Foo @@ -31,20 +31,20 @@ void Bar(ServiceBusTriggeredEndpointConfiguration endpointConfig) }} }}"; - return Assert(diagnosticId, source); - } + return Assert(diagnosticId, source); + } - [TestCase("DefineCriticalErrorAction((errorContext, cancellationToken) => Task.CompletedTask)", DefineCriticalErrorActionNotAllowedId)] - [TestCase("LimitMessageProcessingConcurrencyTo(5)", LimitMessageProcessingToNotAllowedId)] - [TestCase("MakeInstanceUniquelyAddressable(null)", MakeInstanceUniquelyAddressableNotAllowedId)] - [TestCase("OverrideLocalAddress(null)", OverrideLocalAddressNotAllowedId)] - [TestCase("PurgeOnStartup(true)", PurgeOnStartupNotAllowedId)] - [TestCase("SetDiagnosticsPath(null)", SetDiagnosticsPathNotAllowedId)] - [TestCase("UseTransport(new AzureServiceBusTransport(null, default(TopicTopology)))", UseTransportNotAllowedId)] - public Task DiagnosticIsNotReportedForOtherEndpointConfiguration(string configuration, string diagnosticId) - { - var source = - $@"using NServiceBus; + [TestCase("DefineCriticalErrorAction((errorContext, cancellationToken) => Task.CompletedTask)", DefineCriticalErrorActionNotAllowedId)] + [TestCase("LimitMessageProcessingConcurrencyTo(5)", LimitMessageProcessingToNotAllowedId)] + [TestCase("MakeInstanceUniquelyAddressable(null)", MakeInstanceUniquelyAddressableNotAllowedId)] + [TestCase("OverrideLocalAddress(null)", OverrideLocalAddressNotAllowedId)] + [TestCase("PurgeOnStartup(true)", PurgeOnStartupNotAllowedId)] + [TestCase("SetDiagnosticsPath(null)", SetDiagnosticsPathNotAllowedId)] + [TestCase("UseTransport(new AzureServiceBusTransport(null, default(TopicTopology)))", UseTransportNotAllowedId)] + public Task DiagnosticIsNotReportedForOtherEndpointConfiguration(string configuration, string diagnosticId) + { + var source = + $@"using NServiceBus; using System; using System.Threading; using System.Threading.Tasks; @@ -68,7 +68,6 @@ void Bar(SomeOtherClass endpointConfig) }} }}"; - return Assert(diagnosticId, source); - } + return Assert(diagnosticId, source); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTestsCSharp8.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTestsCSharp8.cs index 9f50a20d..f621d928 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTestsCSharp8.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/ConfigurationAnalyzerTestsCSharp8.cs @@ -1,19 +1,19 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests -{ - using System.Threading.Tasks; - using Microsoft.CodeAnalysis.CSharp; - using NUnit.Framework; - using static AzureFunctionsDiagnostics; +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using NUnit.Framework; +using static AzureFunctionsDiagnostics; - [TestFixture] - public class ConfigurationAnalyzerTestsCSharp8 : AnalyzerTestFixture +[TestFixture] +public class ConfigurationAnalyzerTestsCSharp8 : AnalyzerTestFixture +{ + // HINT: In C# 7 this call is ambiguous with the LearningTransport version as the compiler cannot differentiate method calls via generic type constraints + [TestCase("UseTransport(null)", UseTransportNotAllowedId)] + public Task DiagnosticIsReportedForEndpointConfiguration(string configuration, string diagnosticId) { - // HINT: In C# 7 this call is ambiguous with the LearningTransport version as the compiler cannot differentiate method calls via generic type constraints - [TestCase("UseTransport(null)", UseTransportNotAllowedId)] - public Task DiagnosticIsReportedForEndpointConfiguration(string configuration, string diagnosticId) - { - var source = - $@"using NServiceBus; + var source = + $@"using NServiceBus; using System; using System.Threading.Tasks; class Foo @@ -27,8 +27,7 @@ void Bar(ServiceBusTriggeredEndpointConfiguration endpointConfig) }} }}"; - return Assert(diagnosticId, source); - } - protected override LanguageVersion AnalyzerLanguageVersion => LanguageVersion.CSharp8; + return Assert(diagnosticId, source); } + protected override LanguageVersion AnalyzerLanguageVersion => LanguageVersion.CSharp8; } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/CompilationExtensions.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/CompilationExtensions.cs index 2eadb657..b859cab8 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/CompilationExtensions.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/CompilationExtensions.cs @@ -1,60 +1,59 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +static class CompilationExtensions { - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.Diagnostics; - - static class CompilationExtensions + public static void Compile(this Compilation compilation, bool throwOnFailure = true) { - public static void Compile(this Compilation compilation, bool throwOnFailure = true) + using (var peStream = new MemoryStream()) { - using (var peStream = new MemoryStream()) - { - var emitResult = compilation.Emit(peStream); + var emitResult = compilation.Emit(peStream); - if (!emitResult.Success) + if (!emitResult.Success) + { + if (throwOnFailure) { - if (throwOnFailure) - { - throw new Exception("Compilation failed."); - } - else - { - Debug.WriteLine("Compilation failed."); - } + throw new Exception("Compilation failed."); + } + else + { + Debug.WriteLine("Compilation failed."); } } } + } - public static async Task> GetAnalyzerDiagnostics(this Compilation compilation, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken = default) - { - var exceptions = new List(); - - var analysisOptions = new CompilationWithAnalyzersOptions( - new AnalyzerOptions(ImmutableArray.Empty), - (exception, _, __) => exceptions.Add(exception), - concurrentAnalysis: false, - logAnalyzerExecutionTime: false); + public static async Task> GetAnalyzerDiagnostics(this Compilation compilation, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken = default) + { + var exceptions = new List(); - var diagnostics = await compilation - .WithAnalyzers([analyzer], analysisOptions) - .GetAnalyzerDiagnosticsAsync(cancellationToken); + var analysisOptions = new CompilationWithAnalyzersOptions( + new AnalyzerOptions(ImmutableArray.Empty), + (exception, _, __) => exceptions.Add(exception), + concurrentAnalysis: false, + logAnalyzerExecutionTime: false); - if (exceptions.Any()) - { - throw new AggregateException(exceptions); - } + var diagnostics = await compilation + .WithAnalyzers([analyzer], analysisOptions) + .GetAnalyzerDiagnosticsAsync(cancellationToken); - return diagnostics - .OrderBy(diagnostic => diagnostic.Location.SourceSpan) - .ThenBy(diagnostic => diagnostic.Id); + if (exceptions.Any()) + { + throw new AggregateException(exceptions); } + + return diagnostics + .OrderBy(diagnostic => diagnostic.Location.SourceSpan) + .ThenBy(diagnostic => diagnostic.Id); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/DocumentExtensions.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/DocumentExtensions.cs index deeeae96..1fdfe873 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/DocumentExtensions.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/Extensions/DocumentExtensions.cs @@ -1,48 +1,47 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; + +static class DocumentExtensions { - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CodeActions; - using Microsoft.CodeAnalysis.CodeFixes; - using Microsoft.CodeAnalysis.Formatting; - using Microsoft.CodeAnalysis.Simplification; + public static async Task> GetCompilerDiagnostics(this Document document, CancellationToken cancellationToken = default) => + (await document.GetSemanticModelAsync(cancellationToken)) + .GetDiagnostics(cancellationToken: cancellationToken) + .Where(diagnostic => diagnostic.Severity != DiagnosticSeverity.Hidden) + .OrderBy(diagnostic => diagnostic.Location.SourceSpan) + .ThenBy(diagnostic => diagnostic.Id); - static class DocumentExtensions + public static async Task<(Document Document, CodeAction Action)[]> GetCodeActions(this Project project, CodeFixProvider codeFix, Diagnostic diagnostic, CancellationToken cancellationToken = default) { - public static async Task> GetCompilerDiagnostics(this Document document, CancellationToken cancellationToken = default) => - (await document.GetSemanticModelAsync(cancellationToken)) - .GetDiagnostics(cancellationToken: cancellationToken) - .Where(diagnostic => diagnostic.Severity != DiagnosticSeverity.Hidden) - .OrderBy(diagnostic => diagnostic.Location.SourceSpan) - .ThenBy(diagnostic => diagnostic.Id); - - public static async Task<(Document Document, CodeAction Action)[]> GetCodeActions(this Project project, CodeFixProvider codeFix, Diagnostic diagnostic, CancellationToken cancellationToken = default) + var actions = new List<(Document, CodeAction)>(); + foreach (var document in project.Documents) { - var actions = new List<(Document, CodeAction)>(); - foreach (var document in project.Documents) - { - var context = new CodeFixContext(document, diagnostic, (action, _) => actions.Add((document, action)), cancellationToken); - await codeFix.RegisterCodeFixesAsync(context); - } - return actions.ToArray(); + var context = new CodeFixContext(document, diagnostic, (action, _) => actions.Add((document, action)), cancellationToken); + await codeFix.RegisterCodeFixesAsync(context); } + return actions.ToArray(); + } - public static async Task ApplyChanges(this Document document, CodeAction codeAction, CancellationToken cancellationToken = default) - { - var operations = await codeAction.GetOperationsAsync(cancellationToken); - var solution = operations.OfType().Single().ChangedSolution; - return solution.GetDocument(document.Id); - } + public static async Task ApplyChanges(this Document document, CodeAction codeAction, CancellationToken cancellationToken = default) + { + var operations = await codeAction.GetOperationsAsync(cancellationToken); + var solution = operations.OfType().Single().ChangedSolution; + return solution.GetDocument(document.Id); + } - public static async Task GetCode(this Document document, CancellationToken cancellationToken = default) - { - var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken); - var root = await simplifiedDoc.GetSyntaxRootAsync(cancellationToken); - root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace, cancellationToken: cancellationToken); - return root.GetText().ToString(); - } + public static async Task GetCode(this Document document, CancellationToken cancellationToken = default) + { + var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken); + var root = await simplifiedDoc.GetSyntaxRootAsync(cancellationToken); + root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace, cancellationToken: cancellationToken); + return root.GetText().ToString(); } -} +} \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/OptionsAnalyzerTests.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/OptionsAnalyzerTests.cs index 3bc1d76c..ca31652c 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/OptionsAnalyzerTests.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/OptionsAnalyzerTests.cs @@ -1,19 +1,19 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests -{ - using System.Threading.Tasks; - using NUnit.Framework; - using static AzureFunctionsDiagnostics; +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System.Threading.Tasks; +using NUnit.Framework; +using static AzureFunctionsDiagnostics; - [TestFixture] - public class OptionsAnalyzerTests : AnalyzerTestFixture +[TestFixture] +public class OptionsAnalyzerTests : AnalyzerTestFixture +{ + [TestCase("SendOptions", "RouteReplyToThisInstance", RouteReplyToThisInstanceNotAllowedId)] + [TestCase("SendOptions", "RouteToThisInstance", RouteToThisInstanceNotAllowedId)] + [TestCase("ReplyOptions", "RouteReplyToThisInstance", RouteReplyToThisInstanceNotAllowedId)] + public Task DiagnosticIsReportedForOptions(string optionsType, string method, string diagnosticId) { - [TestCase("SendOptions", "RouteReplyToThisInstance", RouteReplyToThisInstanceNotAllowedId)] - [TestCase("SendOptions", "RouteToThisInstance", RouteToThisInstanceNotAllowedId)] - [TestCase("ReplyOptions", "RouteReplyToThisInstance", RouteReplyToThisInstanceNotAllowedId)] - public Task DiagnosticIsReportedForOptions(string optionsType, string method, string diagnosticId) - { - var source = - $@"using NServiceBus; + var source = + $@"using NServiceBus; class Foo {{ void Bar({optionsType} options) @@ -22,15 +22,15 @@ void Bar({optionsType} options) }} }}"; - return Assert(diagnosticId, source); - } + return Assert(diagnosticId, source); + } - [TestCase("SomeOtherClass", "RouteReplyToThisInstance", RouteReplyToThisInstanceNotAllowedId)] - [TestCase("SomeOtherClass", "RouteToThisInstance", RouteToThisInstanceNotAllowedId)] - public Task DiagnosticIsNotReportedForOtherOptions(string optionsType, string method, string diagnosticId) - { - var source = - $@"using NServiceBus; + [TestCase("SomeOtherClass", "RouteReplyToThisInstance", RouteReplyToThisInstanceNotAllowedId)] + [TestCase("SomeOtherClass", "RouteToThisInstance", RouteToThisInstanceNotAllowedId)] + public Task DiagnosticIsNotReportedForOtherOptions(string optionsType, string method, string diagnosticId) + { + var source = + $@"using NServiceBus; using System; using System.Threading.Tasks; @@ -49,7 +49,6 @@ void Bar({optionsType} options) }} }}"; - return Assert(diagnosticId, source); - } + return Assert(diagnosticId, source); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/SourceGeneratorApprovals.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/SourceGeneratorApprovals.cs index f713e7fb..7ddd0255 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/SourceGeneratorApprovals.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/SourceGeneratorApprovals.cs @@ -1,25 +1,25 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.WebJobs; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Particular.Approvals; + +[TestFixture] +public class SourceGeneratorApprovals { - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using Azure.Messaging.ServiceBus; - using Microsoft.Azure.WebJobs; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.Extensions.Logging; - using NUnit.Framework; - using Particular.Approvals; - - [TestFixture] - public class SourceGeneratorApprovals - { - [Test] - public void UsingNamespace() - { - var source = -@"using NServiceBus; + [Test] + public void UsingNamespace() + { + var source = + @"using NServiceBus; [assembly: NServiceBusTriggerFunction(Foo.Startup.EndpointName)] @@ -30,16 +30,16 @@ public class Startup public const string EndpointName = ""endpoint""; } }"; - var (output, _) = GetGeneratedOutput(source); + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [Test] - public void UsingFullyQualifiedAttributeName() - { - var source = -@"[assembly: NServiceBus.NServiceBusTriggerFunction(Foo.Startup.EndpointName)] + [Test] + public void UsingFullyQualifiedAttributeName() + { + var source = + @"[assembly: NServiceBus.NServiceBusTriggerFunction(Foo.Startup.EndpointName)] namespace Foo { @@ -48,88 +48,88 @@ public class Startup public const string EndpointName = ""endpoint""; } }"; - var (output, _) = GetGeneratedOutput(source); + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [Test] - public void NameIsStringValue() - { - var source = @"[assembly: NServiceBus.NServiceBusTriggerFunction(""endpoint"")]"; - var (output, _) = GetGeneratedOutput(source); + [Test] + public void NameIsStringValue() + { + var source = @"[assembly: NServiceBus.NServiceBusTriggerFunction(""endpoint"")]"; + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [Test] - public void No_attribute_should_not_generate_trigger_function() - { - var source = @""; - var (output, _) = GetGeneratedOutput(source); + [Test] + public void No_attribute_should_not_generate_trigger_function() + { + var source = @""; + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [Test] - public void No_attribute_should_not_generate_compilation_error() - { - var source = @"using NServiceBus;"; - var (_, diagnostics) = GetGeneratedOutput(source); + [Test] + public void No_attribute_should_not_generate_compilation_error() + { + var source = @"using NServiceBus;"; + var (_, diagnostics) = GetGeneratedOutput(source); - Assert.That(diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error), Is.False); - } + Assert.That(diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error), Is.False); + } - [Test] - public void Can_override_trigger_function_name() - { - var source = - @"using NServiceBus; + [Test] + public void Can_override_trigger_function_name() + { + var source = + @"using NServiceBus; [assembly: NServiceBusTriggerFunction(""endpoint"", TriggerFunctionName = ""trigger"")] public class Startup { }"; - var (output, _) = GetGeneratedOutput(source); + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [TestCase(null)] - [TestCase("")] - [TestCase(" ")] - public void Invalid_name_should_cause_an_error(string endpointName) - { - var source = @" + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void Invalid_name_should_cause_an_error(string endpointName) + { + var source = @" using NServiceBus; [assembly: NServiceBusTriggerFunction(""" + endpointName + @""")] "; - var (_, diagnostics) = GetGeneratedOutput(source, suppressGeneratedDiagnosticsErrors: true); + var (_, diagnostics) = GetGeneratedOutput(source, suppressGeneratedDiagnosticsErrors: true); - Assert.That(diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error && d.Id == AzureFunctionsDiagnostics.InvalidEndpointNameErrorId), Is.True); - } + Assert.That(diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error && d.Id == AzureFunctionsDiagnostics.InvalidEndpointNameErrorId), Is.True); + } - [TestCase(null)] - [TestCase("")] - [TestCase(" ")] - public void Invalid_trigger_function_name_should_cause_an_error(string triggerFunctionName) - { - var source = @" + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void Invalid_trigger_function_name_should_cause_an_error(string triggerFunctionName) + { + var source = @" using NServiceBus; [assembly: NServiceBusTriggerFunction(""endpoint"", TriggerFunctionName = """ + triggerFunctionName + @""")] "; - var (_, diagnostics) = GetGeneratedOutput(source, suppressGeneratedDiagnosticsErrors: true); + var (_, diagnostics) = GetGeneratedOutput(source, suppressGeneratedDiagnosticsErrors: true); - Assert.That(diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error && d.Id == AzureFunctionsDiagnostics.InvalidTriggerFunctionNameErrorId), Is.True); - } + Assert.That(diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error && d.Id == AzureFunctionsDiagnostics.InvalidTriggerFunctionNameErrorId), Is.True); + } - [Test] - public void Can_enable_transactions() - { - var source = @" + [Test] + public void Can_enable_transactions() + { + var source = @" using NServiceBus; [assembly: NServiceBusTriggerFunction(""endpoint"", SendsAtomicWithReceive = true)] @@ -137,15 +137,15 @@ public void Can_enable_transactions() public class Startup { }"; - var (output, _) = GetGeneratedOutput(source); + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [Test] - public void Use_two_optionals() - { - var source = @" + [Test] + public void Use_two_optionals() + { + var source = @" using NServiceBus; [assembly: NServiceBusTriggerFunction(""endpoint"", TriggerFunctionName = ""trigger"", SendsAtomicWithReceive = true)] @@ -153,15 +153,15 @@ public void Use_two_optionals() public class Startup { }"; - var (output, _) = GetGeneratedOutput(source); + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [Test] - public void Two_optionals_out_of_order() - { - var source = @" + [Test] + public void Two_optionals_out_of_order() + { + var source = @" using NServiceBus; [assembly: NServiceBusTriggerFunction(""endpoint"", SendsAtomicWithReceive = true, TriggerFunctionName = ""trigger"")] @@ -169,70 +169,68 @@ public void Two_optionals_out_of_order() public class Startup { }"; - var (output, _) = GetGeneratedOutput(source); + var (output, _) = GetGeneratedOutput(source); - Approver.Verify(output); - } + Approver.Verify(output); + } - [OneTimeSetUp] - public void Init() - { - // For the unit tests to work, the compilation used by the source generator needs to know that NServiceBusTriggerFunction - // is an attribute from NServiceBus namespace and its full name is NServiceBus.NServiceBusTriggerFunctionAttribute. - // By referencing NServiceBusTriggerFunctionAttribute here, NServiceBus.AzureFunctions.InProcess.ServiceBus is forced to load and participate in the compilation. - _ = new NServiceBusTriggerFunctionAttribute(endpointName: "test"); - } + [OneTimeSetUp] + public void Init() + { + // For the unit tests to work, the compilation used by the source generator needs to know that NServiceBusTriggerFunction + // is an attribute from NServiceBus namespace and its full name is NServiceBus.NServiceBusTriggerFunctionAttribute. + // By referencing NServiceBusTriggerFunctionAttribute here, NServiceBus.AzureFunctions.InProcess.ServiceBus is forced to load and participate in the compilation. + _ = new NServiceBusTriggerFunctionAttribute(endpointName: "test"); + } - static (string output, ImmutableArray diagnostics) GetGeneratedOutput(string source, bool suppressGeneratedDiagnosticsErrors = false) - { - var syntaxTree = CSharpSyntaxTree.ParseText(source); - var references = new List(); - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + static (string output, ImmutableArray diagnostics) GetGeneratedOutput(string source, bool suppressGeneratedDiagnosticsErrors = false) + { + var syntaxTree = CSharpSyntaxTree.ParseText(source); + var references = new List(); + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in assemblies) + foreach (var assembly in assemblies) + { + if (!assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location)) { - if (!assembly.IsDynamic && !string.IsNullOrWhiteSpace(assembly.Location)) - { - references.Add(MetadataReference.CreateFromFile(assembly.Location)); - } + references.Add(MetadataReference.CreateFromFile(assembly.Location)); } + } - var compilation = Compile(new[] - { - syntaxTree - }, references); + var compilation = Compile([ + syntaxTree + ], references); - var generator = new TriggerFunctionGenerator(); + var generator = new TriggerFunctionGenerator(); - var driver = CSharpGeneratorDriver.Create(generator); - driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics); + var driver = CSharpGeneratorDriver.Create(generator); + driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generateDiagnostics); - // add necessary references for the generated trigger - references.Add(MetadataReference.CreateFromFile(typeof(ServiceBusTriggerAttribute).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(ExecutionContext).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(ServiceBusReceivedMessage).Assembly.Location)); - references.Add(MetadataReference.CreateFromFile(typeof(ILogger).Assembly.Location)); - Compile(outputCompilation.SyntaxTrees, references); + // add necessary references for the generated trigger + references.Add(MetadataReference.CreateFromFile(typeof(ServiceBusTriggerAttribute).Assembly.Location)); + references.Add(MetadataReference.CreateFromFile(typeof(ExecutionContext).Assembly.Location)); + references.Add(MetadataReference.CreateFromFile(typeof(ServiceBusReceivedMessage).Assembly.Location)); + references.Add(MetadataReference.CreateFromFile(typeof(ILogger).Assembly.Location)); + Compile(outputCompilation.SyntaxTrees, references); - if (!suppressGeneratedDiagnosticsErrors) - { - Assert.That(generateDiagnostics.Any(d => d.Severity == DiagnosticSeverity.Error), Is.False, "Failed: " + generateDiagnostics.FirstOrDefault()?.GetMessage()); - } - - return (outputCompilation.SyntaxTrees.Last().ToString(), generateDiagnostics); + if (!suppressGeneratedDiagnosticsErrors) + { + Assert.That(generateDiagnostics.Any(d => d.Severity == DiagnosticSeverity.Error), Is.False, "Failed: " + generateDiagnostics.FirstOrDefault()?.GetMessage()); } - static CSharpCompilation Compile(IEnumerable syntaxTrees, IEnumerable references) - { - var compilation = CSharpCompilation.Create("result", syntaxTrees, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + return (outputCompilation.SyntaxTrees.Last().ToString(), generateDiagnostics); + } - // Verify the code compiled: - var compilationErrors = compilation - .GetDiagnostics() - .Where(d => d.Severity >= DiagnosticSeverity.Warning); - Assert.That(compilationErrors, Is.Empty, compilationErrors.FirstOrDefault()?.GetMessage()); + static CSharpCompilation Compile(IEnumerable syntaxTrees, IEnumerable references) + { + var compilation = CSharpCompilation.Create("result", syntaxTrees, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - return compilation; - } + // Verify the code compiled: + var compilationErrors = compilation + .GetDiagnostics() + .Where(d => d.Severity >= DiagnosticSeverity.Warning); + Assert.That(compilationErrors, Is.Empty, compilationErrors.FirstOrDefault()?.GetMessage()); + + return compilation; } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/TransportConfigurationAnalyzerTests.cs b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/TransportConfigurationAnalyzerTests.cs index 23697da5..de571137 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/TransportConfigurationAnalyzerTests.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.Analyzer.Tests/TransportConfigurationAnalyzerTests.cs @@ -1,23 +1,23 @@ -namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests -{ - using System.Threading.Tasks; - using NUnit.Framework; - using static AzureFunctionsDiagnostics; +namespace NServiceBus.AzureFunctions.InProcess.Analyzer.Tests; + +using System.Threading.Tasks; +using NUnit.Framework; +using static AzureFunctionsDiagnostics; - [TestFixture] - public class TransportConfigurationAnalyzerTests : AnalyzerTestFixture +[TestFixture] +public class TransportConfigurationAnalyzerTests : AnalyzerTestFixture +{ + [TestCase("TransportTransactionMode", "TransportTransactionMode.None", TransportTransactionModeNotAllowedId)] + [TestCase("EnablePartitioning", "true", EnablePartitioningNotAllowedId)] + [TestCase("EntityMaximumSize", "5", EntityMaximumSizeNotAllowedId)] + [TestCase("MaxAutoLockRenewalDuration", "new System.TimeSpan(0, 0, 5, 0)", MaxAutoLockRenewalDurationNotAllowedId)] + [TestCase("PrefetchCount", "5", PrefetchCountNotAllowedId)] + [TestCase("PrefetchMultiplier", "5", PrefetchMultiplierNotAllowedId)] + [TestCase("TimeToWaitBeforeTriggeringCircuitBreaker", "new System.TimeSpan(0, 0, 5, 0)", TimeToWaitBeforeTriggeringCircuitBreakerNotAllowedId)] + public Task DiagnosticIsReportedTransportConfigurationDirect(string configName, string configValue, string diagnosticId) { - [TestCase("TransportTransactionMode", "TransportTransactionMode.None", TransportTransactionModeNotAllowedId)] - [TestCase("EnablePartitioning", "true", EnablePartitioningNotAllowedId)] - [TestCase("EntityMaximumSize", "5", EntityMaximumSizeNotAllowedId)] - [TestCase("MaxAutoLockRenewalDuration", "new System.TimeSpan(0, 0, 5, 0)", MaxAutoLockRenewalDurationNotAllowedId)] - [TestCase("PrefetchCount", "5", PrefetchCountNotAllowedId)] - [TestCase("PrefetchMultiplier", "5", PrefetchMultiplierNotAllowedId)] - [TestCase("TimeToWaitBeforeTriggeringCircuitBreaker", "new System.TimeSpan(0, 0, 5, 0)", TimeToWaitBeforeTriggeringCircuitBreakerNotAllowedId)] - public Task DiagnosticIsReportedTransportConfigurationDirect(string configName, string configValue, string diagnosticId) - { - var source = - $@"using NServiceBus; + var source = + $@"using NServiceBus; using System; using System.Threading.Tasks; class Foo @@ -31,20 +31,20 @@ void Direct(ServiceBusTriggeredEndpointConfiguration endpointConfig) }} }}"; - return Assert(diagnosticId, source); - } + return Assert(diagnosticId, source); + } - [TestCase("Transactions", "TransportTransactionMode.None", TransportTransactionModeNotAllowedId)] - [TestCase("EnablePartitioning", "", EnablePartitioningNotAllowedId)] - [TestCase("EntityMaximumSize", "5", EntityMaximumSizeNotAllowedId)] - [TestCase("MaxAutoLockRenewalDuration", "new System.TimeSpan(0, 0, 5, 0)", MaxAutoLockRenewalDurationNotAllowedId)] - [TestCase("PrefetchCount", "5", PrefetchCountNotAllowedId)] - [TestCase("PrefetchMultiplier", "5", PrefetchMultiplierNotAllowedId)] - [TestCase("TimeToWaitBeforeTriggeringCircuitBreaker", "new System.TimeSpan(0, 0, 5, 0)", TimeToWaitBeforeTriggeringCircuitBreakerNotAllowedId)] - public Task DiagnosticIsReportedTransportConfigurationExtension(string configName, string configValue, string diagnosticId) - { - var source = - $@"using NServiceBus; + [TestCase("Transactions", "TransportTransactionMode.None", TransportTransactionModeNotAllowedId)] + [TestCase("EnablePartitioning", "", EnablePartitioningNotAllowedId)] + [TestCase("EntityMaximumSize", "5", EntityMaximumSizeNotAllowedId)] + [TestCase("MaxAutoLockRenewalDuration", "new System.TimeSpan(0, 0, 5, 0)", MaxAutoLockRenewalDurationNotAllowedId)] + [TestCase("PrefetchCount", "5", PrefetchCountNotAllowedId)] + [TestCase("PrefetchMultiplier", "5", PrefetchMultiplierNotAllowedId)] + [TestCase("TimeToWaitBeforeTriggeringCircuitBreaker", "new System.TimeSpan(0, 0, 5, 0)", TimeToWaitBeforeTriggeringCircuitBreakerNotAllowedId)] + public Task DiagnosticIsReportedTransportConfigurationExtension(string configName, string configValue, string diagnosticId) + { + var source = + $@"using NServiceBus; using System; using System.Threading.Tasks; class Foo @@ -55,19 +55,19 @@ void Extension(TransportExtensions transportExtension) }} }}"; - return Assert(diagnosticId, source); - } + return Assert(diagnosticId, source); + } - [TestCase("EnablePartitioning", "true", EnablePartitioningNotAllowedId)] - [TestCase("EntityMaximumSize", "5", EntityMaximumSizeNotAllowedId)] - [TestCase("MaxAutoLockRenewalDuration", "new System.TimeSpan(0, 0, 5, 0)", MaxAutoLockRenewalDurationNotAllowedId)] - [TestCase("PrefetchCount", "5", PrefetchCountNotAllowedId)] - [TestCase("PrefetchMultiplier", "5", PrefetchMultiplierNotAllowedId)] - [TestCase("TimeToWaitBeforeTriggeringCircuitBreaker", "new System.TimeSpan(0, 0, 5, 0)", TimeToWaitBeforeTriggeringCircuitBreakerNotAllowedId)] - public Task DiagnosticIsNotReportedForNonTransportConfiguration(string configName, string configValue, string diagnosticId) - { - var source = - $@"using NServiceBus; + [TestCase("EnablePartitioning", "true", EnablePartitioningNotAllowedId)] + [TestCase("EntityMaximumSize", "5", EntityMaximumSizeNotAllowedId)] + [TestCase("MaxAutoLockRenewalDuration", "new System.TimeSpan(0, 0, 5, 0)", MaxAutoLockRenewalDurationNotAllowedId)] + [TestCase("PrefetchCount", "5", PrefetchCountNotAllowedId)] + [TestCase("PrefetchMultiplier", "5", PrefetchMultiplierNotAllowedId)] + [TestCase("TimeToWaitBeforeTriggeringCircuitBreaker", "new System.TimeSpan(0, 0, 5, 0)", TimeToWaitBeforeTriggeringCircuitBreakerNotAllowedId)] + public Task DiagnosticIsNotReportedForNonTransportConfiguration(string configName, string configValue, string diagnosticId) + { + var source = + $@"using NServiceBus; using System; using System.Threading.Tasks; @@ -89,7 +89,6 @@ void Direct(SomeOtherClass endpointConfig) }} }}"; - return Assert(diagnosticId, source); - } + return Assert(diagnosticId, source); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionExecutionContext.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionExecutionContext.cs index d74dea26..6b785c89 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionExecutionContext.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionExecutionContext.cs @@ -1,30 +1,29 @@ -namespace NServiceBus -{ - using Microsoft.Azure.WebJobs; - using Microsoft.Extensions.Logging; +namespace NServiceBus; + +using Microsoft.Azure.WebJobs; +using Microsoft.Extensions.Logging; +/// +/// Contains specific context information of the current function invocation. +/// +public class FunctionExecutionContext +{ /// - /// Contains specific context information of the current function invocation. + /// Creates a new . /// - public class FunctionExecutionContext + public FunctionExecutionContext(ExecutionContext executionContext, ILogger logger) { - /// - /// Creates a new . - /// - public FunctionExecutionContext(ExecutionContext executionContext, ILogger logger) - { - Logger = logger; - ExecutionContext = executionContext; - } + Logger = logger; + ExecutionContext = executionContext; + } - /// - /// The associated with the current function invocation. - /// - public ExecutionContext ExecutionContext { get; } + /// + /// The associated with the current function invocation. + /// + public ExecutionContext ExecutionContext { get; } - /// - /// The associated with the current function invocation. - /// - public ILogger Logger { get; } - } -} + /// + /// The associated with the current function invocation. + /// + public ILogger Logger { get; } +} \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs index 52b33d4f..1e8dfbf0 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/FunctionsHostBuilderExtensions.cs @@ -1,164 +1,163 @@ -namespace NServiceBus +namespace NServiceBus; + +using System; +using System.IO; +using System.Reflection; +using AzureFunctions.InProcess.ServiceBus; +using Configuration.AdvancedExtensibility; +using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Transport.AzureServiceBus; + +/// +/// Provides extension methods to configure a using . +/// +public static partial class FunctionsHostBuilderExtensions { - using System; - using System.IO; - using System.Reflection; - using AzureFunctions.InProcess.ServiceBus; - using Configuration.AdvancedExtensibility; - using Microsoft.Azure.Functions.Extensions.DependencyInjection; - using Microsoft.Extensions.Azure; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Options; - using Transport.AzureServiceBus; + /// + /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. + /// + public static void UseNServiceBus( + this IFunctionsHostBuilder functionsHostBuilder, + Action configurationFactory = null) => + RegisterEndpointFactory(functionsHostBuilder, null, Assembly.GetCallingAssembly(), (c) => configurationFactory?.Invoke(c)); /// - /// Provides extension methods to configure a using . + /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. /// - public static partial class FunctionsHostBuilderExtensions + public static void UseNServiceBus( + this IFunctionsHostBuilder functionsHostBuilder, + string endpointName, + Action configurationFactory = null) { - /// - /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. - /// - public static void UseNServiceBus( - this IFunctionsHostBuilder functionsHostBuilder, - Action configurationFactory = null) => - RegisterEndpointFactory(functionsHostBuilder, null, Assembly.GetCallingAssembly(), (c) => configurationFactory?.Invoke(c)); - - /// - /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. - /// - public static void UseNServiceBus( - this IFunctionsHostBuilder functionsHostBuilder, - string endpointName, - Action configurationFactory = null) + if (string.IsNullOrWhiteSpace(endpointName)) { - if (string.IsNullOrWhiteSpace(endpointName)) - { - throw new ArgumentException($"{nameof(endpointName)} must have a value"); - } - RegisterEndpointFactory(functionsHostBuilder, endpointName, null, configurationFactory); + throw new ArgumentException($"{nameof(endpointName)} must have a value"); } + RegisterEndpointFactory(functionsHostBuilder, endpointName, null, configurationFactory); + } - /// - /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. - /// - public static void UseNServiceBus( - this IFunctionsHostBuilder functionsHostBuilder, - string endpointName, - string connectionString, - Action configurationFactory = null) + /// + /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. + /// + public static void UseNServiceBus( + this IFunctionsHostBuilder functionsHostBuilder, + string endpointName, + string connectionString, + Action configurationFactory = null) + { + if (string.IsNullOrWhiteSpace(endpointName)) { - if (string.IsNullOrWhiteSpace(endpointName)) - { - throw new ArgumentException($"{nameof(endpointName)} must have a value"); - } - RegisterEndpointFactory(functionsHostBuilder, endpointName, null, configurationFactory, connectionString); + throw new ArgumentException($"{nameof(endpointName)} must have a value"); } + RegisterEndpointFactory(functionsHostBuilder, endpointName, null, configurationFactory, connectionString); + } - /// - /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. - /// - public static void UseNServiceBus( - this IFunctionsHostBuilder functionsHostBuilder, - Func configurationFactory) - { - var functionsHostBuilderContext = functionsHostBuilder.GetContextInternal(); - var configuration = functionsHostBuilderContext.Configuration; - var serviceBusTriggeredEndpointConfiguration = configurationFactory(configuration); + /// + /// Configures an NServiceBus endpoint that can be injected into a function trigger as a via dependency injection. + /// + public static void UseNServiceBus( + this IFunctionsHostBuilder functionsHostBuilder, + Func configurationFactory) + { + var functionsHostBuilderContext = functionsHostBuilder.GetContextInternal(); + var configuration = functionsHostBuilderContext.Configuration; + var serviceBusTriggeredEndpointConfiguration = configurationFactory(configuration); - ConfigureEndpointFactory(functionsHostBuilder.Services, functionsHostBuilderContext, serviceBusTriggeredEndpointConfiguration); - } + ConfigureEndpointFactory(functionsHostBuilder.Services, functionsHostBuilderContext, serviceBusTriggeredEndpointConfiguration); + } + + static void RegisterEndpointFactory(IFunctionsHostBuilder functionsHostBuilder, + string endpointName, + Assembly callingAssembly, + Action configurationFactory, + string connectionString = null) + { + var functionsHostBuilderContext = functionsHostBuilder.GetContextInternal(); + var configuration = functionsHostBuilderContext.Configuration; + var triggerAttribute = callingAssembly + ?.GetCustomAttribute(); + var endpointNameValue = triggerAttribute?.EndpointName; + var connectionName = triggerAttribute?.Connection; - static void RegisterEndpointFactory(IFunctionsHostBuilder functionsHostBuilder, - string endpointName, - Assembly callingAssembly, - Action configurationFactory, - string connectionString = null) + endpointName ??= configuration?.GetValue("ENDPOINT_NAME") ?? endpointNameValue; + + if (string.IsNullOrWhiteSpace(endpointName)) { - var functionsHostBuilderContext = functionsHostBuilder.GetContextInternal(); - var configuration = functionsHostBuilderContext.Configuration; - var triggerAttribute = callingAssembly - ?.GetCustomAttribute(); - var endpointNameValue = triggerAttribute?.EndpointName; - var connectionName = triggerAttribute?.Connection; - - endpointName ??= configuration?.GetValue("ENDPOINT_NAME") ?? endpointNameValue; - - if (string.IsNullOrWhiteSpace(endpointName)) - { - throw new Exception($@"Endpoint name cannot be determined automatically. Use one of the following options to specify endpoint name: + throw new Exception($@"Endpoint name cannot be determined automatically. Use one of the following options to specify endpoint name: - Use `{nameof(NServiceBusTriggerFunctionAttribute)}(endpointName)` to generate a trigger - Use `functionsHostBuilder.UseNServiceBus(endpointName, configuration)` - Add a configuration or environment variable with the key ENDPOINT_NAME"); - } + } - functionsHostBuilder.Services.AddAzureClientsCore(); + functionsHostBuilder.Services.AddAzureClientsCore(); - var functionEndpointConfiguration = new ServiceBusTriggeredEndpointConfiguration(endpointName, configuration, connectionString, connectionName); + var functionEndpointConfiguration = new ServiceBusTriggeredEndpointConfiguration(endpointName, configuration, connectionString, connectionName); - configurationFactory?.Invoke(functionEndpointConfiguration); + configurationFactory?.Invoke(functionEndpointConfiguration); - ConfigureEndpointFactory(functionsHostBuilder.Services, functionsHostBuilderContext, functionEndpointConfiguration); - } + ConfigureEndpointFactory(functionsHostBuilder.Services, functionsHostBuilderContext, functionEndpointConfiguration); + } - static void ConfigureEndpointFactory(IServiceCollection services, FunctionsHostBuilderContext functionsHostBuilderContext, - ServiceBusTriggeredEndpointConfiguration serviceBusTriggeredEndpointConfiguration) - { - var serverless = serviceBusTriggeredEndpointConfiguration.InitializeTransport(); - // When using functions, assemblies are moved to a 'bin' folder within FunctionsHostBuilderContext.ApplicationRootPath. - var advancedConfiguration = serviceBusTriggeredEndpointConfiguration.AdvancedConfiguration; - var assemblyDirectoryName = advancedConfiguration.GetSettings().GetOrDefault("NServiceBus.AzureFunctions.InProcess.ServiceBus.AssemblyDirectoryName") ?? "bin"; - var startableEndpoint = Configure( - advancedConfiguration, - services, - Path.Combine(functionsHostBuilderContext.ApplicationRootPath, assemblyDirectoryName)); - - _ = services.AddSingleton(serviceBusTriggeredEndpointConfiguration); - _ = services.AddSingleton(startableEndpoint); - _ = services.AddSingleton(serverless); - _ = services.AddSingleton(); - _ = services.AddSingleton(sp => sp.GetRequiredService()); + static void ConfigureEndpointFactory(IServiceCollection services, FunctionsHostBuilderContext functionsHostBuilderContext, + ServiceBusTriggeredEndpointConfiguration serviceBusTriggeredEndpointConfiguration) + { + var serverless = serviceBusTriggeredEndpointConfiguration.InitializeTransport(); + // When using functions, assemblies are moved to a 'bin' folder within FunctionsHostBuilderContext.ApplicationRootPath. + var advancedConfiguration = serviceBusTriggeredEndpointConfiguration.AdvancedConfiguration; + var assemblyDirectoryName = advancedConfiguration.GetSettings().GetOrDefault("NServiceBus.AzureFunctions.InProcess.ServiceBus.AssemblyDirectoryName") ?? "bin"; + var startableEndpoint = Configure( + advancedConfiguration, + services, + Path.Combine(functionsHostBuilderContext.ApplicationRootPath, assemblyDirectoryName)); + + _ = services.AddSingleton(serviceBusTriggeredEndpointConfiguration); + _ = services.AddSingleton(startableEndpoint); + _ = services.AddSingleton(serverless); + _ = services.AddSingleton(); + _ = services.AddSingleton(sp => sp.GetRequiredService()); #pragma warning disable CS0618 // Type or member is obsolete - // Validator is registered here in case the user wants to use the options directly. This makes sure that the options are validated. - // The transport still has to validate the options because options validators are only executed when the options are resolved. - _ = services.AddSingleton, MigrationTopologyOptionsValidator>(); - _ = services.AddOptions() + // Validator is registered here in case the user wants to use the options directly. This makes sure that the options are validated. + // The transport still has to validate the options because options validators are only executed when the options are resolved. + _ = services.AddSingleton, MigrationTopologyOptionsValidator>(); + _ = services.AddOptions() #pragma warning restore CS0618 // Type or member is obsolete - .BindConfiguration("AzureServiceBus:MigrationTopologyOptions"); + .BindConfiguration("AzureServiceBus:MigrationTopologyOptions"); - // Validator is registered here in case the user wants to use the options directly. This makes sure that the options are validated. - // The transport still has to validate the options because options validators are only executed when the options are resolved. - _ = services.AddSingleton, TopologyOptionsValidator>(); - _ = services.AddOptions().BindConfiguration("AzureServiceBus:TopologyOptions"); - } + // Validator is registered here in case the user wants to use the options directly. This makes sure that the options are validated. + // The transport still has to validate the options because options validators are only executed when the options are resolved. + _ = services.AddSingleton, TopologyOptionsValidator>(); + _ = services.AddOptions().BindConfiguration("AzureServiceBus:TopologyOptions"); + } - static FunctionsHostBuilderContext GetContextInternal(this IFunctionsHostBuilder functionsHostBuilder) + static FunctionsHostBuilderContext GetContextInternal(this IFunctionsHostBuilder functionsHostBuilder) + { + // This check is for testing purposes only. See more details on the internal interface below. + if (functionsHostBuilder is IFunctionsHostBuilderExt internalBuilder) { - // This check is for testing purposes only. See more details on the internal interface below. - if (functionsHostBuilder is IFunctionsHostBuilderExt internalBuilder) - { - return internalBuilder.Context; - } - - return functionsHostBuilder.GetContext(); + return internalBuilder.Context; } - static IStartableEndpointWithExternallyManagedContainer Configure( - EndpointConfiguration endpointConfiguration, - IServiceCollection serviceCollection, - string appDirectory = null) + return functionsHostBuilder.GetContext(); + } + + static IStartableEndpointWithExternallyManagedContainer Configure( + EndpointConfiguration endpointConfiguration, + IServiceCollection serviceCollection, + string appDirectory = null) + { + var scanner = endpointConfiguration.AssemblyScanner(); + if (appDirectory != null) { - var scanner = endpointConfiguration.AssemblyScanner(); - if (appDirectory != null) - { - scanner.AdditionalAssemblyScanningPath = appDirectory; - } + scanner.AdditionalAssemblyScanningPath = appDirectory; + } - scanner.ExcludeAssemblies(InProcessFunctionEndpoint.AssembliesToExcludeFromScanning); + scanner.ExcludeAssemblies(InProcessFunctionEndpoint.AssembliesToExcludeFromScanning); - return EndpointWithExternallyManagedContainer.Create(endpointConfiguration, serviceCollection); - } + return EndpointWithExternallyManagedContainer.Create(endpointConfiguration, serviceCollection); } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs index 84e0c49a..218a09fc 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionEndpoint.cs @@ -1,99 +1,98 @@ -namespace NServiceBus -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus; - using Microsoft.Azure.WebJobs.ServiceBus; - using Microsoft.Extensions.Logging; - using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; +namespace NServiceBus; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.WebJobs.ServiceBus; +using Microsoft.Extensions.Logging; +using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; +/// +/// An NServiceBus endpoint hosted in Azure Function which does not receive messages automatically but only handles +/// messages explicitly passed to it by the caller. +/// +public interface IFunctionEndpoint +{ /// - /// An NServiceBus endpoint hosted in Azure Function which does not receive messages automatically but only handles - /// messages explicitly passed to it by the caller. + /// Processes the received message in atomic sends with receive mode. /// - public interface IFunctionEndpoint - { - /// - /// Processes the received message in atomic sends with receive mode. - /// - Task ProcessAtomic( - ServiceBusReceivedMessage message, - ExecutionContext executionContext, - ServiceBusClient serviceBusClient, - ServiceBusMessageActions messageActions, - ILogger functionsLogger = null, - CancellationToken cancellationToken = default); + Task ProcessAtomic( + ServiceBusReceivedMessage message, + ExecutionContext executionContext, + ServiceBusClient serviceBusClient, + ServiceBusMessageActions messageActions, + ILogger functionsLogger = null, + CancellationToken cancellationToken = default); - /// - /// Processes the received message in receive only transaction mode. - /// - Task ProcessNonAtomic( - ServiceBusReceivedMessage message, - ExecutionContext executionContext, - ILogger functionsLogger = null, - CancellationToken cancellationToken = default); + /// + /// Processes the received message in receive only transaction mode. + /// + Task ProcessNonAtomic( + ServiceBusReceivedMessage message, + ExecutionContext executionContext, + ILogger functionsLogger = null, + CancellationToken cancellationToken = default); - /// - /// Sends the provided message. - /// - Task Send(object message, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Sends the provided message. + /// + Task Send(object message, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Sends the provided message. - /// - Task Send(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Sends the provided message. + /// + Task Send(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Instantiates a message of type T and sends it. - /// - Task Send(Action messageConstructor, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Instantiates a message of type T and sends it. + /// + Task Send(Action messageConstructor, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Instantiates a message of type T and sends it. - /// - Task Send(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Instantiates a message of type T and sends it. + /// + Task Send(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Publish the message to subscribers. - /// - Task Publish(object message, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Publish the message to subscribers. + /// + Task Publish(object message, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Instantiates a message of type T and publishes it. - /// - Task Publish(Action messageConstructor, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Instantiates a message of type T and publishes it. + /// + Task Publish(Action messageConstructor, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Instantiates a message of type T and publishes it. - /// - Task Publish(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Instantiates a message of type T and publishes it. + /// + Task Publish(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Instantiates a message of type T and publishes it. - /// - Task Publish(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Instantiates a message of type T and publishes it. + /// + Task Publish(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Subscribes to receive published messages of the specified type. - /// This method is only necessary if you turned off auto-subscribe. - /// - Task Subscribe(Type eventType, SubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Subscribes to receive published messages of the specified type. + /// This method is only necessary if you turned off auto-subscribe. + /// + Task Subscribe(Type eventType, SubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Subscribes to receive published messages of the specified type. - /// This method is only necessary if you turned off auto-subscribe. - /// - Task Subscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Subscribes to receive published messages of the specified type. + /// This method is only necessary if you turned off auto-subscribe. + /// + Task Subscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Unsubscribes to receive published messages of the specified type. - /// - Task Unsubscribe(Type eventType, UnsubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); + /// + /// Unsubscribes to receive published messages of the specified type. + /// + Task Unsubscribe(Type eventType, UnsubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - /// - /// Unsubscribes to receive published messages of the specified type. - /// - Task Unsubscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); - } + /// + /// Unsubscribes to receive published messages of the specified type. + /// + Task Unsubscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionsHostBuilderExt.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionsHostBuilderExt.cs index 5f0e6a7e..d0b1bc21 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionsHostBuilderExt.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/IFunctionsHostBuilderExt.cs @@ -1,12 +1,11 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using Microsoft.Azure.Functions.Extensions.DependencyInjection; +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using Microsoft.Azure.Functions.Extensions.DependencyInjection; - // This is an internal marker interface similar to Microsoft.Azure.Functions.Extensions.DependencyInjection.IFunctionsHostBuilderExt - // to facilitate test. It will only ever be implemented by the testing infrastructure and probably only required until in process - // functions properly support the generic host. - interface IFunctionsHostBuilderExt - { - FunctionsHostBuilderContext Context { get; } - } +// This is an internal marker interface similar to Microsoft.Azure.Functions.Extensions.DependencyInjection.IFunctionsHostBuilderExt +// to facilitate test. It will only ever be implemented by the testing infrastructure and probably only required until in process +// functions properly support the generic host. +interface IFunctionsHostBuilderExt +{ + FunctionsHostBuilderContext Context { get; } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/InProcessFunctionEndpoint.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/InProcessFunctionEndpoint.cs index 95863bd5..24af567c 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/InProcessFunctionEndpoint.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/InProcessFunctionEndpoint.cs @@ -1,189 +1,189 @@ -namespace NServiceBus +namespace NServiceBus; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using AzureFunctions.InProcess.ServiceBus; +using Microsoft.Azure.WebJobs.ServiceBus; +using Microsoft.Extensions.Logging; +using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; + +class InProcessFunctionEndpoint : IFunctionEndpoint { - using System; - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus; - using AzureFunctions.InProcess.ServiceBus; - using Microsoft.Azure.WebJobs.ServiceBus; - using Microsoft.Extensions.Logging; - using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; - - class InProcessFunctionEndpoint : IFunctionEndpoint - { - public InProcessFunctionEndpoint( - IStartableEndpointWithExternallyManagedContainer externallyManagedContainerEndpoint, - ServerlessTransport serverlessTransport, - IServiceProvider serviceProvider) + public InProcessFunctionEndpoint( + IStartableEndpointWithExternallyManagedContainer externallyManagedContainerEndpoint, + ServerlessTransport serverlessTransport, + IServiceProvider serviceProvider) + { + this.serverlessTransport = serverlessTransport; + this.serverlessTransport.ServiceProvider = serviceProvider; + endpointFactory = () => externallyManagedContainerEndpoint.Start(serviceProvider); + } + + public async Task ProcessAtomic( + ServiceBusReceivedMessage message, + ExecutionContext executionContext, + ServiceBusClient serviceBusClient, + ServiceBusMessageActions messageActions, + ILogger functionsLogger = null, + CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + + try { - this.serverlessTransport = serverlessTransport; - this.serverlessTransport.ServiceProvider = serviceProvider; - endpointFactory = () => externallyManagedContainerEndpoint.Start(serviceProvider); + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); } - - public async Task ProcessAtomic( - ServiceBusReceivedMessage message, - ExecutionContext executionContext, - ServiceBusClient serviceBusClient, - ServiceBusMessageActions messageActions, - ILogger functionsLogger = null, - CancellationToken cancellationToken = default) + catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - - try - { - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) - { - await messageActions.AbandonMessageAsync(message, cancellationToken: cancellationToken).ConfigureAwait(false); - throw; - } - - await messageProcessor.ProcessAtomic(message, serviceBusClient, messageActions, cancellationToken) - .ConfigureAwait(false); + await messageActions.AbandonMessageAsync(message, cancellationToken: cancellationToken).ConfigureAwait(false); + throw; } - public async Task ProcessNonAtomic( - ServiceBusReceivedMessage message, - ExecutionContext executionContext, - ILogger functionsLogger = null, - CancellationToken cancellationToken = default) - { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + await messageProcessor.ProcessAtomic(message, serviceBusClient, messageActions, cancellationToken) + .ConfigureAwait(false); + } - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); + public async Task ProcessNonAtomic( + ServiceBusReceivedMessage message, + ExecutionContext executionContext, + ILogger functionsLogger = null, + CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - await messageProcessor.ProcessNonAtomic(message, cancellationToken) - .ConfigureAwait(false); - } + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - public async Task Send(object message, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + await messageProcessor.ProcessNonAtomic(message, cancellationToken) + .ConfigureAwait(false); + } - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - await endpoint.Send(message, options, cancellationToken).ConfigureAwait(false); - } + public async Task Send(object message, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - public Task Send(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - return Send(message, new SendOptions(), executionContext, functionsLogger, cancellationToken); - } + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); + await endpoint.Send(message, options, cancellationToken).ConfigureAwait(false); + } - public async Task Send(Action messageConstructor, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + public Task Send(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + return Send(message, new SendOptions(), executionContext, functionsLogger, cancellationToken); + } - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - await endpoint.Send(messageConstructor, options, cancellationToken).ConfigureAwait(false); - } + public async Task Send(Action messageConstructor, SendOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - public Task Send(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - return Send(messageConstructor, new SendOptions(), executionContext, functionsLogger, cancellationToken); - } + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); + await endpoint.Send(messageConstructor, options, cancellationToken).ConfigureAwait(false); + } - public async Task Publish(object message, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + public Task Send(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + return Send(messageConstructor, new SendOptions(), executionContext, functionsLogger, cancellationToken); + } - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - await endpoint.Publish(message, options, cancellationToken).ConfigureAwait(false); - } + public async Task Publish(object message, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - public Task Publish(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - return Publish(message, new PublishOptions(), executionContext, functionsLogger, cancellationToken); - } + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); + await endpoint.Publish(message, options, cancellationToken).ConfigureAwait(false); + } - public async Task Publish(Action messageConstructor, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + public Task Publish(object message, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + return Publish(message, new PublishOptions(), executionContext, functionsLogger, cancellationToken); + } - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - await endpoint.Publish(messageConstructor, options, cancellationToken).ConfigureAwait(false); - } + public async Task Publish(Action messageConstructor, PublishOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - public Task Publish(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - return Publish(messageConstructor, new PublishOptions(), executionContext, functionsLogger, cancellationToken); - } + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); + await endpoint.Publish(messageConstructor, options, cancellationToken).ConfigureAwait(false); + } - public async Task Subscribe(Type eventType, SubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + public Task Publish(Action messageConstructor, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + return Publish(messageConstructor, new PublishOptions(), executionContext, functionsLogger, cancellationToken); + } - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - await endpoint.Subscribe(eventType, options, cancellationToken).ConfigureAwait(false); - } + public async Task Subscribe(Type eventType, SubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - public Task Subscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - return Subscribe(eventType, new SubscribeOptions(), executionContext, functionsLogger, cancellationToken); - } + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); + await endpoint.Subscribe(eventType, options, cancellationToken).ConfigureAwait(false); + } - public async Task Unsubscribe(Type eventType, UnsubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); + public Task Subscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + return Subscribe(eventType, new SubscribeOptions(), executionContext, functionsLogger, cancellationToken); + } - await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); - await endpoint.Unsubscribe(eventType, options, cancellationToken).ConfigureAwait(false); - } + public async Task Unsubscribe(Type eventType, UnsubscribeOptions options, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + FunctionsLoggerFactory.Instance.SetCurrentLogger(functionsLogger); - public Task Unsubscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) - { - return Unsubscribe(eventType, new UnsubscribeOptions(), executionContext, functionsLogger, cancellationToken); - } + await InitializeEndpointIfNecessary(cancellationToken).ConfigureAwait(false); + await endpoint.Unsubscribe(eventType, options, cancellationToken).ConfigureAwait(false); + } + + public Task Unsubscribe(Type eventType, ExecutionContext executionContext, ILogger functionsLogger = null, CancellationToken cancellationToken = default) + { + return Unsubscribe(eventType, new UnsubscribeOptions(), executionContext, functionsLogger, cancellationToken); + } - internal static readonly string[] AssembliesToExcludeFromScanning = { - "Azure.Core.dll", - "Azure.Identity.dll", - "Azure.Security.KeyVault.Secrets.dll", - "Azure.Storage.Blobs.dll", - "Azure.Storage.Common.dll", - "Google.Protobuf.dll", - "Grpc.AspNetCore.Server.ClientFactory.dll", - "Grpc.AspNetCore.Server.dll", - "Grpc.Core.Api.dll", - "Grpc.Net.Client.dll", - "Grpc.Net.ClientFactory.dll", - "Grpc.Net.Common.dll", - "Microsoft.Extensions.Azure.dll", - "Microsoft.Identity.Client.dll", - "Microsoft.Identity.Client.Extensions.Msal.dll", - "NCrontab.Signed.dll", - "NServiceBus.Extensions.DependencyInjection.dll", - "System.ClientModel.dll" - }; - - internal async Task InitializeEndpointIfNecessary(CancellationToken cancellationToken = default) + internal static readonly string[] AssembliesToExcludeFromScanning = + [ + "Azure.Core.dll", + "Azure.Identity.dll", + "Azure.Security.KeyVault.Secrets.dll", + "Azure.Storage.Blobs.dll", + "Azure.Storage.Common.dll", + "Google.Protobuf.dll", + "Grpc.AspNetCore.Server.ClientFactory.dll", + "Grpc.AspNetCore.Server.dll", + "Grpc.Core.Api.dll", + "Grpc.Net.Client.dll", + "Grpc.Net.ClientFactory.dll", + "Grpc.Net.Common.dll", + "Microsoft.Extensions.Azure.dll", + "Microsoft.Identity.Client.dll", + "Microsoft.Identity.Client.Extensions.Msal.dll", + "NCrontab.Signed.dll", + "NServiceBus.Extensions.DependencyInjection.dll", + "System.ClientModel.dll" + ]; + + internal async Task InitializeEndpointIfNecessary(CancellationToken cancellationToken = default) + { + if (messageProcessor == null) { - if (messageProcessor == null) + await semaphoreLock.WaitAsync(cancellationToken).ConfigureAwait(false); + try { - await semaphoreLock.WaitAsync(cancellationToken).ConfigureAwait(false); - try + if (messageProcessor == null) { - if (messageProcessor == null) - { - endpoint = await endpointFactory().ConfigureAwait(false); + endpoint = await endpointFactory().ConfigureAwait(false); - messageProcessor = serverlessTransport.MessageProcessor; - } - } - finally - { - semaphoreLock.Release(); + messageProcessor = serverlessTransport.MessageProcessor; } } + finally + { + semaphoreLock.Release(); + } } + } - IMessageProcessor messageProcessor; - IEndpointInstance endpoint; + IMessageProcessor messageProcessor; + IEndpointInstance endpoint; - readonly Func> endpointFactory; - readonly SemaphoreSlim semaphoreLock = new SemaphoreSlim(initialCount: 1, maxCount: 1); - readonly ServerlessTransport serverlessTransport; - } + readonly Func> endpointFactory; + readonly SemaphoreSlim semaphoreLock = new SemaphoreSlim(initialCount: 1, maxCount: 1); + readonly ServerlessTransport serverlessTransport; } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/FunctionsLoggerFactory.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/FunctionsLoggerFactory.cs index 40fdcd03..8c0e4c91 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/FunctionsLoggerFactory.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/FunctionsLoggerFactory.cs @@ -1,41 +1,40 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System; +using System.Threading; +using Logging; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using ILoggerFactory = Logging.ILoggerFactory; + +class FunctionsLoggerFactory : ILoggerFactory { - using System; - using System.Threading; - using Logging; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Logging.Abstractions; - using ILoggerFactory = Logging.ILoggerFactory; - - class FunctionsLoggerFactory : ILoggerFactory - { - public static FunctionsLoggerFactory Instance { get; } = new FunctionsLoggerFactory(); + public static FunctionsLoggerFactory Instance { get; } = new FunctionsLoggerFactory(); - Logger log; + Logger log; - AsyncLocal logger = new AsyncLocal(); + AsyncLocal logger = new AsyncLocal(); - FunctionsLoggerFactory() - { - log = new Logger(logger); - } + FunctionsLoggerFactory() + { + log = new Logger(logger); + } - public void SetCurrentLogger(ILogger currentLogger) - { - var newLogger = currentLogger ?? NullLogger.Instance; + public void SetCurrentLogger(ILogger currentLogger) + { + var newLogger = currentLogger ?? NullLogger.Instance; - logger.Value = newLogger; - log.Flush(newLogger); - } + logger.Value = newLogger; + log.Flush(newLogger); + } - public ILog GetLogger(Type type) - { - return log; - } + public ILog GetLogger(Type type) + { + return log; + } - public ILog GetLogger(string name) - { - return log; - } + public ILog GetLogger(string name) + { + return log; } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/Logger.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/Logger.cs index 97f33406..00a32de0 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/Logger.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Logging/Logger.cs @@ -1,109 +1,108 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System; +using System.Collections.Concurrent; +using System.Threading; +using Logging; +using Microsoft.Extensions.Logging; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +class Logger : ILog { - using System; - using System.Collections.Concurrent; - using System.Threading; - using Logging; - using Microsoft.Extensions.Logging; - using LogLevel = Microsoft.Extensions.Logging.LogLevel; - - class Logger : ILog + public Logger(AsyncLocal logger) { - public Logger(AsyncLocal logger) - { - this.logger = logger; - } + this.logger = logger; + } - public bool IsDebugEnabled => logger.Value?.IsEnabled(LogLevel.Debug) ?? true; - public bool IsInfoEnabled => logger.Value?.IsEnabled(LogLevel.Information) ?? true; - public bool IsWarnEnabled => logger.Value?.IsEnabled(LogLevel.Warning) ?? true; - public bool IsErrorEnabled => logger.Value?.IsEnabled(LogLevel.Error) ?? true; - public bool IsFatalEnabled => logger.Value?.IsEnabled(LogLevel.Critical) ?? true; + public bool IsDebugEnabled => logger.Value?.IsEnabled(LogLevel.Debug) ?? true; + public bool IsInfoEnabled => logger.Value?.IsEnabled(LogLevel.Information) ?? true; + public bool IsWarnEnabled => logger.Value?.IsEnabled(LogLevel.Warning) ?? true; + public bool IsErrorEnabled => logger.Value?.IsEnabled(LogLevel.Error) ?? true; + public bool IsFatalEnabled => logger.Value?.IsEnabled(LogLevel.Critical) ?? true; - void Log(LogLevel level, string message) + void Log(LogLevel level, string message) + { + var concreteLogger = logger.Value; + if (concreteLogger == null) { - var concreteLogger = logger.Value; - if (concreteLogger == null) - { - deferredMessageLogs.Enqueue((level, message)); - return; - } - concreteLogger.Log(level, message); + deferredMessageLogs.Enqueue((level, message)); + return; } + concreteLogger.Log(level, message); + } - void Log(LogLevel level, string message, Exception exception) + void Log(LogLevel level, string message, Exception exception) + { + var concreteLogger = logger.Value; + if (concreteLogger == null) { - var concreteLogger = logger.Value; - if (concreteLogger == null) - { - deferredExceptionLogs.Enqueue((level, message, exception)); - return; - } - concreteLogger.Log(level, exception, message); + deferredExceptionLogs.Enqueue((level, message, exception)); + return; } + concreteLogger.Log(level, exception, message); + } - void Log(LogLevel level, string format, object[] args) + void Log(LogLevel level, string format, object[] args) + { + var concreteLogger = logger.Value; + if (concreteLogger == null) { - var concreteLogger = logger.Value; - if (concreteLogger == null) - { - deferredFormatLogs.Enqueue((level, format, args)); - return; - } - concreteLogger.Log(level, format, args); + deferredFormatLogs.Enqueue((level, format, args)); + return; } + concreteLogger.Log(level, format, args); + } - public void Debug(string message) => Log(LogLevel.Debug, message); + public void Debug(string message) => Log(LogLevel.Debug, message); - public void Debug(string message, Exception exception) => Log(LogLevel.Debug, message, exception); + public void Debug(string message, Exception exception) => Log(LogLevel.Debug, message, exception); - public void DebugFormat(string format, params object[] args) => Log(LogLevel.Debug, format, args); + public void DebugFormat(string format, params object[] args) => Log(LogLevel.Debug, format, args); - public void Info(string message) => Log(LogLevel.Information, message); + public void Info(string message) => Log(LogLevel.Information, message); - public void Info(string message, Exception exception) => Log(LogLevel.Information, message, exception); + public void Info(string message, Exception exception) => Log(LogLevel.Information, message, exception); - public void InfoFormat(string format, params object[] args) => Log(LogLevel.Information, format, args); + public void InfoFormat(string format, params object[] args) => Log(LogLevel.Information, format, args); - public void Warn(string message) => Log(LogLevel.Warning, message); + public void Warn(string message) => Log(LogLevel.Warning, message); - public void Warn(string message, Exception exception) => Log(LogLevel.Warning, message, exception); + public void Warn(string message, Exception exception) => Log(LogLevel.Warning, message, exception); - public void WarnFormat(string format, params object[] args) => Log(LogLevel.Warning, format, args); + public void WarnFormat(string format, params object[] args) => Log(LogLevel.Warning, format, args); - public void Error(string message) => Log(LogLevel.Error, message); + public void Error(string message) => Log(LogLevel.Error, message); - public void Error(string message, Exception exception) => Log(LogLevel.Error, message, exception); + public void Error(string message, Exception exception) => Log(LogLevel.Error, message, exception); - public void ErrorFormat(string format, params object[] args) => Log(LogLevel.Error, format, args); + public void ErrorFormat(string format, params object[] args) => Log(LogLevel.Error, format, args); - public void Fatal(string message) => Log(LogLevel.Critical, message); + public void Fatal(string message) => Log(LogLevel.Critical, message); - public void Fatal(string message, Exception exception) => Log(LogLevel.Critical, message, exception); + public void Fatal(string message, Exception exception) => Log(LogLevel.Critical, message, exception); - public void FatalFormat(string format, params object[] args) => Log(LogLevel.Critical, format, args); + public void FatalFormat(string format, params object[] args) => Log(LogLevel.Critical, format, args); - internal void Flush(ILogger concreteLogger) + internal void Flush(ILogger concreteLogger) + { + while (deferredMessageLogs.TryDequeue(out var entry)) { - while (deferredMessageLogs.TryDequeue(out var entry)) - { - concreteLogger.Log(entry.level, entry.message); - } - - while (deferredExceptionLogs.TryDequeue(out var entry)) - { - concreteLogger.Log(entry.level, entry.exception, entry.message); - } - - while (deferredFormatLogs.TryDequeue(out var entry)) - { - concreteLogger.Log(entry.level, entry.format, entry.args); - } + concreteLogger.Log(entry.level, entry.message); } - AsyncLocal logger; - readonly ConcurrentQueue<(LogLevel level, string message)> deferredMessageLogs = new ConcurrentQueue<(LogLevel level, string message)>(); - readonly ConcurrentQueue<(LogLevel level, string message, Exception exception)> deferredExceptionLogs = new ConcurrentQueue<(LogLevel level, string message, Exception exception)>(); - readonly ConcurrentQueue<(LogLevel level, string format, object[] args)> deferredFormatLogs = new ConcurrentQueue<(LogLevel level, string format, object[] args)>(); + while (deferredExceptionLogs.TryDequeue(out var entry)) + { + concreteLogger.Log(entry.level, entry.exception, entry.message); + } + + while (deferredFormatLogs.TryDequeue(out var entry)) + { + concreteLogger.Log(entry.level, entry.format, entry.args); + } } + + AsyncLocal logger; + readonly ConcurrentQueue<(LogLevel level, string message)> deferredMessageLogs = new ConcurrentQueue<(LogLevel level, string message)>(); + readonly ConcurrentQueue<(LogLevel level, string message, Exception exception)> deferredExceptionLogs = new ConcurrentQueue<(LogLevel level, string message, Exception exception)>(); + readonly ConcurrentQueue<(LogLevel level, string format, object[] args)> deferredFormatLogs = new ConcurrentQueue<(LogLevel level, string format, object[] args)>(); } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/MessageExtensions.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/MessageExtensions.cs index e77a6da1..bc72228e 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/MessageExtensions.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/MessageExtensions.cs @@ -1,44 +1,43 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using System; - using System.Collections.Generic; - using Azure.Messaging.ServiceBus; - - static class MessageExtensions - { - public static Dictionary GetHeaders(this ServiceBusReceivedMessage message) - { - var headers = new Dictionary(message.ApplicationProperties.Count); +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; - foreach (var kvp in message.ApplicationProperties) - { - headers[kvp.Key] = kvp.Value?.ToString(); - } +using System; +using System.Collections.Generic; +using Azure.Messaging.ServiceBus; - headers.Remove("NServiceBus.Transport.Encoding"); +static class MessageExtensions +{ + public static Dictionary GetHeaders(this ServiceBusReceivedMessage message) + { + var headers = new Dictionary(message.ApplicationProperties.Count); - if (!string.IsNullOrWhiteSpace(message.ReplyTo)) - { - headers[Headers.ReplyToAddress] = message.ReplyTo; - } + foreach (var kvp in message.ApplicationProperties) + { + headers[kvp.Key] = kvp.Value?.ToString(); + } - if (!string.IsNullOrWhiteSpace(message.CorrelationId)) - { - headers[Headers.CorrelationId] = message.CorrelationId; - } + headers.Remove("NServiceBus.Transport.Encoding"); - return headers; + if (!string.IsNullOrWhiteSpace(message.ReplyTo)) + { + headers[Headers.ReplyToAddress] = message.ReplyTo; } - public static string GetMessageId(this ServiceBusReceivedMessage message) + if (!string.IsNullOrWhiteSpace(message.CorrelationId)) { - if (string.IsNullOrEmpty(message.MessageId)) - { - // assume native message w/o message ID - return Guid.NewGuid().ToString("N"); - } + headers[Headers.CorrelationId] = message.CorrelationId; + } + + return headers; + } - return message.MessageId; + public static string GetMessageId(this ServiceBusReceivedMessage message) + { + if (string.IsNullOrEmpty(message.MessageId)) + { + // assume native message w/o message ID + return Guid.NewGuid().ToString("N"); } + + return message.MessageId; } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBusTriggerFunctionAttribute.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBusTriggerFunctionAttribute.cs index 8cf28ed1..5375b29d 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBusTriggerFunctionAttribute.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/NServiceBusTriggerFunctionAttribute.cs @@ -1,39 +1,38 @@ -namespace NServiceBus +namespace NServiceBus; + +/// +/// Assembly attribute to configure generated NServiceBus Azure Function. +/// The attribute is used to wire up an auto-generated Service Bus trigger function, responding to messages in the queue specified by the name provided. +/// +[System.AttributeUsage(System.AttributeTargets.Assembly)] +public sealed class NServiceBusTriggerFunctionAttribute : System.Attribute { - /// - /// Assembly attribute to configure generated NServiceBus Azure Function. - /// The attribute is used to wire up an auto-generated Service Bus trigger function, responding to messages in the queue specified by the name provided. - /// - [System.AttributeUsage(System.AttributeTargets.Assembly)] - public sealed class NServiceBusTriggerFunctionAttribute : System.Attribute - { - /// - /// Endpoint name that is the input queue name. - /// - public string EndpointName { get; } + /// + /// Endpoint name that is the input queue name. + /// + public string EndpointName { get; } - /// - /// Override trigger function name. - /// - public string TriggerFunctionName { get; set; } + /// + /// Override trigger function name. + /// + public string TriggerFunctionName { get; set; } - /// - /// Enable Azure Service Bus transactions to provide transport guarantees. - /// - public bool SendsAtomicWithReceive { get; set; } + /// + /// Enable Azure Service Bus transactions to provide transport guarantees. + /// + public bool SendsAtomicWithReceive { get; set; } - /// - /// Gets or sets the app setting name that contains the Service Bus connection string. - /// - public string Connection { get; set; } + /// + /// Gets or sets the app setting name that contains the Service Bus connection string. + /// + public string Connection { get; set; } - /// - /// Endpoint logical name. - /// - /// Endpoint name that is the input queue name. - public NServiceBusTriggerFunctionAttribute(string endpointName) - { - EndpointName = endpointName; - } + /// + /// Endpoint logical name. + /// + /// Endpoint name that is the input queue name. + public NServiceBusTriggerFunctionAttribute(string endpointName) + { + EndpointName = endpointName; } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/DeterministicGuid.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/DeterministicGuid.cs index 9a693937..1ca3e01b 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/DeterministicGuid.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/DeterministicGuid.cs @@ -1,21 +1,20 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using System; - using System.Security.Cryptography; - using System.Text; +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System; +using System.Security.Cryptography; +using System.Text; - static class DeterministicGuid +static class DeterministicGuid +{ + public static Guid Create(string data) { - public static Guid Create(string data) + // use MD5 hash to get a 16-byte hash of the string + using (var provider = MD5.Create()) { - // use MD5 hash to get a 16-byte hash of the string - using (var provider = MD5.Create()) - { - var inputBytes = Encoding.Default.GetBytes(data); - var hashBytes = provider.ComputeHash(inputBytes); - // generate a guid from the hash: - return new Guid(hashBytes); - } + var inputBytes = Encoding.Default.GetBytes(data); + var hashBytes = provider.ComputeHash(inputBytes); + // generate a guid from the hash: + return new Guid(hashBytes); } } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/Recoverability/ServerlessRecoverabilityPolicy.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/Recoverability/ServerlessRecoverabilityPolicy.cs index 8a7cf8e5..f2503362 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/Recoverability/ServerlessRecoverabilityPolicy.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/Recoverability/ServerlessRecoverabilityPolicy.cs @@ -1,28 +1,27 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System; +using Transport; + +class ServerlessRecoverabilityPolicy { - using System; - using Transport; + public bool SendFailedMessagesToErrorQueue { get; set; } - class ServerlessRecoverabilityPolicy + public RecoverabilityAction Invoke(RecoverabilityConfig config, ErrorContext errorContext) { - public bool SendFailedMessagesToErrorQueue { get; set; } + var action = DefaultRecoverabilityPolicy.Invoke(config, errorContext); - public RecoverabilityAction Invoke(RecoverabilityConfig config, ErrorContext errorContext) + if (action is MoveToError) { - var action = DefaultRecoverabilityPolicy.Invoke(config, errorContext); - - if (action is MoveToError) + if (SendFailedMessagesToErrorQueue) { - if (SendFailedMessagesToErrorQueue) - { - return action; - } - - // 7.2 offers a Discard option, but we want to bubble up the exception so it can fail the function invocation. - throw new Exception("Failed to process message.", errorContext.Exception); + return action; } - return action; + // 7.2 offers a Discard option, but we want to bubble up the exception so it can fail the function invocation. + throw new Exception("Failed to process message.", errorContext.Exception); } + + return action; } } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/IMessageProcessor.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/IMessageProcessor.cs index d2396645..3229a0e9 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/IMessageProcessor.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/IMessageProcessor.cs @@ -1,20 +1,19 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus; - using Microsoft.Azure.WebJobs.ServiceBus; +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.WebJobs.ServiceBus; - interface IMessageProcessor - { - Task ProcessNonAtomic( - ServiceBusReceivedMessage message, - CancellationToken cancellationToken = default); +interface IMessageProcessor +{ + Task ProcessNonAtomic( + ServiceBusReceivedMessage message, + CancellationToken cancellationToken = default); - Task ProcessAtomic( - ServiceBusReceivedMessage message, - ServiceBusClient serviceBusClient, - ServiceBusMessageActions messageActions, - CancellationToken cancellationToken = default); - } + Task ProcessAtomic( + ServiceBusReceivedMessage message, + ServiceBusClient serviceBusClient, + ServiceBusMessageActions messageActions, + CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/PipelineInvokingMessageProcessor.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/PipelineInvokingMessageProcessor.cs index e592c0c6..feac946a 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/PipelineInvokingMessageProcessor.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/PipelineInvokingMessageProcessor.cs @@ -1,154 +1,153 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.WebJobs.ServiceBus; +using NServiceBus.AzureFunctions.InProcess.ServiceBus.Serverless; +using NServiceBus.Extensibility; +using NServiceBus.Transport.AzureServiceBus; +using Transport; + +class PipelineInvokingMessageProcessor : IMessageReceiver, IMessageProcessor { - using System; - using System.Threading; - using System.Threading.Tasks; - using System.Transactions; - using Azure.Messaging.ServiceBus; - using Microsoft.Azure.WebJobs.ServiceBus; - using NServiceBus.AzureFunctions.InProcess.ServiceBus.Serverless; - using NServiceBus.Extensibility; - using NServiceBus.Transport.AzureServiceBus; - using Transport; - - class PipelineInvokingMessageProcessor : IMessageReceiver, IMessageProcessor + public PipelineInvokingMessageProcessor(IMessageReceiver baseTransportReceiver) => this.baseTransportReceiver = baseTransportReceiver; + + public Task Initialize(PushRuntimeSettings limitations, OnMessage onMessage, OnError onError, + CancellationToken cancellationToken = default) { - public PipelineInvokingMessageProcessor(IMessageReceiver baseTransportReceiver) => this.baseTransportReceiver = baseTransportReceiver; + this.onMessage = onMessage; + this.onError = onError; + return baseTransportReceiver.Initialize(limitations, + (_, __) => Task.CompletedTask, + (_, __) => Task.FromResult(ErrorHandleResult.Handled), + cancellationToken) ?? Task.CompletedTask; + } - public Task Initialize(PushRuntimeSettings limitations, OnMessage onMessage, OnError onError, - CancellationToken cancellationToken = default) + public async Task ProcessNonAtomic( + ServiceBusReceivedMessage message, + CancellationToken cancellationToken = default) + { + try { - this.onMessage = onMessage; - this.onError = onError; - return baseTransportReceiver.Initialize(limitations, - (_, __) => Task.CompletedTask, - (_, __) => Task.FromResult(ErrorHandleResult.Handled), - cancellationToken) ?? Task.CompletedTask; - } + var messageContext = CreateMessageContext(message, new TransportTransaction(), false); + + await onMessage(messageContext, cancellationToken).ConfigureAwait(false); - public async Task ProcessNonAtomic( - ServiceBusReceivedMessage message, - CancellationToken cancellationToken = default) + } + catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) { - try - { - var messageContext = CreateMessageContext(message, new TransportTransaction(), false); + var errorContext = CreateErrorContext(message, new TransportTransaction(), ex); - await onMessage(messageContext, cancellationToken).ConfigureAwait(false); + var errorHandleResult = await onError(errorContext, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) + if (errorHandleResult == ErrorHandleResult.Handled) { - var errorContext = CreateErrorContext(message, new TransportTransaction(), ex); - - var errorHandleResult = await onError(errorContext, cancellationToken).ConfigureAwait(false); - - if (errorHandleResult == ErrorHandleResult.Handled) - { - return; - } - throw; + return; } + throw; } + } - public async Task ProcessAtomic( - ServiceBusReceivedMessage message, - ServiceBusClient serviceBusClient, - ServiceBusMessageActions messageActions, - CancellationToken cancellationToken = default) + public async Task ProcessAtomic( + ServiceBusReceivedMessage message, + ServiceBusClient serviceBusClient, + ServiceBusMessageActions messageActions, + CancellationToken cancellationToken = default) + { + try { - try + using (var azureServiceBusTransaction = CreateTransaction(message.PartitionKey, serviceBusClient)) { - using (var azureServiceBusTransaction = CreateTransaction(message.PartitionKey, serviceBusClient)) - { - var messageContext = CreateMessageContext(message, azureServiceBusTransaction.TransportTransaction, true); + var messageContext = CreateMessageContext(message, azureServiceBusTransaction.TransportTransaction, true); - await onMessage(messageContext, cancellationToken).ConfigureAwait(false); + await onMessage(messageContext, cancellationToken).ConfigureAwait(false); - await SafeCompleteMessageAsync(messageActions, message, azureServiceBusTransaction, cancellationToken).ConfigureAwait(false); - azureServiceBusTransaction.Commit(); - } + await SafeCompleteMessageAsync(messageActions, message, azureServiceBusTransaction, cancellationToken).ConfigureAwait(false); + azureServiceBusTransaction.Commit(); } - catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) + } + catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) + { + ErrorHandleResult result; + using (var azureServiceBusTransaction = CreateTransaction(message.PartitionKey, serviceBusClient)) { - ErrorHandleResult result; - using (var azureServiceBusTransaction = CreateTransaction(message.PartitionKey, serviceBusClient)) - { - var errorContext = CreateErrorContext(message, azureServiceBusTransaction.TransportTransaction, ex); - - result = await onError(errorContext, cancellationToken).ConfigureAwait(false); + var errorContext = CreateErrorContext(message, azureServiceBusTransaction.TransportTransaction, ex); - if (result == ErrorHandleResult.Handled) - { - await SafeCompleteMessageAsync(messageActions, message, azureServiceBusTransaction, cancellationToken).ConfigureAwait(false); - } + result = await onError(errorContext, cancellationToken).ConfigureAwait(false); - azureServiceBusTransaction.Commit(); - } - - if (result != ErrorHandleResult.Handled) + if (result == ErrorHandleResult.Handled) { - await messageActions.AbandonMessageAsync(message, cancellationToken: cancellationToken).ConfigureAwait(false); + await SafeCompleteMessageAsync(messageActions, message, azureServiceBusTransaction, cancellationToken).ConfigureAwait(false); } + + azureServiceBusTransaction.Commit(); } - } - ErrorContext CreateErrorContext(ServiceBusReceivedMessage message, TransportTransaction transportTransaction, Exception exception) - { - var errorContext = new ErrorContext( - exception, - message.GetHeaders(), - message.MessageId, - message.Body, - transportTransaction, - message.DeliveryCount, - ReceiveAddress, - new ContextBag()); - return errorContext; + if (result != ErrorHandleResult.Handled) + { + await messageActions.AbandonMessageAsync(message, cancellationToken: cancellationToken).ConfigureAwait(false); + } } + } - MessageContext CreateMessageContext(ServiceBusReceivedMessage message, TransportTransaction transportTransaction, bool atomic) - { - var contextBag = new ContextBag(); - var invocationMode = new FunctionInvocationMode(atomic); - contextBag.Set(invocationMode); - var messageContext = new MessageContext( - message.MessageId, - message.GetHeaders(), - message.Body, - transportTransaction, - ReceiveAddress, - contextBag); - return messageContext; - } + ErrorContext CreateErrorContext(ServiceBusReceivedMessage message, TransportTransaction transportTransaction, Exception exception) + { + var errorContext = new ErrorContext( + exception, + message.GetHeaders(), + message.MessageId, + message.Body, + transportTransaction, + message.DeliveryCount, + ReceiveAddress, + new ContextBag()); + return errorContext; + } - static async Task SafeCompleteMessageAsync(ServiceBusMessageActions messageActions, ServiceBusReceivedMessage message, AzureServiceBusTransportTransaction azureServiceBusTransaction, CancellationToken cancellationToken) - { - using var scope = azureServiceBusTransaction.ToTransactionScope(); - await messageActions.CompleteMessageAsync(message, cancellationToken).ConfigureAwait(false); - scope.Complete(); - } + MessageContext CreateMessageContext(ServiceBusReceivedMessage message, TransportTransaction transportTransaction, bool atomic) + { + var contextBag = new ContextBag(); + var invocationMode = new FunctionInvocationMode(atomic); + contextBag.Set(invocationMode); + var messageContext = new MessageContext( + message.MessageId, + message.GetHeaders(), + message.Body, + transportTransaction, + ReceiveAddress, + contextBag); + return messageContext; + } - static AzureServiceBusTransportTransaction CreateTransaction(string messagePartitionKey, ServiceBusClient serviceBusClient) => - new(serviceBusClient, messagePartitionKey, new TransactionOptions - { - IsolationLevel = IsolationLevel.Serializable, - Timeout = TransactionManager.MaximumTimeout - }); + static async Task SafeCompleteMessageAsync(ServiceBusMessageActions messageActions, ServiceBusReceivedMessage message, AzureServiceBusTransportTransaction azureServiceBusTransaction, CancellationToken cancellationToken) + { + using var scope = azureServiceBusTransaction.ToTransactionScope(); + await messageActions.CompleteMessageAsync(message, cancellationToken).ConfigureAwait(false); + scope.Complete(); + } - public Task StartReceive(CancellationToken cancellationToken = default) => Task.CompletedTask; + static AzureServiceBusTransportTransaction CreateTransaction(string messagePartitionKey, ServiceBusClient serviceBusClient) => + new(serviceBusClient, messagePartitionKey, new TransactionOptions + { + IsolationLevel = IsolationLevel.Serializable, + Timeout = TransactionManager.MaximumTimeout + }); - // No-op because the rate at which Azure Functions pushes messages to the pipeline can't be controlled. - public Task ChangeConcurrency(PushRuntimeSettings limitations, CancellationToken cancellationToken = default) => Task.CompletedTask; + public Task StartReceive(CancellationToken cancellationToken = default) => Task.CompletedTask; - public Task StopReceive(CancellationToken cancellationToken = default) => Task.CompletedTask; - public ISubscriptionManager Subscriptions => baseTransportReceiver.Subscriptions; - public string Id => baseTransportReceiver.Id; + // No-op because the rate at which Azure Functions pushes messages to the pipeline can't be controlled. + public Task ChangeConcurrency(PushRuntimeSettings limitations, CancellationToken cancellationToken = default) => Task.CompletedTask; - public string ReceiveAddress => baseTransportReceiver.ReceiveAddress; + public Task StopReceive(CancellationToken cancellationToken = default) => Task.CompletedTask; + public ISubscriptionManager Subscriptions => baseTransportReceiver.Subscriptions; + public string Id => baseTransportReceiver.Id; - readonly IMessageReceiver baseTransportReceiver; - OnMessage onMessage; - OnError onError; - } + public string ReceiveAddress => baseTransportReceiver.ReceiveAddress; + + readonly IMessageReceiver baseTransportReceiver; + OnMessage onMessage; + OnError onError; } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/SendOnlyMessageProcessor.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/SendOnlyMessageProcessor.cs index 654b4e25..69e1f685 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/SendOnlyMessageProcessor.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/SendOnlyMessageProcessor.cs @@ -1,27 +1,26 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus; - using Microsoft.Azure.WebJobs.ServiceBus; +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.WebJobs.ServiceBus; - class SendOnlyMessageProcessor : IMessageProcessor - { - public Task ProcessNonAtomic( - ServiceBusReceivedMessage message, - CancellationToken cancellationToken = default) - => throw new InvalidOperationException( - $"This endpoint cannot process messages because it is configured in send-only mode. Remove the '{nameof(EndpointConfiguration)}.{nameof(EndpointConfiguration.SendOnly)}' configuration.'" - ); +class SendOnlyMessageProcessor : IMessageProcessor +{ + public Task ProcessNonAtomic( + ServiceBusReceivedMessage message, + CancellationToken cancellationToken = default) + => throw new InvalidOperationException( + $"This endpoint cannot process messages because it is configured in send-only mode. Remove the '{nameof(EndpointConfiguration)}.{nameof(EndpointConfiguration.SendOnly)}' configuration.'" + ); - public Task ProcessAtomic( - ServiceBusReceivedMessage message, - ServiceBusClient serviceBusClient, - ServiceBusMessageActions messageActions, - CancellationToken cancellationToken = default) - => throw new InvalidOperationException( - $"This endpoint cannot process messages because it is configured in send-only mode. Remove the '{nameof(EndpointConfiguration)}.{nameof(EndpointConfiguration.SendOnly)}' configuration.'" - ); - } + public Task ProcessAtomic( + ServiceBusReceivedMessage message, + ServiceBusClient serviceBusClient, + ServiceBusMessageActions messageActions, + CancellationToken cancellationToken = default) + => throw new InvalidOperationException( + $"This endpoint cannot process messages because it is configured in send-only mode. Remove the '{nameof(EndpointConfiguration)}.{nameof(EndpointConfiguration.SendOnly)}' configuration.'" + ); } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs index 92ada331..c4ca1b93 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransport.cs @@ -1,114 +1,113 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Transport; + +class ServerlessTransport(AzureServiceBusTransport transport, string connectionString, string connectionName) : TransportDefinition( + TransportTransactionMode.ReceiveOnly, + transport.SupportsDelayedDelivery, + transport.SupportsPublishSubscribe, + transport.SupportsTTBR) { - using System; - using System.Collections.Generic; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Azure.Core; - using Microsoft.Extensions.Azure; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Transport; - - class ServerlessTransport(AzureServiceBusTransport transport, string connectionString, string connectionName) : TransportDefinition( - TransportTransactionMode.ReceiveOnly, - transport.SupportsDelayedDelivery, - transport.SupportsPublishSubscribe, - transport.SupportsTTBR) - { - // HINT: This constant is defined in NServiceBus but is not exposed - const string MainReceiverId = "Main"; - const string SendOnlyConfigKey = "Endpoint.SendOnly"; + // HINT: This constant is defined in NServiceBus but is not exposed + const string MainReceiverId = "Main"; + const string SendOnlyConfigKey = "Endpoint.SendOnly"; - public IMessageProcessor MessageProcessor { get; private set; } + public IMessageProcessor MessageProcessor { get; private set; } - public IServiceProvider ServiceProvider { get; set; } + public IServiceProvider ServiceProvider { get; set; } - public override async Task Initialize(HostSettings hostSettings, ReceiveSettings[] receivers, - string[] sendingAddresses, - CancellationToken cancellationToken = default) - { - var configuredTransport = ConfigureTransportConnection(connectionString, connectionName, ServiceProvider.GetRequiredService(), transport, - ServiceProvider.GetRequiredService()); + public override async Task Initialize(HostSettings hostSettings, ReceiveSettings[] receivers, + string[] sendingAddresses, + CancellationToken cancellationToken = default) + { + var configuredTransport = ConfigureTransportConnection(connectionString, connectionName, ServiceProvider.GetRequiredService(), transport, + ServiceProvider.GetRequiredService()); - var baseTransportInfrastructure = await configuredTransport.Initialize( - hostSettings, - receivers, - sendingAddresses, - cancellationToken) - .ConfigureAwait(false); + var baseTransportInfrastructure = await configuredTransport.Initialize( + hostSettings, + receivers, + sendingAddresses, + cancellationToken) + .ConfigureAwait(false); - var serverlessTransportInfrastructure = new ServerlessTransportInfrastructure(baseTransportInfrastructure); + var serverlessTransportInfrastructure = new ServerlessTransportInfrastructure(baseTransportInfrastructure); - var isSendOnly = hostSettings.CoreSettings.GetOrDefault(SendOnlyConfigKey); + var isSendOnly = hostSettings.CoreSettings.GetOrDefault(SendOnlyConfigKey); - MessageProcessor = isSendOnly - ? new SendOnlyMessageProcessor() // send-only endpoint - : (IMessageProcessor)serverlessTransportInfrastructure.Receivers[MainReceiverId]; + MessageProcessor = isSendOnly + ? new SendOnlyMessageProcessor() // send-only endpoint + : (IMessageProcessor)serverlessTransportInfrastructure.Receivers[MainReceiverId]; - return serverlessTransportInfrastructure; - } + return serverlessTransportInfrastructure; + } - public override IReadOnlyCollection GetSupportedTransactionModes() => - supportedTransactionModes; + public override IReadOnlyCollection GetSupportedTransactionModes() => + supportedTransactionModes; - static AzureServiceBusTransport ConfigureTransportConnection(string connectionString, string connectionName, IConfiguration configuration, - AzureServiceBusTransport transport, AzureComponentFactory azureComponentFactory) + static AzureServiceBusTransport ConfigureTransportConnection(string connectionString, string connectionName, IConfiguration configuration, + AzureServiceBusTransport transport, AzureComponentFactory azureComponentFactory) + { + if (connectionString != null) + { + GetConnectionStringRef(transport) = connectionString; + } + else { - if (connectionString != null) + var serviceBusConnectionName = string.IsNullOrWhiteSpace(connectionName) ? DefaultServiceBusConnectionName : connectionName; + IConfigurationSection connectionSection = configuration.GetSection(serviceBusConnectionName); + if (!connectionSection.Exists()) { - GetConnectionStringRef(transport) = connectionString; + throw new Exception($"Azure Service Bus connection string/section has not been configured. Specify a connection string through IConfiguration, an environment variable named {serviceBusConnectionName} or passing it to `UseNServiceBus(ENDPOINTNAME,CONNECTIONSTRING)`"); + } + + if (!string.IsNullOrWhiteSpace(connectionSection.Value)) + { + GetConnectionStringRef(transport) = connectionSection.Value; } else { - var serviceBusConnectionName = string.IsNullOrWhiteSpace(connectionName) ? DefaultServiceBusConnectionName : connectionName; - IConfigurationSection connectionSection = configuration.GetSection(serviceBusConnectionName); - if (!connectionSection.Exists()) + string fullyQualifiedNamespace = connectionSection["fullyQualifiedNamespace"]; + if (string.IsNullOrWhiteSpace(fullyQualifiedNamespace)) { - throw new Exception($"Azure Service Bus connection string/section has not been configured. Specify a connection string through IConfiguration, an environment variable named {serviceBusConnectionName} or passing it to `UseNServiceBus(ENDPOINTNAME,CONNECTIONSTRING)`"); + throw new Exception("Connection should have an 'fullyQualifiedNamespace' property or be a string representing a connection string."); } - if (!string.IsNullOrWhiteSpace(connectionSection.Value)) - { - GetConnectionStringRef(transport) = connectionSection.Value; - } - else - { - string fullyQualifiedNamespace = connectionSection["fullyQualifiedNamespace"]; - if (string.IsNullOrWhiteSpace(fullyQualifiedNamespace)) - { - throw new Exception("Connection should have an 'fullyQualifiedNamespace' property or be a string representing a connection string."); - } - - var credential = azureComponentFactory.CreateTokenCredential(connectionSection); - GetFullyQualifiedNamespaceRef(transport) = fullyQualifiedNamespace; - GetTokenCredentialRef(transport) = credential; - } + var credential = azureComponentFactory.CreateTokenCredential(connectionSection); + GetFullyQualifiedNamespaceRef(transport) = fullyQualifiedNamespace; + GetTokenCredentialRef(transport) = credential; } - - return transport; } - // As a temporary workaround we are accessing the properties of the AzureServiceBusTransport using UnsafeAccessor - // This is another blocker to AoT but we are already using the execution assembly in the code base anyway - // Furthermore this allows us to still comply with initializing the transport as late as possible without having to - // expose the properties on the transport itself which would pollute the public API for not much added value. - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] - static extern ref string GetConnectionStringRef(AzureServiceBusTransport transport); + return transport; + } - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] - static extern ref string GetFullyQualifiedNamespaceRef(AzureServiceBusTransport transport); + // As a temporary workaround we are accessing the properties of the AzureServiceBusTransport using UnsafeAccessor + // This is another blocker to AoT but we are already using the execution assembly in the code base anyway + // Furthermore this allows us to still comply with initializing the transport as late as possible without having to + // expose the properties on the transport itself which would pollute the public API for not much added value. + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] + static extern ref string GetConnectionStringRef(AzureServiceBusTransport transport); - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] - static extern ref TokenCredential GetTokenCredentialRef(AzureServiceBusTransport transport); + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] + static extern ref string GetFullyQualifiedNamespaceRef(AzureServiceBusTransport transport); - internal const string DefaultServiceBusConnectionName = "AzureWebJobsServiceBus"; + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "k__BackingField")] + static extern ref TokenCredential GetTokenCredentialRef(AzureServiceBusTransport transport); - readonly TransportTransactionMode[] supportedTransactionModes = - [ - TransportTransactionMode.ReceiveOnly, - TransportTransactionMode.SendsAtomicWithReceive - ]; - } + internal const string DefaultServiceBusConnectionName = "AzureWebJobsServiceBus"; + + readonly TransportTransactionMode[] supportedTransactionModes = + [ + TransportTransactionMode.ReceiveOnly, + TransportTransactionMode.SendsAtomicWithReceive + ]; } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransportInfrastructure.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransportInfrastructure.cs index d2c6fdb4..36e029bd 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransportInfrastructure.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/Serverless/TransportWrapper/ServerlessTransportInfrastructure.cs @@ -1,27 +1,26 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Transport; +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; - class ServerlessTransportInfrastructure : TransportInfrastructure - { - readonly TransportInfrastructure baseTransportInfrastructure; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Transport; - public ServerlessTransportInfrastructure(TransportInfrastructure baseTransportInfrastructure) - { - this.baseTransportInfrastructure = baseTransportInfrastructure; - Dispatcher = baseTransportInfrastructure.Dispatcher; - Receivers = baseTransportInfrastructure.Receivers.ToDictionary( - r => r.Key, - r => (IMessageReceiver)new PipelineInvokingMessageProcessor(r.Value) - ); - } +class ServerlessTransportInfrastructure : TransportInfrastructure +{ + readonly TransportInfrastructure baseTransportInfrastructure; - public override Task Shutdown(CancellationToken cancellationToken = default) - => baseTransportInfrastructure.Shutdown(cancellationToken); - public override string ToTransportAddress(QueueAddress address) - => baseTransportInfrastructure.ToTransportAddress(address); + public ServerlessTransportInfrastructure(TransportInfrastructure baseTransportInfrastructure) + { + this.baseTransportInfrastructure = baseTransportInfrastructure; + Dispatcher = baseTransportInfrastructure.Dispatcher; + Receivers = baseTransportInfrastructure.Receivers.ToDictionary( + r => r.Key, + r => (IMessageReceiver)new PipelineInvokingMessageProcessor(r.Value) + ); } + + public override Task Shutdown(CancellationToken cancellationToken = default) + => baseTransportInfrastructure.Shutdown(cancellationToken); + public override string ToTransportAddress(QueueAddress address) + => baseTransportInfrastructure.ToTransportAddress(address); } \ No newline at end of file diff --git a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs index fae5acb7..30b6ea9c 100644 --- a/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs +++ b/src/NServiceBus.AzureFunctions.InProcess.ServiceBus/ServiceBusTriggeredEndpointConfiguration.cs @@ -1,135 +1,134 @@ -namespace NServiceBus +namespace NServiceBus; + +using System; +using System.Threading; +using System.Threading.Tasks; +using AzureFunctions.InProcess.ServiceBus; +using AzureFunctions.InProcess.ServiceBus.Serverless; +using Configuration.AdvancedExtensibility; +using Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Serialization; +using Settings; +using Transport.AzureServiceBus; + +/// +/// Represents a serverless NServiceBus endpoint. +/// +public partial class ServiceBusTriggeredEndpointConfiguration { - using System; - using System.Threading; - using System.Threading.Tasks; - using AzureFunctions.InProcess.ServiceBus; - using AzureFunctions.InProcess.ServiceBus.Serverless; - using Configuration.AdvancedExtensibility; - using Logging; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Serialization; - using Settings; - using Transport.AzureServiceBus; + static ServiceBusTriggeredEndpointConfiguration() => LogManager.UseFactory(FunctionsLoggerFactory.Instance); + + // Disable diagnostics by default as it will fail to create the diagnostics file in the default path. + Func customDiagnosticsWriter = (_, __) => Task.CompletedTask; + + /// + /// The Azure Service Bus Transport configuration. + /// + public AzureServiceBusTransport Transport { get; } + + /// + /// The routing configuration. + /// + public RoutingSettings Routing { get; } + + /// + /// Gives access to the underlying endpoint configuration for advanced configuration options. + /// + public EndpointConfiguration AdvancedConfiguration { get; } /// - /// Represents a serverless NServiceBus endpoint. + /// Creates a serverless NServiceBus endpoint. /// - public partial class ServiceBusTriggeredEndpointConfiguration + internal ServiceBusTriggeredEndpointConfiguration(string endpointName, IConfiguration configuration, string connectionString = default, string connectionName = default) { - static ServiceBusTriggeredEndpointConfiguration() => LogManager.UseFactory(FunctionsLoggerFactory.Instance); - - // Disable diagnostics by default as it will fail to create the diagnostics file in the default path. - Func customDiagnosticsWriter = (_, __) => Task.CompletedTask; - - /// - /// The Azure Service Bus Transport configuration. - /// - public AzureServiceBusTransport Transport { get; } - - /// - /// The routing configuration. - /// - public RoutingSettings Routing { get; } - - /// - /// Gives access to the underlying endpoint configuration for advanced configuration options. - /// - public EndpointConfiguration AdvancedConfiguration { get; } - - /// - /// Creates a serverless NServiceBus endpoint. - /// - internal ServiceBusTriggeredEndpointConfiguration(string endpointName, IConfiguration configuration, string connectionString = default, string connectionName = default) - { - this.connectionString = connectionString; - this.connectionName = connectionName; - var endpointConfiguration = new EndpointConfiguration(endpointName); - - var recoverability = endpointConfiguration.Recoverability(); - recoverability.Immediate(settings => settings.NumberOfRetries(5)); - recoverability.Delayed(settings => settings.NumberOfRetries(3)); - recoverabilityPolicy.SendFailedMessagesToErrorQueue = true; - recoverability.CustomPolicy(recoverabilityPolicy.Invoke); - - endpointConfiguration.Pipeline.Register(b => new OutboxProcessingValidationBehavior(b.GetRequiredService()), - "Validates the API calls preventing calling ProcessAtomic if the Outbox is enabled."); - - endpointConfiguration.CustomDiagnosticsWriter(customDiagnosticsWriter); - - // 'WEBSITE_SITE_NAME' represents an Azure Function App and the environment variable is set when hosting the function in Azure. - var functionAppName = configuration?.GetValue("WEBSITE_SITE_NAME") ?? Environment.MachineName; - endpointConfiguration.UniquelyIdentifyRunningInstance() - .UsingCustomDisplayName(functionAppName) - .UsingCustomIdentifier(DeterministicGuid.Create(functionAppName)); - - var licenseText = configuration?.GetValue("NSERVICEBUS_LICENSE"); - if (!string.IsNullOrWhiteSpace(licenseText)) - { - endpointConfiguration.License(licenseText); - } - - TopicTopology topicTopology = TopicTopology.Default; - var topologyOptionsSection = configuration?.GetSection("AzureServiceBus:TopologyOptions"); - if (topologyOptionsSection.Exists()) - { - topicTopology = TopicTopology.FromOptions(topologyOptionsSection.Get()); - } - // Migration options take precedence over topology options. We are not doing additional checks here for now. - var migrationOptionsSection = configuration?.GetSection("AzureServiceBus:MigrationTopologyOptions"); - if (migrationOptionsSection.Exists()) - { -#pragma warning disable CS0618 // Type or member is obsolete - topicTopology = TopicTopology.FromOptions(migrationOptionsSection.Get()); -#pragma warning restore CS0618 // Type or member is obsolete - } + this.connectionString = connectionString; + this.connectionName = connectionName; + var endpointConfiguration = new EndpointConfiguration(endpointName); - Transport = new AzureServiceBusTransport("TransportWillBeInitializedCorrectlyLater", topicTopology) - { - // This is required for the Outbox validation to work in NServiceBus 8. It does not affect the actual consistency mode because it is controlled by the functions - // endpoint API (calling ProcessAtomic vs ProcessNonAtomic). - TransportTransactionMode = TransportTransactionMode.ReceiveOnly - }; - Routing = new RoutingSettings(endpointConfiguration.GetSettings()); + var recoverability = endpointConfiguration.Recoverability(); + recoverability.Immediate(settings => settings.NumberOfRetries(5)); + recoverability.Delayed(settings => settings.NumberOfRetries(3)); + recoverabilityPolicy.SendFailedMessagesToErrorQueue = true; + recoverability.CustomPolicy(recoverabilityPolicy.Invoke); - endpointConfiguration.UseSerialization(); + endpointConfiguration.Pipeline.Register(b => new OutboxProcessingValidationBehavior(b.GetRequiredService()), + "Validates the API calls preventing calling ProcessAtomic if the Outbox is enabled."); - AdvancedConfiguration = endpointConfiguration; - } + endpointConfiguration.CustomDiagnosticsWriter(customDiagnosticsWriter); + + // 'WEBSITE_SITE_NAME' represents an Azure Function App and the environment variable is set when hosting the function in Azure. + var functionAppName = configuration?.GetValue("WEBSITE_SITE_NAME") ?? Environment.MachineName; + endpointConfiguration.UniquelyIdentifyRunningInstance() + .UsingCustomDisplayName(functionAppName) + .UsingCustomIdentifier(DeterministicGuid.Create(functionAppName)); - internal ServerlessTransport InitializeTransport() + var licenseText = configuration?.GetValue("NSERVICEBUS_LICENSE"); + if (!string.IsNullOrWhiteSpace(licenseText)) { - var serverlessTransport = new ServerlessTransport(Transport, connectionString, connectionName); - AdvancedConfiguration.UseTransport(serverlessTransport); - return serverlessTransport; + endpointConfiguration.License(licenseText); } - /// - /// Define the serializer to be used. - /// - public SerializationExtensions UseSerialization() where T : SerializationDefinition, new() + TopicTopology topicTopology = TopicTopology.Default; + var topologyOptionsSection = configuration?.GetSection("AzureServiceBus:TopologyOptions"); + if (topologyOptionsSection.Exists()) { - return AdvancedConfiguration.UseSerialization(); + topicTopology = TopicTopology.FromOptions(topologyOptionsSection.Get()); + } + // Migration options take precedence over topology options. We are not doing additional checks here for now. + var migrationOptionsSection = configuration?.GetSection("AzureServiceBus:MigrationTopologyOptions"); + if (migrationOptionsSection.Exists()) + { +#pragma warning disable CS0618 // Type or member is obsolete + topicTopology = TopicTopology.FromOptions(migrationOptionsSection.Get()); +#pragma warning restore CS0618 // Type or member is obsolete } - /// - /// Disables moving messages to the error queue even if an error queue name is configured. - /// - public void DoNotSendMessagesToErrorQueue() => recoverabilityPolicy.SendFailedMessagesToErrorQueue = false; - - /// - /// Logs endpoint diagnostics information to the log. Diagnostics are logged on level . - /// - public void LogDiagnostics() => - customDiagnosticsWriter = (diagnostics, cancellationToken) => - { - LogManager.GetLogger("StartupDiagnostics").Info(diagnostics); - return Task.CompletedTask; - }; - - readonly ServerlessRecoverabilityPolicy recoverabilityPolicy = new ServerlessRecoverabilityPolicy(); - readonly string connectionString; - readonly string connectionName; + Transport = new AzureServiceBusTransport("TransportWillBeInitializedCorrectlyLater", topicTopology) + { + // This is required for the Outbox validation to work in NServiceBus 8. It does not affect the actual consistency mode because it is controlled by the functions + // endpoint API (calling ProcessAtomic vs ProcessNonAtomic). + TransportTransactionMode = TransportTransactionMode.ReceiveOnly + }; + Routing = new RoutingSettings(endpointConfiguration.GetSettings()); + + endpointConfiguration.UseSerialization(); + + AdvancedConfiguration = endpointConfiguration; + } + + internal ServerlessTransport InitializeTransport() + { + var serverlessTransport = new ServerlessTransport(Transport, connectionString, connectionName); + AdvancedConfiguration.UseTransport(serverlessTransport); + return serverlessTransport; + } + + /// + /// Define the serializer to be used. + /// + public SerializationExtensions UseSerialization() where T : SerializationDefinition, new() + { + return AdvancedConfiguration.UseSerialization(); } -} + + /// + /// Disables moving messages to the error queue even if an error queue name is configured. + /// + public void DoNotSendMessagesToErrorQueue() => recoverabilityPolicy.SendFailedMessagesToErrorQueue = false; + + /// + /// Logs endpoint diagnostics information to the log. Diagnostics are logged on level . + /// + public void LogDiagnostics() => + customDiagnosticsWriter = (diagnostics, cancellationToken) => + { + LogManager.GetLogger("StartupDiagnostics").Info(diagnostics); + return Task.CompletedTask; + }; + + readonly ServerlessRecoverabilityPolicy recoverabilityPolicy = new ServerlessRecoverabilityPolicy(); + readonly string connectionString; + readonly string connectionName; +} \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs b/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs index 42e8b343..a713cb39 100644 --- a/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs +++ b/src/ServiceBus.AcceptanceTests/DefaultEndpoint.cs @@ -1,57 +1,56 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NServiceBus; +using NServiceBus.AcceptanceTesting.Customization; +using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.AzureFunctions.InProcess.ServiceBus; + +class DefaultEndpoint : IEndpointSetupTemplate { - using System; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using NServiceBus; - using NServiceBus.AcceptanceTesting.Customization; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AzureFunctions.InProcess.ServiceBus; - - class DefaultEndpoint : IEndpointSetupTemplate + public async Task GetConfiguration( + RunDescriptor runDescriptor, + EndpointCustomizationConfiguration endpointConfiguration, + Func configurationBuilderCustomization) { - public async Task GetConfiguration( - RunDescriptor runDescriptor, - EndpointCustomizationConfiguration endpointConfiguration, - Func configurationBuilderCustomization) - { - var configuration = new EndpointConfiguration(endpointConfiguration.EndpointName); + var configuration = new EndpointConfiguration(endpointConfiguration.EndpointName); - configuration.TypesToIncludeInScan(endpointConfiguration.GetTypesScopedByTestClass()); - configuration.EnableInstallers(); + configuration.TypesToIncludeInScan(endpointConfiguration.GetTypesScopedByTestClass()); + configuration.EnableInstallers(); - configuration.RegisterComponents(c => c - .AddSingleton(runDescriptor.ScenarioContext.GetType(), runDescriptor.ScenarioContext)); + configuration.RegisterComponents(c => c + .AddSingleton(runDescriptor.ScenarioContext.GetType(), runDescriptor.ScenarioContext)); - var recoverability = configuration.Recoverability(); - recoverability.Delayed(delayed => delayed.NumberOfRetries(0)); - recoverability.Immediate(immediate => immediate.NumberOfRetries(0)); - configuration.SendFailedMessagesTo("error"); + var recoverability = configuration.Recoverability(); + recoverability.Delayed(delayed => delayed.NumberOfRetries(0)); + recoverability.Immediate(immediate => immediate.NumberOfRetries(0)); + configuration.SendFailedMessagesTo("error"); - configuration.EnforcePublisherMetadataRegistration(endpointConfiguration.EndpointName, endpointConfiguration.PublisherMetadata); + configuration.EnforcePublisherMetadataRegistration(endpointConfiguration.EndpointName, endpointConfiguration.PublisherMetadata); - var connectionString = - Environment.GetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName); + var connectionString = + Environment.GetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName); - var topology = TopicTopology.Default; - topology.OverrideSubscriptionNameFor(endpointConfiguration.EndpointName, endpointConfiguration.EndpointName.Shorten()); - foreach (var eventType in endpointConfiguration.PublisherMetadata.Publishers.SelectMany(p => p.Events)) - { - topology.PublishTo(eventType, eventType.ToTopicName()); - topology.SubscribeTo(eventType, eventType.ToTopicName()); - } - var azureServiceBusTransport = new AzureServiceBusTransport(connectionString, topology); + var topology = TopicTopology.Default; + topology.OverrideSubscriptionNameFor(endpointConfiguration.EndpointName, endpointConfiguration.EndpointName.Shorten()); + foreach (var eventType in endpointConfiguration.PublisherMetadata.Publishers.SelectMany(p => p.Events)) + { + topology.PublishTo(eventType, eventType.ToTopicName()); + topology.SubscribeTo(eventType, eventType.ToTopicName()); + } + var azureServiceBusTransport = new AzureServiceBusTransport(connectionString, topology); - _ = configuration.UseTransport(azureServiceBusTransport); + _ = configuration.UseTransport(azureServiceBusTransport); - configuration.Pipeline.Register("TestIndependenceBehavior", b => new TestIndependenceSkipBehavior(runDescriptor.ScenarioContext), "Skips messages not created during the current test."); + configuration.Pipeline.Register("TestIndependenceBehavior", b => new TestIndependenceSkipBehavior(runDescriptor.ScenarioContext), "Skips messages not created during the current test."); - configuration.UseSerialization(); + configuration.UseSerialization(); - await configurationBuilderCustomization(configuration); + await configurationBuilderCustomization(configuration); - return configuration; - } + return configuration; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/Extensions.cs b/src/ServiceBus.AcceptanceTests/Extensions.cs index 7a5529c2..b9befcb3 100644 --- a/src/ServiceBus.AcceptanceTests/Extensions.cs +++ b/src/ServiceBus.AcceptanceTests/Extensions.cs @@ -1,55 +1,54 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.Hosting.Helpers; + +public static class Extensions { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.Hosting.Helpers; - - public static class Extensions + public static IEnumerable GetTypesScopedByTestClass(this EndpointCustomizationConfiguration endpointConfiguration) { - public static IEnumerable GetTypesScopedByTestClass(this EndpointCustomizationConfiguration endpointConfiguration) - { - var types = endpointConfiguration.BuilderType.GetTypesScopedByTestClass(); - types = types.Union(endpointConfiguration.TypesToInclude); - return types.Where(t => !endpointConfiguration.TypesToExclude.Contains(t)).ToList(); - } + var types = endpointConfiguration.BuilderType.GetTypesScopedByTestClass(); + types = types.Union(endpointConfiguration.TypesToInclude); + return types.Where(t => !endpointConfiguration.TypesToExclude.Contains(t)).ToList(); + } - public static IEnumerable GetTypesScopedByTestClass(this Type componentType) - { - var assemblies = new AssemblyScanner().GetScannableAssemblies(); + public static IEnumerable GetTypesScopedByTestClass(this Type componentType) + { + var assemblies = new AssemblyScanner().GetScannableAssemblies(); - var assembliesToScan = assemblies.Assemblies - //exclude acceptance tests by default - .Where(a => a != Assembly.GetExecutingAssembly()).ToList(); - var types = assembliesToScan - .SelectMany(a => a.GetTypes()); + var assembliesToScan = assemblies.Assemblies + //exclude acceptance tests by default + .Where(a => a != Assembly.GetExecutingAssembly()).ToList(); + var types = assembliesToScan + .SelectMany(a => a.GetTypes()); - types = types.Union(GetNestedTypeRecursive(componentType.DeclaringType, componentType)); + types = types.Union(GetNestedTypeRecursive(componentType.DeclaringType, componentType)); - return types.ToList(); + return types.ToList(); + } + + static IEnumerable GetNestedTypeRecursive(Type rootType, Type builderType) + { + if (rootType == null) + { + throw new InvalidOperationException("Make sure you nest the endpoint infrastructure inside the TestFixture as nested classes"); + } + + yield return rootType; + + if (typeof(IEndpointConfigurationFactory).IsAssignableFrom(rootType) && rootType != builderType) + { + yield break; } - static IEnumerable GetNestedTypeRecursive(Type rootType, Type builderType) + foreach (var nestedType in rootType.GetNestedTypes(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SelectMany(t => GetNestedTypeRecursive(t, builderType))) { - if (rootType == null) - { - throw new InvalidOperationException("Make sure you nest the endpoint infrastructure inside the TestFixture as nested classes"); - } - - yield return rootType; - - if (typeof(IEndpointConfigurationFactory).IsAssignableFrom(rootType) && rootType != builderType) - { - yield break; - } - - foreach (var nestedType in rootType.GetNestedTypes(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SelectMany(t => GetNestedTypeRecursive(t, builderType))) - { - yield return nestedType; - } + yield return nestedType; } } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs b/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs index a09c9a96..ac0f8ba7 100644 --- a/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs +++ b/src/ServiceBus.AcceptanceTests/FunctionEndpointComponent.cs @@ -1,230 +1,245 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Azure.Messaging.ServiceBus.Administration; +using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.ServiceBus; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NServiceBus.AcceptanceTesting.Customization; +using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.AzureFunctions.InProcess.ServiceBus; +using NServiceBus.MessageMutator; +using NServiceBus.Transport.AzureServiceBus; +using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; +using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; + +abstract class FunctionEndpointComponent : IComponentBehavior { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus; - using Azure.Messaging.ServiceBus.Administration; - using Microsoft.Azure.Functions.Extensions.DependencyInjection; - using Microsoft.Azure.WebJobs; - using Microsoft.Azure.WebJobs.ServiceBus; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Customization; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.AzureFunctions.InProcess.ServiceBus; - using NServiceBus.MessageMutator; - using NServiceBus.Transport.AzureServiceBus; - using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; - using ExecutionContext = Microsoft.Azure.WebJobs.ExecutionContext; - - abstract class FunctionEndpointComponent : IComponentBehavior - { - protected FunctionEndpointComponent(TransportTransactionMode transactionMode = TransportTransactionMode.ReceiveOnly) => - sendsAtomicWithReceive = transactionMode switch - { - TransportTransactionMode.SendsAtomicWithReceive => true, - TransportTransactionMode.ReceiveOnly => false, - TransportTransactionMode.None => throw new Exception("Unsupported transaction mode " + transactionMode), - TransportTransactionMode.TransactionScope => throw new Exception("Unsupported transaction mode " + transactionMode), - _ => throw new Exception("Unsupported transaction mode " + transactionMode), - }; - - public Task CreateRunner(RunDescriptor runDescriptor) => - Task.FromResult( - new FunctionRunner( - Messages, - CustomizeConfiguration, - HostConfigurationCustomization, - OnStartCore, - runDescriptor.ScenarioContext, - PublisherMetadata, - GetType(), - DoNotFailOnErrorMessages, - TypesScopedByTestClassAssemblyScanningEnabled, - sendsAtomicWithReceive, - ServiceBusMessageActionsFactory)); - - protected IList Messages { get; } = []; - - protected bool DoNotFailOnErrorMessages { get; init; } - - protected bool TypesScopedByTestClassAssemblyScanningEnabled { get; init; } = true; - - protected Func ServiceBusMessageActionsFactory { get; init; } = (r, _) => new TestableServiceBusMessageActions(r); - - protected Action CustomizeConfiguration { private get; init; } = _ => { }; - - protected Action HostConfigurationCustomization { private get; init; } = _ => { }; - - protected PublisherMetadata PublisherMetadata { get; } = new PublisherMetadata(); - - protected virtual Task OnStart(IFunctionEndpoint functionEndpoint, ExecutionContext executionContext) => Task.CompletedTask; - - Task OnStartCore(IFunctionEndpoint functionEndpoint, ExecutionContext executionContext) => OnStart(functionEndpoint, executionContext); - - readonly bool sendsAtomicWithReceive; - - class FunctionRunner(IList messages, - Action configurationCustomization, - Action hostConfigurationCustomization, - Func onStart, - ScenarioContext scenarioContext, - PublisherMetadata publisherMetadata, - Type functionComponentType, - bool doNotFailOnErrorMessages, - bool typesScopedByTestClassAssemblyScanningEnabled, - bool sendsAtomicWithReceive, - Func serviceBusMessageActionsFactory) : ComponentRunner + protected FunctionEndpointComponent(TransportTransactionMode transactionMode = TransportTransactionMode.ReceiveOnly) => + sendsAtomicWithReceive = transactionMode switch { - public override string Name { get; } = Conventions.EndpointNamingConvention(functionComponentType); + TransportTransactionMode.SendsAtomicWithReceive => true, + TransportTransactionMode.ReceiveOnly => false, + TransportTransactionMode.None => throw new Exception("Unsupported transaction mode " + transactionMode), + TransportTransactionMode.TransactionScope => throw new Exception("Unsupported transaction mode " + transactionMode), + _ => throw new Exception("Unsupported transaction mode " + transactionMode), + }; + + public Task CreateRunner(RunDescriptor runDescriptor) => + Task.FromResult( + new FunctionRunner( + Messages, + CustomizeConfiguration, + HostConfigurationCustomization, + OnStartCore, + runDescriptor.ScenarioContext, + PublisherMetadata, + GetType(), + DoNotFailOnErrorMessages, + TypesScopedByTestClassAssemblyScanningEnabled, + sendsAtomicWithReceive, + ServiceBusMessageActionsFactory)); + + protected IList Messages { get; } = []; + + protected bool DoNotFailOnErrorMessages { get; init; } + + protected bool TypesScopedByTestClassAssemblyScanningEnabled { get; init; } = true; + + protected Func ServiceBusMessageActionsFactory { get; init; } = (r, _) => new TestableServiceBusMessageActions(r); + + protected Action CustomizeConfiguration { private get; init; } = _ => { }; + + protected Action HostConfigurationCustomization { private get; init; } = _ => { }; + + protected PublisherMetadata PublisherMetadata { get; } = new PublisherMetadata(); + + protected virtual Task OnStart(IFunctionEndpoint functionEndpoint, ExecutionContext executionContext) => Task.CompletedTask; + + Task OnStartCore(IFunctionEndpoint functionEndpoint, ExecutionContext executionContext) => OnStart(functionEndpoint, executionContext); + + readonly bool sendsAtomicWithReceive; + + class FunctionRunner(IList messages, + Action configurationCustomization, + Action hostConfigurationCustomization, + Func onStart, + ScenarioContext scenarioContext, + PublisherMetadata publisherMetadata, + Type functionComponentType, + bool doNotFailOnErrorMessages, + bool typesScopedByTestClassAssemblyScanningEnabled, + bool sendsAtomicWithReceive, + Func serviceBusMessageActionsFactory) : ComponentRunner + { + public override string Name { get; } = Conventions.EndpointNamingConvention(functionComponentType); - public override async Task Start(CancellationToken cancellationToken = default) + public override async Task Start(CancellationToken cancellationToken = default) + { + var hostBuilder = new FunctionHostBuilder(hostConfigurationCustomization); + hostBuilder.UseNServiceBus(Name, triggerConfiguration => { - var hostBuilder = new FunctionHostBuilder(hostConfigurationCustomization); - hostBuilder.UseNServiceBus(Name, triggerConfiguration => + var endpointConfiguration = triggerConfiguration.AdvancedConfiguration; + + if (typesScopedByTestClassAssemblyScanningEnabled) { - var endpointConfiguration = triggerConfiguration.AdvancedConfiguration; + endpointConfiguration.TypesToIncludeInScan(functionComponentType.GetTypesScopedByTestClass()); + } - if (typesScopedByTestClassAssemblyScanningEnabled) + if (triggerConfiguration.Transport.Topology is TopicPerEventTopology topology) + { + topology.OverrideSubscriptionNameFor(Name, Name.Shorten()); + + foreach (var eventType in publisherMetadata.Publishers.SelectMany(p => p.Events)) { - endpointConfiguration.TypesToIncludeInScan(functionComponentType.GetTypesScopedByTestClass()); + topology.PublishTo(eventType, eventType.ToTopicName()); + topology.SubscribeTo(eventType, eventType.ToTopicName()); } + } - if (triggerConfiguration.Transport.Topology is TopicPerEventTopology topology) - { - topology.OverrideSubscriptionNameFor(Name, Name.Shorten()); + endpointConfiguration.EnforcePublisherMetadataRegistration(Name, publisherMetadata); - foreach (var eventType in publisherMetadata.Publishers.SelectMany(p => p.Events)) + endpointConfiguration.Recoverability() + .Immediate(i => i.NumberOfRetries(0)) + .Delayed(d => d.NumberOfRetries(0)) + .Failed(c => c + // track messages sent to the error queue to fail the test + .OnMessageSentToErrorQueue((failedMessage, ct) => { - topology.PublishTo(eventType, eventType.ToTopicName()); - topology.SubscribeTo(eventType, eventType.ToTopicName()); - } - } + _ = scenarioContext.FailedMessages.AddOrUpdate( + Name, + [failedMessage], + (_, fm) => + { + var failedMessages = fm.ToList(); + failedMessages.Add(failedMessage); + return failedMessages; + }); + return Task.CompletedTask; + })); - endpointConfiguration.EnforcePublisherMetadataRegistration(Name, publisherMetadata); - endpointConfiguration.Recoverability() - .Immediate(i => i.NumberOfRetries(0)) - .Delayed(d => d.NumberOfRetries(0)) - .Failed(c => c - // track messages sent to the error queue to fail the test - .OnMessageSentToErrorQueue((failedMessage, ct) => - { - _ = scenarioContext.FailedMessages.AddOrUpdate( - Name, - [failedMessage], - (_, fm) => - { - var failedMessages = fm.ToList(); - failedMessages.Add(failedMessage); - return failedMessages; - }); - return Task.CompletedTask; - })); + endpointConfiguration.RegisterComponents(c => c.AddSingleton(scenarioContext.GetType(), scenarioContext)); + endpointConfiguration.RegisterComponents(c => c.AddSingleton(b => new TestIndependenceMutator(scenarioContext))); - endpointConfiguration.RegisterComponents(c => c.AddSingleton(scenarioContext.GetType(), scenarioContext)); + configurationCustomization(triggerConfiguration); + }); - endpointConfiguration.RegisterComponents(c => c.AddSingleton(b => new TestIndependenceMutator(scenarioContext))); + host = hostBuilder.Build(); + await host.StartAsync(cancellationToken); - configurationCustomization(triggerConfiguration); - }); + endpoint = host.Services.GetRequiredService(); + } - host = hostBuilder.Build(); - await host.StartAsync(cancellationToken); + public override async Task ComponentsStarted(CancellationToken cancellationToken = default) + { + await onStart(endpoint, new ExecutionContext()); - endpoint = host.Services.GetRequiredService(); + if (messages.Count == 0) + { + return; } - public override async Task ComponentsStarted(CancellationToken cancellationToken = default) + var connectionString = Environment.GetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName); + + var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions { - await onStart(endpoint, new ExecutionContext()); + EnableCrossEntityTransactions = sendsAtomicWithReceive + }); + var serviceBusAdministrationClient = new ServiceBusAdministrationClient(connectionString); + var functionInputQueueName = Name; - if (messages.Count == 0) - { - return; - } + if (!await serviceBusAdministrationClient.QueueExistsAsync(functionInputQueueName, cancellationToken)) + { + await serviceBusAdministrationClient.CreateQueueAsync(functionInputQueueName, cancellationToken); + } - var connectionString = Environment.GetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName); + var sender = client.CreateSender(functionInputQueueName); - var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions - { - EnableCrossEntityTransactions = sendsAtomicWithReceive - }); - var serviceBusAdministrationClient = new ServiceBusAdministrationClient(connectionString); - var functionInputQueueName = Name; + foreach (var message in messages) + { + var messageId = Guid.NewGuid().ToString("N"); - if (!await serviceBusAdministrationClient.QueueExistsAsync(functionInputQueueName, cancellationToken)) + var serviceBusMessage = new ServiceBusMessage(BinaryData.FromObjectAsJson(message)) { - await serviceBusAdministrationClient.CreateQueueAsync(functionInputQueueName, cancellationToken); - } + MessageId = messageId, + ApplicationProperties = + { + ["NServiceBus.EnclosedMessageTypes"] = message.GetType().FullName + } + }; + + await sender.SendMessageAsync(serviceBusMessage, cancellationToken); - var sender = client.CreateSender(functionInputQueueName); + var receiver = client.CreateReceiver(functionInputQueueName); - foreach (var message in messages) + IReadOnlyList receivedMessages; + do { - var messageId = Guid.NewGuid().ToString("N"); + receivedMessages = await receiver.ReceiveMessagesAsync(100, TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); - var serviceBusMessage = new ServiceBusMessage(BinaryData.FromObjectAsJson(message)) + foreach (var receivedMessage in receivedMessages) { - MessageId = messageId, - ApplicationProperties = + if (receivedMessage.MessageId != messageId) { - ["NServiceBus.EnclosedMessageTypes"] = message.GetType().FullName + await receiver.CompleteMessageAsync(receivedMessage, cancellationToken); + continue; } - }; - - await sender.SendMessageAsync(serviceBusMessage, cancellationToken); - - var receiver = client.CreateReceiver(functionInputQueueName); - IReadOnlyList receivedMessages; - do - { - receivedMessages = await receiver.ReceiveMessagesAsync(100, TimeSpan.FromSeconds(5), cancellationToken: cancellationToken); - - foreach (var receivedMessage in receivedMessages) + if (sendsAtomicWithReceive) { - if (receivedMessage.MessageId != messageId) - { - await receiver.CompleteMessageAsync(receivedMessage, cancellationToken); - continue; - } - - if (sendsAtomicWithReceive) + await endpoint.ProcessAtomic(receivedMessage, new ExecutionContext(), client, + serviceBusMessageActionsFactory(receiver, scenarioContext), null, + cancellationToken); + } + else + { + try { - await endpoint.ProcessAtomic(receivedMessage, new ExecutionContext(), client, - serviceBusMessageActionsFactory(receiver, scenarioContext), null, + await endpoint.ProcessNonAtomic(receivedMessage, new ExecutionContext(), null, cancellationToken); + await receiver.CompleteMessageAsync(receivedMessage, cancellationToken); } - else + catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) { - try + await receiver.AbandonMessageAsync(receivedMessage, + cancellationToken: cancellationToken); + if (!doNotFailOnErrorMessages) { - await endpoint.ProcessNonAtomic(receivedMessage, new ExecutionContext(), null, - cancellationToken); - await receiver.CompleteMessageAsync(receivedMessage, cancellationToken); - } - catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested) - { - await receiver.AbandonMessageAsync(receivedMessage, - cancellationToken: cancellationToken); - if (!doNotFailOnErrorMessages) - { - throw; + throw; - } } } } - } while (receivedMessages.Count > 0); + } + } while (receivedMessages.Count > 0); + } + if (!doNotFailOnErrorMessages) + { + if (scenarioContext.FailedMessages.TryGetValue(Name, out var failedMessages)) + { + throw new MessageFailedException(failedMessages.First(), scenarioContext); } + } + } + + public override async Task Stop(CancellationToken cancellationToken = default) + { + try + { + await host.StopAsync(cancellationToken); + if (!doNotFailOnErrorMessages) { if (scenarioContext.FailedMessages.TryGetValue(Name, out var failedMessages)) @@ -233,83 +248,67 @@ await receiver.AbandonMessageAsync(receivedMessage, } } } - - public override async Task Stop(CancellationToken cancellationToken = default) + finally { - try - { - await host.StopAsync(cancellationToken); - - if (!doNotFailOnErrorMessages) - { - if (scenarioContext.FailedMessages.TryGetValue(Name, out var failedMessages)) - { - throw new MessageFailedException(failedMessages.First(), scenarioContext); - } - } - } - finally - { - host.Dispose(); - } + host.Dispose(); } + } - // There is some non-trivial hackery going on in order to bypass the azure function host assumptions. In order to - // simulate a similar environment we have to use a host builder so that we also get the possibility to run - // hosted services etc. But the function hosts requires an already initialized service collection early on - // but the host builder used by functions is still using the lambda based approach. To work around this we - // have to forward the service registrations to the host builder and some other things manually. This is not - // great but once the functions host moved to the new host builder this can be simplified. - sealed class FunctionHostBuilder(Action configurationCustomization) : IFunctionsHostBuilder, IFunctionsHostBuilderExt - { - HostBuilderContext context; - readonly HostBuilder hostBuilder = new(); + // There is some non-trivial hackery going on in order to bypass the azure function host assumptions. In order to + // simulate a similar environment we have to use a host builder so that we also get the possibility to run + // hosted services etc. But the function hosts requires an already initialized service collection early on + // but the host builder used by functions is still using the lambda based approach. To work around this we + // have to forward the service registrations to the host builder and some other things manually. This is not + // great but once the functions host moved to the new host builder this can be simplified. + sealed class FunctionHostBuilder(Action configurationCustomization) : IFunctionsHostBuilder, IFunctionsHostBuilderExt + { + HostBuilderContext context; + readonly HostBuilder hostBuilder = new(); - public IServiceCollection Services { get; } = new ServiceCollection(); + public IServiceCollection Services { get; } = new ServiceCollection(); - public FunctionsHostBuilderContext Context + public FunctionsHostBuilderContext Context + { + get { - get + if (context != null) { - if (context != null) - { - return context; - } - - var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.AddEnvironmentVariables(); - configurationCustomization(configurationBuilder); - var configurationRoot = configurationBuilder.Build(); - context = new HostBuilderContext(new WebJobsBuilderContext { Configuration = configurationRoot, ApplicationRootPath = AppDomain.CurrentDomain.BaseDirectory }); return context; } + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddEnvironmentVariables(); + configurationCustomization(configurationBuilder); + var configurationRoot = configurationBuilder.Build(); + context = new HostBuilderContext(new WebJobsBuilderContext { Configuration = configurationRoot, ApplicationRootPath = AppDomain.CurrentDomain.BaseDirectory }); + return context; } + } - public IHost Build() + public IHost Build() + { + _ = hostBuilder.ConfigureHostConfiguration(configuration => { - _ = hostBuilder.ConfigureHostConfiguration(configuration => - { - configuration.AddConfiguration(Context.Configuration); - }); - // Forwarding all the service registrations to the host builder - _ = hostBuilder.ConfigureServices(services => + configuration.AddConfiguration(Context.Configuration); + }); + // Forwarding all the service registrations to the host builder + _ = hostBuilder.ConfigureServices(services => + { + _ = services.AddHostedService(); + foreach (var service in Services) { - _ = services.AddHostedService(); - foreach (var service in Services) - { - services.Add(service); - } - Services.Clear(); - }); - return hostBuilder.Build(); - } - - sealed class HostBuilderContext(WebJobsBuilderContext webJobsBuilderContext) - : FunctionsHostBuilderContext(webJobsBuilderContext); + services.Add(service); + } + Services.Clear(); + }); + return hostBuilder.Build(); } - IHost host; - IFunctionEndpoint endpoint; + sealed class HostBuilderContext(WebJobsBuilderContext webJobsBuilderContext) + : FunctionsHostBuilderContext(webJobsBuilderContext); } + + IHost host; + IFunctionEndpoint endpoint; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/InitializationHost.cs b/src/ServiceBus.AcceptanceTests/InitializationHost.cs index 8eaeabaf..5328718f 100644 --- a/src/ServiceBus.AcceptanceTests/InitializationHost.cs +++ b/src/ServiceBus.AcceptanceTests/InitializationHost.cs @@ -1,13 +1,12 @@ -namespace NServiceBus.AzureFunctions.InProcess.ServiceBus -{ - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.Hosting; +namespace NServiceBus.AzureFunctions.InProcess.ServiceBus; + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; - class InitializationHost(InProcessFunctionEndpoint functionEndpoint) : IHostedService - { - public Task StartAsync(CancellationToken cancellationToken = default) => functionEndpoint.InitializeEndpointIfNecessary(cancellationToken); +class InitializationHost(InProcessFunctionEndpoint functionEndpoint) : IHostedService +{ + public Task StartAsync(CancellationToken cancellationToken = default) => functionEndpoint.InitializeEndpointIfNecessary(cancellationToken); - public Task StopAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; - } + public Task StopAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/OneTimeSetupAndTearDown.cs b/src/ServiceBus.AcceptanceTests/OneTimeSetupAndTearDown.cs index 22d336be..cf3ec67a 100644 --- a/src/ServiceBus.AcceptanceTests/OneTimeSetupAndTearDown.cs +++ b/src/ServiceBus.AcceptanceTests/OneTimeSetupAndTearDown.cs @@ -1,28 +1,27 @@ -namespace ServiceBus.Tests -{ - using System; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus.Administration; - using NServiceBus.AzureFunctions.InProcess.ServiceBus; - using NUnit.Framework; +namespace ServiceBus.Tests; + +using System; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus.Administration; +using NServiceBus.AzureFunctions.InProcess.ServiceBus; +using NUnit.Framework; - [SetUpFixture] - public class OneTimeSetupAndTearDown +[SetUpFixture] +public class OneTimeSetupAndTearDown +{ + [OneTimeSetUp] + public async Task RunBeforeAllTests() { - [OneTimeSetUp] - public async Task RunBeforeAllTests() - { - var connectionString = Environment.GetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName); - Assert.That(connectionString, Is.Not.Null, $"Environment variable '{ServerlessTransport.DefaultServiceBusConnectionName}' should be defined to run tests."); + var connectionString = Environment.GetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName); + Assert.That(connectionString, Is.Not.Null, $"Environment variable '{ServerlessTransport.DefaultServiceBusConnectionName}' should be defined to run tests."); - var client = new ServiceBusAdministrationClient(connectionString); + var client = new ServiceBusAdministrationClient(connectionString); - const string errorQueueName = "error"; + const string errorQueueName = "error"; - if (!await client.QueueExistsAsync(errorQueueName)) - { - await client.CreateQueueAsync(errorQueueName); - } + if (!await client.QueueExistsAsync(errorQueueName)) + { + await client.CreateQueueAsync(errorQueueName); } } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/TestIndependenceMutator.cs b/src/ServiceBus.AcceptanceTests/TestIndependenceMutator.cs index 8a7d9d20..deb239a6 100644 --- a/src/ServiceBus.AcceptanceTests/TestIndependenceMutator.cs +++ b/src/ServiceBus.AcceptanceTests/TestIndependenceMutator.cs @@ -1,22 +1,21 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System.Threading.Tasks; +using NServiceBus.AcceptanceTesting; +using NServiceBus.MessageMutator; + +class TestIndependenceMutator : IMutateOutgoingTransportMessages { - using System.Threading.Tasks; - using NServiceBus.AcceptanceTesting; - using NServiceBus.MessageMutator; + readonly string testRunId; - class TestIndependenceMutator : IMutateOutgoingTransportMessages + public TestIndependenceMutator(ScenarioContext scenarioContext) { - readonly string testRunId; - - public TestIndependenceMutator(ScenarioContext scenarioContext) - { - testRunId = scenarioContext.TestRunId.ToString(); - } + testRunId = scenarioContext.TestRunId.ToString(); + } - public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) - { - context.OutgoingHeaders["$AcceptanceTesting.TestRunId"] = testRunId; - return Task.CompletedTask; - } + public Task MutateOutgoing(MutateOutgoingTransportMessageContext context) + { + context.OutgoingHeaders["$AcceptanceTesting.TestRunId"] = testRunId; + return Task.CompletedTask; } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/TestIndependenceSkipBehavior.cs b/src/ServiceBus.AcceptanceTests/TestIndependenceSkipBehavior.cs index e9238010..4dcb8a9d 100644 --- a/src/ServiceBus.AcceptanceTests/TestIndependenceSkipBehavior.cs +++ b/src/ServiceBus.AcceptanceTests/TestIndependenceSkipBehavior.cs @@ -1,28 +1,27 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Threading.Tasks; +using NServiceBus.AcceptanceTesting; +using NServiceBus.Pipeline; +using NUnit.Framework; + +class TestIndependenceSkipBehavior : IBehavior { - using System; - using System.Threading.Tasks; - using NServiceBus.AcceptanceTesting; - using NServiceBus.Pipeline; - using NUnit.Framework; + readonly string testRunId; - class TestIndependenceSkipBehavior : IBehavior + public TestIndependenceSkipBehavior(ScenarioContext scenarioContext) { - readonly string testRunId; + testRunId = scenarioContext.TestRunId.ToString(); + } - public TestIndependenceSkipBehavior(ScenarioContext scenarioContext) + public Task Invoke(ITransportReceiveContext context, Func next) + { + if (context.Message.Headers.TryGetValue("$AcceptanceTesting.TestRunId", out var runId) && runId != testRunId) { - testRunId = scenarioContext.TestRunId.ToString(); + return TestContext.Out.WriteLineAsync($"Skipping message {context.Message.MessageId} from previous test run"); } - public Task Invoke(ITransportReceiveContext context, Func next) - { - if (context.Message.Headers.TryGetValue("$AcceptanceTesting.TestRunId", out var runId) && runId != testRunId) - { - return TestContext.Out.WriteLineAsync($"Skipping message {context.Message.MessageId} from previous test run"); - } - - return next(context); - } + return next(context); } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/TestableServiceBusMessageActions.cs b/src/ServiceBus.AcceptanceTests/TestableServiceBusMessageActions.cs index 63594746..508e8477 100644 --- a/src/ServiceBus.AcceptanceTests/TestableServiceBusMessageActions.cs +++ b/src/ServiceBus.AcceptanceTests/TestableServiceBusMessageActions.cs @@ -1,22 +1,21 @@ -namespace ServiceBus.Tests -{ - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus; - using Microsoft.Azure.WebJobs.ServiceBus; +namespace ServiceBus.Tests; + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.WebJobs.ServiceBus; - class TestableServiceBusMessageActions : ServiceBusMessageActions - { - readonly ServiceBusReceiver serviceBusReceiver; +class TestableServiceBusMessageActions : ServiceBusMessageActions +{ + readonly ServiceBusReceiver serviceBusReceiver; - public TestableServiceBusMessageActions(ServiceBusReceiver serviceBusReceiver) - => this.serviceBusReceiver = serviceBusReceiver; + public TestableServiceBusMessageActions(ServiceBusReceiver serviceBusReceiver) + => this.serviceBusReceiver = serviceBusReceiver; - public override Task CompleteMessageAsync(ServiceBusReceivedMessage message, CancellationToken cancellationToken = default) - => serviceBusReceiver.CompleteMessageAsync(message, cancellationToken); + public override Task CompleteMessageAsync(ServiceBusReceivedMessage message, CancellationToken cancellationToken = default) + => serviceBusReceiver.CompleteMessageAsync(message, cancellationToken); - public override Task AbandonMessageAsync(ServiceBusReceivedMessage message, IDictionary propertiesToModify = null, CancellationToken cancellationToken = default) - => serviceBusReceiver.AbandonMessageAsync(message, propertiesToModify, cancellationToken); - } + public override Task AbandonMessageAsync(ServiceBusReceivedMessage message, IDictionary propertiesToModify = null, CancellationToken cancellationToken = default) + => serviceBusReceiver.AbandonMessageAsync(message, propertiesToModify, cancellationToken); } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs b/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs index 23eb1a1e..e9609173 100644 --- a/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs +++ b/src/ServiceBus.AcceptanceTests/When_failing_to_process_message.cs @@ -1,136 +1,135 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; +using Microsoft.Azure.WebJobs.ServiceBus; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NUnit.Framework; + +public class When_failing_to_process_message { - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using Azure.Messaging.ServiceBus; - using Microsoft.Azure.WebJobs.ServiceBus; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NUnit.Framework; - - public class When_failing_to_process_message + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public async Task Should_not_publish_to_subscribers(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public async Task Should_not_publish_to_subscribers(TransportTransactionMode transactionMode) + var context = await Scenario.Define() + .WithEndpoint() + .WithComponent(new PublishingFunction(transactionMode)) + .Done(c => c.TerminatingEventReceived) + .Run(); + + Assert.Multiple(() => { - var context = await Scenario.Define() - .WithEndpoint() - .WithComponent(new PublishingFunction(transactionMode)) - .Done(c => c.TerminatingEventReceived) - .Run(); + Assert.That(context.TerminatingEventReceived, Is.True); + Assert.That(context.AbortedEventReceived, Is.False); + }); + } - Assert.Multiple(() => - { - Assert.That(context.TerminatingEventReceived, Is.True); - Assert.That(context.AbortedEventReceived, Is.False); - }); - } + [TestCase(TransportTransactionMode.ReceiveOnly)] + public async Task Should_publish_to_subscribers(TransportTransactionMode transactionMode) + { + var context = await Scenario.Define() + .WithEndpoint() + .WithComponent(new PublishingFunction(transactionMode)) + .Done(c => c.TerminatingEventReceived) + .Run(); - [TestCase(TransportTransactionMode.ReceiveOnly)] - public async Task Should_publish_to_subscribers(TransportTransactionMode transactionMode) + Assert.Multiple(() => { - var context = await Scenario.Define() - .WithEndpoint() - .WithComponent(new PublishingFunction(transactionMode)) - .Done(c => c.TerminatingEventReceived) - .Run(); + Assert.That(context.TerminatingEventReceived, Is.True); + Assert.That(context.AbortedEventReceived, Is.True); + }); + } + + class Context : ScenarioContext + { + public bool AbortedEventReceived { get; set; } + public bool TerminatingEventReceived { get; set; } + public bool FirstCompleteCalled { get; internal set; } + } - Assert.Multiple(() => + class InsideEndpoint : EndpointConfigurationBuilder + { + public InsideEndpoint() => EndpointSetup(cfg => cfg.LimitMessageProcessingConcurrencyTo(1), + metadata => { - Assert.That(context.TerminatingEventReceived, Is.True); - Assert.That(context.AbortedEventReceived, Is.True); + metadata.RegisterPublisherFor(typeof(PublishingFunction)); + metadata.RegisterPublisherFor(typeof(PublishingFunction)); }); - } - - class Context : ScenarioContext - { - public bool AbortedEventReceived { get; set; } - public bool TerminatingEventReceived { get; set; } - public bool FirstCompleteCalled { get; internal set; } - } - class InsideEndpoint : EndpointConfigurationBuilder + public class AbortedEventHandler(Context testContext) : IHandleMessages { - public InsideEndpoint() => EndpointSetup(cfg => cfg.LimitMessageProcessingConcurrencyTo(1), - metadata => - { - metadata.RegisterPublisherFor(typeof(PublishingFunction)); - metadata.RegisterPublisherFor(typeof(PublishingFunction)); - }); - - public class AbortedEventHandler(Context testContext) : IHandleMessages + public Task Handle(AbortedEvent message, IMessageHandlerContext context) { - public Task Handle(AbortedEvent message, IMessageHandlerContext context) - { - testContext.AbortedEventReceived = true; - return Task.CompletedTask; - } - } - - public class TerminatingEventHandler(Context testContext) : IHandleMessages - { - public Task Handle(TerminatingEvent message, IMessageHandlerContext context) - { - testContext.TerminatingEventReceived = true; - return Task.CompletedTask; - } + testContext.AbortedEventReceived = true; + return Task.CompletedTask; } } - class PublishingFunction : FunctionEndpointComponent + public class TerminatingEventHandler(Context testContext) : IHandleMessages { - public PublishingFunction(TransportTransactionMode transportTransactionMode) : base(transportTransactionMode) + public Task Handle(TerminatingEvent message, IMessageHandlerContext context) { - PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); - PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); - Messages.Add(new TriggerMessage()); - Messages.Add(new TerminatingMessage()); - DoNotFailOnErrorMessages = true; - ServiceBusMessageActionsFactory = (r, c) => new FirstCompleteFailingServiceBusMessageActions(r, c); + testContext.TerminatingEventReceived = true; + return Task.CompletedTask; } + } + } - public class PublishingHandler : IHandleMessages - { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) => context.Publish(new AbortedEvent()); - } + class PublishingFunction : FunctionEndpointComponent + { + public PublishingFunction(TransportTransactionMode transportTransactionMode) : base(transportTransactionMode) + { + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); + Messages.Add(new TriggerMessage()); + Messages.Add(new TerminatingMessage()); + DoNotFailOnErrorMessages = true; + ServiceBusMessageActionsFactory = (r, c) => new FirstCompleteFailingServiceBusMessageActions(r, c); + } - public class TerminatingMessageHandler : IHandleMessages - { - public Task Handle(TerminatingMessage message, IMessageHandlerContext context) => context.Publish(new TerminatingEvent()); - } + public class PublishingHandler : IHandleMessages + { + public Task Handle(TriggerMessage message, IMessageHandlerContext context) => context.Publish(new AbortedEvent()); } - class FirstCompleteFailingServiceBusMessageActions( - ServiceBusReceiver serviceBusReceiver, - ScenarioContext scenarioContext) - : ServiceBusMessageActions + public class TerminatingMessageHandler : IHandleMessages { - readonly Context scenarioContext = (Context)scenarioContext; + public Task Handle(TerminatingMessage message, IMessageHandlerContext context) => context.Publish(new TerminatingEvent()); + } + } - public override async Task CompleteMessageAsync(ServiceBusReceivedMessage message, CancellationToken cancellationToken = default) - { - if (!scenarioContext.FirstCompleteCalled) - { - scenarioContext.FirstCompleteCalled = true; - await serviceBusReceiver.CompleteMessageAsync(message, cancellationToken).ConfigureAwait(false); - throw new Exception("Simulated complete failure"); - } + class FirstCompleteFailingServiceBusMessageActions( + ServiceBusReceiver serviceBusReceiver, + ScenarioContext scenarioContext) + : ServiceBusMessageActions + { + readonly Context scenarioContext = (Context)scenarioContext; + public override async Task CompleteMessageAsync(ServiceBusReceivedMessage message, CancellationToken cancellationToken = default) + { + if (!scenarioContext.FirstCompleteCalled) + { + scenarioContext.FirstCompleteCalled = true; await serviceBusReceiver.CompleteMessageAsync(message, cancellationToken).ConfigureAwait(false); + throw new Exception("Simulated complete failure"); } - public override Task AbandonMessageAsync(ServiceBusReceivedMessage message, IDictionary propertiesToModify = null, CancellationToken cancellationToken = default) - => serviceBusReceiver.AbandonMessageAsync(message, propertiesToModify, cancellationToken); + await serviceBusReceiver.CompleteMessageAsync(message, cancellationToken).ConfigureAwait(false); } - class TriggerMessage : IMessage; + public override Task AbandonMessageAsync(ServiceBusReceivedMessage message, IDictionary propertiesToModify = null, CancellationToken cancellationToken = default) + => serviceBusReceiver.AbandonMessageAsync(message, propertiesToModify, cancellationToken); + } + + class TriggerMessage : IMessage; - class TerminatingMessage : IMessage; + class TerminatingMessage : IMessage; - class AbortedEvent : IEvent; + class AbortedEvent : IEvent; - class TerminatingEvent : IEvent; - } + class TerminatingEvent : IEvent; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs b/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs index 0681f413..49ff7cd0 100644 --- a/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs +++ b/src/ServiceBus.AcceptanceTests/When_incoming_message_is_not_acknowledged.cs @@ -1,101 +1,100 @@ -namespace ServiceBus.Tests -{ - using System; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NServiceBus.Pipeline; - using NUnit.Framework; - using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; +namespace ServiceBus.Tests; + +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NServiceBus.Pipeline; +using NUnit.Framework; +using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; - public class When_incoming_message_is_not_acknowledged +public class When_incoming_message_is_not_acknowledged +{ + [TestCase(TransportTransactionMode.ReceiveOnly)] + public async Task Should_dispatch_outgoing_messages_from_the_outbox(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.ReceiveOnly)] - public async Task Should_dispatch_outgoing_messages_from_the_outbox(TransportTransactionMode transactionMode) + var context = await Scenario.Define() + .WithComponent(new FunctionHandler(transactionMode)) + .WithEndpoint() + .Done(c => c.MessageReceived && c.MessageRetried) + .Run(); + + Assert.Multiple(() => { - var context = await Scenario.Define() - .WithComponent(new FunctionHandler(transactionMode)) - .WithEndpoint() - .Done(c => c.MessageReceived && c.MessageRetried) - .Run(); + Assert.That(context.MessageRetried, Is.True); + Assert.That(context.MessageReceived, Is.True); + }); + } - Assert.Multiple(() => - { - Assert.That(context.MessageRetried, Is.True); - Assert.That(context.MessageReceived, Is.True); - }); - } + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + public bool MessageRetried { get; set; } + } - public class Context : ScenarioContext + class FunctionHandler : FunctionEndpointComponent + { + public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) { - public bool MessageReceived { get; set; } - public bool MessageRetried { get; set; } + CustomizeConfiguration = configuration => + { + configuration.AdvancedConfiguration.Pipeline.Register(b => new FailBeforeAckBehavior(b.GetRequiredService()), + "Simulates a failure in ACKing the incoming message"); + configuration.AdvancedConfiguration.EnableOutbox(); + configuration.AdvancedConfiguration.UsePersistence(); + configuration.AdvancedConfiguration.Recoverability().Immediate(x => x.NumberOfRetries(1)); + }; + Messages.Add(new HappyDayMessage()); + DoNotFailOnErrorMessages = true; } - class FunctionHandler : FunctionEndpointComponent + public class HappyDayMessageHandler : IHandleMessages { - public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) - { - CustomizeConfiguration = configuration => - { - configuration.AdvancedConfiguration.Pipeline.Register(b => new FailBeforeAckBehavior(b.GetRequiredService()), - "Simulates a failure in ACKing the incoming message"); - configuration.AdvancedConfiguration.EnableOutbox(); - configuration.AdvancedConfiguration.UsePersistence(); - configuration.AdvancedConfiguration.Recoverability().Immediate(x => x.NumberOfRetries(1)); - }; - Messages.Add(new HappyDayMessage()); - DoNotFailOnErrorMessages = true; - } - - public class HappyDayMessageHandler : IHandleMessages + public Task Handle(HappyDayMessage message, IMessageHandlerContext context) { - public Task Handle(HappyDayMessage message, IMessageHandlerContext context) - { - var sendOptions = new SendOptions(); - sendOptions.SetDestination(Conventions.EndpointNamingConvention(typeof(SpyEndpoint))); - return context.Send(new FollowUpMessage(), sendOptions); - } + var sendOptions = new SendOptions(); + sendOptions.SetDestination(Conventions.EndpointNamingConvention(typeof(SpyEndpoint))); + return context.Send(new FollowUpMessage(), sendOptions); } } + } - class SpyEndpoint : EndpointConfigurationBuilder - { - public SpyEndpoint() => EndpointSetup(); + class SpyEndpoint : EndpointConfigurationBuilder + { + public SpyEndpoint() => EndpointSetup(); - public class EventHandler(Context testContext) : IHandleMessages + public class EventHandler(Context testContext) : IHandleMessages + { + public Task Handle(FollowUpMessage message, IMessageHandlerContext context) { - public Task Handle(FollowUpMessage message, IMessageHandlerContext context) - { - testContext.MessageReceived = true; - return Task.CompletedTask; - } + testContext.MessageReceived = true; + return Task.CompletedTask; } } + } - class FailBeforeAckBehavior(Context testContext) : Behavior + class FailBeforeAckBehavior(Context testContext) : Behavior + { + bool failed; + + public override async Task Invoke(ITransportReceiveContext context, Func next) { - bool failed; + await next(); - public override async Task Invoke(ITransportReceiveContext context, Func next) + if (!failed) { - await next(); - - if (!failed) - { - failed = true; - throw new SimulatedException("Simulating ACK failure"); - } - else - { - testContext.MessageRetried = true; - } + failed = true; + throw new SimulatedException("Simulating ACK failure"); + } + else + { + testContext.MessageRetried = true; } } + } - class HappyDayMessage : IMessage; + class HappyDayMessage : IMessage; - class FollowUpMessage : IMessage; - } + class FollowUpMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs b/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs index aa4e39d1..8641aaac 100644 --- a/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs +++ b/src/ServiceBus.AcceptanceTests/When_message_fails_with_disabled_error_queue.cs @@ -1,47 +1,46 @@ -namespace ServiceBus.Tests -{ - using System; - using System.Threading.Tasks; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NUnit.Framework; +namespace ServiceBus.Tests; + +using System; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NUnit.Framework; - public class When_message_fails_with_disabled_error_queue +public class When_message_fails_with_disabled_error_queue +{ + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public void Should_throw_exception(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.ReceiveOnly)] - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public void Should_throw_exception(TransportTransactionMode transactionMode) + var exception = Assert.ThrowsAsync(() => { - var exception = Assert.ThrowsAsync(() => - { - return Scenario.Define() - .WithComponent(new DisabledErrorQueueFunction(transactionMode)) - .Done(c => c.EndpointsStarted) - .Run(); - }); + return Scenario.Define() + .WithComponent(new DisabledErrorQueueFunction(transactionMode)) + .Done(c => c.EndpointsStarted) + .Run(); + }); - Assert.That(exception.Message, Does.Contain("Failed to process message")); - Assert.That(exception.InnerException, Is.InstanceOf()); - } + Assert.That(exception.Message, Does.Contain("Failed to process message")); + Assert.That(exception.InnerException, Is.InstanceOf()); + } - class DisabledErrorQueueFunction : FunctionEndpointComponent + class DisabledErrorQueueFunction : FunctionEndpointComponent + { + public DisabledErrorQueueFunction(TransportTransactionMode transactionMode) : base(transactionMode) { - public DisabledErrorQueueFunction(TransportTransactionMode transactionMode) : base(transactionMode) - { - CustomizeConfiguration = c => c.DoNotSendMessagesToErrorQueue(); + CustomizeConfiguration = c => c.DoNotSendMessagesToErrorQueue(); - Messages.Add(new TriggerMessage()); - } + Messages.Add(new TriggerMessage()); + } - public class FailingHandler : IHandleMessages + public class FailingHandler : IHandleMessages + { + public Task Handle(TriggerMessage message, IMessageHandlerContext context) { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) - { - throw new SimulatedException(); - } + throw new SimulatedException(); } } - - class TriggerMessage : IMessage; } + + class TriggerMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs b/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs index ac775afa..6c1615e4 100644 --- a/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs +++ b/src/ServiceBus.AcceptanceTests/When_message_is_failing_all_processing_attempts.cs @@ -1,54 +1,53 @@ -namespace ServiceBus.Tests -{ - using System.Linq; - using System.Threading.Tasks; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NUnit.Framework; +namespace ServiceBus.Tests; + +using System.Linq; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NServiceBus.AcceptanceTesting.Support; +using NUnit.Framework; - public class When_message_is_failing_all_processing_attempts +public class When_message_is_failing_all_processing_attempts +{ + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public void Should_be_moved_to_the_error_queue(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.ReceiveOnly)] - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public void Should_be_moved_to_the_error_queue(TransportTransactionMode transactionMode) + Context testContext = null; + var exception = Assert.ThrowsAsync(() => { - Context testContext = null; - var exception = Assert.ThrowsAsync(() => - { - return Scenario.Define(c => testContext = c) - .WithComponent(new MoveToErrorQueueFunction(transactionMode)) - .Done(c => c.EndpointsStarted) - .Run(); - }); + return Scenario.Define(c => testContext = c) + .WithComponent(new MoveToErrorQueueFunction(transactionMode)) + .Done(c => c.EndpointsStarted) + .Run(); + }); - Assert.Multiple(() => - { - Assert.That(testContext.HandlerInvocations, Is.EqualTo(1), "the handler should only be invoked once"); - Assert.That(exception.InnerException, Is.InstanceOf(), "it should be the exception from the handler"); - Assert.That(testContext.FailedMessages.Single().Value, Has.Count.EqualTo(1), "there should be only one failed message"); - }); - } - - class Context : ScenarioContext + Assert.Multiple(() => { - public int HandlerInvocations { get; set; } - } + Assert.That(testContext.HandlerInvocations, Is.EqualTo(1), "the handler should only be invoked once"); + Assert.That(exception.InnerException, Is.InstanceOf(), "it should be the exception from the handler"); + Assert.That(testContext.FailedMessages.Single().Value, Has.Count.EqualTo(1), "there should be only one failed message"); + }); + } - class MoveToErrorQueueFunction : FunctionEndpointComponent - { - public MoveToErrorQueueFunction(TransportTransactionMode transactionMode) : base(transactionMode) => Messages.Add(new TriggerMessage()); + class Context : ScenarioContext + { + public int HandlerInvocations { get; set; } + } + + class MoveToErrorQueueFunction : FunctionEndpointComponent + { + public MoveToErrorQueueFunction(TransportTransactionMode transactionMode) : base(transactionMode) => Messages.Add(new TriggerMessage()); - public class FailingHandler(Context testContext) : IHandleMessages + public class FailingHandler(Context testContext) : IHandleMessages + { + public Task Handle(TriggerMessage message, IMessageHandlerContext context) { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) - { - testContext.HandlerInvocations++; - throw new SimulatedException(); - } + testContext.HandlerInvocations++; + throw new SimulatedException(); } } - - public class TriggerMessage : IMessage; } + + public class TriggerMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_message_is_received.cs b/src/ServiceBus.AcceptanceTests/When_message_is_received.cs index ec2f17ca..3d4d1835 100644 --- a/src/ServiceBus.AcceptanceTests/When_message_is_received.cs +++ b/src/ServiceBus.AcceptanceTests/When_message_is_received.cs @@ -1,48 +1,47 @@ -namespace ServiceBus.Tests -{ - using System.Threading; - using System.Threading.Tasks; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NUnit.Framework; +namespace ServiceBus.Tests; + +using System.Threading; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NUnit.Framework; - public class When_function_receives_a_message +public class When_function_receives_a_message +{ + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public async Task Should_invoke_the_handler_to_process_it(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.ReceiveOnly)] - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public async Task Should_invoke_the_handler_to_process_it(TransportTransactionMode transactionMode) - { - var context = await Scenario.Define() - .WithComponent(new FunctionHandler(transactionMode)) - .Done(c => c.HandlerInvocationCount > 0) - .Run(); + var context = await Scenario.Define() + .WithComponent(new FunctionHandler(transactionMode)) + .Done(c => c.HandlerInvocationCount > 0) + .Run(); - Assert.That(context.HandlerInvocationCount, Is.EqualTo(1)); - } + Assert.That(context.HandlerInvocationCount, Is.EqualTo(1)); + } - public class Context : ScenarioContext - { - public int HandlerInvocationCount => count; + public class Context : ScenarioContext + { + public int HandlerInvocationCount => count; - public void HandlerInvoked() => Interlocked.Increment(ref count); + public void HandlerInvoked() => Interlocked.Increment(ref count); - int count; - } + int count; + } - class FunctionHandler : FunctionEndpointComponent - { - public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) => Messages.Add(new HappyDayMessage()); + class FunctionHandler : FunctionEndpointComponent + { + public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) => Messages.Add(new HappyDayMessage()); - public class HappyDayMessageHandler(Context testContext) : IHandleMessages + public class HappyDayMessageHandler(Context testContext) : IHandleMessages + { + public Task Handle(HappyDayMessage message, IMessageHandlerContext context) { - public Task Handle(HappyDayMessage message, IMessageHandlerContext context) - { - testContext.HandlerInvoked(); - return Task.CompletedTask; - } + testContext.HandlerInvoked(); + return Task.CompletedTask; } } - - class HappyDayMessage : IMessage; } + + class HappyDayMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs b/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs index 2bc651a4..2f3f3f9a 100644 --- a/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs +++ b/src/ServiceBus.AcceptanceTests/When_no_connection_string_is_provided.cs @@ -1,44 +1,43 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using NServiceBus.AcceptanceTesting; +using NServiceBus.AzureFunctions.InProcess.ServiceBus; +using NUnit.Framework; + +[TestFixture] +public class When_no_connection_string_is_provided { - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AzureFunctions.InProcess.ServiceBus; - using NUnit.Framework; + [SetUp] + public void SetUp() + { + var defaultConnectionStringKey = ServerlessTransport.DefaultServiceBusConnectionName; + originalConnectionString = Environment.GetEnvironmentVariable(defaultConnectionStringKey); + + Environment.SetEnvironmentVariable(defaultConnectionStringKey, null, EnvironmentVariableTarget.Process); + } - [TestFixture] - public class When_no_connection_string_is_provided + [Test] + public void Should_guide_user_towards_success() { - [SetUp] - public void SetUp() - { - var defaultConnectionStringKey = ServerlessTransport.DefaultServiceBusConnectionName; - originalConnectionString = Environment.GetEnvironmentVariable(defaultConnectionStringKey); - - Environment.SetEnvironmentVariable(defaultConnectionStringKey, null, EnvironmentVariableTarget.Process); - } - - [Test] - public void Should_guide_user_towards_success() - { - var exception = Assert.ThrowsAsync(async () => await Scenario.Define() - .WithComponent(new FunctionWithoutConnectionString()) - .Done(c => c.EndpointsStarted) - .Run(), - "Exception should be thrown at endpoint creation so that the error will be found during functions startup" - ); - - Assert.That(exception?.Message, Does.Contain("UseNServiceBus"), "Should mention the code-first approach"); - Assert.That(exception?.Message, Does.Contain("environment variable"), - "Should mention the environment variable approach"); - } - - [TearDown] - public void TearDown() => - Environment.SetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName, - originalConnectionString); - - string originalConnectionString; - - class FunctionWithoutConnectionString : FunctionEndpointComponent; + var exception = Assert.ThrowsAsync(async () => await Scenario.Define() + .WithComponent(new FunctionWithoutConnectionString()) + .Done(c => c.EndpointsStarted) + .Run(), + "Exception should be thrown at endpoint creation so that the error will be found during functions startup" + ); + + Assert.That(exception?.Message, Does.Contain("UseNServiceBus"), "Should mention the code-first approach"); + Assert.That(exception?.Message, Does.Contain("environment variable"), + "Should mention the environment variable approach"); } + + [TearDown] + public void TearDown() => + Environment.SetEnvironmentVariable(ServerlessTransport.DefaultServiceBusConnectionName, + originalConnectionString); + + string originalConnectionString; + + class FunctionWithoutConnectionString : FunctionEndpointComponent; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs b/src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs index 18b04cfd..61cb7e39 100644 --- a/src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs +++ b/src/ServiceBus.AcceptanceTests/When_overriding_the_topology.cs @@ -1,74 +1,73 @@ -namespace ServiceBus.Tests -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using Microsoft.Extensions.Configuration; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NUnit.Framework; +namespace ServiceBus.Tests; + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NUnit.Framework; - public class When_overriding_the_topology +public class When_overriding_the_topology +{ + [Test] + public async Task Should_publish_to_subscribers() { - [Test] - public async Task Should_publish_to_subscribers() - { - var context = await Scenario.Define() - .WithEndpoint() - .WithComponent(new PublishingFunction()) - .Done(c => c.EventReceived) - .Run(); + var context = await Scenario.Define() + .WithEndpoint() + .WithComponent(new PublishingFunction()) + .Done(c => c.EventReceived) + .Run(); - Assert.That(context.EventReceived, Is.True); - } + Assert.That(context.EventReceived, Is.True); + } - class Context : ScenarioContext - { - public bool EventReceived { get; set; } - } + class Context : ScenarioContext + { + public bool EventReceived { get; set; } + } - class InsideSubscriber : EndpointConfigurationBuilder - { - public InsideSubscriber() => EndpointSetup(_ => { }, - metadata => metadata.RegisterPublisherFor(typeof(PublishingFunction))); + class InsideSubscriber : EndpointConfigurationBuilder + { + public InsideSubscriber() => EndpointSetup(_ => { }, + metadata => metadata.RegisterPublisherFor(typeof(PublishingFunction))); - class EventHandler(Context testContext) : IHandleMessages + class EventHandler(Context testContext) : IHandleMessages + { + public Task Handle(MyEvent message, IMessageHandlerContext context) { - public Task Handle(MyEvent message, IMessageHandlerContext context) - { - testContext.EventReceived = true; - return Task.CompletedTask; - } + testContext.EventReceived = true; + return Task.CompletedTask; } } + } - class PublishingFunction : FunctionEndpointComponent + class PublishingFunction : FunctionEndpointComponent + { + public PublishingFunction() { - public PublishingFunction() + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); + HostConfigurationCustomization = builder => { - PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); - HostConfigurationCustomization = builder => + var customSettings = new Dictionary { - var customSettings = new Dictionary - { - { "AzureServiceBus:MigrationTopologyOptions:TopicToPublishTo", "bundle-1" }, - { "AzureServiceBus:MigrationTopologyOptions:TopicToSubscribeOn", "bundle-1" }, - { $"AzureServiceBus:MigrationTopologyOptions:PublishedEventToTopicsMap:{typeof(MyEvent).FullName}", $"{typeof(MyEvent).ToTopicName()}" - }, - }; - _ = builder.AddInMemoryCollection(customSettings); + { "AzureServiceBus:MigrationTopologyOptions:TopicToPublishTo", "bundle-1" }, + { "AzureServiceBus:MigrationTopologyOptions:TopicToSubscribeOn", "bundle-1" }, + { $"AzureServiceBus:MigrationTopologyOptions:PublishedEventToTopicsMap:{typeof(MyEvent).FullName}", $"{typeof(MyEvent).ToTopicName()}" + }, }; - Messages.Add(new TriggerMessage()); - } + _ = builder.AddInMemoryCollection(customSettings); + }; + Messages.Add(new TriggerMessage()); + } - class PublishingHandler : IHandleMessages - { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) => - context.Publish(new MyEvent()); - } + class PublishingHandler : IHandleMessages + { + public Task Handle(TriggerMessage message, IMessageHandlerContext context) => + context.Publish(new MyEvent()); } + } - class TriggerMessage : IMessage; + class TriggerMessage : IMessage; - class MyEvent : IEvent; - } + class MyEvent : IEvent; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs b/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs index a5480db5..89a9f75a 100644 --- a/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs +++ b/src/ServiceBus.AcceptanceTests/When_publishing_event_from_function.cs @@ -1,60 +1,59 @@ -namespace ServiceBus.Tests -{ - using System.Threading.Tasks; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NUnit.Framework; +namespace ServiceBus.Tests; + +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NUnit.Framework; - public class When_publishing_event_from_function +public class When_publishing_event_from_function +{ + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public async Task Should_publish_to_subscribers(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.ReceiveOnly)] - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public async Task Should_publish_to_subscribers(TransportTransactionMode transactionMode) - { - var context = await Scenario.Define() - .WithEndpoint() - .WithComponent(new PublishingFunction(transactionMode)) - .Done(c => c.EventReceived) - .Run(); + var context = await Scenario.Define() + .WithEndpoint() + .WithComponent(new PublishingFunction(transactionMode)) + .Done(c => c.EventReceived) + .Run(); - Assert.That(context.EventReceived, Is.True); - } + Assert.That(context.EventReceived, Is.True); + } - class Context : ScenarioContext - { - public bool EventReceived { get; set; } - } + class Context : ScenarioContext + { + public bool EventReceived { get; set; } + } - class InsideEndpoint : EndpointConfigurationBuilder - { - public InsideEndpoint() => EndpointSetup(_ => { }, metadata => metadata.RegisterPublisherFor(typeof(PublishingFunction))); + class InsideEndpoint : EndpointConfigurationBuilder + { + public InsideEndpoint() => EndpointSetup(_ => { }, metadata => metadata.RegisterPublisherFor(typeof(PublishingFunction))); - public class EventHandler(Context testContext) : IHandleMessages + public class EventHandler(Context testContext) : IHandleMessages + { + public Task Handle(InsideEvent message, IMessageHandlerContext context) { - public Task Handle(InsideEvent message, IMessageHandlerContext context) - { - testContext.EventReceived = true; - return Task.CompletedTask; - } + testContext.EventReceived = true; + return Task.CompletedTask; } } + } - class PublishingFunction : FunctionEndpointComponent + class PublishingFunction : FunctionEndpointComponent + { + public PublishingFunction(TransportTransactionMode transactionMode) : base(transactionMode) { - public PublishingFunction(TransportTransactionMode transactionMode) : base(transactionMode) - { - PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); - Messages.Add(new TriggerMessage()); - } + PublisherMetadata.RegisterPublisherFor(typeof(PublishingFunction)); + Messages.Add(new TriggerMessage()); + } - public class PublishingHandler : IHandleMessages - { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) => context.Publish(new InsideEvent()); - } + public class PublishingHandler : IHandleMessages + { + public Task Handle(TriggerMessage message, IMessageHandlerContext context) => context.Publish(new InsideEvent()); } + } - class TriggerMessage : IMessage; + class TriggerMessage : IMessage; - class InsideEvent : IEvent; - } + class InsideEvent : IEvent; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs b/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs index e9fc9a4c..83d8da44 100644 --- a/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs +++ b/src/ServiceBus.AcceptanceTests/When_receiving_with_sendonly.cs @@ -1,41 +1,40 @@ -namespace ServiceBus.Tests -{ - using System; - using NServiceBus.AcceptanceTesting; - using NServiceBus; - using NUnit.Framework; - using System.Threading.Tasks; +namespace ServiceBus.Tests; + +using System; +using NServiceBus.AcceptanceTesting; +using NServiceBus; +using NUnit.Framework; +using System.Threading.Tasks; - public class When_receiving_with_sendonly +public class When_receiving_with_sendonly +{ + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public void Should_invoke_the_handler_to_process_it(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.ReceiveOnly)] - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public void Should_invoke_the_handler_to_process_it(TransportTransactionMode transactionMode) - { - var exception = Assert.ThrowsAsync(() => Scenario.Define() - .WithComponent(new FunctionWithSendOnlyConfiguration(transactionMode)) - .Done(c => c.EndpointsStarted) - .Run()); + var exception = Assert.ThrowsAsync(() => Scenario.Define() + .WithComponent(new FunctionWithSendOnlyConfiguration(transactionMode)) + .Done(c => c.EndpointsStarted) + .Run()); - Assert.That(exception.Message, Does.Contain("This endpoint cannot process messages because it is configured in send-only mode.")); - } + Assert.That(exception.Message, Does.Contain("This endpoint cannot process messages because it is configured in send-only mode.")); + } - class FunctionWithSendOnlyConfiguration : FunctionEndpointComponent + class FunctionWithSendOnlyConfiguration : FunctionEndpointComponent + { + public FunctionWithSendOnlyConfiguration(TransportTransactionMode transactionMode) : base(transactionMode) { - public FunctionWithSendOnlyConfiguration(TransportTransactionMode transactionMode) : base(transactionMode) - { - CustomizeConfiguration = configuration => configuration.AdvancedConfiguration.SendOnly(); - - Messages.Add(new TestMessage()); - } + CustomizeConfiguration = configuration => configuration.AdvancedConfiguration.SendOnly(); - public class TestMessageHandler : IHandleMessages - { - public Task Handle(TestMessage message, IMessageHandlerContext context) => Task.CompletedTask; - } + Messages.Add(new TestMessage()); } - class TestMessage : IMessage; + public class TestMessageHandler : IHandleMessages + { + public Task Handle(TestMessage message, IMessageHandlerContext context) => Task.CompletedTask; + } } + + class TestMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_sending_message.cs b/src/ServiceBus.AcceptanceTests/When_sending_message.cs index bf68208c..a719ea46 100644 --- a/src/ServiceBus.AcceptanceTests/When_sending_message.cs +++ b/src/ServiceBus.AcceptanceTests/When_sending_message.cs @@ -1,59 +1,58 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NUnit.Framework; +using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; + +public class When_sending_message { - using System.Threading.Tasks; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NUnit.Framework; - using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public async Task Should_send_message_to_target_queue(TransportTransactionMode transactionMode) + { + await Scenario.Define() + .WithEndpoint() + .WithComponent(new SendingFunction(transactionMode)) + .Done(c => c.HandlerReceivedMessage) + .Run(); + } - public class When_sending_message + class Context : ScenarioContext { - [TestCase(TransportTransactionMode.ReceiveOnly)] - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public async Task Should_send_message_to_target_queue(TransportTransactionMode transactionMode) - { - await Scenario.Define() - .WithEndpoint() - .WithComponent(new SendingFunction(transactionMode)) - .Done(c => c.HandlerReceivedMessage) - .Run(); - } + public bool HandlerReceivedMessage { get; set; } + } - class Context : ScenarioContext - { - public bool HandlerReceivedMessage { get; set; } - } + public class ReceivingEndpoint : EndpointConfigurationBuilder + { + public ReceivingEndpoint() => EndpointSetup(); - public class ReceivingEndpoint : EndpointConfigurationBuilder + class OutgoingMessageHandler(Context testContext) : IHandleMessages { - public ReceivingEndpoint() => EndpointSetup(); - - class OutgoingMessageHandler(Context testContext) : IHandleMessages + public Task Handle(FollowupMessage message, IMessageHandlerContext context) { - public Task Handle(FollowupMessage message, IMessageHandlerContext context) - { - testContext.HandlerReceivedMessage = true; - return Task.CompletedTask; - } + testContext.HandlerReceivedMessage = true; + return Task.CompletedTask; } } + } - class SendingFunction : FunctionEndpointComponent + class SendingFunction : FunctionEndpointComponent + { + public SendingFunction(TransportTransactionMode transactionMode) : base(transactionMode) { - public SendingFunction(TransportTransactionMode transactionMode) : base(transactionMode) - { - Messages.Add(new TriggerMessage()); - } + Messages.Add(new TriggerMessage()); + } - public class TriggerMessageHandler : IHandleMessages - { - public Task Handle(TriggerMessage message, IMessageHandlerContext context) - => context.Send(Conventions.EndpointNamingConvention(typeof(ReceivingEndpoint)), new FollowupMessage()); - } + public class TriggerMessageHandler : IHandleMessages + { + public Task Handle(TriggerMessage message, IMessageHandlerContext context) + => context.Send(Conventions.EndpointNamingConvention(typeof(ReceivingEndpoint)), new FollowupMessage()); } + } - class TriggerMessage : IMessage; + class TriggerMessage : IMessage; - class FollowupMessage : IMessage; - } + class FollowupMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs b/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs index ac11c87c..c80bc578 100644 --- a/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs +++ b/src/ServiceBus.AcceptanceTests/When_sending_with_sendonly.cs @@ -1,57 +1,56 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System.Threading.Tasks; +using Microsoft.Azure.WebJobs; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NServiceBus.AcceptanceTesting.Customization; +using NUnit.Framework; + +public class When_sending_with_sendonly { - using System.Threading.Tasks; - using Microsoft.Azure.WebJobs; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Customization; - using NUnit.Framework; - - public class When_sending_with_sendonly + [Test] + public async Task Should_send_messages() { - [Test] - public async Task Should_send_messages() - { - await Scenario.Define() - .WithEndpoint() - .WithComponent(new SendOnlyFunction()) - .Done(c => c.HandlerReceivedMessage) - .Run(); - } + await Scenario.Define() + .WithEndpoint() + .WithComponent(new SendOnlyFunction()) + .Done(c => c.HandlerReceivedMessage) + .Run(); + } - class Context : ScenarioContext - { - public bool HandlerReceivedMessage { get; set; } - } + class Context : ScenarioContext + { + public bool HandlerReceivedMessage { get; set; } + } - class ReceivingEndpoint : EndpointConfigurationBuilder - { - public ReceivingEndpoint() => EndpointSetup(); + class ReceivingEndpoint : EndpointConfigurationBuilder + { + public ReceivingEndpoint() => EndpointSetup(); - public class TestMessageHandler(Context testContext) : IHandleMessages + public class TestMessageHandler(Context testContext) : IHandleMessages + { + public Task Handle(TestMessage message, IMessageHandlerContext context) { - public Task Handle(TestMessage message, IMessageHandlerContext context) - { - testContext.HandlerReceivedMessage = true; - return Task.CompletedTask; - } + testContext.HandlerReceivedMessage = true; + return Task.CompletedTask; } } + } - class SendOnlyFunction : FunctionEndpointComponent - { - public SendOnlyFunction() => - CustomizeConfiguration = configuration => - { - configuration.AdvancedConfiguration.SendOnly(); - - configuration.Routing.RouteToEndpoint(typeof(TestMessage), typeof(ReceivingEndpoint)); - }; + class SendOnlyFunction : FunctionEndpointComponent + { + public SendOnlyFunction() => + CustomizeConfiguration = configuration => + { + configuration.AdvancedConfiguration.SendOnly(); - protected override Task OnStart(IFunctionEndpoint endpoint, ExecutionContext executionContext) - => endpoint.Send(new TestMessage(), executionContext); - } + configuration.Routing.RouteToEndpoint(typeof(TestMessage), typeof(ReceivingEndpoint)); + }; - class TestMessage : IMessage; + protected override Task OnStart(IFunctionEndpoint endpoint, ExecutionContext executionContext) + => endpoint.Send(new TestMessage(), executionContext); } + + class TestMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_shipping_handlers_in_dedicated_assembly.cs b/src/ServiceBus.AcceptanceTests/When_shipping_handlers_in_dedicated_assembly.cs index c6cc2757..249b83f0 100644 --- a/src/ServiceBus.AcceptanceTests/When_shipping_handlers_in_dedicated_assembly.cs +++ b/src/ServiceBus.AcceptanceTests/When_shipping_handlers_in_dedicated_assembly.cs @@ -1,66 +1,65 @@ -namespace ServiceBus.Tests -{ - using System; - using System.Linq; - using System.Reflection; - using System.Runtime.Loader; - using System.Threading.Tasks; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NServiceBus.Configuration.AdvancedExtensibility; - using NServiceBus.Settings; - using NServiceBus.Unicast; - using NUnit.Framework; +namespace ServiceBus.Tests; + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NServiceBus.Configuration.AdvancedExtensibility; +using NServiceBus.Settings; +using NServiceBus.Unicast; +using NUnit.Framework; - [TestFixture] - public class When_shipping_handlers_in_dedicated_assembly +[TestFixture] +public class When_shipping_handlers_in_dedicated_assembly +{ + [Test] + public async Task Should_load_handlers_from_assembly() { - [Test] - public async Task Should_load_handlers_from_assembly() - { - // The message handler assembly shouldn't be loaded at this point because there is no reference in the code to it. - Assert.That(AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == "Testing.Handlers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"), Is.False); + // The message handler assembly shouldn't be loaded at this point because there is no reference in the code to it. + Assert.That(AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == "Testing.Handlers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"), Is.False); - var functionWithHandlersInDedicatedAssembly = new FunctionWithHandlersInDedicatedAssembly(); + var functionWithHandlersInDedicatedAssembly = new FunctionWithHandlersInDedicatedAssembly(); - await Scenario.Define() - .WithComponent(functionWithHandlersInDedicatedAssembly) - .Done(c => c.EndpointsStarted) - .Run(); + await Scenario.Define() + .WithComponent(functionWithHandlersInDedicatedAssembly) + .Done(c => c.EndpointsStarted) + .Run(); - // The message handler assembly should be loaded now because scanning should find and load the handler assembly - Assert.That(AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == "Testing.Handlers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"), Is.True); + // The message handler assembly should be loaded now because scanning should find and load the handler assembly + Assert.That(AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == "Testing.Handlers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"), Is.True); - // // Verify the handler and message type have been identified and loaded: - var registry = functionWithHandlersInDedicatedAssembly.SettingsHolder.Get(); - var dummyMessageType = registry.GetMessageTypes().FirstOrDefault(t => t.FullName == "Testing.Handlers.DummyMessage"); - Assert.That(dummyMessageType, Is.Not.Null); - var dummyMessageHandler = registry.GetHandlersFor(dummyMessageType).SingleOrDefault(); - Assert.Multiple(() => - { - Assert.That(dummyMessageHandler.HandlerType.FullName, Is.EqualTo("Testing.Handlers.DummyMessageHandler")); + // // Verify the handler and message type have been identified and loaded: + var registry = functionWithHandlersInDedicatedAssembly.SettingsHolder.Get(); + var dummyMessageType = registry.GetMessageTypes().FirstOrDefault(t => t.FullName == "Testing.Handlers.DummyMessage"); + Assert.That(dummyMessageType, Is.Not.Null); + var dummyMessageHandler = registry.GetHandlersFor(dummyMessageType).SingleOrDefault(); + Assert.Multiple(() => + { + Assert.That(dummyMessageHandler.HandlerType.FullName, Is.EqualTo("Testing.Handlers.DummyMessageHandler")); - // ensure the assembly is loaded into the right context - Assert.That(AssemblyLoadContext.GetLoadContext(dummyMessageType.Assembly), Is.EqualTo(AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()))); - }); - } + // ensure the assembly is loaded into the right context + Assert.That(AssemblyLoadContext.GetLoadContext(dummyMessageType.Assembly), Is.EqualTo(AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()))); + }); + } - class FunctionWithHandlersInDedicatedAssembly : FunctionEndpointComponent + class FunctionWithHandlersInDedicatedAssembly : FunctionEndpointComponent + { + public FunctionWithHandlersInDedicatedAssembly() { - public FunctionWithHandlersInDedicatedAssembly() + TypesScopedByTestClassAssemblyScanningEnabled = false; + CustomizeConfiguration = configuration => { - TypesScopedByTestClassAssemblyScanningEnabled = false; - CustomizeConfiguration = configuration => - { - configuration.AdvancedConfiguration.UsePersistence(); + configuration.AdvancedConfiguration.UsePersistence(); - SettingsHolder = configuration.AdvancedConfiguration.GetSettings(); - // This is using a backdoor to set the assembly directory name to the one that contains the handlers - SettingsHolder.Set("NServiceBus.AzureFunctions.InProcess.ServiceBus.AssemblyDirectoryName", "ExternalHandlers"); - }; - } - - public SettingsHolder SettingsHolder { get; private set; } + SettingsHolder = configuration.AdvancedConfiguration.GetSettings(); + // This is using a backdoor to set the assembly directory name to the one that contains the handlers + SettingsHolder.Set("NServiceBus.AzureFunctions.InProcess.ServiceBus.AssemblyDirectoryName", "ExternalHandlers"); + }; } + + public SettingsHolder SettingsHolder { get; private set; } } } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs b/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs index ab27dbea..5016ac75 100644 --- a/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs +++ b/src/ServiceBus.AcceptanceTests/When_using_processatomic_with_outbox.cs @@ -1,100 +1,99 @@ -namespace ServiceBus.Tests -{ - using System; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Support; - using NServiceBus.Pipeline; - using NUnit.Framework; - using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; +namespace ServiceBus.Tests; + +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NServiceBus.AcceptanceTesting.Support; +using NServiceBus.Pipeline; +using NUnit.Framework; +using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; - public class When_using_processatomic_with_outbox +public class When_using_processatomic_with_outbox +{ + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public void Should_dispatch_outgoing_messages_from_the_outbox(TransportTransactionMode transactionMode) { - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public void Should_dispatch_outgoing_messages_from_the_outbox(TransportTransactionMode transactionMode) + var exception = Assert.ThrowsAsync(() => { - var exception = Assert.ThrowsAsync(() => - { - return Scenario.Define() - .WithComponent(new FunctionHandler(transactionMode)) - .WithEndpoint() - .Done(c => c.MessageReceived && c.MessageRetried) - .Run(); - }); + return Scenario.Define() + .WithComponent(new FunctionHandler(transactionMode)) + .WithEndpoint() + .Done(c => c.MessageReceived && c.MessageRetried) + .Run(); + }); - Assert.That(exception.InnerException.Message, Does.Contain("Atomic sends with receive is not supported when the Outbox is enabled as it would risk message loss. Set `SendsAtomicWithReceive` to `false` on the `NServiceBusTriggerFunction` attribute or make sure to call `ProcessNonAtomic` instead of `ProcessAtomic` if using a custom trigger.")); - } + Assert.That(exception.InnerException.Message, Does.Contain("Atomic sends with receive is not supported when the Outbox is enabled as it would risk message loss. Set `SendsAtomicWithReceive` to `false` on the `NServiceBusTriggerFunction` attribute or make sure to call `ProcessNonAtomic` instead of `ProcessAtomic` if using a custom trigger.")); + } - public class Context : ScenarioContext - { - public bool MessageReceived { get; set; } - public bool MessageRetried { get; set; } - } + public class Context : ScenarioContext + { + public bool MessageReceived { get; set; } + public bool MessageRetried { get; set; } + } - class FunctionHandler : FunctionEndpointComponent + class FunctionHandler : FunctionEndpointComponent + { + public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) { - public FunctionHandler(TransportTransactionMode transactionMode) : base(transactionMode) + CustomizeConfiguration = configuration => { - CustomizeConfiguration = configuration => - { - configuration.AdvancedConfiguration.Pipeline.Register(b => new FailBeforeAckBehavior(b.GetRequiredService()), - "Simulates a failure in ACKing the incoming message"); - configuration.AdvancedConfiguration.EnableOutbox(); - configuration.AdvancedConfiguration.UsePersistence(); - }; - Messages.Add(new HappyDayMessage()); - } + configuration.AdvancedConfiguration.Pipeline.Register(b => new FailBeforeAckBehavior(b.GetRequiredService()), + "Simulates a failure in ACKing the incoming message"); + configuration.AdvancedConfiguration.EnableOutbox(); + configuration.AdvancedConfiguration.UsePersistence(); + }; + Messages.Add(new HappyDayMessage()); + } - public class HappyDayMessageHandler : IHandleMessages + public class HappyDayMessageHandler : IHandleMessages + { + public Task Handle(HappyDayMessage message, IMessageHandlerContext context) { - public Task Handle(HappyDayMessage message, IMessageHandlerContext context) - { - var sendOptions = new SendOptions(); - sendOptions.SetDestination(Conventions.EndpointNamingConvention(typeof(SpyEndpoint))); - return context.Send(new FollowUpMessage(), sendOptions); - } + var sendOptions = new SendOptions(); + sendOptions.SetDestination(Conventions.EndpointNamingConvention(typeof(SpyEndpoint))); + return context.Send(new FollowUpMessage(), sendOptions); } } + } - class SpyEndpoint : EndpointConfigurationBuilder - { - public SpyEndpoint() => EndpointSetup(); + class SpyEndpoint : EndpointConfigurationBuilder + { + public SpyEndpoint() => EndpointSetup(); - public class EventHandler(Context testContext) : IHandleMessages + public class EventHandler(Context testContext) : IHandleMessages + { + public Task Handle(FollowUpMessage message, IMessageHandlerContext context) { - public Task Handle(FollowUpMessage message, IMessageHandlerContext context) - { - testContext.MessageReceived = true; - return Task.CompletedTask; - } + testContext.MessageReceived = true; + return Task.CompletedTask; } } + } - class FailBeforeAckBehavior(Context testContext) : Behavior + class FailBeforeAckBehavior(Context testContext) : Behavior + { + bool failed; + + public override async Task Invoke(ITransportReceiveContext context, Func next) { - bool failed; + await next(); - public override async Task Invoke(ITransportReceiveContext context, Func next) + if (!failed) { - await next(); - - if (!failed) - { - failed = true; - throw new SimulatedException("Simulating ACK failure"); - } - else - { - testContext.MessageRetried = true; - } + failed = true; + throw new SimulatedException("Simulating ACK failure"); + } + else + { + testContext.MessageRetried = true; } } + } - class HappyDayMessage : IMessage; + class HappyDayMessage : IMessage; - class FollowUpMessage : IMessage; - } + class FollowUpMessage : IMessage; } \ No newline at end of file diff --git a/src/ServiceBus.AcceptanceTests/When_using_sagas.cs b/src/ServiceBus.AcceptanceTests/When_using_sagas.cs index 83458a1e..62d1c997 100644 --- a/src/ServiceBus.AcceptanceTests/When_using_sagas.cs +++ b/src/ServiceBus.AcceptanceTests/When_using_sagas.cs @@ -1,95 +1,94 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.AcceptanceTesting; +using NUnit.Framework; + +public class When_using_sagas { - using System; - using System.Threading.Tasks; - using NServiceBus; - using NServiceBus.AcceptanceTesting; - using NUnit.Framework; + [TestCase(TransportTransactionMode.ReceiveOnly)] + [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] + public async Task Should_invoke_saga_message_handlers(TransportTransactionMode transactionMode) + { + var context = await Scenario.Define() + .WithComponent(new SagaFunction(transactionMode)) + .Done(c => c.EndpointsStarted) + .Run(); + + Assert.That(context.CounterValue, Is.EqualTo(42)); + } + + class Context : ScenarioContext + { + public int CounterValue { get; set; } + } - public class When_using_sagas + class SagaFunction : FunctionEndpointComponent { - [TestCase(TransportTransactionMode.ReceiveOnly)] - [TestCase(TransportTransactionMode.SendsAtomicWithReceive)] - public async Task Should_invoke_saga_message_handlers(TransportTransactionMode transactionMode) + public SagaFunction(TransportTransactionMode transportTransactionMode) : base(transportTransactionMode) { - var context = await Scenario.Define() - .WithComponent(new SagaFunction(transactionMode)) - .Done(c => c.EndpointsStarted) - .Run(); + CustomizeConfiguration = configuration => + configuration.AdvancedConfiguration.UsePersistence(); - Assert.That(context.CounterValue, Is.EqualTo(42)); + var correlationProperty = Guid.NewGuid().ToString("N"); + Messages.Add(new StartSagaMessage { CorrelationProperty = correlationProperty }); + Messages.Add(new UpdateSagaMessage { CorrelationProperty = correlationProperty, UpdateValue = 42 }); + Messages.Add(new ReadSagaDataValueMessage { CorrelationProperty = correlationProperty }); } - class Context : ScenarioContext + public class DemoSaga(Context testContext) : Saga, + IAmStartedByMessages, + IHandleMessages, + IHandleMessages { - public int CounterValue { get; set; } - } + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) => + mapper.MapSaga(saga => saga.CorrelationProperty) + .ToMessage(m => m.CorrelationProperty) + .ToMessage(m => m.CorrelationProperty) + .ToMessage(m => m.CorrelationProperty); - class SagaFunction : FunctionEndpointComponent - { - public SagaFunction(TransportTransactionMode transportTransactionMode) : base(transportTransactionMode) + public Task Handle(StartSagaMessage message, IMessageHandlerContext context) { - CustomizeConfiguration = configuration => - configuration.AdvancedConfiguration.UsePersistence(); - - var correlationProperty = Guid.NewGuid().ToString("N"); - Messages.Add(new StartSagaMessage { CorrelationProperty = correlationProperty }); - Messages.Add(new UpdateSagaMessage { CorrelationProperty = correlationProperty, UpdateValue = 42 }); - Messages.Add(new ReadSagaDataValueMessage { CorrelationProperty = correlationProperty }); + Data.SomeCounter = 1; + return Task.CompletedTask; } - public class DemoSaga(Context testContext) : Saga, - IAmStartedByMessages, - IHandleMessages, - IHandleMessages + public Task Handle(UpdateSagaMessage message, IMessageHandlerContext context) { - protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) => - mapper.MapSaga(saga => saga.CorrelationProperty) - .ToMessage(m => m.CorrelationProperty) - .ToMessage(m => m.CorrelationProperty) - .ToMessage(m => m.CorrelationProperty); - - public Task Handle(StartSagaMessage message, IMessageHandlerContext context) - { - Data.SomeCounter = 1; - return Task.CompletedTask; - } - - public Task Handle(UpdateSagaMessage message, IMessageHandlerContext context) - { - Data.SomeCounter = message.UpdateValue; - return Task.CompletedTask; - } - - public Task Handle(ReadSagaDataValueMessage message, IMessageHandlerContext context) - { - testContext.CounterValue = Data.SomeCounter; - MarkAsComplete(); - return Task.CompletedTask; - } + Data.SomeCounter = message.UpdateValue; + return Task.CompletedTask; } - public class DemoSagaData : ContainSagaData + public Task Handle(ReadSagaDataValueMessage message, IMessageHandlerContext context) { - public string CorrelationProperty { get; set; } - public int SomeCounter { get; set; } + testContext.CounterValue = Data.SomeCounter; + MarkAsComplete(); + return Task.CompletedTask; } } - class StartSagaMessage : IMessage + public class DemoSagaData : ContainSagaData { public string CorrelationProperty { get; set; } + public int SomeCounter { get; set; } } + } - class UpdateSagaMessage : IMessage - { - public string CorrelationProperty { get; set; } - public int UpdateValue { get; set; } - } + class StartSagaMessage : IMessage + { + public string CorrelationProperty { get; set; } + } - class ReadSagaDataValueMessage : IMessage - { - public string CorrelationProperty { get; set; } - } + class UpdateSagaMessage : IMessage + { + public string CorrelationProperty { get; set; } + public int UpdateValue { get; set; } + } + + class ReadSagaDataValueMessage : IMessage + { + public string CorrelationProperty { get; set; } } -} +} \ No newline at end of file diff --git a/src/ServiceBus.Tests/APIApprovals.cs b/src/ServiceBus.Tests/APIApprovals.cs index ac90c1d5..db31bb22 100644 --- a/src/ServiceBus.Tests/APIApprovals.cs +++ b/src/ServiceBus.Tests/APIApprovals.cs @@ -1,21 +1,20 @@ -namespace ServiceBus.Tests -{ - using NServiceBus; - using NUnit.Framework; - using Particular.Approvals; - using PublicApiGenerator; +namespace ServiceBus.Tests; + +using NServiceBus; +using NUnit.Framework; +using Particular.Approvals; +using PublicApiGenerator; - [TestFixture] - public class APIApprovals +[TestFixture] +public class APIApprovals +{ + [Test] + public void Approve() { - [Test] - public void Approve() + var publicApi = typeof(ServiceBusTriggeredEndpointConfiguration).Assembly.GeneratePublicApi(new ApiGeneratorOptions { - var publicApi = typeof(ServiceBusTriggeredEndpointConfiguration).Assembly.GeneratePublicApi(new ApiGeneratorOptions - { - ExcludeAttributes = new[] { "System.Runtime.Versioning.TargetFrameworkAttribute", "System.Reflection.AssemblyMetadataAttribute" } - }); - Approver.Verify(publicApi); - } + ExcludeAttributes = ["System.Runtime.Versioning.TargetFrameworkAttribute", "System.Reflection.AssemblyMetadataAttribute"] + }); + Approver.Verify(publicApi); } } \ No newline at end of file diff --git a/src/ServiceBus.Tests/LoggingTests.cs b/src/ServiceBus.Tests/LoggingTests.cs index 5cf17fd7..5a1ed9ef 100644 --- a/src/ServiceBus.Tests/LoggingTests.cs +++ b/src/ServiceBus.Tests/LoggingTests.cs @@ -1,125 +1,124 @@ -namespace ServiceBus.Tests +namespace ServiceBus.Tests; + +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NServiceBus.AzureFunctions.InProcess.ServiceBus; +using NServiceBus.Logging; +using NUnit.Framework; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +[TestFixture] +class LoggingTests { - using System; - using System.Collections.Concurrent; - using System.Threading.Tasks; - using Microsoft.Extensions.Logging; - using NServiceBus.AzureFunctions.InProcess.ServiceBus; - using NServiceBus.Logging; - using NUnit.Framework; - using LogLevel = Microsoft.Extensions.Logging.LogLevel; - - [TestFixture] - class LoggingTests + [Test] + public void Always_returns_same_logger() { - [Test] - public void Always_returns_same_logger() - { - var loggerA = FunctionsLoggerFactory.Instance.GetLogger("A"); - var loggerB = FunctionsLoggerFactory.Instance.GetLogger("B"); + var loggerA = FunctionsLoggerFactory.Instance.GetLogger("A"); + var loggerB = FunctionsLoggerFactory.Instance.GetLogger("B"); - Assert.That(loggerB, Is.SameAs(loggerA)); - } + Assert.That(loggerB, Is.SameAs(loggerA)); + } - [Test] - public void Captures_logs_before_handed_logger() - { - var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); - logger.Info("Deferred message"); + [Test] + public void Captures_logs_before_handed_logger() + { + var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); + logger.Info("Deferred message"); - var fakeLogger = new FakeLogger(); + var fakeLogger = new FakeLogger(); - FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); + FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); - Assert.That(fakeLogger.CapturedLogs, Has.Count.EqualTo(1)); - fakeLogger.CapturedLogs.TryDequeue(out var capturedLog); - Assert.That(capturedLog.message, Is.EqualTo("Deferred message")); - } + Assert.That(fakeLogger.CapturedLogs, Has.Count.EqualTo(1)); + fakeLogger.CapturedLogs.TryDequeue(out var capturedLog); + Assert.That(capturedLog.message, Is.EqualTo("Deferred message")); + } - [Test] - public void Forwards_logs_after_handed_logger() - { - var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); + [Test] + public void Forwards_logs_after_handed_logger() + { + var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); - var fakeLogger = new FakeLogger(); + var fakeLogger = new FakeLogger(); - FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); + FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); - logger.Info("Forwarded message"); + logger.Info("Forwarded message"); - Assert.That(fakeLogger.CapturedLogs, Has.Count.EqualTo(1)); - fakeLogger.CapturedLogs.TryDequeue(out var capturedLog); - Assert.That(capturedLog.message, Is.EqualTo("Forwarded message")); - } + Assert.That(fakeLogger.CapturedLogs, Has.Count.EqualTo(1)); + fakeLogger.CapturedLogs.TryDequeue(out var capturedLog); + Assert.That(capturedLog.message, Is.EqualTo("Forwarded message")); + } - [Test] - public void Only_first_logger_gets_deferred_messages() + [Test] + public void Only_first_logger_gets_deferred_messages() + { + var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); + logger.Info("Deferred message"); + + var firstLogger = new FakeLogger(); + var secondLogger = new FakeLogger(); + + FunctionsLoggerFactory.Instance.SetCurrentLogger(firstLogger); + FunctionsLoggerFactory.Instance.SetCurrentLogger(secondLogger); + + Assert.Multiple(() => { - var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); - logger.Info("Deferred message"); + Assert.That(firstLogger.CapturedLogs, Has.Count.EqualTo(1)); + Assert.That(secondLogger.CapturedLogs.Count, Is.EqualTo(0)); + }); + } - var firstLogger = new FakeLogger(); - var secondLogger = new FakeLogger(); + [Test] + public async Task Concurrent_loggers_are_isolated() + { + var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); + var firstLoggerTask = Execute(logger, 1); + var secondLoggerTask = Execute(logger, 2); - FunctionsLoggerFactory.Instance.SetCurrentLogger(firstLogger); - FunctionsLoggerFactory.Instance.SetCurrentLogger(secondLogger); + await Task.WhenAll(firstLoggerTask, secondLoggerTask); - Assert.Multiple(() => - { - Assert.That(firstLogger.CapturedLogs, Has.Count.EqualTo(1)); - Assert.That(secondLogger.CapturedLogs.Count, Is.EqualTo(0)); - }); - } + var firstLogger = firstLoggerTask.Result; + var secondLogger = secondLoggerTask.Result; - [Test] - public async Task Concurrent_loggers_are_isolated() + Assert.Multiple(() => { - var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); - var firstLoggerTask = Execute(logger, 1); - var secondLoggerTask = Execute(logger, 2); - - await Task.WhenAll(firstLoggerTask, secondLoggerTask); - - var firstLogger = firstLoggerTask.Result; - var secondLogger = secondLoggerTask.Result; - - Assert.Multiple(() => - { - Assert.That(firstLogger.CapturedLogs, Has.Count.EqualTo(1)); - Assert.That(secondLogger.CapturedLogs, Has.Count.EqualTo(1)); - }); - - firstLogger.CapturedLogs.TryDequeue(out var firstLog); - Assert.That(firstLog.message, Is.EqualTo("Running task 1")); - - secondLogger.CapturedLogs.TryDequeue(out var secondLog); - Assert.That(secondLog.message, Is.EqualTo("Running task 2")); - - async Task Execute(ILog log, int n) - { - await Task.Yield(); - var fakeLogger = new FakeLogger(); - FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); - log.Info($"Running task {n}"); - return fakeLogger; - } + Assert.That(firstLogger.CapturedLogs, Has.Count.EqualTo(1)); + Assert.That(secondLogger.CapturedLogs, Has.Count.EqualTo(1)); + }); + + firstLogger.CapturedLogs.TryDequeue(out var firstLog); + Assert.That(firstLog.message, Is.EqualTo("Running task 1")); + + secondLogger.CapturedLogs.TryDequeue(out var secondLog); + Assert.That(secondLog.message, Is.EqualTo("Running task 2")); + + async Task Execute(ILog log, int n) + { + await Task.Yield(); + var fakeLogger = new FakeLogger(); + FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); + log.Info($"Running task {n}"); + return fakeLogger; } + } - class FakeLogger : ILogger + class FakeLogger : ILogger + { + public ConcurrentQueue<(LogLevel level, EventId eventId, Exception exception, string message)> CapturedLogs { - public ConcurrentQueue<(LogLevel level, EventId eventId, Exception exception, string message)> CapturedLogs - { - get; - } = new ConcurrentQueue<(LogLevel level, EventId eventId, Exception exception, string message)>(); + get; + } = new ConcurrentQueue<(LogLevel level, EventId eventId, Exception exception, string message)>(); - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, - Func formatter) - => CapturedLogs.Enqueue((logLevel, eventId, exception, formatter(state, exception))); + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, + Func formatter) + => CapturedLogs.Enqueue((logLevel, eventId, exception, formatter(state, exception))); - public bool IsEnabled(LogLevel logLevel) => true; + public bool IsEnabled(LogLevel logLevel) => true; - public IDisposable BeginScope(TState state) => throw new NotImplementedException(); - } + public IDisposable BeginScope(TState state) => throw new NotImplementedException(); } -} +} \ No newline at end of file diff --git a/src/ServiceBus.Tests/MessageHelper.cs b/src/ServiceBus.Tests/MessageHelper.cs index 35dc0aef..26de89da 100644 --- a/src/ServiceBus.Tests/MessageHelper.cs +++ b/src/ServiceBus.Tests/MessageHelper.cs @@ -1,18 +1,17 @@ -namespace ServiceBus.Tests -{ - using System; - using System.Collections.Generic; - using Azure.Messaging.ServiceBus; +namespace ServiceBus.Tests; + +using System; +using System.Collections.Generic; +using Azure.Messaging.ServiceBus; - public class MessageHelper +public class MessageHelper +{ + public static ServiceBusReceivedMessage GenerateMessage(object message) { - public static ServiceBusReceivedMessage GenerateMessage(object message) - { - return ServiceBusModelFactory.ServiceBusReceivedMessage( - body: BinaryData.FromObjectAsJson(message), - messageId: Guid.NewGuid().ToString("N"), - deliveryCount: 1, - properties: new Dictionary { { "NServiceBus.EnclosedMessageTypes", message.GetType().FullName } }); - } + return ServiceBusModelFactory.ServiceBusReceivedMessage( + body: BinaryData.FromObjectAsJson(message), + messageId: Guid.NewGuid().ToString("N"), + deliveryCount: 1, + properties: new Dictionary { { "NServiceBus.EnclosedMessageTypes", message.GetType().FullName } }); } } \ No newline at end of file diff --git a/src/Testing.Handlers/DummyMessageHandler.cs b/src/Testing.Handlers/DummyMessageHandler.cs index aabcf7a9..874d8e7f 100644 --- a/src/Testing.Handlers/DummyMessageHandler.cs +++ b/src/Testing.Handlers/DummyMessageHandler.cs @@ -1,24 +1,23 @@ -namespace Testing.Handlers -{ - using System.Threading.Tasks; - using NServiceBus; +namespace Testing.Handlers; - /** - * These types are used to test the assembly scanning logic which is required to load additional handler assemblies - * which are optimized away when the calling code doesn't explicitly reference a type from the assembly. - * To update the assembly used by the tests, rebuild this project and replace the dll which has been copied to the - * test projects manually. - * Referencing this project from the test project doesn't work as Core's assembly scanning mechanism will find and load it. - */ - public class DummyMessageHandler : IHandleMessages - { - public Task Handle(DummyMessage message, IMessageHandlerContext context) - { - return Task.CompletedTask; - } - } +using System.Threading.Tasks; +using NServiceBus; - public class DummyMessage : IMessage +/** + * These types are used to test the assembly scanning logic which is required to load additional handler assemblies + * which are optimized away when the calling code doesn't explicitly reference a type from the assembly. + * To update the assembly used by the tests, rebuild this project and replace the dll which has been copied to the + * test projects manually. + * Referencing this project from the test project doesn't work as Core's assembly scanning mechanism will find and load it. + */ +public class DummyMessageHandler : IHandleMessages +{ + public Task Handle(DummyMessage message, IMessageHandlerContext context) { + return Task.CompletedTask; } +} + +public class DummyMessage : IMessage +{ } \ No newline at end of file