This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git

commit 65ab43cdf57e00024b96244c04656edf1fe3ccc6
Author: Shad Storhaug <[email protected]>
AuthorDate: Sat Nov 6 14:05:56 2021 +0700

    Lucene.Net.Tests.Replicator.Http.HttpReplicatorTest: Added 
TestServerErrors() test from 4.8.1
---
 Directory.Build.targets                            |   7 ++
 build/Dependencies.props                           |   3 +-
 .../Http/HttpReplicatorTest.cs                     |  49 ++++++++-
 .../Http/ReplicationServlet.cs                     |  85 +++++++++++++++-
 .../ReplicatorTestCase.cs                          | 110 ++++++++++++++++++++-
 5 files changed, 243 insertions(+), 11 deletions(-)

diff --git a/Directory.Build.targets b/Directory.Build.targets
index 8e87632..2e1dc9e 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -20,6 +20,13 @@
 -->
 <Project>
 
+  <!-- Features in .NET 5.x and .NET 6.x only -->
+  <PropertyGroup Condition=" $(TargetFramework.StartsWith('net5.')) Or 
$(TargetFramework.StartsWith('net6.')) ">
+
+    
<DefineConstants>$(DefineConstants);FEATURE_ASPNETCORE_ENDPOINT_CONFIG</DefineConstants>
+
+  </PropertyGroup>
+  
   <!-- Features in .NET Core 3.x, .NET 5.x, and .NET 6.x only -->
   <PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp3.')) Or 
$(TargetFramework.StartsWith('net5.')) Or '$(TargetFramework)' == 'net6.0' ">
 
diff --git a/build/Dependencies.props b/build/Dependencies.props
index 90afcab..836c8bc 100644
--- a/build/Dependencies.props
+++ b/build/Dependencies.props
@@ -41,7 +41,8 @@
     <J2NPackageVersion>2.0.0-beta-0017</J2NPackageVersion>
     
<LiquidTestReportsMarkdownPackageVersion>1.0.9</LiquidTestReportsMarkdownPackageVersion>
     
<MicrosoftAspNetCoreHttpAbstractionsPackageVersion>2.0.0</MicrosoftAspNetCoreHttpAbstractionsPackageVersion>
-    
<MicrosoftAspNetCoreTestHostPackageVersion>2.0.0</MicrosoftAspNetCoreTestHostPackageVersion>
+    
<MicrosoftAspNetCoreTestHostPackageVersion>5.0.0</MicrosoftAspNetCoreTestHostPackageVersion>
+    <MicrosoftAspNetCoreTestHostPackageVersion Condition=" 
$(TargetFramework.StartsWith('net4')) Or 
$(TargetFramework.StartsWith('netcoreapp')) 
">2.0.0</MicrosoftAspNetCoreTestHostPackageVersion>
     
<MicrosoftCodeAnalysisAnalyzersPackageVersion>2.9.8</MicrosoftCodeAnalysisAnalyzersPackageVersion>
     
<MicrosoftCodeAnalysisCSharpPackageVersion>2.6.1</MicrosoftCodeAnalysisCSharpPackageVersion>
     
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>$(MicrosoftCodeAnalysisCSharpPackageVersion)</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
diff --git a/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs 
b/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs
index b3061ad..e9d9927 100644
--- a/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs
+++ b/src/Lucene.Net.Tests.Replicator/Http/HttpReplicatorTest.cs
@@ -1,9 +1,10 @@
-using Lucene.Net.Documents;
+using Lucene.Net.Documents;
 using Lucene.Net.Index;
 using Lucene.Net.Support;
 using Lucene.Net.Util;
 using Microsoft.AspNetCore.TestHost;
 using NUnit.Framework;
+using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
@@ -42,11 +43,18 @@ namespace Lucene.Net.Replicator.Http
         private Directory serverIndexDir;
         private Directory handlerIndexDir;
 
+        private MockErrorConfig mockErrorConfig;
+
         private void StartServer()
         {
             ReplicationService service = new ReplicationService(new 
Dictionary<string, IReplicator> { { "s1", serverReplicator } });
 
-            server = NewHttpServer<ReplicationServlet>(service);
+#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG
+            server = NewHttpServer(service, mockErrorConfig); // Call like 
this to use ReplicationServerMiddleware on the specific path 
/replicate/{shard?}/{action?}, but allow other paths to be served
+#else
+            server = NewHttpServer<ReplicationServlet>(service, 
mockErrorConfig); // Call like this to use ReplicationServlet as a Startup Class
+#endif
+
             port = ServerPort(server);
             host = ServerHost(server);
         }
@@ -57,6 +65,7 @@ namespace Lucene.Net.Replicator.Http
             clientWorkDir = CreateTempDir("httpReplicatorTest");
             handlerIndexDir = NewDirectory();
             serverIndexDir = NewDirectory();
+            mockErrorConfig = new MockErrorConfig(); // LUCENENET specific
             serverReplicator = new LocalReplicator();
             StartServer();
 
@@ -108,5 +117,41 @@ namespace Lucene.Net.Replicator.Http
             ReopenReader();
             assertEquals(2, int.Parse(reader.IndexCommit.UserData["ID"], 
NumberStyles.HexNumber));
         }
+
+        [Test]
+        public void TestServerErrors()
+        {
+            // tests the behaviour of the client when the server sends an error
+            IReplicator replicator = new HttpReplicator(host, port, 
ReplicationService.REPLICATION_CONTEXT + "/s1", server.CreateHandler());
+            using ReplicationClient client = new ReplicationClient(replicator, 
new IndexReplicationHandler(handlerIndexDir, null),
+                new PerSessionDirectoryFactory(clientWorkDir.FullName));
+
+            try
+            {
+                PublishRevision(5);
+
+                try
+                {
+                    mockErrorConfig.RespondWithError = true;
+                    client.UpdateNow();
+                    fail("expected exception");
+                }
+                catch (Exception t) when (t.IsThrowable())
+                {
+                    // expected
+                }
+
+                mockErrorConfig.RespondWithError = false;
+                client.UpdateNow(); // now it should work
+                ReopenReader();
+                assertEquals(5, 
J2N.Numerics.Int32.Parse(reader.IndexCommit.UserData["ID"], 16));
+
+                client.Dispose();
+            }
+            finally
+            {
+                mockErrorConfig.RespondWithError = false;
+            }
+        }
     }
 }
diff --git a/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs 
b/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs
index 1fa3b05..fd93eeb 100644
--- a/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs
+++ b/src/Lucene.Net.Tests.Replicator/Http/ReplicationServlet.cs
@@ -1,9 +1,16 @@
-using Lucene.Net.Replicator.AspNetCore;
+using Lucene.Net.Replicator.AspNetCore;
 using Lucene.Net.Replicator.Http.Abstractions;
 using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http.Features;
+using System;
 using System.Threading.Tasks;
 
+#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+#endif
+
 namespace Lucene.Net.Replicator.Http
 {
     /*
@@ -23,15 +30,85 @@ namespace Lucene.Net.Replicator.Http
      * limitations under the License.
      */
 
+    // ********************** Option 1: Use a Startup Class 
********************************************
+    // The startup class must define all middleware (app.Use) before the 
terminating endpoint (app.Run)
+
     public class ReplicationServlet
     {
-        public void Configure(IApplicationBuilder app, IHostingEnvironment 
env, IReplicationService service)
+        public void Configure(IApplicationBuilder app, IReplicationService 
service, ReplicatorTestCase.MockErrorConfig mockErrorConfig)
         {
-            app.Run(async context =>
+            // Middleware to throw an exception conditionally from our test 
server.
+            app.Use(async (context, next) =>
+            {
+                if (mockErrorConfig.RespondWithError)
+                {
+                    throw new ReplicatorTestCase.HttpResponseException();
+                }
+
+                await next();
+            });
+
+            app.Run(async (context) =>
             {
+                // LUCENENET: This is to allow synchronous IO to happen for 
these requests.
+                // LUCENENET TODO: Allow async operations from Replicator.
+                var syncIoFeature = 
context.Features.Get<IHttpBodyControlFeature>();
+                if (syncIoFeature != null)
+                {
+                    syncIoFeature.AllowSynchronousIO = true;
+                }
+
                 await Task.Yield();
                 service.Perform(context.Request, context.Response);
             });
         }
     }
+
+    // ********************** Option 2: Use Middleware with Endpoint Routing 
*******************************
+    // Running ReplicationService as middleware allows registering other URL 
patterns so other services
+    // (such as controllers or razor pages) can be served from the same 
application.
+
+#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG // Only available in .NET 5+
+    public class ReplicationServiceMiddleware
+    {
+        private readonly RequestDelegate next;
+        private readonly IReplicationService service;
+
+        public ReplicationServiceMiddleware(RequestDelegate next, 
IReplicationService service)
+        {
+            this.next = next ?? throw new ArgumentNullException(nameof(next));
+            this.service = service ?? throw new 
ArgumentNullException(nameof(service));
+        }
+
+        public async Task InvokeAsync(HttpContext context)
+        {
+            // LUCENENET: This is to allow synchronous IO to happen for these 
requests.
+            // LUCENENET TODO: Allow async operations from Replicator.
+            var syncIoFeature = 
context.Features.Get<IHttpBodyControlFeature>();
+            if (syncIoFeature != null)
+            {
+                syncIoFeature.AllowSynchronousIO = true;
+            }
+
+            await Task.Yield();
+            service.Perform(context.Request, context.Response);
+
+            // This is a terminating endpoint. Do not call the next 
delegate/middleware in the pipeline.
+        }
+    }
+
+    public static class ReplicationServiceRouteBuilderExtensions
+    {
+        public static IEndpointConventionBuilder MapReplicator(this 
IEndpointRouteBuilder endpoints, string pattern)
+        {
+            var pipeline = endpoints.CreateApplicationBuilder()
+                .UseMiddleware<ReplicationServiceMiddleware>()
+                .Build();
+
+            return endpoints
+                .Map(pattern, pipeline)
+                .WithDisplayName("Replication Service");
+        }
+    }
+#endif
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs 
b/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs
index 4e5562f..b0abc94 100644
--- a/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs
+++ b/src/Lucene.Net.Tests.Replicator/ReplicatorTestCase.cs
@@ -1,10 +1,16 @@
-using Lucene.Net.Replicator.Http;
+using Lucene.Net.Replicator.Http;
 using Lucene.Net.Replicator.Http.Abstractions;
 using Lucene.Net.Util;
 using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.TestHost;
 using Microsoft.Extensions.DependencyInjection;
 using System;
+using System.Threading.Tasks;
+
+#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG
+using Microsoft.AspNetCore.Builder;
+#endif
 
 namespace Lucene.Net.Replicator
 {
@@ -28,16 +34,73 @@ namespace Lucene.Net.Replicator
     [SuppressCodecs("Lucene3x")]
     public class ReplicatorTestCase : LuceneTestCase
     {
-        public static TestServer NewHttpServer<TStartUp>(IReplicationService 
service) where TStartUp : class
+
+#if FEATURE_ASPNETCORE_ENDPOINT_CONFIG
+        /// <summary>
+        /// Call this overload to use <see 
cref="ReplicationServiceMiddleware"/> to host <see cref="ReplicationService"/>.
+        /// </summary>
+        /// <param name="service">The <see cref="ReplicationService"/> that 
will be registered as singleton.</param>
+        /// <param name="mockErrorConfig">The <see cref="MockErrorConfig"/> 
that will be registered as singleton.</param>
+        /// <returns>A configured <see cref="TestServer"/> instance.</returns>
+        public static TestServer NewHttpServer(IReplicationService service, 
MockErrorConfig mockErrorConfig)
+        {
+            var builder = new WebHostBuilder()
+                .ConfigureServices(container =>
+                {
+                    container.AddRouting();
+                    container.AddSingleton(service);
+                    container.AddSingleton(mockErrorConfig);
+                    container.AddSingleton<ReplicationServiceMiddleware>();
+                    container.AddSingleton<MockErrorMiddleware>();
+                })
+                .Configure(app =>
+                {
+                    app.UseRouting();
+
+                    // Middleware so we can mock a server exception and toggle 
the exception on and off.
+                    app.UseMiddleware<MockErrorMiddleware>();
+
+                    app.UseEndpoints(endpoints =>
+                    {
+                        // This is to define the endpoint for Replicator.
+                        // All URLs with the pattern 
/replicate/{shard?}/{action?} terminate here and any middleware that
+                        // is expected to run for Replicator must be 
registered before this call.
+                        
endpoints.MapReplicator(ReplicationService.REPLICATION_CONTEXT + 
"/{shard?}/{action?}");
+
+                        endpoints.MapGet("/{controller?}/{action?}/{id?}", 
async context =>
+                        {
+                            // This is just to demonstrate allowing requests 
to other services/controllers in the same
+                            // application. This isn't required, but is 
allowed.
+                            await context.Response.WriteAsync("Hello World!");
+                        });
+                    });
+                });
+            var server = new TestServer(builder);
+            return server;
+        }
+#else
+        /// <summary>
+        /// Call this overload to use <typeparamref name="TStartUp"/> as the 
Startup Class.
+        /// </summary>
+        /// <typeparam name="TStartUp">The type of startup class.</typeparam>
+        /// <param name="service">The <see cref="ReplicationService"/> that 
will be registered as singleton.</param>
+        /// <param name="mockErrorConfig">The <see cref="MockErrorConfig"/> 
that will be registered as singleton.</param>
+        /// <returns>A configured <see cref="TestServer"/> instance.</returns>
+        public static TestServer NewHttpServer<TStartUp>(IReplicationService 
service, MockErrorConfig mockErrorConfig) where TStartUp : class
         {
-            var server = new TestServer(new WebHostBuilder()
+            var builder = new WebHostBuilder()
                 .ConfigureServices(container =>
                 {
                     container.AddSingleton(service);
-                }).UseStartup<TStartUp>());
+                    container.AddSingleton(mockErrorConfig);
+                })
+                .UseStartup<TStartUp>();
+
+            var server = new TestServer(builder);
             server.BaseAddress = new Uri("http://localhost"; + 
ReplicationService.REPLICATION_CONTEXT);
             return server;
         }
+#endif
 
         /// <summary>
         /// Returns a <see cref="server"/>'s port. 
@@ -62,5 +125,44 @@ namespace Lucene.Net.Replicator
         {
             server.Dispose();
         }
+
+        public class HttpResponseException : Exception
+        {
+            public int Status { get; set; } = 500;
+
+            public object Value { get; set; }
+        }
+
+        public class MockErrorMiddleware
+        {
+            private readonly RequestDelegate next;
+            private readonly MockErrorConfig mockErrorConfig;
+
+            public MockErrorMiddleware(RequestDelegate next, MockErrorConfig 
mockErrorConfig)
+            {
+                this.next = next ?? throw new 
ArgumentNullException(nameof(next));
+                this.mockErrorConfig = mockErrorConfig ?? throw new 
ArgumentNullException(nameof(mockErrorConfig));
+            }
+
+            public async Task InvokeAsync(HttpContext context)
+            {
+                var path = context.Request.Path;
+                if 
(path.StartsWithSegments(ReplicationService.REPLICATION_CONTEXT))
+                {
+                    if (mockErrorConfig.RespondWithError)
+                    {
+                        throw new HttpResponseException();
+                    }
+                }
+
+                // Call the next delegate/middleware in the pipeline
+                await next(context);
+            }
+        }
+
+        public class MockErrorConfig
+        {
+            public bool RespondWithError { get; set; } = false;
+        }
     }
 }
\ No newline at end of file

Reply via email to