Merge pull request #15214 from jtattermusch/test_double_for_server_call_context

Allow creating instances of ServerCallContext in tests
diff --git a/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs b/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs
new file mode 100644
index 0000000..5418417
--- /dev/null
+++ b/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs
@@ -0,0 +1,58 @@
+#region Copyright notice and license
+
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#endregion
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Grpc.Core;
+
+namespace Grpc.Core.Testing
+{
+    /// <summary>
+    /// Creates test doubles for <c>ServerCallContext</c>.
+    /// </summary>
+    public static class TestServerCallContext
+    {
+        /// <summary>
+        /// Creates a test double for <c>ServerCallContext</c>. Only for testing.
+        /// Note: experimental API that can change or be removed without any prior notice.
+        /// </summary>
+        public static ServerCallContext Create(string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
+            string peer, AuthContext authContext, ContextPropagationToken contextPropagationToken,
+            Func<Metadata, Task> writeHeadersFunc, Func<WriteOptions> writeOptionsGetter, Action<WriteOptions> writeOptionsSetter)
+        {
+            return new ServerCallContext(null, method, host, deadline, requestHeaders, cancellationToken,
+                writeHeadersFunc, new WriteOptionsHolder(writeOptionsGetter, writeOptionsSetter),
+                () => peer, () => authContext, () => contextPropagationToken);
+        }
+
+        private class WriteOptionsHolder : IHasWriteOptions
+        {
+            Func<WriteOptions> writeOptionsGetter;
+            Action<WriteOptions> writeOptionsSetter;
+
+            public WriteOptionsHolder(Func<WriteOptions> writeOptionsGetter, Action<WriteOptions> writeOptionsSetter)
+            {
+                this.writeOptionsGetter = writeOptionsGetter;
+                this.writeOptionsSetter = writeOptionsSetter;
+            }
+
+            public WriteOptions WriteOptions { get => writeOptionsGetter(); set => writeOptionsSetter(value); }
+        }
+    }
+}
diff --git a/src/csharp/Grpc.Core/ServerCallContext.cs b/src/csharp/Grpc.Core/ServerCallContext.cs
index c63a4c4..74a7dea 100644
--- a/src/csharp/Grpc.Core/ServerCallContext.cs
+++ b/src/csharp/Grpc.Core/ServerCallContext.cs
@@ -36,14 +36,25 @@
         private readonly Metadata requestHeaders;
         private readonly CancellationToken cancellationToken;
         private readonly Metadata responseTrailers = new Metadata();
+        private readonly Func<Metadata, Task> writeHeadersFunc;
+        private readonly IHasWriteOptions writeOptionsHolder;
+        private readonly Lazy<AuthContext> authContext;
+        private readonly Func<string> testingOnlyPeerGetter;
+        private readonly Func<AuthContext> testingOnlyAuthContextGetter;
+        private readonly Func<ContextPropagationToken> testingOnlyContextPropagationTokenFactory;
 
         private Status status = Status.DefaultSuccess;
-        private Func<Metadata, Task> writeHeadersFunc;
-        private IHasWriteOptions writeOptionsHolder;
-        private Lazy<AuthContext> authContext;
 
         internal ServerCallContext(CallSafeHandle callHandle, string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
             Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder)
+            : this(callHandle, method, host, deadline, requestHeaders, cancellationToken, writeHeadersFunc, writeOptionsHolder, null, null, null)
+        {
+        }
+
+        // Additional constructor params should be used for testing only
+        internal ServerCallContext(CallSafeHandle callHandle, string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken,
+            Func<Metadata, Task> writeHeadersFunc, IHasWriteOptions writeOptionsHolder,
+            Func<string> testingOnlyPeerGetter, Func<AuthContext> testingOnlyAuthContextGetter, Func<ContextPropagationToken> testingOnlyContextPropagationTokenFactory)
         {
             this.callHandle = callHandle;
             this.method = method;
@@ -54,6 +65,9 @@
             this.writeHeadersFunc = writeHeadersFunc;
             this.writeOptionsHolder = writeOptionsHolder;
             this.authContext = new Lazy<AuthContext>(GetAuthContextEager);
+            this.testingOnlyPeerGetter = testingOnlyPeerGetter;
+            this.testingOnlyAuthContextGetter = testingOnlyAuthContextGetter;
+            this.testingOnlyContextPropagationTokenFactory = testingOnlyContextPropagationTokenFactory;
         }
 
         /// <summary>
@@ -73,6 +87,10 @@
         /// </summary>
         public ContextPropagationToken CreatePropagationToken(ContextPropagationOptions options = null)
         {
+            if (testingOnlyContextPropagationTokenFactory != null)
+            {
+                return testingOnlyContextPropagationTokenFactory();
+            }
             return new ContextPropagationToken(callHandle, deadline, cancellationToken, options);
         }
             
@@ -99,6 +117,10 @@
         {
             get
             {
+                if (testingOnlyPeerGetter != null)
+                {
+                    return testingOnlyPeerGetter();
+                }
                 // Getting the peer lazily is fine as the native call is guaranteed
                 // not to be disposed before user-supplied server side handler returns.
                 // Most users won't need to read this field anyway.
@@ -182,6 +204,10 @@
         {
             get
             {
+                if (testingOnlyAuthContextGetter != null)
+                {
+                    return testingOnlyAuthContextGetter();
+                }
                 return authContext.Value;
             }
         }
@@ -198,7 +224,7 @@
     /// <summary>
     /// Allows sharing write options between ServerCallContext and other objects.
     /// </summary>
-    public interface IHasWriteOptions
+    internal interface IHasWriteOptions
     {
         /// <summary>
         /// Gets or sets the write options.