diff --git a/test/E2ETests/DeploymentUtility.cs b/test/E2ETests/DeploymentUtility.cs new file mode 100644 index 0000000000..2bfa7241af --- /dev/null +++ b/test/E2ETests/DeploymentUtility.cs @@ -0,0 +1,99 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; + +namespace E2ETests +{ + internal class DeploymentUtility + { + private static string GetIISExpressPath() + { + var iisExpressPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "IIS Express", "iisexpress.exe"); + + //If X86 version does not exist + if (!File.Exists(iisExpressPath)) + { + iisExpressPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "IIS Express", "iisexpress.exe"); + + if (!File.Exists(iisExpressPath)) + { + throw new Exception("Unable to find IISExpress on the machine"); + } + } + + return iisExpressPath; + } + + /// + /// Copy AspNet.Loader.dll to bin folder + /// + /// + private static void CopyAspNetLoader(string applicationPath) + { + string packagesDirectory = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @"..\..\Packages")); + var aspNetLoaderSrcPath = Path.Combine(Directory.GetDirectories(packagesDirectory, "Microsoft.AspNet.Loader.IIS.Interop.*").First(), @"tools\AspNet.Loader.dll"); + var aspNetLoaderDestPath = Path.Combine(applicationPath, @"bin\AspNet.Loader.dll"); + if (!File.Exists(aspNetLoaderDestPath)) + { + File.Copy(aspNetLoaderSrcPath, aspNetLoaderDestPath); + } + } + + private const string APP_RELATIVE_PATH = @"..\..\src\MusicStore\"; + + public static Process StartApplication(HostType hostType, KreFlavor kreFlavor) + { + string applicationPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, APP_RELATIVE_PATH)); + + if (hostType == HostType.Helios) + { + return StartHeliosHost(applicationPath); + } + else + { + throw new NotImplementedException("Self-Host variation not implemented"); + } + } + + private static Process StartHeliosHost(string applicationPath) + { + CopyAspNetLoader(applicationPath); + + var startInfo = new ProcessStartInfo + { + FileName = GetIISExpressPath(), + Arguments = string.Format("/port:5001 /path:{0}", applicationPath), + UseShellExecute = true, + CreateNoWindow = true + }; + + var hostProcess = Process.Start(startInfo); + Console.WriteLine("Started iisexpress. Process Id : {0}", hostProcess.Id); + Thread.Sleep(2 * 1000); + + return hostProcess; + } + + //private static Process StartSelfHost(string applicationPath) + //{ + // var klrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @".kre\packges", "KRE-svr50-x86.0.1-alpha-build-0450", @"bin\klr.exe"); + // Console.WriteLine(klrPath); + + // var startInfo = new ProcessStartInfo + // { + // FileName = klrPath, + // Arguments = string.Format("--appbase {0} \"Microsoft.Framework.ApplicationHost\" web", applicationPath), + // UseShellExecute = true, + // CreateNoWindow = true + // }; + + // var hostProcess = Process.Start(startInfo); + // Console.WriteLine("Started klr.exe. Process Id : {0}", hostProcess.Id); + // Thread.Sleep(10 * 1000); + + // return hostProcess; + //} + } +} \ No newline at end of file diff --git a/test/E2ETests/Extensions.cs b/test/E2ETests/Extensions.cs new file mode 100644 index 0000000000..0f901fe922 --- /dev/null +++ b/test/E2ETests/Extensions.cs @@ -0,0 +1,20 @@ +using System.Net; + +namespace System.Net +{ + public static class Extensions + { + public static Cookie GetCookieWithName(this CookieCollection cookieCollection, string cookieName) + { + foreach (Cookie cookie in cookieCollection) + { + if (cookie.Name == cookieName) + { + return cookie; + } + } + + return null; + } + } +} diff --git a/test/E2ETests/HostType.cs b/test/E2ETests/HostType.cs new file mode 100644 index 0000000000..5587a1d475 --- /dev/null +++ b/test/E2ETests/HostType.cs @@ -0,0 +1,8 @@ +namespace E2ETests +{ + public enum HostType + { + Helios, + SelfHost + } +} diff --git a/test/E2ETests/HtmlDOMHelper.cs b/test/E2ETests/HtmlDOMHelper.cs new file mode 100644 index 0000000000..126a36fb3e --- /dev/null +++ b/test/E2ETests/HtmlDOMHelper.cs @@ -0,0 +1,60 @@ +using System; +using System.Xml; + +namespace E2ETests +{ + public class HtmlDOMHelper + { + public static string RetrieveAntiForgeryToken(string htmlContent, string actionUrl) + { + int startSearchIndex = 0; + + while (startSearchIndex < htmlContent.Length) + { + var antiForgeryToken = RetrieveAntiForgeryToken(htmlContent, actionUrl, ref startSearchIndex); + + if (antiForgeryToken != null) + { + return antiForgeryToken; + } + } + + return string.Empty; + } + + private static string RetrieveAntiForgeryToken(string htmlContent, string actionLocation, ref int startIndex) + { + var formStartIndex = htmlContent.IndexOf("", startIndex, StringComparison.OrdinalIgnoreCase); + + if (formStartIndex == -1 || formEndIndex == -1) + { + //Unable to find the form start or end - finish the search + startIndex = htmlContent.Length; + return null; + } + + formEndIndex = formEndIndex + "".Length; + startIndex = formEndIndex + 1; + + var htmlDocument = new XmlDocument(); + htmlDocument.LoadXml(htmlContent.Substring(formStartIndex, formEndIndex - formStartIndex)); + + foreach (XmlAttribute attribute in htmlDocument.DocumentElement.Attributes) + { + if (string.Compare(attribute.Name, "action", true) == 0 && attribute.Value.EndsWith(actionLocation, StringComparison.OrdinalIgnoreCase)) + { + foreach (XmlNode input in htmlDocument.GetElementsByTagName("input")) + { + if (input.Attributes["name"].Value == "__RequestVerificationToken" && input.Attributes["type"].Value == "hidden") + { + return input.Attributes["value"].Value; + } + } + } + } + + return null; + } + } +} diff --git a/test/E2ETests/KreFlavor.cs b/test/E2ETests/KreFlavor.cs new file mode 100644 index 0000000000..938aaa2ff6 --- /dev/null +++ b/test/E2ETests/KreFlavor.cs @@ -0,0 +1,8 @@ +namespace E2ETests +{ + public enum KreFlavor + { + DesktopClr, + CoreClr + } +} diff --git a/test/E2ETests/SmokeTests.cs b/test/E2ETests/SmokeTests.cs index bab7a62869..6519644a11 100644 --- a/test/E2ETests/SmokeTests.cs +++ b/test/E2ETests/SmokeTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Net; using System.Net.Http; using Xunit; @@ -9,44 +8,45 @@ namespace E2ETests { public class SmokeTests { - private const string APP_BASE_URL = "http://localhost:5001/"; - private const string APP_RELATIVE_PATH = @"..\..\src\MusicStore\"; - - [Fact] - public void SmokeTestSuite() + [Theory] + [InlineData(HostType.Helios, KreFlavor.DesktopClr, "http://localhost:5001/")] + //[InlineData(HostType.SelfHost, KreFlavor.DesktopClr, "http://localhost:5002/")] + public void SmokeTestSuite(HostType hostType, KreFlavor kreFlavor, string applicationBaseUrl) { - string applicationPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, APP_RELATIVE_PATH)); - Utility.CopyAspNetLoader(applicationPath); - var hostProcess = Utility.StartHeliosHost(applicationPath); + var hostProcess = DeploymentUtility.StartApplication(hostType, kreFlavor); try { var httpClientHandler = new HttpClientHandler(); - var httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(APP_BASE_URL) }; + var httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(applicationBaseUrl) }; //Request to base address and check if various parts of the body are rendered var response = httpClient.GetAsync(string.Empty).Result; var responseContent = response.Content.ReadAsStringAsync().Result; - Console.WriteLine("Response from the server: {0}", responseContent); + Console.WriteLine("Home page content : {0}", responseContent); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Contains("ASP.NET MVC Music Store", responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("Register", responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("Login", responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("mvcmusicstore.codeplex.com", responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("/Images/home-showcase.png", responseContent, StringComparison.OrdinalIgnoreCase); + Console.WriteLine("Application initialization successful."); //Making a request to a protected resource should automatically redirect to login page + Console.WriteLine("Trying to access StoreManager without signing in.."); response = httpClient.GetAsync("/StoreManager/").Result; responseContent = response.Content.ReadAsStringAsync().Result; Assert.Contains("

Use a local account to log in.

", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Equal(APP_BASE_URL + "Account/Login?ReturnUrl=%2FStoreManager%2F", response.RequestMessage.RequestUri.AbsoluteUri); + Assert.Equal(applicationBaseUrl + "Account/Login?ReturnUrl=%2FStoreManager%2F", response.RequestMessage.RequestUri.AbsoluteUri); + Console.WriteLine("Redirected to login page as expected."); //Register a user - Need a way to get the antiforgery token and send it in the request as a form encoded parameter response = httpClient.GetAsync("/Account/Register").Result; responseContent = response.Content.ReadAsStringAsync().Result; - var antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/Account/Register"); + var antiForgeryToken = HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register"); var generatedUserName = Guid.NewGuid().ToString().Replace("-", string.Empty); + Console.WriteLine("Creating a new user with name '{0}'", generatedUserName); var formParameters = new List> { new KeyValuePair("UserName", generatedUserName), @@ -60,18 +60,21 @@ namespace E2ETests responseContent = response.Content.ReadAsStringAsync().Result; Assert.Contains(string.Format("Hello {0}!", generatedUserName), responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); - //Verify cookie sent - Assert.NotNull(Utility.GetCookieWithName(httpClientHandler.CookieContainer.GetCookies(new Uri(APP_BASE_URL)), ".AspNet.Microsoft.AspNet.Identity.Security.Application")); + Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(applicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Security.Application")); + Console.WriteLine("Successfully registered user '{0}' and signed in", generatedUserName); //Making a request to a protected resource that this user does not have access to - should automatically redirect to login page again + Console.WriteLine("Trying to access StoreManager that needs special permissions that {0} does not claim", generatedUserName); response = httpClient.GetAsync("/StoreManager/").Result; responseContent = response.Content.ReadAsStringAsync().Result; Assert.Contains("

Use a local account to log in.

", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Equal(APP_BASE_URL + "Account/Login?ReturnUrl=%2FStoreManager%2F", response.RequestMessage.RequestUri.AbsoluteUri); + Assert.Equal(applicationBaseUrl + "Account/Login?ReturnUrl=%2FStoreManager%2F", response.RequestMessage.RequestUri.AbsoluteUri); + Console.WriteLine("Redirected to login page as expected."); //Logout from this user session - This should take back to the home page - antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff"); + Console.WriteLine("Signing out from '{0}''s session", generatedUserName); + antiForgeryToken = HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff"); formParameters = new List> { new KeyValuePair("__RequestVerificationToken", antiForgeryToken), @@ -86,12 +89,14 @@ namespace E2ETests Assert.Contains("mvcmusicstore.codeplex.com", responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("/Images/home-showcase.png", responseContent, StringComparison.OrdinalIgnoreCase); //Verify cookie cleared on logout - Assert.Null(Utility.GetCookieWithName(httpClientHandler.CookieContainer.GetCookies(new Uri(APP_BASE_URL)), ".AspNet.Microsoft.AspNet.Identity.Security.Application")); + Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(applicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Security.Application")); + Console.WriteLine("Successfully signed out of '{0}''s session"); //Login as an admin user + Console.WriteLine("Signing in as '{0}'", "Administrator"); response = httpClient.GetAsync("/Account/Login").Result; responseContent = response.Content.ReadAsStringAsync().Result; - antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/Account/Login"); + antiForgeryToken = HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Login"); formParameters = new List> { new KeyValuePair("UserName", "Administrator"), @@ -104,17 +109,21 @@ namespace E2ETests responseContent = response.Content.ReadAsStringAsync().Result; Assert.Contains(string.Format("Hello {0}!", "Administrator"), responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); + Console.WriteLine("Successfully signed in as '{0}'", "Administrator"); //Now navigating to the store manager should work fine as this user has the necessary permission to administer the store. + Console.WriteLine("Trying to access the store inventory.."); response = httpClient.GetAsync("/StoreManager/").Result; responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Equal(APP_BASE_URL + "StoreManager/", response.RequestMessage.RequestUri.AbsoluteUri); + Assert.Equal(applicationBaseUrl + "StoreManager/", response.RequestMessage.RequestUri.AbsoluteUri); + Console.WriteLine("Successfully acccessed the store inventory"); //Create an album var albumName = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 12); + Console.WriteLine("Trying to create an album with name '{0}'", albumName); response = httpClient.GetAsync("/StoreManager/create").Result; responseContent = response.Content.ReadAsStringAsync().Result; - antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/StoreManager/create"); + antiForgeryToken = HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/StoreManager/create"); formParameters = new List> { new KeyValuePair("__RequestVerificationToken", antiForgeryToken), @@ -128,11 +137,13 @@ namespace E2ETests content = new FormUrlEncodedContent(formParameters.ToArray()); response = httpClient.PostAsync("/StoreManager/create", content).Result; responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Equal(APP_BASE_URL + "StoreManager", response.RequestMessage.RequestUri.AbsoluteUri); + Assert.Equal(applicationBaseUrl + "StoreManager", response.RequestMessage.RequestUri.AbsoluteUri); Assert.Contains(albumName, responseContent); + Console.WriteLine("Successfully created an album with name '{0}' in the store", albumName); //Logout from this user session - This should take back to the home page - antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff"); + Console.WriteLine("Signing out of '{0}''s session", "Administrator"); + antiForgeryToken = HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff"); formParameters = new List> { new KeyValuePair("__RequestVerificationToken", antiForgeryToken), @@ -147,7 +158,8 @@ namespace E2ETests Assert.Contains("mvcmusicstore.codeplex.com", responseContent, StringComparison.OrdinalIgnoreCase); Assert.Contains("/Images/home-showcase.png", responseContent, StringComparison.OrdinalIgnoreCase); //Verify cookie cleared on logout - Assert.Null(Utility.GetCookieWithName(httpClientHandler.CookieContainer.GetCookies(new Uri(APP_BASE_URL)), ".AspNet.Microsoft.AspNet.Identity.Security.Application")); + Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(applicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Security.Application")); + Console.WriteLine("Successfully signed out of '{0}''s session", "Administrator"); } finally { diff --git a/test/E2ETests/Utility.cs b/test/E2ETests/Utility.cs deleted file mode 100644 index b466c1ad55..0000000000 --- a/test/E2ETests/Utility.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using System.Xml; - -namespace E2ETests -{ - internal class Utility - { - public static string GetIISExpressPath() - { - var iisExpressPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "IIS Express", "iisexpress.exe"); - - //If X86 version does not exist - if (!File.Exists(iisExpressPath)) - { - iisExpressPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "IIS Express", "iisexpress.exe"); - - if (!File.Exists(iisExpressPath)) - { - throw new Exception("Unable to find IISExpress on the machine"); - } - } - - return iisExpressPath; - } - - public static Cookie GetCookieWithName(CookieCollection cookieCollection, string cookieName) - { - foreach (Cookie cookie in cookieCollection) - { - if (cookie.Name == cookieName) - { - return cookie; - } - } - - return null; - } - - public static string RetrieveAntiForgeryToken(string htmlContent, string actionUrl) - { - int startSearchIndex = 0; - - while (startSearchIndex < htmlContent.Length) - { - var antiForgeryToken = RetrieveAntiForgeryToken(htmlContent, actionUrl, ref startSearchIndex); - - if (antiForgeryToken != null) - { - return antiForgeryToken; - } - } - - return string.Empty; - } - - private static string RetrieveAntiForgeryToken(string htmlContent, string actionLocation, ref int startIndex) - { - var formStartIndex = htmlContent.IndexOf("", startIndex, StringComparison.OrdinalIgnoreCase); - - if (formStartIndex == -1 || formEndIndex == -1) - { - //Unable to find the form start or end - finish the search - startIndex = htmlContent.Length; - return null; - } - - formEndIndex = formEndIndex + "".Length; - startIndex = formEndIndex + 1; - - var htmlDocument = new XmlDocument(); - htmlDocument.LoadXml(htmlContent.Substring(formStartIndex, formEndIndex - formStartIndex)); - - foreach (XmlAttribute attribute in htmlDocument.DocumentElement.Attributes) - { - if (string.Compare(attribute.Name, "action", true) == 0 && attribute.Value.EndsWith(actionLocation, StringComparison.OrdinalIgnoreCase)) - { - foreach (XmlNode input in htmlDocument.GetElementsByTagName("input")) - { - if (input.Attributes["name"].Value == "__RequestVerificationToken" && input.Attributes["type"].Value == "hidden") - { - return input.Attributes["value"].Value; - } - } - } - } - - return null; - } - - /// - /// Copy AspNet.Loader.dll to bin folder - /// - /// - public static void CopyAspNetLoader(string applicationPath) - { - string packagesDirectory = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, @"..\..\Packages")); - var aspNetLoaderSrcPath = Path.Combine(Directory.GetDirectories(packagesDirectory, "Microsoft.AspNet.Loader.IIS.Interop.*").First(), @"tools\AspNet.Loader.dll"); - var aspNetLoaderDestPath = Path.Combine(applicationPath, @"bin\AspNet.Loader.dll"); - if (!File.Exists(aspNetLoaderDestPath)) - { - File.Copy(aspNetLoaderSrcPath, aspNetLoaderDestPath); - } - } - - public static Process StartHeliosHost(string applicationPath) - { - var startInfo = new ProcessStartInfo - { - FileName = Utility.GetIISExpressPath(), - Arguments = string.Format("/port:5001 /path:{0}", applicationPath), - UseShellExecute = true, - CreateNoWindow = true - }; - - var hostProcess = Process.Start(startInfo); - Console.WriteLine("Started iisexpress. Process Id : {0}", hostProcess.Id); - Thread.Sleep(2 * 1000); - - return hostProcess; - } - } -} \ No newline at end of file