571 lines
38 KiB
C#
571 lines
38 KiB
C#
// 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.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
|
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
|
using MsgPack;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
|
|
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
|
{
|
|
using static MessagePackHelpers;
|
|
using static HubMessageHelpers;
|
|
|
|
public class MessagePackHubProtocolTests
|
|
{
|
|
private static readonly IDictionary<string, string> TestHeaders = new Dictionary<string, string>
|
|
{
|
|
{ "Foo", "Bar" },
|
|
{ "KeyWith\nNew\r\nLines", "Still Works" },
|
|
{ "ValueWithNewLines", "Also\nWorks\r\nFine" },
|
|
};
|
|
|
|
private static MessagePackObject TestHeadersSerialized = Map(
|
|
("Foo", "Bar"),
|
|
("KeyWith\nNew\r\nLines", "Still Works"),
|
|
("ValueWithNewLines", "Also\nWorks\r\nFine"));
|
|
|
|
private static readonly MessagePackHubProtocol _hubProtocol
|
|
= new MessagePackHubProtocol();
|
|
|
|
private static MessagePackObject CustomObjectSerialized = Map(
|
|
("ByteArrProp", new MessagePackObject(new byte[] { 1, 2, 3 }, isBinary: true)),
|
|
("DateTimeProp", new DateTime(2017, 4, 11).Ticks),
|
|
("DoubleProp", 6.2831853071),
|
|
("IntProp", 42),
|
|
("NullProp", MessagePackObject.Nil),
|
|
("StringProp", "SignalR!"));
|
|
|
|
// Test Data for Parse/WriteMessages:
|
|
// * Name: A string name that is used when reporting the test (it's the ToString value for ProtocolTestData)
|
|
// * Message: The HubMessage that is either expected (in Parse) or used as input (in Write)
|
|
// * Encoded: Raw MessagePackObject values (using the MessagePackHelpers static "Arr" and "Map" helpers) describing the message
|
|
// * Binary: Base64-encoded binary "baseline" to sanity-check MsgPack-Cli behavior
|
|
//
|
|
// The Encoded value is used as input to "Parse" and as the expected output that is verified in "Write". So if our encoding changes,
|
|
// those values will change and the Assert will give you a useful error telling you how the MsgPack structure itself changed (rather than just
|
|
// a bunch of random bytes). However, we want to be sure MsgPack-Cli doesn't change behavior, so we also verify that the binary encoding
|
|
// matches our expectation by comparing against a base64-string.
|
|
//
|
|
// If you change MsgPack encoding, you should update the 'encoded' values for these items, and then re-run the test. You'll get a failure which will
|
|
// provide a new Base64 binary string to replace in the 'binary' value. Use a tool like https://sugendran.github.io/msgpack-visualizer/ to verify
|
|
// that the MsgPack is correct and then just replace the Base64 value.
|
|
public static IEnumerable<object[]> TestData => new[]
|
|
{
|
|
// Invocation messages
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithNoHeadersAndNoArgs",
|
|
message: new InvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), "xyz", "method", Array()),
|
|
binary: "lQGAo3h5eqZtZXRob2SQ"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithNoHeadersNoIdAndNoArgs",
|
|
message: new InvocationMessage(target: "method", argumentBindingException: null),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array()),
|
|
binary: "lQGAwKZtZXRob2SQ"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithNoHeadersNoIdAndSingleNullArg",
|
|
message: new InvocationMessage(target: "method", argumentBindingException: null, new object[] { null }),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(MessagePackObject.Nil)),
|
|
binary: "lQGAwKZtZXRob2SRwA=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithNoHeadersNoIdAndSingleIntArg",
|
|
message: new InvocationMessage(target: "method", argumentBindingException: null, 42),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42)),
|
|
binary: "lQGAwKZtZXRob2SRKg=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithNoHeadersNoIdIntAndStringArgs",
|
|
message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string"),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42, "string")),
|
|
binary: "lQGAwKZtZXRob2SSKqZzdHJpbmc="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithNoHeadersNoIdAndCustomObjectArg",
|
|
message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string", new CustomObject()),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42, "string", CustomObjectSerialized)),
|
|
binary: "lQGAwKZtZXRob2STKqZzdHJpbmeGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithNoHeadersNoIdAndArrayOfCustomObjectArgs",
|
|
message: new InvocationMessage(target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() }),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lQGAwKZtZXRob2SShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNMI1IBtsnbAAKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNMI1IBtsnbAAKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "InvocationWithHeadersNoIdAndArrayOfCustomObjectArgs",
|
|
message: AddHeaders(TestHeaders, new InvocationMessage(target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() })),
|
|
encoded: Array(HubProtocolConstants.InvocationMessageType, TestHeadersSerialized, MessagePackObject.Nil, "method", Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lQGDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmXApm1ldGhvZJKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
|
|
},
|
|
|
|
// StreamItem Messages
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithNoHeadersAndNullItem",
|
|
message: new StreamItemMessage(invocationId: "xyz", item: null),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", MessagePackObject.Nil),
|
|
binary: "lAKAo3h5esA="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithNoHeadersAndIntItem",
|
|
message: new StreamItemMessage(invocationId: "xyz", item: 42),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42),
|
|
binary: "lAKAo3h5eio="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithNoHeadersAndFloatItem",
|
|
message: new StreamItemMessage(invocationId: "xyz", item: 42.0f),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42.0f),
|
|
binary: "lAKAo3h5espCKAAA"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithNoHeadersAndStringItem",
|
|
message: new StreamItemMessage(invocationId: "xyz", item: "string"),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", "string"),
|
|
binary: "lAKAo3h5eqZzdHJpbmc="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithNoHeadersAndBoolItem",
|
|
message: new StreamItemMessage(invocationId: "xyz", item: true),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", true),
|
|
binary: "lAKAo3h5esM="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithNoHeadersAndCustomObjectItem",
|
|
message: new StreamItemMessage(invocationId: "xyz", item: new CustomObject()),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", CustomObjectSerialized),
|
|
binary: "lAKAo3h5eoarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DTCNSAbbJ2wACqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIQ=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithNoHeadersAndCustomObjectArrayItem",
|
|
message: new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() }),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lAKAo3h5epKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamItemWithHeadersAndCustomObjectArrayItem",
|
|
message: AddHeaders(TestHeaders, new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() })),
|
|
encoded: Array(HubProtocolConstants.StreamItemMessageType, TestHeadersSerialized, "xyz", Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lAKDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6koarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DTCNSAbbJ2wACqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIYarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DTCNSAbbJ2wACqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIQ=="),
|
|
},
|
|
|
|
// Completion Messages
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndError",
|
|
message: CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!"),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1, "Error not found!"),
|
|
binary: "lQOAo3h5egGwRXJyb3Igbm90IGZvdW5kIQ=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithHeadersAndError",
|
|
message: AddHeaders(TestHeaders, CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!")),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 1, "Error not found!"),
|
|
binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6AbBFcnJvciBub3QgZm91bmQh"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndNoResult",
|
|
message: CompletionMessage.Empty(invocationId: "xyz"),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 2),
|
|
binary: "lAOAo3h5egI="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithHeadersAndNoResult",
|
|
message: AddHeaders(TestHeaders, CompletionMessage.Empty(invocationId: "xyz")),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 2),
|
|
binary: "lAODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6Ag=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndNullResult",
|
|
message: CompletionMessage.WithResult(invocationId: "xyz", payload: null),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, MessagePackObject.Nil),
|
|
binary: "lQOAo3h5egPA"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndIntResult",
|
|
message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42),
|
|
binary: "lQOAo3h5egMq"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndFloatResult",
|
|
message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42.0f),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42.0f),
|
|
binary: "lQOAo3h5egPKQigAAA=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndStringResult",
|
|
message: CompletionMessage.WithResult(invocationId: "xyz", payload: "string"),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, "string"),
|
|
binary: "lQOAo3h5egOmc3RyaW5n"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndBooleanResult",
|
|
message: CompletionMessage.WithResult(invocationId: "xyz", payload: true),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, true),
|
|
binary: "lQOAo3h5egPD"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndCustomObjectResult",
|
|
message: CompletionMessage.WithResult(invocationId: "xyz", payload: new CustomObject()),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, CustomObjectSerialized),
|
|
binary: "lQOAo3h5egOGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithNoHeadersAndCustomObjectArrayResult",
|
|
message: CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() }),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lQOAo3h5egOShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNMI1IBtsnbAAKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNMI1IBtsnbAAKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CompletionWithHeadersAndCustomObjectArrayResult",
|
|
message: AddHeaders(TestHeaders, CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() })),
|
|
encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 3, Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6A5KGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
|
|
},
|
|
|
|
// StreamInvocation Messages
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamInvocationWithNoHeadersAndNoArgs",
|
|
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null),
|
|
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array()),
|
|
binary: "lQSAo3h5eqZtZXRob2SQ"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamInvocationWithNoHeadersAndNullArg",
|
|
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new object[] { null }),
|
|
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(MessagePackObject.Nil)),
|
|
binary: "lQSAo3h5eqZtZXRob2SRwA=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamInvocationWithNoHeadersAndIntArg",
|
|
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42),
|
|
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42)),
|
|
binary: "lQSAo3h5eqZtZXRob2SRKg=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamInvocationWithNoHeadersAndIntAndStringArgs",
|
|
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42, "string"),
|
|
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42, "string")),
|
|
binary: "lQSAo3h5eqZtZXRob2SSKqZzdHJpbmc="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamInvocationWithNoHeadersAndIntStringAndCustomObjectArgs",
|
|
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42, "string", new CustomObject()),
|
|
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42, "string", CustomObjectSerialized)),
|
|
binary: "lQSAo3h5eqZtZXRob2STKqZzdHJpbmeGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamInvocationWithNoHeadersAndCustomObjectArrayArg",
|
|
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() }),
|
|
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lQSAo3h5eqZtZXRob2SShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNMI1IBtsnbAAKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNMI1IBtsnbAAKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "StreamInvocationWithHeadersAndCustomObjectArrayArg",
|
|
message: AddHeaders(TestHeaders, new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() })),
|
|
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, TestHeadersSerialized, "xyz", "method", Array(CustomObjectSerialized, CustomObjectSerialized)),
|
|
binary: "lQSDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6pm1ldGhvZJKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w0wjUgG2ydsAAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
|
|
},
|
|
|
|
// CancelInvocation Messages
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CancelInvocationWithNoHeaders",
|
|
message: new CancelInvocationMessage(invocationId: "xyz"),
|
|
encoded: Array(HubProtocolConstants.CancelInvocationMessageType, Map(), "xyz"),
|
|
binary: "kwWAo3h5eg=="),
|
|
},
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "CancelInvocationWithHeaders",
|
|
message: AddHeaders(TestHeaders, new CancelInvocationMessage(invocationId: "xyz")),
|
|
encoded: Array(HubProtocolConstants.CancelInvocationMessageType, TestHeadersSerialized, "xyz"),
|
|
binary: "kwWDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6"),
|
|
},
|
|
|
|
// Ping Messages
|
|
new object[] {
|
|
new ProtocolTestData(
|
|
name: "Ping",
|
|
message: PingMessage.Instance,
|
|
encoded: Array(HubProtocolConstants.PingMessageType),
|
|
binary: "kQY="),
|
|
},
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(TestData))]
|
|
public void ParseMessages(ProtocolTestData testData)
|
|
{
|
|
// Verify that the input binary string decodes to the expected MsgPack primitives
|
|
var bytes = Convert.FromBase64String(testData.Binary);
|
|
var obj = Unpack(bytes);
|
|
Assert.Equal(testData.Encoded, obj);
|
|
|
|
// Parse the input fully now.
|
|
bytes = Frame(bytes);
|
|
var protocol = new MessagePackHubProtocol();
|
|
var messages = new List<HubMessage>();
|
|
Assert.True(protocol.TryParseMessages(bytes, new TestBinder(testData.Message), messages));
|
|
|
|
Assert.Single(messages);
|
|
Assert.Equal(testData.Message, messages[0], TestHubMessageEqualityComparer.Instance);
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(TestData))]
|
|
public void WriteMessages(ProtocolTestData testData)
|
|
{
|
|
var bytes = Write(testData.Message);
|
|
AssertMessages(testData.Encoded, bytes);
|
|
|
|
// Unframe the message to check the binary encoding
|
|
var byteSpan = bytes.AsReadOnlySpan();
|
|
Assert.True(BinaryMessageParser.TryParseMessage(ref byteSpan, out var unframed));
|
|
|
|
// Check the baseline binary encoding, use Assert.True in order to configure the error message
|
|
var actual = Convert.ToBase64String(unframed.ToArray());
|
|
Assert.True(string.Equals(actual, testData.Binary, StringComparison.Ordinal), $"Binary encoding changed from{Environment.NewLine} [{testData.Binary}]{Environment.NewLine} to{Environment.NewLine} [{actual}]{Environment.NewLine}Please verify the MsgPack output and update the baseline");
|
|
}
|
|
|
|
public static IEnumerable<object[]> InvalidPayloads => new[]
|
|
{
|
|
// Message Type
|
|
new object[] { new InvalidMessageData("MessageTypeString", Array("foo"), "Reading 'messageType' as Int32 failed.") },
|
|
new object[] { new InvalidMessageData("MessageTypeOutOfRange", Array(10), "Invalid message type: 10.") },
|
|
|
|
// Headers
|
|
new object[] { new InvalidMessageData("HeadersNotAMap", Array(HubProtocolConstants.InvocationMessageType, "foo"), "Reading map length for 'headers' failed.") },
|
|
new object[] { new InvalidMessageData("HeaderKeyInt", Array(HubProtocolConstants.InvocationMessageType, Map((42, "foo"))), "Reading 'headers[0].Key' as String failed.") },
|
|
new object[] { new InvalidMessageData("HeaderValueInt", Array(HubProtocolConstants.InvocationMessageType, Map(("foo", 42))), "Reading 'headers[0].Value' as String failed.") },
|
|
new object[] { new InvalidMessageData("HeaderKeyArray", Array(HubProtocolConstants.InvocationMessageType, Map(("biz", "boz"), (Array(), "foo"))), "Reading 'headers[1].Key' as String failed.") },
|
|
new object[] { new InvalidMessageData("HeaderValueArray", Array(HubProtocolConstants.InvocationMessageType, Map(("biz", "boz"), ("foo", Array()))), "Reading 'headers[1].Value' as String failed.") },
|
|
|
|
// InvocationMessage
|
|
new object[] { new InvalidMessageData("InvocationMissingId", Array(HubProtocolConstants.InvocationMessageType, Map()), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("InvocationIdBoolean", Array(HubProtocolConstants.InvocationMessageType, Map(), false), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("InvocationTargetMissing", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc"), "Reading 'target' as String failed.") },
|
|
new object[] { new InvalidMessageData("InvocationTargetInt", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", 42), "Reading 'target' as String failed.") },
|
|
|
|
// StreamInvocationMessage
|
|
new object[] { new InvalidMessageData("StreamInvocationMissingId", Array(HubProtocolConstants.StreamInvocationMessageType, Map()), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("StreamInvocationIdBoolean", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), false), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("StreamInvocationTargetMissing", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc"), "Reading 'target' as String failed.") },
|
|
new object[] { new InvalidMessageData("StreamInvocationTargetInt", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", 42), "Reading 'target' as String failed.") },
|
|
|
|
// StreamItemMessage
|
|
new object[] { new InvalidMessageData("StreamItemMissingId", Array(HubProtocolConstants.StreamItemMessageType, Map()), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("StreamItemInvocationIdBoolean", Array(HubProtocolConstants.StreamItemMessageType, Map(), false), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("StreamItemMissing", Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz"), "Deserializing object of the `String` type for 'item' failed.") },
|
|
new object[] { new InvalidMessageData("StreamItemTypeMismatch", Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42), "Deserializing object of the `String` type for 'item' failed.") },
|
|
|
|
// CompletionMessage
|
|
new object[] { new InvalidMessageData("CompletionMissingId", Array(HubProtocolConstants.CompletionMessageType, Map()), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("CompletionIdBoolean", Array(HubProtocolConstants.CompletionMessageType, Map(), false), "Reading 'invocationId' as String failed.") },
|
|
new object[] { new InvalidMessageData("CompletionResultKindString", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", "abc"), "Reading 'resultKind' as Int32 failed.") },
|
|
new object[] { new InvalidMessageData("CompletionResultKindOutOfRange", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 42), "Invalid invocation result kind.") },
|
|
new object[] { new InvalidMessageData("CompletionErrorMissing", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1), "Reading 'error' as String failed.") },
|
|
new object[] { new InvalidMessageData("CompletionErrorInt", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1, 42), "Reading 'error' as String failed.") },
|
|
new object[] { new InvalidMessageData("CompletionResultMissing", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3), "Deserializing object of the `String` type for 'argument' failed.") },
|
|
new object[] { new InvalidMessageData("CompletionResultTypeMismatch", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42), "Deserializing object of the `String` type for 'argument' failed.") },
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(InvalidPayloads))]
|
|
public void ParserThrowsForInvalidMessages(InvalidMessageData testData)
|
|
{
|
|
var buffer = Frame(Pack(testData.Encoded));
|
|
var binder = new TestBinder(new[] { typeof(string) }, typeof(string));
|
|
var messages = new List<HubMessage>();
|
|
var exception = Assert.Throws<FormatException>(() => _hubProtocol.TryParseMessages(buffer, binder, messages));
|
|
|
|
Assert.Equal(testData.ErrorMessage, exception.Message);
|
|
}
|
|
|
|
public static IEnumerable<object[]> ArgumentBindingErrors => new[]
|
|
{
|
|
// InvocationMessage
|
|
new object[] {new InvalidMessageData("InvocationArgumentArrayMissing", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz"), "Reading array length for 'arguments' failed.") },
|
|
new object[] {new InvalidMessageData("InvocationArgumentArrayNotAnArray", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", 42), "Reading array length for 'arguments' failed.") },
|
|
new object[] {new InvalidMessageData("InvocationArgumentArraySizeMismatchEmpty", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", Array()), "Invocation provides 0 argument(s) but target expects 1.") },
|
|
new object[] {new InvalidMessageData("InvocationArgumentArraySizeMismatchTooLarge", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", Array("a", "b")), "Invocation provides 2 argument(s) but target expects 1.") },
|
|
new object[] {new InvalidMessageData("InvocationArgumentTypeMismatch", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", Array(42)), "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.") },
|
|
|
|
// StreamInvocationMessage
|
|
new object[] {new InvalidMessageData("StreamInvocationArgumentArrayMissing", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz"), "Reading array length for 'arguments' failed.") }, // array is missing
|
|
new object[] {new InvalidMessageData("StreamInvocationArgumentArrayNotAnArray", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", 42), "Reading array length for 'arguments' failed.") }, // arguments isn't an array
|
|
new object[] {new InvalidMessageData("StreamInvocationArgumentArraySizeMismatchEmpty", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", Array()), "Invocation provides 0 argument(s) but target expects 1.") }, // array is missing elements
|
|
new object[] {new InvalidMessageData("StreamInvocationArgumentArraySizeMismatchTooLarge", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", Array("a", "b")), "Invocation provides 2 argument(s) but target expects 1.") }, // argument count does not match binder argument count
|
|
new object[] {new InvalidMessageData("StreamInvocationArgumentTypeMismatch", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", Array(42)), "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.") }, // argument type mismatch
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(ArgumentBindingErrors))]
|
|
public void GettingArgumentsThrowsIfBindingFailed(InvalidMessageData testData)
|
|
{
|
|
var buffer = Frame(Pack(testData.Encoded));
|
|
var binder = new TestBinder(new[] { typeof(string) }, typeof(string));
|
|
var messages = new List<HubMessage>();
|
|
_hubProtocol.TryParseMessages(buffer, binder, messages);
|
|
var exception = Assert.Throws<FormatException>(() => ((HubMethodInvocationMessage)messages[0]).Arguments);
|
|
|
|
Assert.Equal(testData.ErrorMessage, exception.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(new object[] { new byte[] { 0x05, 0x01 }, 0 })]
|
|
public void ParserDoesNotConsumePartialData(byte[] payload, int expectedMessagesCount)
|
|
{
|
|
var binder = new TestBinder(new[] { typeof(string) }, typeof(string));
|
|
var messages = new List<HubMessage>();
|
|
var result = _hubProtocol.TryParseMessages(payload, binder, messages);
|
|
Assert.True(result || messages.Count == 0);
|
|
Assert.Equal(expectedMessagesCount, messages.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void SerializerCanSerializeTypesWithNoDefaultCtor()
|
|
{
|
|
var result = Write(CompletionMessage.WithResult("0", new List<int> { 42 }.AsReadOnly()));
|
|
AssertMessages(Array(HubProtocolConstants.CompletionMessageType, Map(), "0", 3, Array(42)), result);
|
|
}
|
|
|
|
private static void AssertMessages(MessagePackObject expectedOutput, ReadOnlySpan<byte> bytes)
|
|
{
|
|
Assert.True(BinaryMessageParser.TryParseMessage(ref bytes, out var message));
|
|
var obj = Unpack(message.ToArray());
|
|
Assert.Equal(expectedOutput, obj);
|
|
}
|
|
|
|
private static byte[] Frame(byte[] input)
|
|
{
|
|
using (var stream = new MemoryStream())
|
|
{
|
|
BinaryMessageFormatter.WriteMessage(input, stream);
|
|
stream.Flush();
|
|
return stream.ToArray();
|
|
}
|
|
}
|
|
|
|
private static MessagePackObject Unpack(byte[] input)
|
|
{
|
|
using (var stream = new MemoryStream(input))
|
|
{
|
|
using (var unpacker = Unpacker.Create(stream))
|
|
{
|
|
Assert.True(unpacker.ReadObject(out var obj));
|
|
return obj;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static byte[] Pack(MessagePackObject input)
|
|
{
|
|
var options = new PackingOptions()
|
|
{
|
|
StringEncoding = Encoding.UTF8
|
|
};
|
|
|
|
using (var stream = new MemoryStream())
|
|
{
|
|
using (var packer = Packer.Create(stream))
|
|
{
|
|
input.PackToMessage(packer, options);
|
|
packer.Flush();
|
|
}
|
|
stream.Flush();
|
|
return stream.ToArray();
|
|
}
|
|
}
|
|
|
|
private static byte[] Write(HubMessage message)
|
|
{
|
|
var protocol = new MessagePackHubProtocol();
|
|
using (var stream = new MemoryStream())
|
|
{
|
|
protocol.WriteMessage(message, stream);
|
|
stream.Flush();
|
|
return stream.ToArray();
|
|
}
|
|
}
|
|
|
|
public class InvalidMessageData
|
|
{
|
|
public string Name { get; private set; }
|
|
public MessagePackObject Encoded { get; private set; }
|
|
public string ErrorMessage { get; private set; }
|
|
|
|
public InvalidMessageData(string name, MessagePackObject encoded, string errorMessage)
|
|
{
|
|
Name = name;
|
|
Encoded = encoded;
|
|
ErrorMessage = errorMessage;
|
|
}
|
|
|
|
public override string ToString() => Name;
|
|
}
|
|
|
|
public class ProtocolTestData
|
|
{
|
|
public string Name { get; }
|
|
public string Binary { get; }
|
|
public MessagePackObject Encoded { get; }
|
|
public HubMessage Message { get; }
|
|
|
|
public ProtocolTestData(string name, HubMessage message, MessagePackObject encoded, string binary)
|
|
{
|
|
Name = name;
|
|
Message = message;
|
|
Encoded = encoded;
|
|
Binary = binary;
|
|
}
|
|
|
|
public override string ToString() => Name;
|
|
}
|
|
}
|
|
}
|