commit
36f05d42f5
|
|
@ -1,10 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HttpConnection, HttpTransportType, IHttpConnectionOptions, LogLevel, TransferFormat } from "@aspnet/signalr";
|
||||
import { HttpTransportType, IHttpConnectionOptions, LogLevel, TransferFormat } from "@aspnet/signalr";
|
||||
import { eachTransport, ECHOENDPOINT_URL } from "./Common";
|
||||
import { TestLogger } from "./TestLogger";
|
||||
|
||||
// We want to continue testing HttpConnection, but we don't export it anymore. So just pull it in directly from the source file.
|
||||
import { HttpConnection } from "../../signalr/src/HttpConnection";
|
||||
|
||||
const commonOptions: IHttpConnectionOptions = {
|
||||
logMessageContent: true,
|
||||
logger: TestLogger.instance,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
console.log("SignalR Functional Tests Loaded");
|
||||
|
||||
import "es6-promise/dist/es6-promise.auto.js";
|
||||
import "./ConnectionTests";
|
||||
import "./HubConnectionTests";
|
||||
import "./WebDriverReporter";
|
||||
import "./WebSocketTests";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HttpRequest } from "../src/index";
|
||||
import { HttpRequest } from "../src/HttpClient";
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
import { asyncit as it } from "./Utils";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HttpResponse } from "../src/HttpClient";
|
||||
import { HttpConnection } from "../src/HttpConnection";
|
||||
import { IHttpConnectionOptions } from "../src/HttpConnection";
|
||||
import { HttpResponse } from "../src/index";
|
||||
import { IHttpConnectionOptions } from "../src/IHttpConnectionOptions";
|
||||
import { HttpTransportType, ITransport, TransferFormat } from "../src/ITransport";
|
||||
import { eachEndpointUrl, eachTransport } from "./Common";
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
|
|
@ -435,16 +435,26 @@ describe("HttpConnection", () => {
|
|||
it("authorization header removed when token factory returns null and using LongPolling", async (done) => {
|
||||
const availableTransport = { transport: "LongPolling", transferFormats: ["Text"] };
|
||||
|
||||
var httpClientGetCount = 0;
|
||||
var accessTokenFactoryCount = 0;
|
||||
let httpClientGetCount = 0;
|
||||
let accessTokenFactoryCount = 0;
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
accessTokenFactory: () => {
|
||||
accessTokenFactoryCount++;
|
||||
if (accessTokenFactoryCount === 1) {
|
||||
return "A token value";
|
||||
} else {
|
||||
// Return a null value after the first call to test the header being removed
|
||||
return null;
|
||||
}
|
||||
},
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", (r) => ({ connectionId: "42", availableTransports: [availableTransport] }))
|
||||
.on("GET", (r) => {
|
||||
httpClientGetCount++;
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
const authorizationValue = r.headers["Authorization"];
|
||||
if (httpClientGetCount == 1) {
|
||||
if (httpClientGetCount === 1) {
|
||||
if (authorizationValue) {
|
||||
fail("First long poll request should have a authorization header.");
|
||||
}
|
||||
|
|
@ -458,15 +468,6 @@ describe("HttpConnection", () => {
|
|||
throw new Error("fail");
|
||||
}
|
||||
}),
|
||||
accessTokenFactory: () => {
|
||||
accessTokenFactoryCount++;
|
||||
if (accessTokenFactoryCount == 1) {
|
||||
return "A token value";
|
||||
} else {
|
||||
// Return a null value after the first call to test the header being removed
|
||||
return null;
|
||||
}
|
||||
},
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
|
@ -485,14 +486,14 @@ describe("HttpConnection", () => {
|
|||
it("sets inherentKeepAlive feature when using LongPolling", async (done) => {
|
||||
const availableTransport = { transport: "LongPolling", transferFormats: ["Text"] };
|
||||
|
||||
var httpClientGetCount = 0;
|
||||
let httpClientGetCount = 0;
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", (r) => ({ connectionId: "42", availableTransports: [availableTransport] }))
|
||||
.on("GET", (r) => {
|
||||
httpClientGetCount++;
|
||||
if (httpClientGetCount == 1) {
|
||||
if (httpClientGetCount === 1) {
|
||||
// First long polling request must succeed so start completes
|
||||
return "";
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HubConnection, JsonHubProtocol } from "../src/HubConnection";
|
||||
import { HubConnection } from "../src/HubConnection";
|
||||
import { IConnection } from "../src/IConnection";
|
||||
import { HubMessage, IHubProtocol, MessageType } from "../src/IHubProtocol";
|
||||
import { ILogger, LogLevel } from "../src/ILogger";
|
||||
import { HttpTransportType, ITransport, TransferFormat } from "../src/ITransport";
|
||||
import { JsonHubProtocol } from "../src/JsonHubProtocol";
|
||||
import { NullLogger } from "../src/Loggers";
|
||||
import { IStreamSubscriber } from "../src/Stream";
|
||||
import { TextMessageFormat } from "../src/TextMessageFormat";
|
||||
|
|
@ -13,7 +14,7 @@ import { TextMessageFormat } from "../src/TextMessageFormat";
|
|||
import { asyncit as it, captureException, delay, PromiseSource } from "./Utils";
|
||||
|
||||
function createHubConnection(connection: IConnection, logger?: ILogger, protocol?: IHubProtocol) {
|
||||
return new HubConnection(connection, logger || NullLogger.instance, protocol || new JsonHubProtocol());
|
||||
return HubConnection.create(connection, logger || NullLogger.instance, protocol || new JsonHubProtocol());
|
||||
}
|
||||
|
||||
describe("HubConnection", () => {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
|
||||
import { HubConnectionBuilder } from "../src/HubConnectionBuilder";
|
||||
|
||||
import { HubConnection } from "../src";
|
||||
import { HttpRequest, HttpResponse } from "../src/HttpClient";
|
||||
import { IHttpConnectionOptions } from "../src/HttpConnection";
|
||||
import { HubConnection } from "../src/HubConnection";
|
||||
import { IHttpConnectionOptions } from "../src/IHttpConnectionOptions";
|
||||
import { HubMessage, IHubProtocol } from "../src/IHubProtocol";
|
||||
import { ILogger, LogLevel } from "../src/ILogger";
|
||||
import { TransferFormat, HttpTransportType } from "../src/ITransport";
|
||||
import { HttpTransportType, TransferFormat } from "../src/ITransport";
|
||||
import { NullLogger } from "../src/Loggers";
|
||||
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
import { asyncit as it, PromiseSource } from "./Utils";
|
||||
|
||||
|
|
@ -123,14 +124,13 @@ describe("HubConnectionBuilder", () => {
|
|||
await closed;
|
||||
});
|
||||
|
||||
|
||||
it("allows logger to be replaced", async () => {
|
||||
let loggedMessages = 0;
|
||||
const logger = {
|
||||
log() {
|
||||
loggedMessages += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
const connection = createConnectionBuilder(logger)
|
||||
.withUrl("http://example.com")
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { DefaultHttpClient, HttpClient } from "./HttpClient";
|
||||
import { IConnection } from "./IConnection";
|
||||
import { IHttpConnectionOptions } from "./IHttpConnectionOptions";
|
||||
import { ILogger, LogLevel } from "./ILogger";
|
||||
import { HttpTransportType, ITransport, TransferFormat } from "./ITransport";
|
||||
import { LongPollingTransport } from "./LongPollingTransport";
|
||||
|
|
@ -10,15 +11,6 @@ import { ServerSentEventsTransport } from "./ServerSentEventsTransport";
|
|||
import { Arg, createLogger } from "./Utils";
|
||||
import { WebSocketTransport } from "./WebSocketTransport";
|
||||
|
||||
export interface IHttpConnectionOptions {
|
||||
httpClient?: HttpClient;
|
||||
transport?: HttpTransportType | ITransport;
|
||||
logger?: ILogger | LogLevel;
|
||||
accessTokenFactory?: () => string | Promise<string>;
|
||||
logMessageContent?: boolean;
|
||||
skipNegotiation?: boolean;
|
||||
}
|
||||
|
||||
const enum ConnectionState {
|
||||
Connecting,
|
||||
Connected,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HandshakeProtocol, HandshakeRequestMessage, HandshakeResponseMessage } from "./HandshakeProtocol";
|
||||
import { HttpConnection, IHttpConnectionOptions } from "./HttpConnection";
|
||||
import { HttpConnection } from "./HttpConnection";
|
||||
import { IConnection } from "./IConnection";
|
||||
import { CancelInvocationMessage, CompletionMessage, HubMessage, IHubProtocol, InvocationMessage, MessageType, StreamInvocationMessage, StreamItemMessage } from "./IHubProtocol";
|
||||
import { ILogger, LogLevel } from "./ILogger";
|
||||
|
|
@ -12,8 +12,6 @@ import { IStreamResult } from "./Stream";
|
|||
import { TextMessageFormat } from "./TextMessageFormat";
|
||||
import { Arg, createLogger, Subject } from "./Utils";
|
||||
|
||||
export { JsonHubProtocol };
|
||||
|
||||
const DEFAULT_TIMEOUT_IN_MS: number = 30 * 1000;
|
||||
|
||||
export class HubConnection {
|
||||
|
|
@ -30,7 +28,16 @@ export class HubConnection {
|
|||
|
||||
public serverTimeoutInMilliseconds: number;
|
||||
|
||||
constructor(connection: IConnection, logger: ILogger, protocol: IHubProtocol) {
|
||||
/** @internal */
|
||||
// Using a public static factory method means we can have a private constructor and an _internal_
|
||||
// create method that can be used by HubConnectionBuilder. An "internal" constructor would just
|
||||
// be stripped away and the '.d.ts' file would have no constructor, which is interpreted as a
|
||||
// public parameter-less constructor.
|
||||
public static create(connection: IConnection, logger: ILogger, protocol: IHubProtocol): HubConnection {
|
||||
return new HubConnection(connection, logger, protocol);
|
||||
}
|
||||
|
||||
private constructor(connection: IConnection, logger: ILogger, protocol: IHubProtocol) {
|
||||
Arg.isRequired(connection, "connection");
|
||||
Arg.isRequired(logger, "logger");
|
||||
Arg.isRequired(protocol, "protocol");
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HttpConnection, IHttpConnectionOptions } from "./HttpConnection";
|
||||
import { HubConnection, JsonHubProtocol } from "./HubConnection";
|
||||
import { HttpConnection } from "./HttpConnection";
|
||||
import { HubConnection } from "./HubConnection";
|
||||
import { IHttpConnectionOptions } from "./IHttpConnectionOptions";
|
||||
import { IHubProtocol } from "./IHubProtocol";
|
||||
import { ILogger, LogLevel } from "./ILogger";
|
||||
import { HttpTransportType } from "./ITransport";
|
||||
import { JsonHubProtocol } from "./JsonHubProtocol";
|
||||
import { NullLogger } from "./Loggers";
|
||||
import { Arg, ConsoleLogger } from "./Utils";
|
||||
|
||||
|
|
@ -76,7 +78,7 @@ export class HubConnectionBuilder {
|
|||
}
|
||||
const connection = new HttpConnection(this.url, httpConnectionOptions);
|
||||
|
||||
return new HubConnection(
|
||||
return HubConnection.create(
|
||||
connection,
|
||||
this.logger || NullLogger.instance,
|
||||
this.protocol || new JsonHubProtocol());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HttpClient } from "./HttpClient";
|
||||
import { ILogger, LogLevel } from "./ILogger";
|
||||
import { HttpTransportType, ITransport } from "./ITransport";
|
||||
|
||||
export interface IHttpConnectionOptions {
|
||||
httpClient?: HttpClient;
|
||||
transport?: HttpTransportType | ITransport;
|
||||
logger?: ILogger | LogLevel;
|
||||
accessTokenFactory?: () => string | Promise<string>;
|
||||
logMessageContent?: boolean;
|
||||
skipNegotiation?: boolean;
|
||||
}
|
||||
|
|
@ -4,12 +4,12 @@
|
|||
// Everything that users need to access must be exported here. Including interfaces.
|
||||
export * from "./Errors";
|
||||
export * from "./HttpClient";
|
||||
export * from "./HttpConnection";
|
||||
export * from "./IHttpConnectionOptions";
|
||||
export * from "./HubConnection";
|
||||
export * from "./HubConnectionBuilder";
|
||||
export * from "./IConnection";
|
||||
export * from "./IHubProtocol";
|
||||
export * from "./ILogger";
|
||||
export * from "./ITransport";
|
||||
export * from "./Stream";
|
||||
export * from "./Loggers";
|
||||
export * from "./JsonHubProtocol";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
|
@ -26,7 +25,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
|||
public static void Validate()
|
||||
{
|
||||
// The following will throw if T is not a valid type
|
||||
_ = _builder.Value;
|
||||
_ = _builder.Value;
|
||||
}
|
||||
|
||||
private static Func<IClientProxy, T> GenerateClientBuilder()
|
||||
|
|
@ -151,7 +150,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
|||
for (var i = 0; i < paramTypes.Length; i++)
|
||||
{
|
||||
generator.Emit(OpCodes.Ldloc_0); // Object array loaded
|
||||
generator.Emit(OpCodes.Ldc_I4, i);
|
||||
generator.Emit(OpCodes.Ldc_I4, i);
|
||||
generator.Emit(OpCodes.Ldarg, i + 1); // i + 1
|
||||
generator.Emit(OpCodes.Box, paramTypes[i]);
|
||||
generator.Emit(OpCodes.Stelem_Ref);
|
||||
|
|
@ -161,13 +160,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
|||
generator.Emit(OpCodes.Ldloc_0);
|
||||
generator.Emit(OpCodes.Callvirt, invokeMethod);
|
||||
|
||||
if (interfaceMethodInfo.ReturnType == typeof(void))
|
||||
{
|
||||
// void return
|
||||
generator.Emit(OpCodes.Pop);
|
||||
}
|
||||
|
||||
generator.Emit(OpCodes.Ret); // Return
|
||||
generator.Emit(OpCodes.Ret); // Return the Task returned by 'invokeMethod'
|
||||
}
|
||||
|
||||
private static void VerifyInterface(Type interfaceType)
|
||||
|
|
@ -184,7 +177,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
|||
|
||||
if (interfaceType.GetEvents().Length != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Type can not contain events.");
|
||||
throw new InvalidOperationException("Type must not contain events.");
|
||||
}
|
||||
|
||||
foreach (var method in interfaceType.GetMethods())
|
||||
|
|
@ -200,28 +193,26 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
|||
|
||||
private static void VerifyMethod(Type interfaceType, MethodInfo interfaceMethod)
|
||||
{
|
||||
if (interfaceMethod.ReturnType != typeof(void) && interfaceMethod.ReturnType != typeof(Task))
|
||||
if (interfaceMethod.ReturnType != typeof(Task))
|
||||
{
|
||||
throw new InvalidOperationException("Method must return Void or Task.");
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot generate proxy implementation for '{typeof(T).FullName}.{interfaceMethod.Name}'. All client proxy methods must return '{typeof(Task).FullName}'.");
|
||||
}
|
||||
|
||||
foreach (var parameter in interfaceMethod.GetParameters())
|
||||
{
|
||||
VerifyParameter(interfaceType, interfaceMethod, parameter);
|
||||
}
|
||||
}
|
||||
if (parameter.IsOut)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot generate proxy implementation for '{typeof(T).FullName}.{interfaceMethod.Name}'. Client proxy methods must not have 'out' parameters.");
|
||||
}
|
||||
|
||||
private static void VerifyParameter(Type interfaceType, MethodInfo interfaceMethod, ParameterInfo parameter)
|
||||
{
|
||||
if (parameter.IsOut)
|
||||
{
|
||||
throw new InvalidOperationException("Method must not take out parameters.");
|
||||
}
|
||||
|
||||
if (parameter.ParameterType.IsByRef)
|
||||
{
|
||||
throw new InvalidOperationException("Method must not take reference parameters.");
|
||||
if (parameter.ParameterType.IsByRef)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot generate proxy implementation for '{typeof(T).FullName}.{interfaceMethod.Name}'. Client proxy methods must not have 'ref' parameters.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -484,6 +484,16 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
|
||||
public class SimpleVoidReturningTypedHub : Hub<IVoidReturningTypedHubClient>
|
||||
{
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
// Derefernce Clients, to force initialization of the TypedHubClient
|
||||
Clients.All.Send("herp");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleTypedHub : Hub<ITypedHubClient>
|
||||
{
|
||||
public override async Task OnConnectedAsync()
|
||||
|
|
@ -534,6 +544,11 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
Task Send(string message);
|
||||
}
|
||||
|
||||
public interface IVoidReturningTypedHubClient
|
||||
{
|
||||
void Send(string message);
|
||||
}
|
||||
|
||||
public class ConnectionLifetimeHub : Hub
|
||||
{
|
||||
private readonly ConnectionLifetimeState _state;
|
||||
|
|
|
|||
|
|
@ -143,6 +143,15 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
await context.Clients.All.Send("test");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FailsToLoadInvalidTypedHubClient()
|
||||
{
|
||||
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
serviceProvider.GetRequiredService<IHubContext<SimpleVoidReturningTypedHub, IVoidReturningTypedHubClient>>());
|
||||
Assert.Equal($"Cannot generate proxy implementation for '{typeof(IVoidReturningTypedHubClient).FullName}.{nameof(IVoidReturningTypedHubClient.Send)}'. All client proxy methods must return '{typeof(Task).FullName}'.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandshakeFailureFromUnknownProtocolSendsResponseWithError()
|
||||
{
|
||||
|
|
@ -958,6 +967,22 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FailsToInitializeInvalidTypedHub()
|
||||
{
|
||||
var connectionHandler = HubConnectionHandlerTestUtils.GetHubConnectionHandler(typeof(SimpleVoidReturningTypedHub));
|
||||
|
||||
using (var firstClient = new TestClient())
|
||||
{
|
||||
// ConnectAsync returns a Task<Task> and it's the INNER Task that will be faulted.
|
||||
var connectionTask = await firstClient.ConnectAsync(connectionHandler);
|
||||
|
||||
// We should get a close frame now
|
||||
var close = Assert.IsType<CloseMessage>(await firstClient.ReadAsync());
|
||||
Assert.Equal("Connection closed with an error.", close.Error);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(HubTypes))]
|
||||
public async Task SendToAllExcept(Type hubType)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR.Internal;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Tests.Internal
|
||||
{
|
||||
public class TypedClientBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ProducesImplementationThatProxiesMethodsToIClientProxyAsync()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var typedProxy = TypedClientBuilder<ITestClient>.Build(clientProxy);
|
||||
|
||||
var objArg = new object();
|
||||
var task = typedProxy.Method("foo", 42, objArg);
|
||||
Assert.False(task.IsCompleted);
|
||||
|
||||
Assert.Collection(clientProxy.Sends,
|
||||
send =>
|
||||
{
|
||||
Assert.Equal("Method", send.Method);
|
||||
Assert.Equal("foo", send.Arguments[0]);
|
||||
Assert.Equal(42, send.Arguments[1]);
|
||||
Assert.Same(objArg, send.Arguments[2]);
|
||||
send.Complete();
|
||||
});
|
||||
|
||||
await task.OrTimeout();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsSubInterfaces()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var typedProxy = TypedClientBuilder<IInheritedClient>.Build(clientProxy);
|
||||
|
||||
var objArg = new object();
|
||||
var task1 = typedProxy.Method("foo", 42, objArg);
|
||||
Assert.False(task1.IsCompleted);
|
||||
|
||||
var task2 = typedProxy.SubMethod("bar");
|
||||
Assert.False(task2.IsCompleted);
|
||||
|
||||
Assert.Collection(clientProxy.Sends,
|
||||
send1 =>
|
||||
{
|
||||
Assert.Equal("Method", send1.Method);
|
||||
Assert.Collection(send1.Arguments,
|
||||
arg1 => Assert.Equal("foo", arg1),
|
||||
arg2 => Assert.Equal(42, arg2),
|
||||
arg3 => Assert.Same(objArg, arg3));
|
||||
send1.Complete();
|
||||
},
|
||||
send2 =>
|
||||
{
|
||||
Assert.Equal("SubMethod", send2.Method);
|
||||
Assert.Collection(send2.Arguments,
|
||||
arg1 => Assert.Equal("bar", arg1));
|
||||
send2.Complete();
|
||||
});
|
||||
|
||||
await task1.OrTimeout();
|
||||
await task2.OrTimeout();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfProvidedAClass()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<object>.Build(clientProxy));
|
||||
Assert.Equal("Type must be an interface.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfProvidedAStruct()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<ValueTask>.Build(clientProxy));
|
||||
Assert.Equal("Type must be an interface.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfProvidedADelegate()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<EventHandler>.Build(clientProxy));
|
||||
Assert.Equal("Type must be an interface.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfInterfaceHasVoidReturningMethod()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<IVoidMethodClient>.Build(clientProxy));
|
||||
Assert.Equal($"Cannot generate proxy implementation for '{typeof(IVoidMethodClient).FullName}.{nameof(IVoidMethodClient.Method)}'. All client proxy methods must return '{typeof(Task).FullName}'.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfInterfaceHasNonTaskReturns()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<IStringMethodClient>.Build(clientProxy));
|
||||
Assert.Equal($"Cannot generate proxy implementation for '{typeof(IStringMethodClient).FullName}.{nameof(IStringMethodClient.Method)}'. All client proxy methods must return '{typeof(Task).FullName}'.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfInterfaceMethodHasOutParam()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<IOutParamMethodClient>.Build(clientProxy));
|
||||
Assert.Equal(
|
||||
$"Cannot generate proxy implementation for '{typeof(IOutParamMethodClient).FullName}.{nameof(IOutParamMethodClient.Method)}'. Client proxy methods must not have 'out' parameters.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfInterfaceMethodHasRefParam()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<IRefParamMethodClient>.Build(clientProxy));
|
||||
Assert.Equal(
|
||||
$"Cannot generate proxy implementation for '{typeof(IRefParamMethodClient).FullName}.{nameof(IRefParamMethodClient.Method)}'. Client proxy methods must not have 'ref' parameters.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfInterfaceHasProperties()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<IPropertiesClient>.Build(clientProxy));
|
||||
Assert.Equal("Type must not contain properties.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsIfInterfaceHasEvents()
|
||||
{
|
||||
var clientProxy = new MockProxy();
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => TypedClientBuilder<IEventsClient>.Build(clientProxy));
|
||||
Assert.Equal("Type must not contain events.", ex.Message);
|
||||
}
|
||||
|
||||
public interface ITestClient
|
||||
{
|
||||
Task Method(string arg1, int arg2, object arg3);
|
||||
}
|
||||
|
||||
public interface IVoidMethodClient
|
||||
{
|
||||
void Method(string arg1, int arg2, object arg3);
|
||||
}
|
||||
|
||||
public interface IStringMethodClient
|
||||
{
|
||||
string Method(string arg1, int arg2, object arg3);
|
||||
}
|
||||
|
||||
public interface IOutParamMethodClient
|
||||
{
|
||||
Task Method(out string arg1);
|
||||
}
|
||||
|
||||
public interface IRefParamMethodClient
|
||||
{
|
||||
Task Method(ref string arg1);
|
||||
}
|
||||
|
||||
public interface IInheritedClient : ITestClient
|
||||
{
|
||||
Task SubMethod(string foo);
|
||||
}
|
||||
|
||||
public interface IPropertiesClient
|
||||
{
|
||||
string Property { get; }
|
||||
}
|
||||
|
||||
public interface IEventsClient
|
||||
{
|
||||
event EventHandler Event;
|
||||
}
|
||||
|
||||
private class MockProxy : IClientProxy
|
||||
{
|
||||
public IList<SendContext> Sends { get; } = new List<SendContext>();
|
||||
|
||||
public Task SendCoreAsync(string method, object[] args)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
|
||||
Sends.Add(new SendContext(method, args, tcs));
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
private struct SendContext
|
||||
{
|
||||
private TaskCompletionSource<object> _tcs;
|
||||
|
||||
public string Method { get; }
|
||||
public object[] Arguments { get; }
|
||||
|
||||
public SendContext(string method, object[] arguments, TaskCompletionSource<object> tcs) : this()
|
||||
{
|
||||
Method = method;
|
||||
Arguments = arguments;
|
||||
_tcs = tcs;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
_tcs.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue