using System;
using NUnit.Framework;
using DotNetMock.Dynamic;
using System.Collections;
using System.IO;

namespace Orciid.Core
{
	/// <summary>
	/// Test class for <see cref="SingleSignOn"/> class.
	/// </summary>
	/// <remarks>
	/// This class contains test methods.  It does not need to be compiled into the package.
	/// </remarks>
	[TestFixture]
	public class TestSingleSignOn
	{
		private string TEMPFILE_TEXTPLAIN = 
@"<singlesignon>
	<site url='http://localhost/test.aspx?user=${user}&amp;token=${token}' 
	  type='text/plain' id='test' timeout='5'>
		<validate parameter='user'>^\w+$</validate>
		<validate parameter='token'>^\d+$</validate>
		<success>^Ok \w+$</success>
		<login from='result'>^Ok (\w+)$</login>
		<attribute name='firstname'>^FirstName (.+)$</attribute>
		<attribute name='lastname'>^LastName (.+)$</attribute>
		<attribute name='role'>^Role (?:([^,]+),)*([^,]+)$</attribute>
	</site>
</singlesignon>";

		private string SUCCESS_RESPONSE_TEXTPLAIN = SingleSignOn.CleanResponse(
@"Ok knab2ar
FirstName Andreas Robert
LastName Knab
Role Faculty,Staff,Administrator");

		private string FAIL_RESPONSE_TEXTPLAIN = SingleSignOn.CleanResponse("Failed");

		private string TEMPFILE_TEXTXML = 
@"<singlesignon>
	<site url='http://localhost/test.aspx?user=${user}&amp;token=${token}' 
	  type='text/xml' id='test' timeout='5'>
		<validate parameter='user'>^\w+$</validate>
		<validate parameter='token'>^\d+$</validate>
		<success>/response/code/text()='Ok'</success>
		<login from='result'>/response/user/text()</login>
		<attribute name='firstname'>/response/attributes/first/text()</attribute>
		<attribute name='lastname'>/response/attributes/last/text()</attribute>
		<attribute name='role'>/response/attributes/role/text()</attribute>
	</site>
</singlesignon>";

		private string SUCCESS_RESPONSE_TEXTXML = SingleSignOn.CleanResponse(
@"<response>
	<code>Ok</code>
	<user>knab2ar</user>
	<attributes>
		<first>Andreas Robert</first>
		<last>Knab</last>
		<role>Faculty</role>
		<role>Staff</role>
		<role>Administrator</role>
	</attributes>
</response>");

		private string FAIL_RESPONSE_TEXTXML = SingleSignOn.CleanResponse(
@"<response>
	<code>Failed</code>
</response>");

		private IDynamicMock mockconfig;
		private IDynamicMock mocktransactionlog;
		private string tempfile;
		private IConfiguration oldconfig;
		private ITransactionLog oldlog;

		private void CreateTempFile(string content)
		{
			tempfile = Path.GetTempFileName();
			StreamWriter writer = new StreamWriter(tempfile);
			writer.WriteLine(content);
			writer.Close();
		}

		private void RemoveTempFile()
		{
			File.Delete(tempfile);
		}

		private Hashtable CreateParameters()
		{
			Hashtable parameters = new Hashtable();
			parameters.Add("user", "andreas");
			parameters.Add("token", "12345");
			return parameters;
		}

		/// <summary>
		/// Test setup
		/// </summary>
		/// <remarks>
		/// Sets up test environment
		/// </remarks>
		[SetUp]
		public void Setup()
		{
			mockconfig = new DynamicMock(typeof(IConfiguration));
			oldconfig = Configuration.SetInstance((IConfiguration)mockconfig.Object);
			mocktransactionlog = new DynamicMock(typeof(ITransactionLog));
			oldlog = TransactionLog.SetInstance((ITransactionLog)mocktransactionlog.Object);
			tempfile = null;
		}

		/// <summary>
		/// Test teardown
		/// </summary>
		/// <remarks>
		/// Tears down test environment
		/// </remarks>
		[TearDown]
		public void TearDown()
		{
			if (tempfile != null)
				RemoveTempFile();
			mockconfig.Verify();
			mocktransactionlog.Verify();
			Configuration.SetInstance(oldconfig);
			TransactionLog.SetInstance(oldlog);
		}

		/// <summary>
		/// Test
		/// </summary>
		/// <remarks>
		/// Tests single sign-on methods in environment where no single sign-on sites are defined.
		/// </remarks>
		[Test]
		public void NoConfigurationTest()
		{
			mockconfig.ExpectAndReturn("ContainsKey", false, "authentication.singlesignonconfig");

			SingleSignOn singlesignon = new SingleSignOn("test", new Hashtable());
			Assert.IsFalse(singlesignon.Valid);
		}

		/// <summary>
		/// Test
		/// </summary>
		/// <remarks>
		/// Tests single sign-on methods in environment where no single sign-on configuration file exists.
		/// </remarks>
		[Test]
		public void NoConfigurationFileTest()
		{
			mocktransactionlog.Expect("AddException");
			mockconfig.ExpectAndReturn("ContainsKey", true, "authentication.singlesignonconfig");
			mockconfig.ExpectAndReturn("GetString", "c:\no.such.file.xml", "authentication.singlesignonconfig");

			SingleSignOn singlesignon = new SingleSignOn("test", new Hashtable());

			Assert.IsFalse(singlesignon.Valid);
		}
	
		/// <summary>
		/// Test
		/// </summary>
		/// <remarks>
		/// Tests reading single sign-on configuration file.
		/// </remarks>
		[Test]
		public void ReadConfigurationFileTest()
		{
			CreateTempFile(TEMPFILE_TEXTPLAIN);
			mockconfig.ExpectAndReturn("ContainsKey", true, "authentication.singlesignonconfig");
			mockconfig.ExpectAndReturn("GetString", tempfile, "authentication.singlesignonconfig");
			
			SingleSignOn singlesignon = new SingleSignOn("test", CreateParameters());

			Assert.IsTrue(singlesignon.Valid);
			Assert.AreEqual("http://localhost/test.aspx?user=${user}&token=${token}", singlesignon.Url);
			Assert.AreEqual("text/plain", singlesignon.Type);
			Assert.AreEqual(5, singlesignon.Timeout);
			Assert.AreEqual(@"^Ok (\w+)$", singlesignon.LoginPattern);
			Assert.AreEqual("result", singlesignon.LoginFrom);
			Hashtable validations = singlesignon.Validations;
			Assert.AreEqual(2, validations.Count);
			Assert.AreEqual(@"^\w+$", validations["user"]);
			Assert.AreEqual(@"^\d+$", validations["token"]);
			ArrayList success = singlesignon.Success;
			Assert.AreEqual(1, success.Count);
			Assert.AreEqual(@"^Ok \w+$", success[0]);
			Hashtable attributepatterns = singlesignon.AttributePatterns;
			Assert.AreEqual(3, attributepatterns.Count);
			Assert.IsTrue(attributepatterns.ContainsKey("firstname"));
			Assert.IsTrue(attributepatterns.ContainsKey("lastname"));
			Assert.IsTrue(attributepatterns.ContainsKey("role"));

			Assert.IsTrue(singlesignon.Valid);
			Assert.AreEqual("http://localhost/test.aspx?user=andreas&token=12345", singlesignon.RequestUrl);
		}

		/// <summary>
		/// Test
		/// </summary>
		/// <remarks>
		/// Tests generation of request URL
		/// </remarks>
		[Test]
		public void GenerateRequestUrlTest()
		{
			CreateTempFile(TEMPFILE_TEXTPLAIN);
			mockconfig.ExpectAndReturn("ContainsKey", true, "authentication.singlesignonconfig");
			mockconfig.ExpectAndReturn("GetString", tempfile, "authentication.singlesignonconfig");
			
			SingleSignOn singlesignon = new SingleSignOn("test", CreateParameters());
			
			Assert.IsTrue(singlesignon.Valid);
			Assert.AreEqual("http://localhost/test.aspx?user=andreas&token=12345", singlesignon.RequestUrl);

			RemoveTempFile();
		}

		/// <summary>
		/// Test
		/// </summary>
		/// <remarks>
		/// Tests plain text response
		/// </remarks>
		[Test]
		public void CheckTextPlainTest()
		{
			CreateTempFile(TEMPFILE_TEXTPLAIN);
			mockconfig.ExpectAndReturn("ContainsKey", true, "authentication.singlesignonconfig");
			mockconfig.ExpectAndReturn("GetString", tempfile, "authentication.singlesignonconfig");

			SingleSignOn singlesignon = new SingleSignOn("test", CreateParameters());

			Assert.IsTrue(singlesignon.CheckResponseSuccess(SUCCESS_RESPONSE_TEXTPLAIN));
			Assert.IsFalse(singlesignon.CheckResponseSuccess(FAIL_RESPONSE_TEXTPLAIN));

			Assert.AreEqual("knab2ar", singlesignon.GetLoginFromResponse(SUCCESS_RESPONSE_TEXTPLAIN));
			try
			{
				singlesignon.GetLoginFromResponse(FAIL_RESPONSE_TEXTPLAIN);
				Assert.Fail("Should have raised exception");
			}
			catch (CoreException)
			{
			}

			Hashtable attributes = singlesignon.GetAttributesFromResponse(SUCCESS_RESPONSE_TEXTPLAIN);
			Assert.IsNotNull(attributes);
			Assert.AreEqual(3, attributes.Count);
			Assert.IsTrue(attributes.ContainsKey("firstname"));
			Assert.IsTrue(attributes.ContainsKey("lastname"));
			Assert.IsTrue(attributes.ContainsKey("role"));
			Assert.AreEqual("Andreas Robert", attributes["firstname"]);
			Assert.AreEqual("Knab", attributes["lastname"]);
			string[] roles = ((string)attributes["role"]).Split('\n');
			Array.Sort(roles);
			Assert.AreEqual(3, roles.Length);
			Assert.AreEqual("Administrator", roles[0]);
			Assert.AreEqual("Faculty", roles[1]);
			Assert.AreEqual("Staff", roles[2]);

			Assert.IsNull(singlesignon.GetAttributesFromResponse(FAIL_RESPONSE_TEXTPLAIN));
		}

		/// <summary>
		/// Test
		/// </summary>
		/// <remarks>
		/// Tests XML response
		/// </remarks>
		[Test]
		public void CheckTextXmlTest()
		{
			CreateTempFile(TEMPFILE_TEXTXML);
			mockconfig.ExpectAndReturn("ContainsKey", true, "authentication.singlesignonconfig");
			mockconfig.ExpectAndReturn("GetString", tempfile, "authentication.singlesignonconfig");

			SingleSignOn singlesignon = new SingleSignOn("test", CreateParameters());

			Assert.IsTrue(singlesignon.CheckResponseSuccess(SUCCESS_RESPONSE_TEXTXML));
			Assert.IsFalse(singlesignon.CheckResponseSuccess(FAIL_RESPONSE_TEXTXML));

			Assert.AreEqual("knab2ar", singlesignon.GetLoginFromResponse(SUCCESS_RESPONSE_TEXTXML));
			try
			{
				singlesignon.GetLoginFromResponse(FAIL_RESPONSE_TEXTXML);
				Assert.Fail("Should have raised exception");
			}
			catch (CoreException)
			{
			}

			Assert.IsNull(singlesignon.GetAttributesFromResponse(FAIL_RESPONSE_TEXTXML));

			Hashtable attributes = singlesignon.GetAttributesFromResponse(SUCCESS_RESPONSE_TEXTXML);
			Assert.IsNotNull(attributes);
			Assert.AreEqual(3, attributes.Count);
			Assert.IsTrue(attributes.ContainsKey("firstname"));
			Assert.IsTrue(attributes.ContainsKey("lastname"));
			Assert.IsTrue(attributes.ContainsKey("role"));
			Assert.AreEqual("Andreas Robert", attributes["firstname"]);
			Assert.AreEqual("Knab", attributes["lastname"]);
			string[] roles = ((string)attributes["role"]).Split('\n');
			Array.Sort(roles);
			Assert.AreEqual(3, roles.Length);
			Assert.AreEqual("Administrator", roles[0]);
			Assert.AreEqual("Faculty", roles[1]);
			Assert.AreEqual("Staff", roles[2]);
		}
	}
}
