diff --git a/test/E2ETests/SmokeTests.cs b/test/E2ETests/SmokeTests.cs new file mode 100644 index 0000000000..b9065eee6d --- /dev/null +++ b/test/E2ETests/SmokeTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using Xunit; + +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() + { + string applicationPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, APP_RELATIVE_PATH)); + Utility.CopyAspNetLoader(applicationPath); + var hostProcess = Utility.StartHeliosHost(applicationPath); + + try + { + var httpClientHandler = new HttpClientHandler(); + var httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(APP_BASE_URL) }; + + //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; + 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); + + //Making a request to a protected resource should automatically redirect to login page + 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); + + //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 generatedUserName = Guid.NewGuid().ToString().Replace("-", string.Empty); + var formParameters = new List> + { + new KeyValuePair("UserName", generatedUserName), + new KeyValuePair("Password", "Password~1"), + new KeyValuePair("ConfirmPassword", "Password~1"), + new KeyValuePair("__RequestVerificationToken", antiForgeryToken), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("/Account/Register", content).Result; + 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")); + + //Making a request to a protected resource that this user does not have access to - should automatically redirect to login page again + 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); + + //Logout from this user session - This should take back to the home page + antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff"); + formParameters = new List> + { + new KeyValuePair("__RequestVerificationToken", antiForgeryToken), + }; + + content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("/Account/LogOff", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + 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); + //Verify cookie cleared on logout + Assert.Null(Utility.GetCookieWithName(httpClientHandler.CookieContainer.GetCookies(new Uri(APP_BASE_URL)), ".AspNet.Microsoft.AspNet.Identity.Security.Application")); + + //Login as an admin user + response = httpClient.GetAsync("/Account/Login").Result; + responseContent = response.Content.ReadAsStringAsync().Result; + antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/Account/Login"); + formParameters = new List> + { + new KeyValuePair("UserName", "Administrator"), + new KeyValuePair("Password", "YouShouldChangeThisPassword1!"), + new KeyValuePair("__RequestVerificationToken", antiForgeryToken), + }; + + content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("/Account/Login", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains(string.Format("Hello {0}!", "Administrator"), responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); + + //Now navigating to the store manager should work fine as this user has the necessary permission to administer the store. + response = httpClient.GetAsync("/StoreManager/").Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Equal(APP_BASE_URL + "StoreManager/", response.RequestMessage.RequestUri.AbsoluteUri); + + //Create an album + var albumName = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 12); + response = httpClient.GetAsync("/StoreManager/create").Result; + responseContent = response.Content.ReadAsStringAsync().Result; + antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/StoreManager/create"); + formParameters = new List> + { + new KeyValuePair("__RequestVerificationToken", antiForgeryToken), + new KeyValuePair("GenreId", "1"), + new KeyValuePair("ArtistId", "1"), + new KeyValuePair("Title", albumName), + new KeyValuePair("Price", "9.99"), + new KeyValuePair("AlbumArtUrl", "TestUrl"), + }; + + 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.Contains(albumName, responseContent); + + //Logout from this user session - This should take back to the home page + antiForgeryToken = Utility.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff"); + formParameters = new List> + { + new KeyValuePair("__RequestVerificationToken", antiForgeryToken), + }; + + content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("/Account/LogOff", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + 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); + //Verify cookie cleared on logout + Assert.Null(Utility.GetCookieWithName(httpClientHandler.CookieContainer.GetCookies(new Uri(APP_BASE_URL)), ".AspNet.Microsoft.AspNet.Identity.Security.Application")); + } + finally + { + //Shutdown the host process + hostProcess.Kill(); + } + } + } +} \ No newline at end of file diff --git a/test/E2ETests/Utility.cs b/test/E2ETests/Utility.cs new file mode 100644 index 0000000000..b466c1ad55 --- /dev/null +++ b/test/E2ETests/Utility.cs @@ -0,0 +1,128 @@ +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 diff --git a/test/E2ETests/project.json b/test/E2ETests/project.json new file mode 100644 index 0000000000..8414f46473 --- /dev/null +++ b/test/E2ETests/project.json @@ -0,0 +1,25 @@ +{ + "version": "0.1-alpha-*", + "commands": { + "test": "Xunit.KRunner" + }, + "dependencies": { + "Xunit.KRunner": "0.1-alpha-*", + "xunit.abstractions": "2.0.0-aspnet-*", + "xunit.assert": "2.0.0-aspnet-*", + "xunit.core": "2.0.0-aspnet-*", + "xunit.execution": "2.0.0-aspnet-*" + }, + "configurations": { + "net45": { + "dependencies": { + "System.Runtime": "", + "System.Runtime.Extensions": "", + "System.Net.Http": "", + "System.Net.Http.WebRequest": "", + "System.Diagnostics.Process": "", + "System.Xml": "" + } + } + } +} \ No newline at end of file