diff --git a/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs b/src/csharp/Grpc.Core.Testing/TestServerCallContext.cs new file mode 100644 index 00000000000..5418417d7ed --- /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 +{ + /// + /// Creates test doubles for ServerCallContext. + /// + public static class TestServerCallContext + { + /// + /// Creates a test double for ServerCallContext. Only for testing. + /// Note: experimental API that can change or be removed without any prior notice. + /// + public static ServerCallContext Create(string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken, + string peer, AuthContext authContext, ContextPropagationToken contextPropagationToken, + Func writeHeadersFunc, Func writeOptionsGetter, Action writeOptionsSetter) + { + return new ServerCallContext(null, method, host, deadline, requestHeaders, cancellationToken, + writeHeadersFunc, new WriteOptionsHolder(writeOptionsGetter, writeOptionsSetter), + () => peer, () => authContext, () => contextPropagationToken); + } + + private class WriteOptionsHolder : IHasWriteOptions + { + Func writeOptionsGetter; + Action writeOptionsSetter; + + public WriteOptionsHolder(Func writeOptionsGetter, Action 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 c63a4c45db1..74a7deabea0 100644 --- a/src/csharp/Grpc.Core/ServerCallContext.cs +++ b/src/csharp/Grpc.Core/ServerCallContext.cs @@ -36,14 +36,25 @@ namespace Grpc.Core private readonly Metadata requestHeaders; private readonly CancellationToken cancellationToken; private readonly Metadata responseTrailers = new Metadata(); + private readonly Func writeHeadersFunc; + private readonly IHasWriteOptions writeOptionsHolder; + private readonly Lazy authContext; + private readonly Func testingOnlyPeerGetter; + private readonly Func testingOnlyAuthContextGetter; + private readonly Func testingOnlyContextPropagationTokenFactory; private Status status = Status.DefaultSuccess; - private Func writeHeadersFunc; - private IHasWriteOptions writeOptionsHolder; - private Lazy authContext; internal ServerCallContext(CallSafeHandle callHandle, string method, string host, DateTime deadline, Metadata requestHeaders, CancellationToken cancellationToken, Func 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 writeHeadersFunc, IHasWriteOptions writeOptionsHolder, + Func testingOnlyPeerGetter, Func testingOnlyAuthContextGetter, Func testingOnlyContextPropagationTokenFactory) { this.callHandle = callHandle; this.method = method; @@ -54,6 +65,9 @@ namespace Grpc.Core this.writeHeadersFunc = writeHeadersFunc; this.writeOptionsHolder = writeOptionsHolder; this.authContext = new Lazy(GetAuthContextEager); + this.testingOnlyPeerGetter = testingOnlyPeerGetter; + this.testingOnlyAuthContextGetter = testingOnlyAuthContextGetter; + this.testingOnlyContextPropagationTokenFactory = testingOnlyContextPropagationTokenFactory; } /// @@ -73,6 +87,10 @@ namespace Grpc.Core /// public ContextPropagationToken CreatePropagationToken(ContextPropagationOptions options = null) { + if (testingOnlyContextPropagationTokenFactory != null) + { + return testingOnlyContextPropagationTokenFactory(); + } return new ContextPropagationToken(callHandle, deadline, cancellationToken, options); } @@ -99,6 +117,10 @@ namespace Grpc.Core { 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 @@ namespace Grpc.Core { get { + if (testingOnlyAuthContextGetter != null) + { + return testingOnlyAuthContextGetter(); + } return authContext.Value; } } @@ -198,7 +224,7 @@ namespace Grpc.Core /// /// Allows sharing write options between ServerCallContext and other objects. /// - public interface IHasWriteOptions + internal interface IHasWriteOptions { /// /// Gets or sets the write options.