diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-11-08 19:11:56 -0800 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-11-08 19:11:56 -0800 |
commit | 9ebbf7e45657fca07ec70374bab820f21c964e47 (patch) | |
tree | ad5a342117750d7ec537b68cda1d0e4e78613c10 /src | |
parent | e16b0c1bfd1825485cb2c8461655e884f0ecac5a (diff) | |
download | DotNetOpenAuth-9ebbf7e45657fca07ec70374bab820f21c964e47.zip DotNetOpenAuth-9ebbf7e45657fca07ec70374bab820f21c964e47.tar.gz DotNetOpenAuth-9ebbf7e45657fca07ec70374bab820f21c964e47.tar.bz2 |
Added tests for OpenIdChannel and KeyValueForm encoder.
Diffstat (limited to 'src')
7 files changed, 336 insertions, 30 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index ca1e93c..18cd704 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -92,17 +92,19 @@ <Compile Include="OAuth\ConsumerDescription.cs" /> <Compile Include="OAuth\ProtocolTests.cs" /> <Compile Include="OAuth\ServiceProviderDescriptionTests.cs" /> + <Compile Include="OpenId\ChannelElements\KeyValueFormEncodingTests.cs" /> <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateUnsuccessfulResponseTests.cs" /> <Compile Include="OpenId\Messages\AssociateUnencryptedResponseTests.cs" /> - <Compile Include="OpenId\OpenIdChannelTests.cs" /> + <Compile Include="OpenId\ChannelElements\OpenIdChannelTests.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="TestUtilities.cs" /> <Compile Include="UriUtilTests.cs" /> </ItemGroup> <ItemGroup> diff --git a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs index d7092b4..7117bc1 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs @@ -15,8 +15,14 @@ namespace DotNetOpenAuth.Test.Mocks { internal class TestWebRequestHandler : IWebRequestHandler { private StringBuilder postEntity; + /// <summary> + /// Gets or sets the callback used to provide the mock response for the mock request. + /// </summary> internal Func<HttpWebRequest, Response> Callback { get; set; } + /// <summary> + /// Gets the stream that was written out as if on an HTTP request. + /// </summary> internal Stream RequestEntityStream { get { if (this.postEntity == null) { @@ -26,13 +32,37 @@ namespace DotNetOpenAuth.Test.Mocks { } } + /// <summary> + /// Gets the stream that was written out as if on an HTTP request as an ordinary string. + /// </summary> + internal string RequestEntityAsString { + get { + return this.postEntity != null ? this.postEntity.ToString() : null; + } + } + #region IWebRequestHandler Members + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> public TextWriter GetRequestStream(HttpWebRequest request) { this.postEntity = new StringBuilder(); return new StringWriter(this.postEntity); } + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="Response"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <returns> + /// An instance of <see cref="Response"/> describing the response. + /// </returns> public Response GetResponse(HttpWebRequest request) { if (this.Callback == null) { throw new InvalidOperationException("Set the Callback property first."); diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/KeyValueFormEncodingTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/KeyValueFormEncodingTests.cs new file mode 100644 index 0000000..e625499 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/KeyValueFormEncodingTests.cs @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------- +// <copyright file="KeyValueFormEncodingTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.ChannelElements; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class KeyValueFormEncodingTests : TestBase { + private Dictionary<string, string> sampleData = new Dictionary<string, string> { + { "key1", "value1" }, + { "key2", "value2:a" }, + { "Key3", "Value3" }, + }; + + private KeyValueFormEncoding keyValueForm = new KeyValueFormEncoding(); + + [Flags] + public enum TestMode { + Encoder = 0x1, + Decoder = 0x2, + Both = 0x3, + } + + [TestMethod] + public void BasicEncodingTest() { + var kvf = new KeyValueFormEncoding(); + + byte[] kvfBytes = kvf.GetBytes(this.sampleData); + string responseString = Encoding.UTF8.GetString(kvfBytes); + + Assert.IsFalse(responseString.Contains("\n\n")); + Assert.IsTrue(responseString.EndsWith("\n", StringComparison.Ordinal)); + int count = 0; + foreach (string line in responseString.Split('\n')) { + if (line.Length == 0) { + break; + } + int colon = line.IndexOf(':'); + Assert.IsTrue(colon > 0); + string key = line.Substring(0, colon); + string value = line.Substring(colon + 1); + Assert.AreEqual(this.sampleData[key], value); + count++; + } + + Assert.AreEqual(this.sampleData.Count, count); + } + + public void KVDictTest(byte[] kvform, IDictionary<string, string> dict, TestMode mode) { + if ((mode & TestMode.Decoder) == TestMode.Decoder) { + var d = this.keyValueForm.GetDictionary(new MemoryStream(kvform)); + foreach (string key in dict.Keys) { + Assert.AreEqual(d[key], dict[key], "Decoder fault: " + d[key] + " and " + dict[key] + " do not match."); + } + } + if ((mode & TestMode.Encoder) == TestMode.Encoder) { + var e = this.keyValueForm.GetBytes(dict); + Assert.IsTrue(TestUtilities.AreEquivalent(e, kvform), "Encoder did not produced expected result."); + } + } + + [TestMethod] + public void EncodeDecode() { + this.KVDictTest(UTF8Encoding.UTF8.GetBytes(string.Empty), new Dictionary<string, string>(), TestMode.Both); + + Dictionary<string, string> d1 = new Dictionary<string, string>(); + d1.Add("college", "harvey mudd"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes("college:harvey mudd\n"), d1, TestMode.Both); + + Dictionary<string, string> d2 = new Dictionary<string, string>(); + d2.Add("city", "claremont"); + d2.Add("state", "CA"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes("city:claremont\nstate:CA\n"), d2, TestMode.Both); + + Dictionary<string, string> d3 = new Dictionary<string, string>(); + d3.Add("is_valid", "true"); + d3.Add("invalidate_handle", "{HMAC-SHA1:2398410938412093}"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes("is_valid:true\ninvalidate_handle:{HMAC-SHA1:2398410938412093}\n"), d3, TestMode.Both); + + Dictionary<string, string> d4 = new Dictionary<string, string>(); + d4.Add(string.Empty, string.Empty); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes(":\n"), d4, TestMode.Both); + + Dictionary<string, string> d5 = new Dictionary<string, string>(); + d5.Add(string.Empty, "missingkey"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes(":missingkey\n"), d5, TestMode.Both); + + Dictionary<string, string> d6 = new Dictionary<string, string>(); + d6.Add("street", "foothill blvd"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes("street:foothill blvd\n"), d6, TestMode.Both); + + Dictionary<string, string> d7 = new Dictionary<string, string>(); + d7.Add("major", "computer science"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes("major:computer science\n"), d7, TestMode.Both); + + Dictionary<string, string> d8 = new Dictionary<string, string>(); + d8.Add("dorm", "east"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes(" dorm : east \n"), d8, TestMode.Decoder); + + Dictionary<string, string> d9 = new Dictionary<string, string>(); + d9.Add("e^(i*pi)+1", "0"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes("e^(i*pi)+1:0"), d9, TestMode.Decoder); + + Dictionary<string, string> d10 = new Dictionary<string, string>(); + d10.Add("east", "west"); + d10.Add("north", "south"); + this.KVDictTest(UTF8Encoding.UTF8.GetBytes("east:west\nnorth:south"), d10, TestMode.Decoder); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void NoValue() { + this.Illegal("x\n", KeyValueFormConformanceLevel.OpenId11); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void NoValueLoose() { + Dictionary<string, string> d = new Dictionary<string, string>(); + this.KVDictTest(Encoding.UTF8.GetBytes("x\n"), d, TestMode.Decoder); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void EmptyLine() { + this.Illegal("x:b\n\n", KeyValueFormConformanceLevel.OpenId20); + } + + [TestMethod] + public void EmptyLineLoose() { + Dictionary<string, string> d = new Dictionary<string, string>(); + d.Add("x", "b"); + this.KVDictTest(Encoding.UTF8.GetBytes("x:b\n\n"), d, TestMode.Decoder); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void LastLineNotTerminated() { + this.Illegal("x:y\na:b", KeyValueFormConformanceLevel.OpenId11); + } + + [TestMethod] + public void LastLineNotTerminatedLoose() { + Dictionary<string, string> d = new Dictionary<string, string>(); + d.Add("x", "y"); + d.Add("a", "b"); + this.KVDictTest(Encoding.UTF8.GetBytes("x:y\na:b"), d, TestMode.Decoder); + } + + private void Illegal(string s, KeyValueFormConformanceLevel level) { + new KeyValueFormEncoding(level).GetDictionary(new MemoryStream(Encoding.UTF8.GetBytes(s))); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs new file mode 100644 index 0000000..898e1ce --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdChannelTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId.ChannelElements { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId.ChannelElements; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class OpenIdChannelTests : TestBase { + private OpenIdChannel channel; + private OpenIdChannel_Accessor accessor; + private Mocks.TestWebRequestHandler webHandler; + + [TestInitialize] + public void Setup() { + this.webHandler = new Mocks.TestWebRequestHandler(); + this.channel = new OpenIdChannel(); + this.accessor = OpenIdChannel_Accessor.AttachShadow(this.channel); + this.channel.WebRequestHandler = this.webHandler; + } + + [TestMethod] + public void Ctor() { + } + + /// <summary> + /// Verifies that the channel sends direct message requests as HTTP POST requests. + /// </summary> + [TestMethod] + public void DirectRequestsUsePost() { + IDirectedProtocolMessage requestMessage = new Mocks.TestDirectedMessage(MessageTransport.Direct) { + Recipient = new Uri("http://host"), + Name = "Andrew", + }; + HttpWebRequest httpRequest = this.accessor.CreateHttpRequest(requestMessage); + Assert.AreEqual("POST", httpRequest.Method); + StringAssert.Contains(this.webHandler.RequestEntityAsString, "Name=Andrew"); + } + + /// <summary> + /// Verifies that direct response messages are encoded using Key Value Form. + /// </summary> + /// <remarks> + /// The validity of the actual KVF encoding is not checked here. We assume that the KVF encoding + /// class is verified elsewhere. We're only checking that the KVF class is being used by the + /// <see cref="OpenIdChannel.SendDirectMessageResponse"/> method. + /// </remarks> + [TestMethod] + public void DirectResponsesSentUsingKeyValueForm() { + IProtocolMessage message = MessagingTestBase.GetStandardTestMessage(MessagingTestBase.FieldFill.AllRequired); + MessageDictionary messageFields = new MessageDictionary(message); + byte[] expectedBytes = new KeyValueFormEncoding().GetBytes(messageFields); + string expectedContentType = OpenIdChannel_Accessor.KeyValueFormContentType; + + Response directResponse = this.accessor.SendDirectMessageResponse(message); + Assert.AreEqual(expectedContentType, directResponse.Headers[HttpResponseHeader.ContentType]); + byte[] actualBytes = new byte[directResponse.ResponseStream.Length]; + directResponse.ResponseStream.Read(actualBytes, 0, actualBytes.Length); + Assert.IsTrue(TestUtilities.AreEquivalent(expectedBytes, actualBytes)); + } + + /// <summary> + /// Verifies that direct message responses are read in using the Key Value Form decoder. + /// </summary> + [TestMethod] + public void DirectResponsesReceivedAsKeyValueForm() { + var fields = new Dictionary<string, string> { + { "var1", "value1" }, + { "var2", "value2" }, + }; + KeyValueFormEncoding kvf = new KeyValueFormEncoding(); + Response response = new Response { + ResponseStream = new MemoryStream(kvf.GetBytes(fields)), + }; + Assert.IsTrue(TestUtilities.AreEquivalent(fields, this.accessor.ReadFromResponseInternal(response))); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdChannelTests.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdChannelTests.cs deleted file mode 100644 index 0843601..0000000 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdChannelTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="OpenIdChannelTests.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.Test.OpenId { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using DotNetOpenAuth.OpenId.ChannelElements; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - [TestClass] - public class OpenIdChannelTests : TestBase { - private OpenIdChannel channel; - - [TestInitialize] - public void Setup() { - this.channel = new OpenIdChannel(); - } - - [TestMethod] - public void Ctor() { - } - } -} diff --git a/src/DotNetOpenAuth.Test/TestUtilities.cs b/src/DotNetOpenAuth.Test/TestUtilities.cs new file mode 100644 index 0000000..3b70cc2 --- /dev/null +++ b/src/DotNetOpenAuth.Test/TestUtilities.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="TestUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using System; + using System.Collections.Generic; + using System.Linq; + + /// <summary> + /// An assortment of methods useful for testing. + /// </summary> + internal class TestUtilities { + /// <summary> + /// Tests whether two arrays are equal in length and contents. + /// </summary> + /// <typeparam name="T">The type of elements in the arrays.</typeparam> + /// <param name="first">The first array to test. May not be null.</param> + /// <param name="second">The second array to test. May not be null.</param> + /// <returns>True if the arrays equal; false otherwise.</returns> + public static bool AreEquivalent<T>(T[] first, T[] second) { + if (first == null) { + throw new ArgumentNullException("first"); + } + if (second == null) { + throw new ArgumentNullException("second"); + } + if (first.Length != second.Length) { + return false; + } + for (int i = 0; i < first.Length; i++) { + if (!first[i].Equals(second[i])) { + return false; + } + } + return true; + } + + public static bool AreEquivalent<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second) { + return AreEquivalent(first.ToArray(), second.ToArray()); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs index e13e09c..c514baf 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs @@ -20,6 +20,12 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <summary> /// The HTTP Content-Type to use in Key-Value Form responses. /// </summary> + /// <remarks> + /// OpenID 2.0 section 5.1.2 says this SHOULD be text/plain. But this value + /// does not prevent free hosters like GoDaddy from tacking on their ads + /// to the end of the direct response, corrupting the data. So we deviate + /// from the spec a bit here to improve the story for free Providers. + /// </remarks> private const string KeyValueFormContentType = "application/x-openid-kvf"; /// <summary> @@ -89,7 +95,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { byte[] keyValueEncoding = this.keyValueForm.GetBytes(fields); Response preparedResponse = new Response(); - preparedResponse.Headers.Add(HttpResponseHeader.ContentType, "application/x-openid-kvf"); + preparedResponse.Headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType); preparedResponse.OriginalMessage = response; preparedResponse.ResponseStream = new MemoryStream(keyValueEncoding); |