summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Test
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2008-11-03 17:22:00 -0800
committerAndrew <andrewarnott@gmail.com>2008-11-04 08:12:52 -0800
commit462e19abd9034c11a12cad30e9899740f2bef8ff (patch)
treee08667f1d69249f8daa6c348a919bd0fd5434415 /src/DotNetOpenAuth.Test
parent6a79be0eca3929d8fb4e797799dac8d6f7875475 (diff)
downloadDotNetOpenAuth-462e19abd9034c11a12cad30e9899740f2bef8ff.zip
DotNetOpenAuth-462e19abd9034c11a12cad30e9899740f2bef8ff.tar.gz
DotNetOpenAuth-462e19abd9034c11a12cad30e9899740f2bef8ff.tar.bz2
Changed namepace and project names in preparation for merge with DotNetOpenId.
Diffstat (limited to 'src/DotNetOpenAuth.Test')
-rw-r--r--src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj117
-rw-r--r--src/DotNetOpenAuth.Test/Logging.config33
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs39
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs290
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs19
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs36
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs130
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs188
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs99
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs97
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs24
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs347
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs101
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs24
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs39
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs106
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs48
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/MockSigningBindingElement.cs48
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/MockTransformationBindingElement.cs55
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs54
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestBaseMessage.cs56
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestChannel.cs35
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestDerivedMessage.cs33
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestDirectedMessage.cs43
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestExpiringMessage.cs35
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestMessage.cs63
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestMessageTypeProvider.cs56
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestReplayProtectedMessage.cs30
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestSignedDirectedMessage.cs33
-rw-r--r--src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs46
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs22
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs293
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs78
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs40
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ConsumerDescription.cs38
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs34
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/ServiceProviderDescriptionTests.cs76
-rw-r--r--src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs29
-rw-r--r--src/DotNetOpenAuth.Test/Scenarios/AppendixScenarios.cs67
-rw-r--r--src/DotNetOpenAuth.Test/Scenarios/CoordinatingOAuthChannel.cs144
-rw-r--r--src/DotNetOpenAuth.Test/Scenarios/Coordinator.cs120
-rw-r--r--src/DotNetOpenAuth.Test/Settings.StyleCop29
-rw-r--r--src/DotNetOpenAuth.Test/Test References/DotNetOpenAuth.accessor1
-rw-r--r--src/DotNetOpenAuth.Test/TestBase.cs45
-rw-r--r--src/DotNetOpenAuth.Test/UriUtilTests.cs21
45 files changed, 3361 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
new file mode 100644
index 0000000..ae8525c
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj
@@ -0,0 +1,117 @@
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>DotNetOpenAuth.Test</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.Test</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\..\bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\..\bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Sign)' == 'true' ">
+ <SignAssembly>true</SignAssembly>
+ <AssemblyOriginatorKeyFile>..\official-build-key.pfx</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\lib\log4net.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+ <Reference Include="System" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="System.Data.DataSetExtensions">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Runtime.Serialization">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Web" />
+ <Reference Include="System.Xml" />
+ <Reference Include="System.Xml.Linq">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Messaging\CollectionAssert.cs" />
+ <Compile Include="Messaging\MessageSerializerTests.cs" />
+ <Compile Include="Messaging\Reflection\MessageDescriptionTests.cs" />
+ <Compile Include="Messaging\Reflection\MessageDictionaryTests.cs" />
+ <Compile Include="Messaging\MessagingTestBase.cs" />
+ <Compile Include="Messaging\MessagingUtilitiesTests.cs" />
+ <Compile Include="Messaging\ChannelTests.cs" />
+ <Compile Include="Messaging\HttpRequestInfoTests.cs" />
+ <Compile Include="Messaging\ProtocolExceptionTests.cs" />
+ <Compile Include="Messaging\Bindings\StandardExpirationBindingElementTests.cs" />
+ <Compile Include="Messaging\Reflection\MessagePartTests.cs" />
+ <Compile Include="Messaging\Reflection\ValueMappingTests.cs" />
+ <Compile Include="Mocks\InMemoryTokenManager.cs" />
+ <Compile Include="Mocks\MockTransformationBindingElement.cs" />
+ <Compile Include="Mocks\MockReplayProtectionBindingElement.cs" />
+ <Compile Include="Mocks\TestBaseMessage.cs" />
+ <Compile Include="Mocks\TestDerivedMessage.cs" />
+ <Compile Include="Mocks\TestReplayProtectedMessage.cs" />
+ <Compile Include="Mocks\TestDirectedMessage.cs" />
+ <Compile Include="Mocks\TestBadChannel.cs" />
+ <Compile Include="Mocks\TestExpiringMessage.cs" />
+ <Compile Include="Mocks\TestSignedDirectedMessage.cs" />
+ <Compile Include="Mocks\MockSigningBindingElement.cs" />
+ <Compile Include="Mocks\TestWebRequestHandler.cs" />
+ <Compile Include="Mocks\TestChannel.cs" />
+ <Compile Include="Mocks\TestMessage.cs" />
+ <Compile Include="Mocks\TestMessageTypeProvider.cs" />
+ <Compile Include="OAuth\ChannelElements\HmacSha1SigningBindingElementTests.cs" />
+ <Compile Include="OAuth\ChannelElements\OAuthChannelTests.cs" />
+ <Compile Include="OAuth\ChannelElements\PlaintextSigningBindingElementTest.cs" />
+ <Compile Include="OAuth\ChannelElements\SigningBindingElementBaseTests.cs" />
+ <Compile Include="OAuth\ConsumerDescription.cs" />
+ <Compile Include="OAuth\ProtocolTests.cs" />
+ <Compile Include="OAuth\ServiceProviderDescriptionTests.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Messaging\ResponseTests.cs" />
+ <Compile Include="Scenarios\AppendixScenarios.cs" />
+ <Compile Include="Scenarios\CoordinatingOAuthChannel.cs" />
+ <Compile Include="Scenarios\Coordinator.cs" />
+ <Compile Include="TestBase.cs" />
+ <Compile Include="UriUtilTests.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\DotNetOpenAuth\DotNetOpenAuth.csproj">
+ <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project>
+ <Name>DotNetOpenAuth</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Logging.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <Shadow Include="Test References\DotNetOpenAuth.accessor" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" />
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Test/Logging.config b/src/DotNetOpenAuth.Test/Logging.config
new file mode 100644
index 0000000..f67c031
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Logging.config
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<log4net>
+ <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
+ <file value="Testing.log" />
+ <appendToFile value="true" />
+ <rollingStyle value="Size" />
+ <maxSizeRollBackups value="10" />
+ <maximumFileSize value="1024KB" />
+ <staticLogFileName value="true" />
+ <layout type="log4net.Layout.PatternLayout">
+ <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
+ </layout>
+ </appender>
+ <appender name="TraceAppender" type="log4net.Appender.TraceAppender">
+ <immediateFlush value="true" />
+ <layout type="log4net.Layout.PatternLayout">
+ <conversionPattern value="%-5level - %message%newline" />
+ </layout>
+ </appender>
+ <!-- Setup the root category, add the appenders and set the default level -->
+ <root>
+ <level value="Info" />
+ <!--<appender-ref ref="RollingFileAppender" />-->
+ <appender-ref ref="TraceAppender" />
+ </root>
+ <!-- Specify the level for some specific categories -->
+ <logger name="DotNetOpenAuth">
+ <level value="Debug" />
+ </logger>
+ <logger name="DotNetOpenAuth.Test">
+ <level value="Debug" />
+ </logger>
+</log4net>
diff --git a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs
new file mode 100644
index 0000000..69f95f9
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------
+// <copyright file="StandardExpirationBindingElementTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging.Bindings {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Test.Mocks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class StandardExpirationBindingElementTests : MessagingTestBase {
+ [TestMethod]
+ public void SendSetsTimestamp() {
+ TestExpiringMessage message = new TestExpiringMessage(MessageTransport.Indirect);
+ message.Recipient = new Uri("http://localtest");
+ ((IExpiringProtocolMessage)message).UtcCreationDate = DateTime.Parse("1/1/1990");
+
+ Channel channel = CreateChannel(MessageProtections.Expiration);
+ channel.Send(message);
+ Assert.IsTrue(DateTime.UtcNow - ((IExpiringProtocolMessage)message).UtcCreationDate < TimeSpan.FromSeconds(3), "The timestamp on the message was not set on send.");
+ }
+
+ [TestMethod]
+ public void VerifyGoodTimestampIsAccepted() {
+ this.Channel = CreateChannel(MessageProtections.Expiration);
+ this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false);
+ }
+
+ [TestMethod, ExpectedException(typeof(ExpiredMessageException))]
+ public void VerifyBadTimestampIsRejected() {
+ this.Channel = CreateChannel(MessageProtections.Expiration);
+ this.ParameterizedReceiveProtectedTest(DateTime.UtcNow - StandardExpirationBindingElement.DefaultMaximumMessageAge - TimeSpan.FromSeconds(1), false);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs b/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs
new file mode 100644
index 0000000..4127b5a
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs
@@ -0,0 +1,290 @@
+//-----------------------------------------------------------------------
+// <copyright file="ChannelTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Test.Mocks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ChannelTests : MessagingTestBase {
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNull() {
+ // This bad channel is deliberately constructed to pass null to
+ // its protected base class' constructor.
+ new TestBadChannel(true);
+ }
+
+ [TestMethod]
+ public void ReadFromRequestQueryString() {
+ this.ParameterizedReceiveTest("GET");
+ }
+
+ [TestMethod]
+ public void ReadFromRequestForm() {
+ this.ParameterizedReceiveTest("POST");
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SendNull() {
+ this.Channel.Send(null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void SendIndirectedUndirectedMessage() {
+ IProtocolMessage message = new TestMessage(MessageTransport.Indirect);
+ this.Channel.Send(message);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void SendDirectedNoRecipientMessage() {
+ IProtocolMessage message = new TestDirectedMessage(MessageTransport.Indirect);
+ this.Channel.Send(message);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void SendInvalidMessageTransport() {
+ IProtocolMessage message = new TestDirectedMessage((MessageTransport)100);
+ this.Channel.Send(message);
+ }
+
+ [TestMethod]
+ public void SendIndirectMessage301Get() {
+ TestDirectedMessage message = new TestDirectedMessage(MessageTransport.Indirect);
+ GetStandardTestMessage(FieldFill.CompleteBeforeBindings, message);
+ message.Recipient = new Uri("http://provider/path");
+ var expected = GetStandardTestFields(FieldFill.CompleteBeforeBindings);
+
+ Response response = this.Channel.Send(message);
+ Assert.AreEqual(HttpStatusCode.Redirect, response.Status);
+ StringAssert.StartsWith(response.Headers[HttpResponseHeader.Location], "http://provider/path");
+ foreach (var pair in expected) {
+ string key = HttpUtility.UrlEncode(pair.Key);
+ string value = HttpUtility.UrlEncode(pair.Value);
+ string substring = string.Format("{0}={1}", key, value);
+ StringAssert.Contains(response.Headers[HttpResponseHeader.Location], substring);
+ }
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SendIndirectMessage301GetNullMessage() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ badChannel.Create301RedirectResponse(null, new Dictionary<string, string>());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void SendIndirectMessage301GetEmptyRecipient() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ var message = new TestDirectedMessage(MessageTransport.Indirect);
+ badChannel.Create301RedirectResponse(message, new Dictionary<string, string>());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SendIndirectMessage301GetNullFields() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ var message = new TestDirectedMessage(MessageTransport.Indirect);
+ message.Recipient = new Uri("http://someserver");
+ badChannel.Create301RedirectResponse(message, null);
+ }
+
+ [TestMethod]
+ public void SendIndirectMessageFormPost() {
+ // We craft a very large message to force fallback to form POST.
+ // We'll also stick some HTML reserved characters in the string value
+ // to test proper character escaping.
+ var message = new TestDirectedMessage(MessageTransport.Indirect) {
+ Age = 15,
+ Name = "c<b" + new string('a', 10 * 1024),
+ Location = new Uri("http://host/path"),
+ Recipient = new Uri("http://provider/path"),
+ };
+ Response response = this.Channel.Send(message);
+ Assert.AreEqual(HttpStatusCode.OK, response.Status, "A form redirect should be an HTTP successful response.");
+ Assert.IsNull(response.Headers[HttpResponseHeader.Location], "There should not be a redirection header in the response.");
+ string body = response.Body;
+ StringAssert.Contains(body, "<form ");
+ StringAssert.Contains(body, "action=\"http://provider/path\"");
+ StringAssert.Contains(body, "method=\"post\"");
+ StringAssert.Contains(body, "<input type=\"hidden\" name=\"age\" value=\"15\" />");
+ StringAssert.Contains(body, "<input type=\"hidden\" name=\"Location\" value=\"http://host/path\" />");
+ StringAssert.Contains(body, "<input type=\"hidden\" name=\"Name\" value=\"" + HttpUtility.HtmlEncode(message.Name) + "\" />");
+ StringAssert.Contains(body, ".submit()", "There should be some javascript to automate form submission.");
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SendIndirectMessageFormPostNullMessage() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ badChannel.CreateFormPostResponse(null, new Dictionary<string, string>());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void SendIndirectMessageFormPostEmptyRecipient() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ var message = new TestDirectedMessage(MessageTransport.Indirect);
+ badChannel.CreateFormPostResponse(message, new Dictionary<string, string>());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SendIndirectMessageFormPostNullFields() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ var message = new TestDirectedMessage(MessageTransport.Indirect);
+ message.Recipient = new Uri("http://someserver");
+ badChannel.CreateFormPostResponse(message, null);
+ }
+
+ /// <summary>
+ /// Tests that a direct message is sent when the appropriate message type is provided.
+ /// </summary>
+ /// <remarks>
+ /// Since this is a mock channel that doesn't actually formulate a direct message response,
+ /// we just check that the right method was called.
+ /// </remarks>
+ [TestMethod, ExpectedException(typeof(NotImplementedException), "SendDirectMessageResponse")]
+ public void SendDirectMessageResponse() {
+ IProtocolMessage message = new TestMessage {
+ Age = 15,
+ Name = "Andrew",
+ Location = new Uri("http://host/path"),
+ };
+ this.Channel.Send(message);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SendIndirectMessageNull() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ badChannel.SendIndirectMessage(null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void ReceiveNull() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ badChannel.Receive(null, null);
+ }
+
+ [TestMethod]
+ public void ReceiveUnrecognizedMessage() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ Assert.IsNull(badChannel.Receive(new Dictionary<string, string>(), null));
+ }
+
+ [TestMethod]
+ public void ReadFromRequestWithContext() {
+ var fields = GetStandardTestFields(FieldFill.AllRequired);
+ TestMessage expectedMessage = GetStandardTestMessage(FieldFill.AllRequired);
+ HttpRequest request = new HttpRequest("somefile", "http://someurl", MessagingUtilities.CreateQueryString(fields));
+ HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
+ IProtocolMessage message = this.Channel.ReadFromRequest();
+ Assert.IsNotNull(message);
+ Assert.IsInstanceOfType(message, typeof(TestMessage));
+ Assert.AreEqual(expectedMessage.Age, ((TestMessage)message).Age);
+ }
+
+ [TestMethod, ExpectedException(typeof(InvalidOperationException))]
+ public void ReadFromRequestNoContext() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ badChannel.ReadFromRequest();
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void ReadFromRequestNull() {
+ TestBadChannel badChannel = new TestBadChannel(false);
+ badChannel.ReadFromRequest(null);
+ }
+
+ [TestMethod]
+ public void SendReplayProtectedMessageSetsNonce() {
+ TestReplayProtectedMessage message = new TestReplayProtectedMessage(MessageTransport.Indirect);
+ message.Recipient = new Uri("http://localtest");
+
+ this.Channel = CreateChannel(MessageProtections.ReplayProtection);
+ this.Channel.Send(message);
+ Assert.IsNotNull(((IReplayProtectedProtocolMessage)message).Nonce);
+ }
+
+ [TestMethod, ExpectedException(typeof(InvalidSignatureException))]
+ public void ReceivedInvalidSignature() {
+ this.Channel = CreateChannel(MessageProtections.TamperProtection);
+ this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, true);
+ }
+
+ [TestMethod]
+ public void ReceivedReplayProtectedMessageJustOnce() {
+ this.Channel = CreateChannel(MessageProtections.ReplayProtection);
+ this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false);
+ }
+
+ [TestMethod, ExpectedException(typeof(ReplayedMessageException))]
+ public void ReceivedReplayProtectedMessageTwice() {
+ this.Channel = CreateChannel(MessageProtections.ReplayProtection);
+ this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false);
+ this.ParameterizedReceiveProtectedTest(DateTime.UtcNow, false);
+ }
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void MessageExpirationWithoutTamperResistance() {
+ new TestChannel(
+ new TestMessageTypeProvider(),
+ new StandardExpirationBindingElement());
+ }
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void TooManyBindingElementsProvidingSameProtection() {
+ new TestChannel(
+ new TestMessageTypeProvider(),
+ new MockSigningBindingElement(),
+ new MockSigningBindingElement());
+ }
+
+ [TestMethod]
+ public void BindingElementsOrdering() {
+ IChannelBindingElement transformA = new MockTransformationBindingElement("a");
+ IChannelBindingElement transformB = new MockTransformationBindingElement("b");
+ IChannelBindingElement sign = new MockSigningBindingElement();
+ IChannelBindingElement replay = new MockReplayProtectionBindingElement();
+ IChannelBindingElement expire = new StandardExpirationBindingElement();
+
+ Channel channel = new TestChannel(
+ new TestMessageTypeProvider(),
+ sign,
+ replay,
+ expire,
+ transformB,
+ transformA);
+
+ Assert.AreEqual(5, channel.BindingElements.Count);
+ Assert.AreSame(transformB, channel.BindingElements[0]);
+ Assert.AreSame(transformA, channel.BindingElements[1]);
+ Assert.AreSame(replay, channel.BindingElements[2]);
+ Assert.AreSame(expire, channel.BindingElements[3]);
+ Assert.AreSame(sign, channel.BindingElements[4]);
+ }
+
+ [TestMethod, ExpectedException(typeof(UnprotectedMessageException))]
+ public void InsufficientlyProtectedMessageSent() {
+ var message = new TestSignedDirectedMessage(MessageTransport.Direct);
+ message.Recipient = new Uri("http://localtest");
+ this.Channel.Send(message);
+ }
+
+ [TestMethod, ExpectedException(typeof(UnprotectedMessageException))]
+ public void InsufficientlyProtectedMessageReceived() {
+ this.Channel = CreateChannel(MessageProtections.None, MessageProtections.TamperProtection);
+ this.ParameterizedReceiveProtectedTest(DateTime.Now, false);
+ }
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void IncomingMessageMissingRequiredParameters() {
+ var fields = GetStandardTestFields(FieldFill.IdentifiableButNotAllRequired);
+ this.Channel.ReadFromRequest(CreateHttpRequestInfo("GET", fields));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs
new file mode 100644
index 0000000..d06ef62
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs
@@ -0,0 +1,19 @@
+//-----------------------------------------------------------------------
+// <copyright file="CollectionAssert.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging {
+ using System.Collections;
+ using System.Collections.Generic;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ internal class CollectionAssert<T> {
+ internal static void AreEquivalent(ICollection<T> expected, ICollection<T> actual) {
+ ICollection expectedNonGeneric = new List<T>(expected);
+ ICollection actualNonGeneric = new List<T>(actual);
+ CollectionAssert.AreEquivalent(expectedNonGeneric, actualNonGeneric);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs
new file mode 100644
index 0000000..5333f97
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------
+// <copyright file="HttpRequestInfoTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging {
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class HttpRequestInfoTests : TestBase {
+ [TestMethod]
+ public void CtorRequest() {
+ HttpRequest request = new HttpRequest("file", "http://someserver?a=b", "a=b");
+ ////request.Headers["headername"] = "headervalue"; // PlatformNotSupportedException prevents us mocking this up
+ HttpRequestInfo info = new HttpRequestInfo(request);
+ Assert.AreEqual(request.Headers["headername"], info.Headers["headername"]);
+ Assert.AreEqual(request.Url.Query, info.Query);
+ Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]);
+ Assert.AreEqual(request.Url, info.Url);
+ Assert.AreEqual(request.HttpMethod, info.HttpMethod);
+ }
+
+ /// <summary>
+ /// Checks that a property dependent on another null property
+ /// doesn't generate a NullReferenceException.
+ /// </summary>
+ [TestMethod]
+ public void QueryBeforeSettingUrl() {
+ HttpRequestInfo info = new HttpRequestInfo();
+ Assert.IsNull(info.Query);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs
new file mode 100644
index 0000000..62b6393
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs
@@ -0,0 +1,130 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageSerializerTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ /// <summary>
+ /// Tests for the <see cref="MessageSerializer"/> class.
+ /// </summary>
+ [TestClass()]
+ public class MessageSerializerTests : MessagingTestBase {
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SerializeNull() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ serializer.Serialize(null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void GetInvalidMessageType() {
+ MessageSerializer.Get(typeof(string));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void GetNullType() {
+ MessageSerializer.Get(null);
+ }
+
+ [TestMethod()]
+ public void SerializeTest() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ var message = GetStandardTestMessage(FieldFill.CompleteBeforeBindings);
+ var expected = GetStandardTestFields(FieldFill.CompleteBeforeBindings);
+ IDictionary<string, string> actual = serializer.Serialize(message);
+ Assert.AreEqual(4, actual.Count);
+
+ // Test case sensitivity of generated dictionary
+ Assert.IsFalse(actual.ContainsKey("Age"));
+ Assert.IsTrue(actual.ContainsKey("age"));
+
+ // Test contents of dictionary
+ Assert.AreEqual(expected["age"], actual["age"]);
+ Assert.AreEqual(expected["Name"], actual["Name"]);
+ Assert.AreEqual(expected["Location"], actual["Location"]);
+ Assert.AreEqual(expected["Timestamp"], actual["Timestamp"]);
+ Assert.IsFalse(actual.ContainsKey("EmptyMember"));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void DeserializeNull() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ serializer.Deserialize(null, null);
+ }
+
+ [TestMethod]
+ public void DeserializeSimple() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ fields["Name"] = "Andrew";
+ fields["age"] = "15";
+ fields["Timestamp"] = "1990-01-01T00:00:00";
+ var actual = (Mocks.TestMessage)serializer.Deserialize(fields, null);
+ Assert.AreEqual(15, actual.Age);
+ Assert.AreEqual("Andrew", actual.Name);
+ Assert.AreEqual(DateTime.Parse("1/1/1990"), actual.Timestamp);
+ Assert.IsNull(actual.EmptyMember);
+ }
+
+ /// <summary>
+ /// This tests deserialization of a message that is comprised of [MessagePart]'s
+ /// that are defined in multiple places in the inheritance tree.
+ /// </summary>
+ /// <remarks>
+ /// The element sorting rules are first inheritance order, then alphabetical order.
+ /// This test validates correct behavior on both.
+ /// </remarks>
+ [TestMethod]
+ public void DeserializeVerifyElementOrdering() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestDerivedMessage));
+ Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ // We deliberately do this OUT of order,
+ // since DataContractSerializer demands elements to be in
+ // 1) inheritance then 2) alphabetical order.
+ // Proper xml element order would be: Name, age, Second..., TheFirst...
+ fields["TheFirstDerivedElement"] = "first";
+ fields["age"] = "15";
+ fields["Name"] = "Andrew";
+ fields["SecondDerivedElement"] = "second";
+ fields["explicit"] = "explicitValue";
+ fields["private"] = "privateValue";
+ var actual = (Mocks.TestDerivedMessage)serializer.Deserialize(fields, null);
+ Assert.AreEqual(15, actual.Age);
+ Assert.AreEqual("Andrew", actual.Name);
+ Assert.AreEqual("first", actual.TheFirstDerivedElement);
+ Assert.AreEqual("second", actual.SecondDerivedElement);
+ Assert.AreEqual("explicitValue", ((Mocks.IBaseMessageExplicitMembers)actual).ExplicitProperty);
+ Assert.AreEqual("privateValue", actual.PrivatePropertyAccessor);
+ }
+
+ [TestMethod]
+ public void DeserializeWithExtraFields() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ fields["age"] = "15";
+ fields["Name"] = "Andrew";
+ fields["Timestamp"] = XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc);
+ // Add some field that is not recognized by the class. This simulates a querystring with
+ // more parameters than are actually interesting to the protocol message.
+ fields["someExtraField"] = "asdf";
+ var actual = (Mocks.TestMessage)serializer.Deserialize(fields, null);
+ Assert.AreEqual(15, actual.Age);
+ Assert.AreEqual("Andrew", actual.Name);
+ Assert.IsNull(actual.EmptyMember);
+ }
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void DeserializeInvalidMessage() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ var fields = GetStandardTestFields(FieldFill.AllRequired);
+ fields["age"] = "-1"; // Set an disallowed value.
+ serializer.Deserialize(fields, null);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs
new file mode 100644
index 0000000..4413c67
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs
@@ -0,0 +1,188 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessagingTestBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Test.Mocks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ /// <summary>
+ /// The base class that all messaging test classes inherit from.
+ /// </summary>
+ public class MessagingTestBase : TestBase {
+ internal enum FieldFill {
+ /// <summary>
+ /// An empty dictionary is returned.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Only enough fields for the <see cref="TestMessageTypeProvider"/>
+ /// to identify the message are included.
+ /// </summary>
+ IdentifiableButNotAllRequired,
+
+ /// <summary>
+ /// All fields marked as required are included.
+ /// </summary>
+ AllRequired,
+
+ /// <summary>
+ /// All user-fillable fields in the message, leaving out those whose
+ /// values are to be set by channel binding elements.
+ /// </summary>
+ CompleteBeforeBindings,
+ }
+
+ internal Channel Channel { get; set; }
+
+ [TestInitialize]
+ public override void SetUp() {
+ base.SetUp();
+
+ this.Channel = new TestChannel();
+ }
+
+ internal static HttpRequestInfo CreateHttpRequestInfo(string method, IDictionary<string, string> fields) {
+ string query = MessagingUtilities.CreateQueryString(fields);
+ UriBuilder requestUri = new UriBuilder("http://localhost/path");
+ WebHeaderCollection headers = new WebHeaderCollection();
+ MemoryStream ms = new MemoryStream();
+ if (method == "POST") {
+ headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
+ StreamWriter sw = new StreamWriter(ms);
+ sw.Write(query);
+ sw.Flush();
+ ms.Position = 0;
+ } else if (method == "GET") {
+ requestUri.Query = query;
+ } else {
+ throw new ArgumentOutOfRangeException("method", method, "Expected POST or GET");
+ }
+ HttpRequestInfo request = new HttpRequestInfo {
+ HttpMethod = method,
+ Url = requestUri.Uri,
+ Headers = headers,
+ InputStream = ms,
+ };
+
+ return request;
+ }
+
+ internal static Channel CreateChannel(MessageProtections capabilityAndRecognition) {
+ return CreateChannel(capabilityAndRecognition, capabilityAndRecognition);
+ }
+
+ internal static Channel CreateChannel(MessageProtections capability, MessageProtections recognition) {
+ var bindingElements = new List<IChannelBindingElement>();
+ if (capability >= MessageProtections.TamperProtection) {
+ bindingElements.Add(new MockSigningBindingElement());
+ }
+ if (capability >= MessageProtections.Expiration) {
+ bindingElements.Add(new StandardExpirationBindingElement());
+ }
+ if (capability >= MessageProtections.ReplayProtection) {
+ bindingElements.Add(new MockReplayProtectionBindingElement());
+ }
+
+ bool signing = false, expiration = false, replay = false;
+ if (recognition >= MessageProtections.TamperProtection) {
+ signing = true;
+ }
+ if (recognition >= MessageProtections.Expiration) {
+ expiration = true;
+ }
+ if (recognition >= MessageProtections.ReplayProtection) {
+ replay = true;
+ }
+
+ var typeProvider = new TestMessageTypeProvider(signing, expiration, replay);
+ return new TestChannel(typeProvider, bindingElements.ToArray());
+ }
+
+ internal static IDictionary<string, string> GetStandardTestFields(FieldFill fill) {
+ TestMessage expectedMessage = GetStandardTestMessage(fill);
+
+ var fields = new Dictionary<string, string>();
+ if (fill >= FieldFill.IdentifiableButNotAllRequired) {
+ fields.Add("age", expectedMessage.Age.ToString());
+ }
+ if (fill >= FieldFill.AllRequired) {
+ fields.Add("Timestamp", XmlConvert.ToString(expectedMessage.Timestamp, XmlDateTimeSerializationMode.Utc));
+ }
+ if (fill >= FieldFill.CompleteBeforeBindings) {
+ fields.Add("Name", expectedMessage.Name);
+ fields.Add("Location", expectedMessage.Location.AbsoluteUri);
+ }
+
+ return fields;
+ }
+
+ internal static TestMessage GetStandardTestMessage(FieldFill fill) {
+ TestMessage message = new TestMessage();
+ GetStandardTestMessage(fill, message);
+ return message;
+ }
+
+ internal static void GetStandardTestMessage(FieldFill fill, TestMessage message) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ if (fill >= FieldFill.IdentifiableButNotAllRequired) {
+ message.Age = 15;
+ }
+ if (fill >= FieldFill.AllRequired) {
+ message.Timestamp = DateTime.SpecifyKind(DateTime.Parse("9/19/2008 8 AM"), DateTimeKind.Utc);
+ }
+ if (fill >= FieldFill.CompleteBeforeBindings) {
+ message.Name = "Andrew";
+ message.Location = new Uri("http://localtest/path");
+ }
+ }
+
+ internal void ParameterizedReceiveTest(string method) {
+ var fields = GetStandardTestFields(FieldFill.CompleteBeforeBindings);
+ TestMessage expectedMessage = GetStandardTestMessage(FieldFill.CompleteBeforeBindings);
+
+ IProtocolMessage requestMessage = this.Channel.ReadFromRequest(CreateHttpRequestInfo(method, fields));
+ Assert.IsNotNull(requestMessage);
+ Assert.IsInstanceOfType(requestMessage, typeof(TestMessage));
+ TestMessage actualMessage = (TestMessage)requestMessage;
+ Assert.AreEqual(expectedMessage.Age, actualMessage.Age);
+ Assert.AreEqual(expectedMessage.Name, actualMessage.Name);
+ Assert.AreEqual(expectedMessage.Location, actualMessage.Location);
+ }
+
+ internal void ParameterizedReceiveProtectedTest(DateTime? utcCreatedDate, bool invalidSignature) {
+ TestMessage expectedMessage = GetStandardTestMessage(FieldFill.CompleteBeforeBindings);
+ var fields = GetStandardTestFields(FieldFill.CompleteBeforeBindings);
+ fields.Add("Signature", invalidSignature ? "badsig" : MockSigningBindingElement.MessageSignature);
+ fields.Add("Nonce", "someNonce");
+ if (utcCreatedDate.HasValue) {
+ utcCreatedDate = DateTime.Parse(utcCreatedDate.Value.ToUniversalTime().ToString()); // round off the milliseconds so comparisons work later
+ fields.Add("created_on", XmlConvert.ToString(utcCreatedDate.Value, XmlDateTimeSerializationMode.Utc));
+ }
+ IProtocolMessage requestMessage = this.Channel.ReadFromRequest(CreateHttpRequestInfo("GET", fields));
+ Assert.IsNotNull(requestMessage);
+ Assert.IsInstanceOfType(requestMessage, typeof(TestSignedDirectedMessage));
+ TestSignedDirectedMessage actualMessage = (TestSignedDirectedMessage)requestMessage;
+ Assert.AreEqual(expectedMessage.Age, actualMessage.Age);
+ Assert.AreEqual(expectedMessage.Name, actualMessage.Name);
+ Assert.AreEqual(expectedMessage.Location, actualMessage.Location);
+ if (utcCreatedDate.HasValue) {
+ IExpiringProtocolMessage expiringMessage = (IExpiringProtocolMessage)requestMessage;
+ Assert.AreEqual(utcCreatedDate.Value, expiringMessage.UtcCreationDate);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs
new file mode 100644
index 0000000..fc60ef6
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/MessagingUtilitiesTests.cs
@@ -0,0 +1,99 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessagingUtilitiesTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.IO;
+ using System.Net;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MessagingUtilitiesTests : TestBase {
+ [TestMethod]
+ public void CreateQueryString() {
+ var args = new Dictionary<string, string>();
+ args.Add("a", "b");
+ args.Add("c/d", "e/f");
+ Assert.AreEqual("a=b&c%2fd=e%2ff", MessagingUtilities.CreateQueryString(args));
+ }
+
+ [TestMethod]
+ public void CreateQueryStringEmptyCollection() {
+ Assert.AreEqual(0, MessagingUtilities.CreateQueryString(new Dictionary<string, string>()).Length);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CreateQueryStringNullDictionary() {
+ MessagingUtilities.CreateQueryString(null);
+ }
+
+ [TestMethod]
+ public void AppendQueryArgs() {
+ UriBuilder uri = new UriBuilder("http://baseline.org/page");
+ var args = new Dictionary<string, string>();
+ args.Add("a", "b");
+ args.Add("c/d", "e/f");
+ MessagingUtilities.AppendQueryArgs(uri, args);
+ Assert.AreEqual("http://baseline.org/page?a=b&c%2fd=e%2ff", uri.Uri.AbsoluteUri);
+ args.Clear();
+ args.Add("g", "h");
+ MessagingUtilities.AppendQueryArgs(uri, args);
+ Assert.AreEqual("http://baseline.org/page?a=b&c%2fd=e%2ff&g=h", uri.Uri.AbsoluteUri);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void AppendQueryArgsNullUriBuilder() {
+ MessagingUtilities.AppendQueryArgs(null, new Dictionary<string, string>());
+ }
+
+ [TestMethod]
+ public void AppendQueryArgsNullDictionary() {
+ MessagingUtilities.AppendQueryArgs(new UriBuilder(), null);
+ }
+
+ [TestMethod]
+ public void ToDictionary() {
+ NameValueCollection nvc = new NameValueCollection();
+ nvc["a"] = "b";
+ nvc["c"] = "d";
+ Dictionary<string, string> actual = MessagingUtilities.ToDictionary(nvc);
+ Assert.AreEqual(nvc.Count, actual.Count);
+ Assert.AreEqual(nvc["a"], actual["a"]);
+ Assert.AreEqual(nvc["c"], actual["c"]);
+ }
+
+ [TestMethod]
+ public void ToDictionaryNull() {
+ Assert.IsNull(MessagingUtilities.ToDictionary(null));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void ApplyHeadersToResponseNullResponse() {
+ MessagingUtilities.ApplyHeadersToResponse(new WebHeaderCollection(), null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void ApplyHeadersToResponseNullHeaders() {
+ MessagingUtilities.ApplyHeadersToResponse(null, new HttpResponse(new StringWriter()));
+ }
+
+ [TestMethod]
+ public void ApplyHeadersToResponse() {
+ var headers = new WebHeaderCollection();
+ headers[HttpResponseHeader.ContentType] = "application/binary";
+
+ var response = new HttpResponse(new StringWriter());
+ MessagingUtilities.ApplyHeadersToResponse(headers, response);
+
+ Assert.AreEqual(headers[HttpResponseHeader.ContentType], response.ContentType);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs b/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs
new file mode 100644
index 0000000..a2e3eaa
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/ProtocolExceptionTests.cs
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------
+// <copyright file="ProtocolExceptionTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ProtocolExceptionTests : TestBase {
+ [TestMethod]
+ public void CtorDefault() {
+ ProtocolException ex = new ProtocolException();
+ }
+
+ [TestMethod]
+ public void CtorWithTextMessage() {
+ ProtocolException ex = new ProtocolException("message");
+ Assert.AreEqual("message", ex.Message);
+ }
+
+ [TestMethod]
+ public void CtorWithTextMessageAndInnerException() {
+ Exception innerException = new Exception();
+ ProtocolException ex = new ProtocolException("message", innerException);
+ Assert.AreEqual("message", ex.Message);
+ Assert.AreSame(innerException, ex.InnerException);
+ }
+
+ [TestMethod]
+ public void CtorWithProtocolMessage() {
+ IProtocolMessage request = new Mocks.TestMessage();
+ Uri receiver = new Uri("http://receiver");
+ ProtocolException ex = new ProtocolException("some error occurred", request, receiver);
+ IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex;
+ Assert.AreEqual("some error occurred", ex.Message);
+ Assert.AreSame(receiver, msg.Recipient);
+ Assert.AreEqual(request.ProtocolVersion, msg.ProtocolVersion);
+ Assert.AreEqual(request.Transport, msg.Transport);
+ msg.EnsureValidMessage();
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorWithNullProtocolMessage() {
+ new ProtocolException("message", null, new Uri("http://receiver"));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorWithNullReceiver() {
+ new ProtocolException("message", new Mocks.TestDirectedMessage(MessageTransport.Indirect), null);
+ }
+
+ /// <summary>
+ /// Tests that exceptions being sent as direct responses do not need an explicit receiver.
+ /// </summary>
+ [TestMethod]
+ public void CtorUndirectedMessageWithNullReceiver() {
+ IProtocolMessage request = new Mocks.TestDirectedMessage(MessageTransport.Direct);
+ ProtocolException ex = new ProtocolException("message", request, null);
+ IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex;
+ Assert.IsNull(msg.Recipient);
+ Assert.AreEqual(request.ProtocolVersion, msg.ProtocolVersion);
+ Assert.AreEqual(request.Transport, msg.Transport);
+ }
+
+ [TestMethod, ExpectedException(typeof(InvalidOperationException))]
+ public void ProtocolVersionWithoutMessage() {
+ ProtocolException ex = new ProtocolException();
+ IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex;
+ var temp = msg.ProtocolVersion;
+ }
+
+ [TestMethod, ExpectedException(typeof(InvalidOperationException))]
+ public void TransportWithoutMessage() {
+ ProtocolException ex = new ProtocolException();
+ IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex;
+ var temp = msg.Transport;
+ }
+
+ [TestMethod, ExpectedException(typeof(InvalidOperationException))]
+ public void RecipientWithoutMessage() {
+ ProtocolException ex = new ProtocolException();
+ IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex;
+ var temp = msg.Recipient;
+ }
+
+ [TestMethod, ExpectedException(typeof(InvalidOperationException))]
+ public void EnsureValidMessageWithoutMessage() {
+ ProtocolException ex = new ProtocolException();
+ IDirectedProtocolMessage msg = (IDirectedProtocolMessage)ex;
+ msg.EnsureValidMessage();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs
new file mode 100644
index 0000000..da5275b
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDescriptionTests.cs
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageDescriptionTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging.Reflection {
+ using System;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MessageDescriptionTests : MessagingTestBase {
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void GetNull() {
+ MessageDescription.Get(null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void GetNonMessageType() {
+ MessageDescription.Get(typeof(string));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs
new file mode 100644
index 0000000..039743e
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs
@@ -0,0 +1,347 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageDictionaryTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging.Reflection {
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MessageDictionaryTests : MessagingTestBase {
+ private Mocks.TestMessage message;
+
+ [TestInitialize]
+ public override void SetUp() {
+ base.SetUp();
+
+ this.message = new Mocks.TestMessage();
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNull() {
+ new MessageDictionary(null);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IDictionary&lt;System.String,System.String>.Values
+ /// </summary>
+ [TestMethod]
+ public void Values() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ Collection<string> expected = new Collection<string> {
+ this.message.Age.ToString(),
+ XmlConvert.ToString(DateTime.SpecifyKind(this.message.Timestamp, DateTimeKind.Utc), XmlDateTimeSerializationMode.Utc),
+ };
+ CollectionAssert<string>.AreEquivalent(expected, target.Values);
+
+ this.message.Age = 15;
+ this.message.Location = new Uri("http://localtest");
+ this.message.Name = "Andrew";
+ target["extra"] = "a";
+ expected = new Collection<string> {
+ this.message.Age.ToString(),
+ this.message.Location.AbsoluteUri,
+ this.message.Name,
+ XmlConvert.ToString(DateTime.SpecifyKind(this.message.Timestamp, DateTimeKind.Utc), XmlDateTimeSerializationMode.Utc),
+ "a",
+ };
+ CollectionAssert<string>.AreEquivalent(expected, target.Values);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IDictionary&lt;System.String,System.String>.Keys
+ /// </summary>
+ [TestMethod]
+ public void Keys() {
+ // We expect that non-nullable value type fields will automatically have keys
+ // in the dictionary for them.
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ Collection<string> expected = new Collection<string> {
+ "age",
+ "Timestamp",
+ };
+ CollectionAssert<string>.AreEquivalent(expected, target.Keys);
+
+ this.message.Name = "Andrew";
+ expected.Add("Name");
+ target["extraField"] = string.Empty;
+ expected.Add("extraField");
+ CollectionAssert<string>.AreEquivalent(expected, target.Keys);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IDictionary&lt;System.String,System.String>.Item
+ /// </summary>
+ [TestMethod]
+ public void Item() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+
+ // Test setting of declared message properties.
+ this.message.Age = 15;
+ Assert.AreEqual("15", target["age"]);
+ target["age"] = "13";
+ Assert.AreEqual(13, this.message.Age);
+
+ // Test setting extra fields
+ target["extra"] = "fun";
+ Assert.AreEqual("fun", target["extra"]);
+ Assert.AreEqual("fun", ((IProtocolMessage)this.message).ExtraData["extra"]);
+
+ // Test clearing extra fields
+ target["extra"] = null;
+ Assert.IsFalse(target.ContainsKey("extra"));
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.ICollection&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.IsReadOnly
+ /// </summary>
+ [TestMethod]
+ public void IsReadOnly() {
+ ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message);
+ Assert.IsFalse(target.IsReadOnly);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.ICollection&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.Count
+ /// </summary>
+ [TestMethod]
+ public void Count() {
+ ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message);
+ IDictionary<string, string> targetDictionary = (IDictionary<string, string>)target;
+ Assert.AreEqual(targetDictionary.Keys.Count, target.Count);
+ targetDictionary["extraField"] = "hi";
+ Assert.AreEqual(targetDictionary.Keys.Count, target.Count);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IEnumerable&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.GetEnumerator
+ /// </summary>
+ [TestMethod]
+ public void GetEnumerator() {
+ IEnumerable<KeyValuePair<string, string>> target = new MessageDictionary(this.message);
+ IDictionary<string, string> targetDictionary = (IDictionary<string, string>)target;
+ var keys = targetDictionary.Keys.GetEnumerator();
+ var values = targetDictionary.Values.GetEnumerator();
+ IEnumerator<KeyValuePair<string, string>> actual = target.GetEnumerator();
+
+ bool keysLast = true, valuesLast = true, actualLast = true;
+ while (true) {
+ keysLast = keys.MoveNext();
+ valuesLast = values.MoveNext();
+ actualLast = actual.MoveNext();
+ if (!keysLast || !valuesLast || !actualLast) {
+ break;
+ }
+
+ Assert.AreEqual(keys.Current, actual.Current.Key);
+ Assert.AreEqual(values.Current, actual.Current.Value);
+ }
+ Assert.IsTrue(keysLast == valuesLast && keysLast == actualLast);
+ }
+
+ [TestMethod]
+ public void GetEnumeratorUntyped() {
+ IEnumerable target = new MessageDictionary(this.message);
+ IDictionary<string, string> targetDictionary = (IDictionary<string, string>)target;
+ var keys = targetDictionary.Keys.GetEnumerator();
+ var values = targetDictionary.Values.GetEnumerator();
+ IEnumerator actual = target.GetEnumerator();
+
+ bool keysLast = true, valuesLast = true, actualLast = true;
+ while (true) {
+ keysLast = keys.MoveNext();
+ valuesLast = values.MoveNext();
+ actualLast = actual.MoveNext();
+ if (!keysLast || !valuesLast || !actualLast) {
+ break;
+ }
+
+ KeyValuePair<string, string> current = (KeyValuePair<string, string>)actual.Current;
+ Assert.AreEqual(keys.Current, current.Key);
+ Assert.AreEqual(values.Current, current.Value);
+ }
+ Assert.IsTrue(keysLast == valuesLast && keysLast == actualLast);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IDictionary&lt;System.String,System.String>.TryGetValue
+ /// </summary>
+ [TestMethod]
+ public void TryGetValue() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ this.message.Name = "andrew";
+ string name;
+ Assert.IsTrue(target.TryGetValue("Name", out name));
+ Assert.AreEqual(this.message.Name, name);
+
+ Assert.IsFalse(target.TryGetValue("name", out name));
+ Assert.IsNull(name);
+
+ target["extra"] = "value";
+ string extra;
+ Assert.IsTrue(target.TryGetValue("extra", out extra));
+ Assert.AreEqual("value", extra);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IDictionary&lt;System.String,System.String>.Remove
+ /// </summary>
+ [TestMethod]
+ public void RemoveTest1() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ this.message.Name = "andrew";
+ Assert.IsTrue(target.Remove("Name"));
+ Assert.IsNull(this.message.Name);
+ Assert.IsFalse(target.Remove("Name"));
+
+ Assert.IsFalse(target.Remove("extra"));
+ target["extra"] = "value";
+ Assert.IsTrue(target.Remove("extra"));
+ Assert.IsFalse(target.ContainsKey("extra"));
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IDictionary&lt;System.String,System.String>.ContainsKey
+ /// </summary>
+ [TestMethod]
+ public void ContainsKey() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ Assert.IsTrue(target.ContainsKey("age"), "Value type declared element should have a key.");
+ Assert.IsFalse(target.ContainsKey("Name"), "Null declared element should NOT have a key.");
+
+ Assert.IsFalse(target.ContainsKey("extra"));
+ target["extra"] = "value";
+ Assert.IsTrue(target.ContainsKey("extra"));
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.IDictionary&lt;System.String,System.String&gt;.Add
+ /// </summary>
+ [TestMethod]
+ public void AddByKeyAndValue() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ target.Add("extra", "value");
+ Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("extra", "value")));
+ target.Add("Name", "Andrew");
+ Assert.AreEqual("Andrew", this.message.Name);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void AddNullValue() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ target.Add("extra", null);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.ICollection&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.Add
+ /// </summary>
+ [TestMethod]
+ public void AddByKeyValuePair() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ target.Add(new KeyValuePair<string, string>("extra", "value"));
+ Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("extra", "value")));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void AddExtraFieldThatAlreadyExists() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ target.Add("extra", "value");
+ target.Add("extra", "value");
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void AddDeclaredValueThatAlreadyExists() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ target.Add("Name", "andrew");
+ target.Add("Name", "andrew");
+ }
+
+ [TestMethod]
+ public void DefaultReferenceTypeDeclaredPropertyHasNoKey() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ Assert.IsFalse(target.ContainsKey("Name"), "A null value should result in no key.");
+ Assert.IsFalse(target.Keys.Contains("Name"), "A null value should result in no key.");
+ }
+
+ [TestMethod]
+ public void RemoveStructDeclaredProperty() {
+ IDictionary<string, string> target = new MessageDictionary(this.message);
+ this.message.Age = 5;
+ Assert.IsTrue(target.ContainsKey("age"));
+ target.Remove("age");
+ Assert.IsTrue(target.ContainsKey("age"));
+ Assert.AreEqual(0, this.message.Age);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.ICollection&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.Remove
+ /// </summary>
+ [TestMethod]
+ public void RemoveByKeyValuePair() {
+ ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message);
+ this.message.Name = "Andrew";
+ Assert.IsFalse(target.Remove(new KeyValuePair<string, string>("Name", "andrew")));
+ Assert.AreEqual("Andrew", this.message.Name);
+ Assert.IsTrue(target.Remove(new KeyValuePair<string, string>("Name", "Andrew")));
+ Assert.IsNull(this.message.Name);
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.ICollection&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.CopyTo
+ /// </summary>
+ [TestMethod]
+ public void CopyTo() {
+ ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message);
+ IDictionary<string, string> targetAsDictionary = ((IDictionary<string, string>)target);
+ KeyValuePair<string, string>[] array = new KeyValuePair<string, string>[target.Count + 1];
+ int arrayIndex = 1;
+ target.CopyTo(array, arrayIndex);
+ Assert.AreEqual(new KeyValuePair<string, string>(), array[0]);
+ for (int i = 1; i < array.Length; i++) {
+ Assert.IsNotNull(array[i].Key);
+ Assert.AreEqual(targetAsDictionary[array[i].Key], array[i].Value);
+ }
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.ICollection&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.Contains
+ /// </summary>
+ [TestMethod]
+ public void ContainsKeyValuePair() {
+ ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message);
+ IDictionary<string, string> targetAsDictionary = ((IDictionary<string, string>)target);
+ Assert.IsFalse(target.Contains(new KeyValuePair<string, string>("age", "1")));
+ Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("age", "0")));
+
+ targetAsDictionary["extra"] = "value";
+ Assert.IsFalse(target.Contains(new KeyValuePair<string, string>("extra", "Value")));
+ Assert.IsTrue(target.Contains(new KeyValuePair<string, string>("extra", "value")));
+ Assert.IsFalse(target.Contains(new KeyValuePair<string, string>("wayoff", "value")));
+ }
+
+ /// <summary>
+ /// A test for System.Collections.Generic.ICollection&lt;System.Collections.Generic.KeyValuePair&lt;System.String,System.String&lt;&lt;.Clear
+ /// </summary>
+ [TestMethod]
+ public void Clear() {
+ ICollection<KeyValuePair<string, string>> target = new MessageDictionary(this.message);
+ IDictionary<string, string> targetAsDictionary = ((IDictionary<string, string>)target);
+ this.message.Name = "Andrew";
+ this.message.Age = 15;
+ targetAsDictionary["extra"] = "value";
+ target.Clear();
+ Assert.AreEqual(2, target.Count, "Clearing should remove all keys except for declared non-nullable structs.");
+ Assert.IsFalse(targetAsDictionary.ContainsKey("extra"));
+ Assert.IsNull(this.message.Name);
+ Assert.AreEqual(0, this.message.Age);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs
new file mode 100644
index 0000000..c3c69c2
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs
@@ -0,0 +1,101 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessagePartTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging.Reflection {
+ using System;
+ using System.Linq;
+ using System.Reflection;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.Test.Mocks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MessagePartTests : MessagingTestBase {
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void OptionalNonNullableStruct() {
+ this.ParameterizedMessageTypeTest(typeof(MessageWithNonNullableOptionalStruct));
+ }
+
+ [TestMethod]
+ public void RequiredNonNullableStruct() {
+ this.ParameterizedMessageTypeTest(typeof(MessageWithNonNullableRequiredStruct));
+ }
+
+ [TestMethod]
+ public void OptionalNullableStruct() {
+ this.ParameterizedMessageTypeTest(typeof(MessageWithNullableOptionalStruct));
+ }
+
+ [TestMethod]
+ public void RequiredNullableStruct() {
+ this.ParameterizedMessageTypeTest(typeof(MessageWithNullableRequiredStruct));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullMember() {
+ new MessagePart(null, new MessagePartAttribute());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullAttribute() {
+ PropertyInfo field = typeof(MessageWithNullableOptionalStruct).GetProperty("OptionalInt", BindingFlags.NonPublic | BindingFlags.Instance);
+ new MessagePart(field, null);
+ }
+
+ [TestMethod]
+ public void SetValue() {
+ var message = new MessageWithNonNullableRequiredStruct();
+ MessagePart part = this.ParameterizedMessageTypeTest(message.GetType());
+ part.SetValue(message, "5");
+ Assert.AreEqual(5, message.OptionalInt);
+ }
+
+ [TestMethod]
+ public void GetValue() {
+ var message = new MessageWithNonNullableRequiredStruct();
+ message.OptionalInt = 8;
+ MessagePart part = this.ParameterizedMessageTypeTest(message.GetType());
+ Assert.AreEqual("8", part.GetValue(message));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void NonFieldOrPropertyMember() {
+ MemberInfo method = typeof(MessageWithNullableOptionalStruct).GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance);
+ new MessagePart(method, new MessagePartAttribute());
+ }
+
+ private MessagePart ParameterizedMessageTypeTest(Type messageType) {
+ PropertyInfo field = messageType.GetProperty("OptionalInt", BindingFlags.NonPublic | BindingFlags.Instance);
+ MessagePartAttribute attribute = field.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>().Single();
+ return new MessagePart(field, attribute);
+ }
+
+ private class MessageWithNonNullableOptionalStruct : TestMessage {
+ // Optional structs like int must be nullable for Optional to make sense.
+ [MessagePart(IsRequired = false)]
+ internal int OptionalInt { get; set; }
+ }
+
+ private class MessageWithNonNullableRequiredStruct : TestMessage {
+ // This should work because a required field will always have a value so it
+ // need not be nullable.
+ [MessagePart(IsRequired = true)]
+ internal int OptionalInt { get; set; }
+ }
+
+ private class MessageWithNullableOptionalStruct : TestMessage {
+ // Optional structs like int must be nullable for Optional to make sense.
+ [MessagePart(IsRequired = false)]
+ internal int? OptionalInt { get; set; }
+ }
+
+ private class MessageWithNullableRequiredStruct : TestMessage {
+ [MessagePart(IsRequired = true)]
+ private int? OptionalInt { get; set; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs
new file mode 100644
index 0000000..c4a79b5
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/ValueMappingTests.cs
@@ -0,0 +1,24 @@
+//-----------------------------------------------------------------------
+// <copyright file="ValueMappingTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging.Reflection {
+ using System;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ValueMappingTests {
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullToString() {
+ new ValueMapping(null, str => new object());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullToObject() {
+ new ValueMapping(obj => obj.ToString(), null);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs b/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs
new file mode 100644
index 0000000..972bb58
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------
+// <copyright file="ResponseTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Messaging {
+ using System;
+ using System.IO;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ResponseTests : TestBase {
+ [TestMethod, ExpectedException(typeof(InvalidOperationException))]
+ public void SendWithoutAspNetContext() {
+ new Response().Send();
+ }
+
+ [TestMethod]
+ public void Send() {
+ StringWriter writer = new StringWriter();
+ HttpRequest httpRequest = new HttpRequest("file", "http://server", string.Empty);
+ HttpResponse httpResponse = new HttpResponse(writer);
+ HttpContext context = new HttpContext(httpRequest, httpResponse);
+ HttpContext.Current = context;
+
+ Response response = new Response();
+ response.Status = System.Net.HttpStatusCode.OK;
+ response.Headers["someHeaderName"] = "someHeaderValue";
+ response.Body = "some body";
+ response.Send();
+ string results = writer.ToString();
+ // For some reason the only output in test is the body... the headers require a web host
+ Assert.AreEqual(response.Body, results);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs
new file mode 100644
index 0000000..571bba7
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs
@@ -0,0 +1,106 @@
+//-----------------------------------------------------------------------
+// <copyright file="InMemoryTokenManager.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ internal class InMemoryTokenManager : ITokenManager {
+ private Dictionary<string, string> consumersAndSecrets = new Dictionary<string, string>();
+ private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Request tokens that have been issued, and whether they have been authorized yet.
+ /// </summary>
+ private Dictionary<string, bool> requestTokens = new Dictionary<string, bool>();
+
+ /// <summary>
+ /// Access tokens that have been issued and have not yet expired.
+ /// </summary>
+ private List<string> accessTokens = new List<string>();
+
+ #region ITokenManager Members
+
+ public string GetConsumerSecret(string consumerKey) {
+ return this.consumersAndSecrets[consumerKey];
+ }
+
+ public string GetTokenSecret(string token) {
+ return this.tokensAndSecrets[token];
+ }
+
+ public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) {
+ this.tokensAndSecrets[response.Token] = response.TokenSecret;
+ this.requestTokens.Add(response.Token, false);
+ }
+
+ /// <summary>
+ /// Checks whether a given request token has already been authorized
+ /// by some user for use by the Consumer that requested it.
+ /// </summary>
+ /// <param name="requestToken">The Consumer's request token.</param>
+ /// <returns>
+ /// True if the request token has already been fully authorized by the user
+ /// who owns the relevant protected resources. False if the token has not yet
+ /// been authorized, has expired or does not exist.
+ /// </returns>
+ public bool IsRequestTokenAuthorized(string requestToken) {
+ return this.requestTokens[requestToken];
+ }
+
+ public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) {
+ // The following line is commented out because consumers don't mark their own tokens
+ // as authorized... only the SPs do. And since we multi-purpose this test class for
+ // both SPs and Consumers, we won't do this extra check.
+ ////Debug.Assert(this.requestTokens[requestToken], "Unauthorized token should not be exchanged for access token.");
+ this.requestTokens.Remove(requestToken);
+ this.accessTokens.Add(accessToken);
+ this.tokensAndSecrets.Remove(requestToken);
+ this.tokensAndSecrets[accessToken] = accessTokenSecret;
+ }
+
+ /// <summary>
+ /// Classifies a token as a request token or an access token.
+ /// </summary>
+ /// <param name="token">The token to classify.</param>
+ /// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
+ public TokenType GetTokenType(string token) {
+ if (this.requestTokens.ContainsKey(token)) {
+ return TokenType.RequestToken;
+ } else if (this.accessTokens.Contains(token)) {
+ return TokenType.AccessToken;
+ } else {
+ return TokenType.InvalidToken;
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Tells a Service Provider's token manager about a consumer and its secret
+ /// so that the SP can verify the Consumer's signed messages.
+ /// </summary>
+ /// <param name="consumerDescription">The consumer description.</param>
+ internal void AddConsumer(ConsumerDescription consumerDescription) {
+ this.consumersAndSecrets.Add(consumerDescription.ConsumerKey, consumerDescription.ConsumerSecret);
+ }
+
+ /// <summary>
+ /// Marks an existing token as authorized.
+ /// </summary>
+ /// <param name="requestToken">The request token.</param>
+ internal void AuthorizeRequestToken(string requestToken) {
+ if (requestToken == null) {
+ throw new ArgumentNullException("requestToken");
+ }
+
+ this.requestTokens[requestToken] = true;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs b/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs
new file mode 100644
index 0000000..5e65a59
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/MockReplayProtectionBindingElement.cs
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------
+// <copyright file="MockReplayProtectionBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ internal class MockReplayProtectionBindingElement : IChannelBindingElement {
+ private bool messageReceived;
+
+ #region IChannelBindingElement Members
+
+ MessageProtections IChannelBindingElement.Protection {
+ get { return MessageProtections.ReplayProtection; }
+ }
+
+ bool IChannelBindingElement.PrepareMessageForSending(IProtocolMessage message) {
+ var replayMessage = message as IReplayProtectedProtocolMessage;
+ if (replayMessage != null) {
+ replayMessage.Nonce = "someNonce";
+ return true;
+ }
+
+ return false;
+ }
+
+ bool IChannelBindingElement.PrepareMessageForReceiving(IProtocolMessage message) {
+ var replayMessage = message as IReplayProtectedProtocolMessage;
+ if (replayMessage != null) {
+ Assert.AreEqual("someNonce", replayMessage.Nonce, "The nonce didn't serialize correctly, or something");
+ // this mock implementation passes the first time and fails subsequent times.
+ if (this.messageReceived) {
+ throw new ReplayedMessageException(message);
+ }
+ this.messageReceived = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/MockSigningBindingElement.cs b/src/DotNetOpenAuth.Test/Mocks/MockSigningBindingElement.cs
new file mode 100644
index 0000000..eab9a39
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/MockSigningBindingElement.cs
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------
+// <copyright file="MockSigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+
+ internal class MockSigningBindingElement : IChannelBindingElement {
+ internal const string MessageSignature = "mocksignature";
+
+ #region IChannelBindingElement Members
+
+ MessageProtections IChannelBindingElement.Protection {
+ get { return MessageProtections.TamperProtection; }
+ }
+
+ bool IChannelBindingElement.PrepareMessageForSending(IProtocolMessage message) {
+ ITamperResistantProtocolMessage signedMessage = message as ITamperResistantProtocolMessage;
+ if (signedMessage != null) {
+ signedMessage.Signature = MessageSignature;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool IChannelBindingElement.PrepareMessageForReceiving(IProtocolMessage message) {
+ ITamperResistantProtocolMessage signedMessage = message as ITamperResistantProtocolMessage;
+ if (signedMessage != null) {
+ if (signedMessage.Signature != MessageSignature) {
+ throw new InvalidSignatureException(message);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/MockTransformationBindingElement.cs b/src/DotNetOpenAuth.Test/Mocks/MockTransformationBindingElement.cs
new file mode 100644
index 0000000..7c5a240
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/MockTransformationBindingElement.cs
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------
+// <copyright file="MockTransformationBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ internal class MockTransformationBindingElement : IChannelBindingElement {
+ private string transform;
+
+ internal MockTransformationBindingElement(string transform) {
+ if (transform == null) {
+ throw new ArgumentNullException("transform");
+ }
+
+ this.transform = transform;
+ }
+
+ #region IChannelBindingElement Members
+
+ MessageProtections IChannelBindingElement.Protection {
+ get { return MessageProtections.None; }
+ }
+
+ bool IChannelBindingElement.PrepareMessageForSending(IProtocolMessage message) {
+ var testMessage = message as TestMessage;
+ if (testMessage != null) {
+ testMessage.Name = this.transform + testMessage.Name;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool IChannelBindingElement.PrepareMessageForReceiving(IProtocolMessage message) {
+ var testMessage = message as TestMessage;
+ if (testMessage != null) {
+ StringAssert.StartsWith(testMessage.Name, this.transform);
+ testMessage.Name = testMessage.Name.Substring(this.transform.Length);
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs
new file mode 100644
index 0000000..8fbfaf9
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestBadChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A Channel derived type that passes null to the protected constructor.
+ /// </summary>
+ internal class TestBadChannel : Channel {
+ internal TestBadChannel(bool badConstructorParam)
+ : base(badConstructorParam ? null : new TestMessageTypeProvider()) {
+ }
+
+ internal new void Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
+ base.Create301RedirectResponse(message, fields);
+ }
+
+ internal new void CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
+ base.CreateFormPostResponse(message, fields);
+ }
+
+ internal new void SendIndirectMessage(IDirectedProtocolMessage message) {
+ base.SendIndirectMessage(message);
+ }
+
+ internal new IProtocolMessage Receive(Dictionary<string, string> fields, MessageReceivingEndpoint recipient) {
+ return base.Receive(fields, recipient);
+ }
+
+ internal new IProtocolMessage ReadFromRequest(HttpRequestInfo request) {
+ return base.ReadFromRequest(request);
+ }
+
+ protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
+ throw new NotImplementedException();
+ }
+
+ protected override IProtocolMessage ReadFromResponseInternal(System.IO.Stream responseStream) {
+ throw new NotImplementedException();
+ }
+
+ protected override Response SendDirectMessageResponse(IProtocolMessage response) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestBaseMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestBaseMessage.cs
new file mode 100644
index 0000000..ef0693c
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestBaseMessage.cs
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestBaseMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ internal interface IBaseMessageExplicitMembers {
+ string ExplicitProperty { get; set; }
+ }
+
+ internal class TestBaseMessage : IProtocolMessage, IBaseMessageExplicitMembers {
+ private Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ [MessagePart("age", IsRequired = true)]
+ public int Age { get; set; }
+
+ [MessagePart]
+ public string Name { get; set; }
+
+ [MessagePart("explicit")]
+ string IBaseMessageExplicitMembers.ExplicitProperty { get; set; }
+
+ Version IProtocolMessage.ProtocolVersion {
+ get { return new Version(1, 0); }
+ }
+
+ MessageProtections IProtocolMessage.RequiredProtection {
+ get { return MessageProtections.None; }
+ }
+
+ MessageTransport IProtocolMessage.Transport {
+ get { return MessageTransport.Indirect; }
+ }
+
+ IDictionary<string, string> IProtocolMessage.ExtraData {
+ get { return this.extraData; }
+ }
+
+ internal string PrivatePropertyAccessor {
+ get { return this.PrivateProperty; }
+ set { this.PrivateProperty = value; }
+ }
+
+ [MessagePart("private")]
+ private string PrivateProperty { get; set; }
+
+ void IProtocolMessage.EnsureValidMessage() { }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs
new file mode 100644
index 0000000..ebb5858
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestChannel.cs
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class TestChannel : Channel {
+ internal TestChannel()
+ : this(new TestMessageTypeProvider()) {
+ }
+
+ internal TestChannel(IMessageTypeProvider messageTypeProvider, params IChannelBindingElement[] bindingElements)
+ : base(messageTypeProvider, bindingElements) {
+ }
+
+ protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
+ throw new NotImplementedException("Request");
+ }
+
+ protected override IProtocolMessage ReadFromResponseInternal(System.IO.Stream responseStream) {
+ throw new NotImplementedException("ReadFromResponse");
+ }
+
+ protected override Response SendDirectMessageResponse(IProtocolMessage response) {
+ throw new NotImplementedException("SendDirectMessageResponse");
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestDerivedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestDerivedMessage.cs
new file mode 100644
index 0000000..de34329
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestDerivedMessage.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestDerivedMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System.Runtime.Serialization;
+ using DotNetOpenAuth.Messaging;
+
+ internal class TestDerivedMessage : TestBaseMessage {
+ /// <summary>
+ /// Gets or sets the first value.
+ /// </summary>
+ /// <remarks>
+ /// This element should appear AFTER <see cref="SecondDerivedElement"/>
+ /// due to alphabetical ordering rules, but after all the elements in the
+ /// base class due to inheritance rules.
+ /// </remarks>
+ [MessagePart]
+ public string TheFirstDerivedElement { get; set; }
+
+ /// <summary>
+ /// Gets or sets the second value.
+ /// </summary>
+ /// <remarks>
+ /// This element should appear BEFORE <see cref="TheFirstDerivedElement"/>,
+ /// but after all the elements in the base class.
+ /// </remarks>
+ [MessagePart]
+ public string SecondDerivedElement { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestDirectedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestDirectedMessage.cs
new file mode 100644
index 0000000..67d0eb0
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestDirectedMessage.cs
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestDirectedMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+
+ internal class TestDirectedMessage : TestMessage, IOAuthDirectedMessage {
+ internal TestDirectedMessage() {
+ }
+
+ internal TestDirectedMessage(MessageTransport transport) : base(transport) {
+ }
+
+ #region IDirectedProtocolMessage Members
+
+ public Uri Recipient { get; set; }
+
+ #endregion
+
+ #region IProtocolMessage Properties
+
+ MessageProtections IProtocolMessage.RequiredProtection {
+ get { return this.RequiredProtection; }
+ }
+
+ #endregion
+
+ #region IOAuthDirectedMessage Members
+
+ public HttpDeliveryMethods HttpMethods { get; internal set; }
+
+ #endregion
+
+ protected virtual MessageProtections RequiredProtection {
+ get { return MessageProtections.None; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestExpiringMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestExpiringMessage.cs
new file mode 100644
index 0000000..0aae6ae
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestExpiringMessage.cs
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestExpiringMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Diagnostics;
+ using System.Runtime.Serialization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ internal class TestExpiringMessage : TestSignedDirectedMessage, IExpiringProtocolMessage {
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private DateTime utcCreationDate;
+
+ internal TestExpiringMessage() { }
+
+ internal TestExpiringMessage(MessageTransport transport)
+ : base(transport) {
+ }
+
+ #region IExpiringProtocolMessage Members
+
+ [MessagePart("created_on", IsRequired = true)]
+ DateTime IExpiringProtocolMessage.UtcCreationDate {
+ get { return this.utcCreationDate; }
+ set { this.utcCreationDate = value.ToUniversalTime(); }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestMessage.cs
new file mode 100644
index 0000000..301e70d
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestMessage.cs
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Runtime.Serialization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ internal class TestMessage : IProtocolMessage {
+ private MessageTransport transport;
+ private Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ internal TestMessage()
+ : this(MessageTransport.Direct) {
+ }
+
+ internal TestMessage(MessageTransport transport) {
+ this.transport = transport;
+ }
+
+ [MessagePart("age", IsRequired = true)]
+ public int Age { get; set; }
+ [MessagePart("Name")]
+ public string Name { get; set; }
+ [MessagePart]
+ public string EmptyMember { get; set; }
+ [MessagePart(null)] // null name tests that Location is still the name.
+ public Uri Location { get; set; }
+ [MessagePart(IsRequired = true)]
+ public DateTime Timestamp { get; set; }
+
+ #region IProtocolMessage Members
+
+ Version IProtocolMessage.ProtocolVersion {
+ get { return new Version(1, 0); }
+ }
+
+ MessageProtections IProtocolMessage.RequiredProtection {
+ get { return MessageProtections.None; }
+ }
+
+ MessageTransport IProtocolMessage.Transport {
+ get { return this.transport; }
+ }
+
+ IDictionary<string, string> IProtocolMessage.ExtraData {
+ get { return this.extraData; }
+ }
+
+ void IProtocolMessage.EnsureValidMessage() {
+ if (this.EmptyMember != null || this.Age < 0) {
+ throw new ProtocolException();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestMessageTypeProvider.cs b/src/DotNetOpenAuth.Test/Mocks/TestMessageTypeProvider.cs
new file mode 100644
index 0000000..c6bb0c7
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestMessageTypeProvider.cs
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestMessageTypeProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class TestMessageTypeProvider : IMessageTypeProvider {
+ private bool signedMessages;
+ private bool expiringMessages;
+ private bool replayMessages;
+
+ internal TestMessageTypeProvider()
+ : this(false, false, false) {
+ }
+
+ internal TestMessageTypeProvider(bool signed, bool expiring, bool replay) {
+ if ((!signed && expiring) || (!expiring && replay)) {
+ throw new ArgumentException("Invalid combination of protection.");
+ }
+ this.signedMessages = signed;
+ this.expiringMessages = expiring;
+ this.replayMessages = replay;
+ }
+
+ #region IMessageTypeProvider Members
+
+ public Type GetRequestMessageType(IDictionary<string, string> fields) {
+ if (fields.ContainsKey("age")) {
+ if (this.signedMessages) {
+ if (this.expiringMessages) {
+ if (this.replayMessages) {
+ return typeof(TestReplayProtectedMessage);
+ }
+ return typeof(TestExpiringMessage);
+ }
+ return typeof(TestSignedDirectedMessage);
+ }
+ return typeof(TestMessage);
+ }
+ return null;
+ }
+
+ public Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) {
+ return this.GetRequestMessageType(fields);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestReplayProtectedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestReplayProtectedMessage.cs
new file mode 100644
index 0000000..396db44
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestReplayProtectedMessage.cs
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestReplayProtectedMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System.Runtime.Serialization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ internal class TestReplayProtectedMessage : TestExpiringMessage, IReplayProtectedProtocolMessage {
+ internal TestReplayProtectedMessage() { }
+
+ internal TestReplayProtectedMessage(MessageTransport transport)
+ : base(transport) {
+ }
+
+ #region IReplayProtectedProtocolMessage Members
+
+ [MessagePart("Nonce")]
+ string IReplayProtectedProtocolMessage.Nonce {
+ get;
+ set;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestSignedDirectedMessage.cs b/src/DotNetOpenAuth.Test/Mocks/TestSignedDirectedMessage.cs
new file mode 100644
index 0000000..d665db8
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestSignedDirectedMessage.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestSignedDirectedMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ internal class TestSignedDirectedMessage : TestDirectedMessage, ITamperResistantProtocolMessage {
+ internal TestSignedDirectedMessage() { }
+
+ internal TestSignedDirectedMessage(MessageTransport transport)
+ : base(transport) {
+ }
+
+ #region ISignedProtocolMessage Members
+
+ [MessagePart]
+ public string Signature {
+ get;
+ set;
+ }
+
+ #endregion
+
+ protected override MessageProtections RequiredProtection {
+ get { return MessageProtections.TamperProtection; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs
new file mode 100644
index 0000000..d7092b4
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestWebRequestHandler.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Mocks {
+ using System;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+
+ internal class TestWebRequestHandler : IWebRequestHandler {
+ private StringBuilder postEntity;
+
+ internal Func<HttpWebRequest, Response> Callback { get; set; }
+
+ internal Stream RequestEntityStream {
+ get {
+ if (this.postEntity == null) {
+ return null;
+ }
+ return new MemoryStream(Encoding.UTF8.GetBytes(this.postEntity.ToString()));
+ }
+ }
+
+ #region IWebRequestHandler Members
+
+ public TextWriter GetRequestStream(HttpWebRequest request) {
+ this.postEntity = new StringBuilder();
+ return new StringWriter(this.postEntity);
+ }
+
+ public Response GetResponse(HttpWebRequest request) {
+ if (this.Callback == null) {
+ throw new InvalidOperationException("Set the Callback property first.");
+ }
+
+ return this.Callback(request);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs
new file mode 100644
index 0000000..ac2c0b1
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------
+// <copyright file="HmacSha1SigningBindingElementTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.ChannelElements {
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class HmacSha1SigningBindingElementTests : MessagingTestBase {
+ [TestMethod]
+ public void SignatureTest() {
+ UnauthorizedTokenRequest message = SigningBindingElementBaseTests.CreateTestRequestTokenMessage();
+
+ HmacSha1SigningBindingElement_Accessor hmac = new HmacSha1SigningBindingElement_Accessor();
+ Assert.AreEqual("kR0LhH8UqylaLfR/esXVVlP4sQI=", hmac.GetSignature(message));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs
new file mode 100644
index 0000000..c5d3b14
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs
@@ -0,0 +1,293 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthChannelTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.Test.Mocks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class OAuthChannelTests : TestBase {
+ private OAuthChannel channel;
+ private TestWebRequestHandler webRequestHandler;
+ private SigningBindingElementBase signingElement;
+ private INonceStore nonceStore;
+
+ [TestInitialize]
+ public override void SetUp() {
+ base.SetUp();
+
+ this.webRequestHandler = new TestWebRequestHandler();
+ this.signingElement = new RsaSha1SigningBindingElement();
+ this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
+ this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), new TestMessageTypeProvider(), this.webRequestHandler);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullHandler() {
+ new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, new InMemoryTokenManager(), new TestMessageTypeProvider(), null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void CtorNullSigner() {
+ new OAuthChannel(null, this.nonceStore, new InMemoryTokenManager(), new TestMessageTypeProvider(), this.webRequestHandler);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullStore() {
+ new OAuthChannel(new RsaSha1SigningBindingElement(), null, new InMemoryTokenManager(), new TestMessageTypeProvider(), this.webRequestHandler);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNullTokenManager() {
+ new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, null, new TestMessageTypeProvider(), this.webRequestHandler);
+ }
+
+ [TestMethod]
+ public void CtorSimpleConsumer() {
+ new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, new InMemoryTokenManager(), true);
+ }
+
+ [TestMethod]
+ public void CtorSimpleServiceProvider() {
+ new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, new InMemoryTokenManager(), false);
+ }
+
+ [TestMethod]
+ public void ReadFromRequestAuthorization() {
+ this.ParameterizedReceiveTest(HttpDeliveryMethods.AuthorizationHeaderRequest);
+ }
+
+ [TestMethod]
+ public void ReadFromRequestForm() {
+ this.ParameterizedReceiveTest(HttpDeliveryMethods.PostRequest);
+ }
+
+ [TestMethod]
+ public void ReadFromRequestQueryString() {
+ this.ParameterizedReceiveTest(HttpDeliveryMethods.GetRequest);
+ }
+
+ [TestMethod]
+ public void SendDirectMessageResponse() {
+ IProtocolMessage message = new TestMessage {
+ Age = 15,
+ Name = "Andrew",
+ Location = new Uri("http://hostb/pathB"),
+ };
+
+ Response response = this.channel.Send(message);
+ Assert.AreSame(message, response.OriginalMessage);
+ Assert.AreEqual(HttpStatusCode.OK, response.Status);
+ Assert.AreEqual(0, response.Headers.Count);
+
+ NameValueCollection body = HttpUtility.ParseQueryString(response.Body);
+ Assert.AreEqual("15", body["age"]);
+ Assert.AreEqual("Andrew", body["Name"]);
+ Assert.AreEqual("http://hostb/pathB", body["Location"]);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void ReadFromResponseNull() {
+ Channel_Accessor accessor = Channel_Accessor.AttachShadow(this.channel);
+ accessor.ReadFromResponse(null);
+ }
+
+ [TestMethod]
+ public void ReadFromResponse() {
+ var fields = new Dictionary<string, string> {
+ { "age", "15" },
+ { "Name", "Andrew" },
+ { "Location", "http://hostb/pathB" },
+ { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) },
+ };
+
+ MemoryStream ms = new MemoryStream();
+ StreamWriter writer = new StreamWriter(ms);
+ writer.Write(MessagingUtilities.CreateQueryString(fields));
+ writer.Flush();
+ ms.Seek(0, SeekOrigin.Begin);
+ Channel_Accessor channelAccessor = Channel_Accessor.AttachShadow(this.channel);
+ IProtocolMessage message = channelAccessor.ReadFromResponse(ms);
+ Assert.IsNotNull(message);
+ Assert.IsInstanceOfType(message, typeof(TestMessage));
+ TestMessage testMessage = (TestMessage)message;
+ Assert.AreEqual(15, testMessage.Age);
+ Assert.AreEqual("Andrew", testMessage.Name);
+ Assert.AreEqual("http://hostb/pathB", testMessage.Location.AbsoluteUri);
+ Assert.IsNull(testMessage.EmptyMember);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void RequestNull() {
+ this.channel.Request(null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void RequestNullRecipient() {
+ IDirectedProtocolMessage message = new TestDirectedMessage(MessageTransport.Direct);
+ this.channel.Request(message);
+ }
+
+ [TestMethod, ExpectedException(typeof(NotSupportedException))]
+ public void RequestBadPreferredScheme() {
+ TestDirectedMessage message = new TestDirectedMessage(MessageTransport.Direct);
+ message.Recipient = new Uri("http://localtest");
+ message.HttpMethods = HttpDeliveryMethods.None;
+ this.channel.Request(message);
+ }
+
+ [TestMethod]
+ public void RequestUsingAuthorizationHeader() {
+ this.ParameterizedRequestTest(HttpDeliveryMethods.AuthorizationHeaderRequest);
+ }
+
+ [TestMethod]
+ public void RequestUsingGet() {
+ this.ParameterizedRequestTest(HttpDeliveryMethods.GetRequest);
+ }
+
+ [TestMethod]
+ public void RequestUsingPost() {
+ this.ParameterizedRequestTest(HttpDeliveryMethods.PostRequest);
+ }
+
+ private static string CreateAuthorizationHeader(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ StringBuilder authorization = new StringBuilder();
+ authorization.Append("OAuth ");
+ foreach (var pair in fields) {
+ string key = Uri.EscapeDataString(pair.Key);
+ string value = Uri.EscapeDataString(pair.Value);
+ authorization.Append(key);
+ authorization.Append("=\"");
+ authorization.Append(value);
+ authorization.Append("\",");
+ }
+ authorization.Length--; // remove trailing comma
+
+ return authorization.ToString();
+ }
+
+ private static HttpRequestInfo CreateHttpRequestInfo(HttpDeliveryMethods scheme, IDictionary<string, string> fields) {
+ string query = MessagingUtilities.CreateQueryString(fields);
+ UriBuilder requestUri = new UriBuilder("http://localhost/path");
+ WebHeaderCollection headers = new WebHeaderCollection();
+ MemoryStream ms = new MemoryStream();
+ string method;
+ switch (scheme) {
+ case HttpDeliveryMethods.PostRequest:
+ method = "POST";
+ headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
+ StreamWriter sw = new StreamWriter(ms);
+ sw.Write(query);
+ sw.Flush();
+ ms.Position = 0;
+ break;
+ case HttpDeliveryMethods.GetRequest:
+ method = "GET";
+ requestUri.Query = query;
+ break;
+ case HttpDeliveryMethods.AuthorizationHeaderRequest:
+ method = "GET";
+ headers.Add(HttpRequestHeader.Authorization, CreateAuthorizationHeader(fields));
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("scheme", scheme, "Unexpected value");
+ }
+ HttpRequestInfo request = new HttpRequestInfo {
+ HttpMethod = method,
+ Url = requestUri.Uri,
+ Headers = headers,
+ InputStream = ms,
+ };
+
+ return request;
+ }
+
+ private static HttpRequestInfo ConvertToRequestInfo(HttpWebRequest request, Stream postEntity) {
+ HttpRequestInfo info = new HttpRequestInfo {
+ HttpMethod = request.Method,
+ Url = request.RequestUri,
+ Headers = request.Headers,
+ InputStream = postEntity,
+ };
+ return info;
+ }
+
+ private void ParameterizedRequestTest(HttpDeliveryMethods scheme) {
+ TestDirectedMessage request = new TestDirectedMessage(MessageTransport.Direct) {
+ Age = 15,
+ Name = "Andrew",
+ Location = new Uri("http://hostb/pathB"),
+ Recipient = new Uri("http://localtest"),
+ Timestamp = DateTime.UtcNow,
+ HttpMethods = scheme,
+ };
+
+ Response rawResponse = null;
+ this.webRequestHandler.Callback = (req) => {
+ Assert.IsNotNull(req);
+ HttpRequestInfo reqInfo = ConvertToRequestInfo(req, this.webRequestHandler.RequestEntityStream);
+ Assert.AreEqual(scheme == HttpDeliveryMethods.PostRequest ? "POST" : "GET", reqInfo.HttpMethod);
+ var incomingMessage = this.channel.ReadFromRequest(reqInfo) as TestMessage;
+ Assert.IsNotNull(incomingMessage);
+ Assert.AreEqual(request.Age, incomingMessage.Age);
+ Assert.AreEqual(request.Name, incomingMessage.Name);
+ Assert.AreEqual(request.Location, incomingMessage.Location);
+ Assert.AreEqual(request.Timestamp, incomingMessage.Timestamp);
+
+ var responseFields = new Dictionary<string, string> {
+ { "age", request.Age.ToString() },
+ { "Name", request.Name },
+ { "Location", request.Location.AbsoluteUri },
+ { "Timestamp", XmlConvert.ToString(request.Timestamp, XmlDateTimeSerializationMode.Utc) },
+ };
+ rawResponse = new Response {
+ Body = MessagingUtilities.CreateQueryString(responseFields),
+ };
+ return rawResponse;
+ };
+
+ IProtocolMessage response = this.channel.Request(request);
+ Assert.IsNotNull(response);
+ Assert.IsInstanceOfType(response, typeof(TestMessage));
+ TestMessage responseMessage = (TestMessage)response;
+ Assert.AreEqual(request.Age, responseMessage.Age);
+ Assert.AreEqual(request.Name, responseMessage.Name);
+ Assert.AreEqual(request.Location, responseMessage.Location);
+ }
+
+ private void ParameterizedReceiveTest(HttpDeliveryMethods scheme) {
+ var fields = new Dictionary<string, string> {
+ { "age", "15" },
+ { "Name", "Andrew" },
+ { "Location", "http://hostb/pathB" },
+ { "Timestamp", XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.Utc) },
+ };
+ IProtocolMessage requestMessage = this.channel.ReadFromRequest(CreateHttpRequestInfo(scheme, fields));
+ Assert.IsNotNull(requestMessage);
+ Assert.IsInstanceOfType(requestMessage, typeof(TestMessage));
+ TestMessage testMessage = (TestMessage)requestMessage;
+ Assert.AreEqual(15, testMessage.Age);
+ Assert.AreEqual("Andrew", testMessage.Name);
+ Assert.AreEqual("http://hostb/pathB", testMessage.Location.AbsoluteUri);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs
new file mode 100644
index 0000000..22b6243
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------
+// <copyright file="PlaintextSigningBindingElementTest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.ChannelElements
+{
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class PlaintextSigningBindingElementTest {
+ [TestMethod]
+ public void HttpsSignatureGeneration() {
+ SigningBindingElementBase target = new PlaintextSigningBindingElement();
+ MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest);
+ ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint);
+ message.ConsumerSecret = "cs";
+ message.TokenSecret = "ts";
+ Assert.IsTrue(target.PrepareMessageForSending(message));
+ Assert.AreEqual("PLAINTEXT", message.SignatureMethod);
+ Assert.AreEqual("cs&ts", message.Signature);
+ }
+
+ [TestMethod]
+ public void HttpsSignatureVerification() {
+ MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest);
+ ITamperProtectionChannelBindingElement target = new PlaintextSigningBindingElement();
+ ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint);
+ message.ConsumerSecret = "cs";
+ message.TokenSecret = "ts";
+ message.SignatureMethod = "PLAINTEXT";
+ message.Signature = "cs&ts";
+ Assert.IsTrue(target.PrepareMessageForReceiving(message));
+ }
+
+ [TestMethod]
+ public void HttpsSignatureVerificationNotApplicable() {
+ SigningBindingElementBase target = new PlaintextSigningBindingElement();
+ MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest);
+ ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint);
+ message.ConsumerSecret = "cs";
+ message.TokenSecret = "ts";
+ message.SignatureMethod = "ANOTHERALGORITHM";
+ message.Signature = "somethingelse";
+ Assert.IsFalse(target.PrepareMessageForReceiving(message), "PLAINTEXT binding element should opt-out where it doesn't understand.");
+ }
+
+ [TestMethod]
+ public void HttpSignatureGeneration() {
+ SigningBindingElementBase target = new PlaintextSigningBindingElement();
+ MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest);
+ ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint);
+ message.ConsumerSecret = "cs";
+ message.TokenSecret = "ts";
+
+ // Since this is (non-encrypted) HTTP, so the plain text signer should not be used
+ Assert.IsFalse(target.PrepareMessageForSending(message));
+ Assert.IsNull(message.SignatureMethod);
+ Assert.IsNull(message.Signature);
+ }
+
+ [TestMethod]
+ public void HttpSignatureVerification() {
+ SigningBindingElementBase target = new PlaintextSigningBindingElement();
+ MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest);
+ ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint);
+ message.ConsumerSecret = "cs";
+ message.TokenSecret = "ts";
+ message.SignatureMethod = "PLAINTEXT";
+ message.Signature = "cs%26ts";
+ Assert.IsFalse(target.PrepareMessageForReceiving(message), "PLAINTEXT signature binding element should refuse to participate in non-encrypted messages.");
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs
new file mode 100644
index 0000000..8f09ef6
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs
@@ -0,0 +1,40 @@
+//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementBaseTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.ChannelElements {
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class SigningBindingElementBaseTests : MessagingTestBase {
+ [TestMethod]
+ public void BaseSignatureStringTest() {
+ UnauthorizedTokenRequest message = CreateTestRequestTokenMessage();
+
+ Assert.AreEqual(
+ "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F",
+ SigningBindingElementBase_Accessor.ConstructSignatureBaseString(message));
+ }
+
+ internal static UnauthorizedTokenRequest CreateTestRequestTokenMessage() {
+ MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest);
+ UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint);
+ message.ConsumerKey = "nerdbank.org";
+ ((ITamperResistantOAuthMessage)message).ConsumerSecret = "nerdbanksecret";
+ var signedMessage = (ITamperResistantOAuthMessage)message;
+ signedMessage.HttpMethod = "GET";
+ signedMessage.SignatureMethod = "HMAC-SHA1";
+ MessageDictionary dictionary = new MessageDictionary(message);
+ dictionary["oauth_timestamp"] = "1222665749";
+ dictionary["oauth_nonce"] = "fe4045a3f0efdd1e019fa8f8ae3f5c38";
+ dictionary["scope"] = "http://www.google.com/m8/feeds/";
+ return message;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ConsumerDescription.cs b/src/DotNetOpenAuth.Test/OAuth/ConsumerDescription.cs
new file mode 100644
index 0000000..625f416
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OAuth/ConsumerDescription.cs
@@ -0,0 +1,38 @@
+//-----------------------------------------------------------------------
+// <copyright file="ConsumerDescription.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test {
+ /// <summary>
+ /// Information necessary to initialize a <see cref="Consumer"/>,
+ /// and to tell a <see cref="ServiceProvider"/> about it.
+ /// </summary>
+ /// <remarks>
+ /// Immutable.
+ /// </remarks>
+ internal class ConsumerDescription {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConsumerDescription"/> class.
+ /// </summary>
+ /// <param name="key">The consumer key.</param>
+ /// <param name="secret">The consumer secret.</param>
+ internal ConsumerDescription(string key, string secret) {
+ this.ConsumerKey = key;
+ this.ConsumerSecret = secret;
+ }
+
+ /// <summary>
+ /// Gets the consumer key.
+ /// </summary>
+ /// <value>The consumer key.</value>
+ internal string ConsumerKey { get; private set; }
+
+ /// <summary>
+ /// Gets the consumer secret.
+ /// </summary>
+ /// <value>The consumer secret.</value>
+ internal string ConsumerSecret { get; private set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs
new file mode 100644
index 0000000..6a2551a
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="ProtocolTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test {
+ using DotNetOpenAuth.OAuth;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ProtocolTests {
+ [TestMethod]
+ public void Default() {
+ Assert.AreSame(Protocol.V10, Protocol.Default);
+ }
+
+ [TestMethod]
+ public void DataContractNamespace() {
+ Assert.AreEqual("http://oauth.net/core/1.0/", Protocol.V10.DataContractNamespace);
+ Assert.AreEqual("http://oauth.net/core/1.0/", Protocol.DataContractNamespaceV10);
+ }
+
+ [TestMethod]
+ public void AuthorizationHeaderScheme() {
+ Assert.AreEqual("OAuth", Protocol.V10.AuthorizationHeaderScheme);
+ }
+
+ [TestMethod]
+ public void ParameterPrefix() {
+ Assert.AreEqual("oauth_", Protocol.V10.ParameterPrefix);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/OAuth/ServiceProviderDescriptionTests.cs b/src/DotNetOpenAuth.Test/OAuth/ServiceProviderDescriptionTests.cs
new file mode 100644
index 0000000..760a9e9
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/OAuth/ServiceProviderDescriptionTests.cs
@@ -0,0 +1,76 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceProviderDescriptionTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ /// <summary>
+ /// Tests for the <see cref="ServiceProviderEndpoints"/> class.
+ /// </summary>
+ [TestClass]
+ public class ServiceProviderDescriptionTests : TestBase {
+ /// <summary>
+ /// A test for UserAuthorizationUri
+ /// </summary>
+ [TestMethod]
+ public void UserAuthorizationUriTest() {
+ ServiceProviderDescription target = new ServiceProviderDescription();
+ MessageReceivingEndpoint expected = new MessageReceivingEndpoint("http://localhost/authorization", HttpDeliveryMethods.GetRequest);
+ MessageReceivingEndpoint actual;
+ target.UserAuthorizationEndpoint = expected;
+ actual = target.UserAuthorizationEndpoint;
+ Assert.AreEqual(expected, actual);
+
+ target.UserAuthorizationEndpoint = null;
+ Assert.IsNull(target.UserAuthorizationEndpoint);
+ }
+
+ /// <summary>
+ /// A test for RequestTokenUri
+ /// </summary>
+ [TestMethod]
+ public void RequestTokenUriTest() {
+ var target = new ServiceProviderDescription();
+ MessageReceivingEndpoint expected = new MessageReceivingEndpoint("http://localhost/requesttoken", HttpDeliveryMethods.GetRequest);
+ MessageReceivingEndpoint actual;
+ target.RequestTokenEndpoint = expected;
+ actual = target.RequestTokenEndpoint;
+ Assert.AreEqual(expected, actual);
+
+ target.RequestTokenEndpoint = null;
+ Assert.IsNull(target.RequestTokenEndpoint);
+ }
+
+ /// <summary>
+ /// Verifies that oauth parameters are not allowed in <see cref="ServiceProvider.RequestTokenUri"/>,
+ /// per section OAuth 1.0 section 4.1.
+ /// </summary>
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void RequestTokenUriWithOAuthParametersTest() {
+ var target = new ServiceProviderDescription();
+ target.RequestTokenEndpoint = new MessageReceivingEndpoint("http://localhost/requesttoken?oauth_token=something", HttpDeliveryMethods.GetRequest);
+ }
+
+ /// <summary>
+ /// A test for AccessTokenUri
+ /// </summary>
+ [TestMethod]
+ public void AccessTokenUriTest() {
+ var target = new ServiceProviderDescription();
+ MessageReceivingEndpoint expected = new MessageReceivingEndpoint("http://localhost/accesstoken", HttpDeliveryMethods.GetRequest);
+ MessageReceivingEndpoint actual;
+ target.AccessTokenEndpoint = expected;
+ actual = target.AccessTokenEndpoint;
+ Assert.AreEqual(expected, actual);
+
+ target.AccessTokenEndpoint = null;
+ Assert.IsNull(target.AccessTokenEndpoint);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..88c17e6
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,29 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssemblyInfo.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DotNetOpenAuth.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DotNetOpenAuth.Test")]
+[assembly: AssemblyCopyright("Copyright © 2008")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM componenets. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("aef0bb13-b79c-4854-a69a-de58b8feb5d1")]
diff --git a/src/DotNetOpenAuth.Test/Scenarios/AppendixScenarios.cs b/src/DotNetOpenAuth.Test/Scenarios/AppendixScenarios.cs
new file mode 100644
index 0000000..5fb7538
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Scenarios/AppendixScenarios.cs
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------
+// <copyright file="AppendixScenarios.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test {
+ using System;
+ using System.IO;
+ using System.Net;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.Test.Mocks;
+ using DotNetOpenAuth.Test.Scenarios;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class AppendixScenarios : TestBase {
+ [TestMethod]
+ public void SpecAppendixAExample() {
+ ServiceProviderDescription serviceDescription = new ServiceProviderDescription() {
+ RequestTokenEndpoint = new MessageReceivingEndpoint("https://photos.example.net/request_token", HttpDeliveryMethods.PostRequest),
+ UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://photos.example.net/authorize", HttpDeliveryMethods.GetRequest),
+ AccessTokenEndpoint = new MessageReceivingEndpoint("https://photos.example.net/access_token", HttpDeliveryMethods.PostRequest),
+ TamperProtectionElements = new ITamperProtectionChannelBindingElement[] {
+ new PlaintextSigningBindingElement(),
+ new HmacSha1SigningBindingElement(),
+ },
+ };
+ MessageReceivingEndpoint accessPhotoEndpoint = new MessageReceivingEndpoint("http://photos.example.net/photos?file=vacation.jpg&size=original", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest);
+ ConsumerDescription consumerDescription = new ConsumerDescription("dpf43f3p2l4k3l03", "kd94hf93k423kf44");
+
+ Coordinator coordinator = new Coordinator(
+ consumerDescription,
+ serviceDescription,
+ consumer => {
+ consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(new Uri("http://printer.example.com/request_token_ready"), null, null)); // .Send() dropped because this is just a simulation
+ string accessToken = consumer.ProcessUserAuthorization().AccessToken;
+ var photoRequest = consumer.CreateAuthorizingMessage(accessPhotoEndpoint, accessToken);
+ Response protectedPhoto = ((CoordinatingOAuthChannel)consumer.Channel).RequestProtectedResource(photoRequest);
+ Assert.IsNotNull(protectedPhoto);
+ Assert.AreEqual(HttpStatusCode.OK, protectedPhoto.Status);
+ Assert.AreEqual("image/jpeg", protectedPhoto.Headers[HttpResponseHeader.ContentType]);
+ Assert.AreNotEqual(0, protectedPhoto.ResponseStream.Length);
+ },
+ sp => {
+ var requestTokenMessage = sp.ReadTokenRequest();
+ sp.Channel.Send(sp.PrepareUnauthorizedTokenMessage(requestTokenMessage)); // .Send() dropped because this is just a simulation
+ var authRequest = sp.ReadAuthorizationRequest();
+ ((InMemoryTokenManager)sp.TokenManager).AuthorizeRequestToken(authRequest.RequestToken);
+ sp.Channel.Send(sp.PrepareAuthorizationResponse(authRequest)); // .Send() dropped because this is just a simulation
+ var accessRequest = sp.ReadAccessTokenRequest();
+ sp.Channel.Send(sp.PrepareAccessTokenMessage(accessRequest)); // .Send() dropped because this is just a simulation
+ string accessToken = sp.ReadProtectedResourceAuthorization().AccessToken;
+ ((CoordinatingOAuthChannel)sp.Channel).SendDirectRawResponse(new Response {
+ ResponseStream = new MemoryStream(new byte[] { 0x33, 0x66 }),
+ Headers = new WebHeaderCollection {
+ { HttpResponseHeader.ContentType, "image/jpeg" },
+ },
+ });
+ });
+
+ coordinator.Run();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Scenarios/CoordinatingOAuthChannel.cs b/src/DotNetOpenAuth.Test/Scenarios/CoordinatingOAuthChannel.cs
new file mode 100644
index 0000000..9fae4a9
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Scenarios/CoordinatingOAuthChannel.cs
@@ -0,0 +1,144 @@
+//-----------------------------------------------------------------------
+// <copyright file="CoordinatingOAuthChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Scenarios {
+ using System;
+ using System.Threading;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+ using DotNetOpenAuth.OAuth.Messages;
+ using DotNetOpenAuth.Test.Mocks;
+
+ /// <summary>
+ /// A special channel used in test simulations to pass messages directly between two parties.
+ /// </summary>
+ internal class CoordinatingOAuthChannel : OAuthChannel {
+ private EventWaitHandle incomingMessageSignal = new AutoResetEvent(false);
+ private IProtocolMessage incomingMessage;
+ private Response incomingRawResponse;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CoordinatingOAuthChannel"/> class for Consumers.
+ /// </summary>
+ /// <param name="signingBindingElement">
+ /// The signing element for the Consumer to use. Null for the Service Provider.
+ /// </param>
+ /// <param name="isConsumer">True if this channel is constructed for a Consumer.</param>
+ /// <param name="tokenManager">The token manager to use.</param>
+ internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, bool isConsumer, ITokenManager tokenManager)
+ : base(
+ signingBindingElement,
+ new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge),
+ tokenManager,
+ isConsumer ? (IMessageTypeProvider)new OAuthConsumerMessageTypeProvider() : new OAuthServiceProviderMessageTypeProvider(tokenManager),
+ new TestWebRequestHandler()) {
+ }
+
+ /// <summary>
+ /// Gets or sets the coordinating channel used by the other party.
+ /// </summary>
+ internal CoordinatingOAuthChannel RemoteChannel { get; set; }
+
+ internal Response RequestProtectedResource(AccessProtectedResourceRequest request) {
+ ((ITamperResistantOAuthMessage)request).HttpMethod = this.GetHttpMethod(((ITamperResistantOAuthMessage)request).HttpMethods);
+ this.PrepareMessageForSending(request);
+ HttpRequestInfo requestInfo = this.SpoofHttpMethod(request);
+ TestBase.TestLogger.InfoFormat("Sending protected resource request: {0}", requestInfo.Message);
+ // Drop the outgoing message in the other channel's in-slot and let them know it's there.
+ this.RemoteChannel.incomingMessage = requestInfo.Message;
+ this.RemoteChannel.incomingMessageSignal.Set();
+ return this.AwaitIncomingRawResponse();
+ }
+
+ internal void SendDirectRawResponse(Response response) {
+ this.RemoteChannel.incomingRawResponse = response;
+ this.RemoteChannel.incomingMessageSignal.Set();
+ }
+
+ protected internal override HttpRequestInfo GetRequestFromContext() {
+ return new HttpRequestInfo(this.AwaitIncomingMessage());
+ }
+
+ protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
+ HttpRequestInfo requestInfo = this.SpoofHttpMethod(request);
+ // Drop the outgoing message in the other channel's in-slot and let them know it's there.
+ this.RemoteChannel.incomingMessage = requestInfo.Message;
+ this.RemoteChannel.incomingMessageSignal.Set();
+ // Now wait for a response...
+ return this.AwaitIncomingMessage();
+ }
+
+ protected override Response SendDirectMessageResponse(IProtocolMessage response) {
+ this.RemoteChannel.incomingMessage = CloneSerializedParts(response, null);
+ this.RemoteChannel.incomingMessageSignal.Set();
+ return null;
+ }
+
+ protected override Response SendIndirectMessage(IDirectedProtocolMessage message) {
+ // In this mock transport, direct and indirect messages are the same.
+ return this.SendDirectMessageResponse(message);
+ }
+
+ protected override IProtocolMessage ReadFromRequestInternal(HttpRequestInfo request) {
+ return request.Message;
+ }
+
+ /// <summary>
+ /// Spoof HTTP request information for signing/verification purposes.
+ /// </summary>
+ /// <param name="message">The message to add a pretend HTTP method to.</param>
+ /// <returns>A spoofed HttpRequestInfo that wraps the new message.</returns>
+ private HttpRequestInfo SpoofHttpMethod(IDirectedProtocolMessage message) {
+ HttpRequestInfo requestInfo = new HttpRequestInfo(message);
+
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null) {
+ string httpMethod = this.GetHttpMethod(signedMessage.HttpMethods);
+ requestInfo.HttpMethod = httpMethod;
+ requestInfo.Url = message.Recipient;
+ signedMessage.HttpMethod = httpMethod;
+ }
+
+ requestInfo.Message = this.CloneSerializedParts(message, requestInfo);
+
+ return requestInfo;
+ }
+
+ private IProtocolMessage AwaitIncomingMessage() {
+ this.incomingMessageSignal.WaitOne();
+ IProtocolMessage response = this.incomingMessage;
+ this.incomingMessage = null;
+ return response;
+ }
+
+ private Response AwaitIncomingRawResponse() {
+ this.incomingMessageSignal.WaitOne();
+ Response response = this.incomingRawResponse;
+ this.incomingRawResponse = null;
+ return response;
+ }
+
+ private T CloneSerializedParts<T>(T message, HttpRequestInfo requestInfo) where T : class, IProtocolMessage {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ MessageReceivingEndpoint recipient = null;
+ IOAuthDirectedMessage directedMessage = message as IOAuthDirectedMessage;
+ if (directedMessage != null && directedMessage.Recipient != null) {
+ recipient = new MessageReceivingEndpoint(directedMessage.Recipient, directedMessage.HttpMethods);
+ }
+
+ MessageSerializer serializer = MessageSerializer.Get(message.GetType());
+ return (T)serializer.Deserialize(serializer.Serialize(message), recipient);
+ }
+
+ private string GetHttpMethod(HttpDeliveryMethods methods) {
+ return (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET";
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Scenarios/Coordinator.cs b/src/DotNetOpenAuth.Test/Scenarios/Coordinator.cs
new file mode 100644
index 0000000..0479092
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Scenarios/Coordinator.cs
@@ -0,0 +1,120 @@
+//-----------------------------------------------------------------------
+// <copyright file="Coordinator.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test.Scenarios {
+ using System;
+ using System.Threading;
+ using DotNetOpenAuth.OAuth;
+ using DotNetOpenAuth.Test.Mocks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ /// <summary>
+ /// Runs a Consumer and Service Provider simultaneously so they can interact in a full simulation.
+ /// </summary>
+ internal class Coordinator {
+ private ConsumerDescription consumerDescription;
+ private ServiceProviderDescription serviceDescription;
+ private Action<WebConsumer> consumerAction;
+ private Action<ServiceProvider> serviceProviderAction;
+
+ /// <summary>Initializes a new instance of the <see cref="Coordinator"/> class.</summary>
+ /// <param name="consumerDescription">The description of the consumer.</param>
+ /// <param name="serviceDescription">The service description that will be used to construct the Consumer and ServiceProvider objects.</param>
+ /// <param name="consumerAction">The code path of the Consumer.</param>
+ /// <param name="serviceProviderAction">The code path of the Service Provider.</param>
+ internal Coordinator(ConsumerDescription consumerDescription, ServiceProviderDescription serviceDescription, Action<WebConsumer> consumerAction, Action<ServiceProvider> serviceProviderAction) {
+ if (consumerDescription == null) {
+ throw new ArgumentNullException("consumerDescription");
+ }
+ if (serviceDescription == null) {
+ throw new ArgumentNullException("serviceDescription");
+ }
+ if (consumerAction == null) {
+ throw new ArgumentNullException("consumerAction");
+ }
+ if (serviceProviderAction == null) {
+ throw new ArgumentNullException("serviceProviderAction");
+ }
+
+ this.consumerDescription = consumerDescription;
+ this.serviceDescription = serviceDescription;
+ this.consumerAction = consumerAction;
+ this.serviceProviderAction = serviceProviderAction;
+ }
+
+ /// <summary>
+ /// Starts the simulation.
+ /// </summary>
+ internal void Run() {
+ // Clone the template signing binding element.
+ var signingElement = this.serviceDescription.CreateTamperProtectionElement();
+ var consumerSigningElement = signingElement.Clone();
+ var spSigningElement = signingElement.Clone();
+
+ // Prepare token managers
+ InMemoryTokenManager consumerTokenManager = new InMemoryTokenManager();
+ InMemoryTokenManager serviceTokenManager = new InMemoryTokenManager();
+ consumerTokenManager.AddConsumer(this.consumerDescription);
+ serviceTokenManager.AddConsumer(this.consumerDescription);
+
+ // Prepare channels that will pass messages directly back and forth.
+ CoordinatingOAuthChannel consumerChannel = new CoordinatingOAuthChannel(consumerSigningElement, true, consumerTokenManager);
+ CoordinatingOAuthChannel serviceProviderChannel = new CoordinatingOAuthChannel(spSigningElement, false, serviceTokenManager);
+ consumerChannel.RemoteChannel = serviceProviderChannel;
+ serviceProviderChannel.RemoteChannel = consumerChannel;
+
+ // Prepare the Consumer and Service Provider objects
+ WebConsumer consumer = new WebConsumer(this.serviceDescription, consumerTokenManager) {
+ OAuthChannel = consumerChannel,
+ ConsumerKey = this.consumerDescription.ConsumerKey,
+ };
+ ServiceProvider serviceProvider = new ServiceProvider(this.serviceDescription, serviceTokenManager) {
+ OAuthChannel = serviceProviderChannel,
+ };
+
+ Thread consumerThread = null, serviceProviderThread = null;
+ Exception failingException = null;
+
+ // Each thread we create needs a surrounding exception catcher so that we can
+ // terminate the other thread and inform the test host that the test failed.
+ Action<Action> safeWrapper = (action) => {
+ try {
+ action();
+ } catch (Exception ex) {
+ // We may be the second thread in an ThreadAbortException, so check the "flag"
+ if (failingException == null) {
+ failingException = ex;
+ if (Thread.CurrentThread == consumerThread) {
+ serviceProviderThread.Abort();
+ } else {
+ consumerThread.Abort();
+ }
+ }
+ }
+ };
+
+ // Run the threads, and wait for them to complete.
+ // If this main thread is aborted (test run aborted), go ahead and abort the other two threads.
+ consumerThread = new Thread(() => { safeWrapper(() => { consumerAction(consumer); }); });
+ serviceProviderThread = new Thread(() => { safeWrapper(() => { serviceProviderAction(serviceProvider); }); });
+ try {
+ consumerThread.Start();
+ serviceProviderThread.Start();
+ consumerThread.Join();
+ serviceProviderThread.Join();
+ } catch (ThreadAbortException) {
+ consumerThread.Abort();
+ serviceProviderThread.Abort();
+ throw;
+ }
+
+ // Use the failing reason of a failing sub-thread as our reason, if anything failed.
+ if (failingException != null) {
+ throw new AssertFailedException("Coordinator thread threw unhandled exception: " + failingException, failingException);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/Settings.StyleCop b/src/DotNetOpenAuth.Test/Settings.StyleCop
new file mode 100644
index 0000000..26389d0
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Settings.StyleCop
@@ -0,0 +1,29 @@
+<StyleCopSettings Version="4.3">
+ <Analyzers>
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules">
+ <Rules>
+ <Rule Name="ElementsMustBeDocumented">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="EnumerationItemsMustBeDocumented">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings />
+ </Analyzer>
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.LayoutRules">
+ <Rules>
+ <Rule Name="SingleLineCommentMustBePrecededByBlankLine">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings />
+ </Analyzer>
+ </Analyzers>
+</StyleCopSettings> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.Test/Test References/DotNetOpenAuth.accessor b/src/DotNetOpenAuth.Test/Test References/DotNetOpenAuth.accessor
new file mode 100644
index 0000000..35adf51
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/Test References/DotNetOpenAuth.accessor
@@ -0,0 +1 @@
+DotNetOpenAuth.dll
diff --git a/src/DotNetOpenAuth.Test/TestBase.cs b/src/DotNetOpenAuth.Test/TestBase.cs
new file mode 100644
index 0000000..9ec0858
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/TestBase.cs
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test {
+ using System.Reflection;
+ using DotNetOpenAuth.OAuth.Messages;
+ using log4net;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ /// <summary>
+ /// The base class that all test classes inherit from.
+ /// </summary>
+ public class TestBase {
+ /// <summary>
+ /// The logger that tests should use.
+ /// </summary>
+ internal static readonly ILog TestLogger = LogManager.GetLogger("DotNetOpenAuth.Test");
+
+ /// <summary>
+ /// Gets or sets the test context which provides
+ /// information about and functionality for the current test run.
+ /// </summary>
+ public TestContext TestContext { get; set; }
+
+ /// <summary>
+ /// The TestInitialize method for the test cases.
+ /// </summary>
+ [TestInitialize]
+ public virtual void SetUp() {
+ log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("DotNetOpenAuth.Test.Logging.config"));
+ MessageBase.LowSecurityMode = true;
+ }
+
+ /// <summary>
+ /// The TestCleanup method for the test cases.
+ /// </summary>
+ [TestCleanup]
+ public virtual void Cleanup() {
+ log4net.LogManager.Shutdown();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.Test/UriUtilTests.cs b/src/DotNetOpenAuth.Test/UriUtilTests.cs
new file mode 100644
index 0000000..3bd0772
--- /dev/null
+++ b/src/DotNetOpenAuth.Test/UriUtilTests.cs
@@ -0,0 +1,21 @@
+//-----------------------------------------------------------------------
+// <copyright file="UriUtilTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Test {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class UriUtilTests {
+ [TestMethod]
+ public void QueryStringContainsOAuthParametersNull() {
+ Assert.IsFalse(UriUtil.QueryStringContainsOAuthParameters(null));
+ }
+ }
+}