From b56cecc817482d7394a7abdecd04f3c3797947c9 Mon Sep 17 00:00:00 2001 From: Praburaj Date: Fri, 29 Aug 2014 16:35:02 -0700 Subject: [PATCH] Refactoring the test code a little bit to enable adding more scenarios And some general cleanup. --- src/MusicStore/Startup.cs | 15 +- src/MusicStore/project.json | 5 +- test/E2ETests/DeploymentUtility.cs | 45 +++- test/E2ETests/Helpers.cs | 15 ++ test/E2ETests/Scenarios.cs | 389 +++++++++++++++++++++++++++ test/E2ETests/SmokeTests.cs | 406 +---------------------------- test/E2ETests/project.json | 3 +- 7 files changed, 460 insertions(+), 418 deletions(-) create mode 100644 test/E2ETests/Helpers.cs create mode 100644 test/E2ETests/Scenarios.cs diff --git a/src/MusicStore/Startup.cs b/src/MusicStore/Startup.cs index 5f087c1d79..e78599007a 100644 --- a/src/MusicStore/Startup.cs +++ b/src/MusicStore/Startup.cs @@ -16,17 +16,14 @@ namespace MusicStore { public void Configure(IBuilder app) { - /* Adding IConfiguration as a service in the IoC to avoid instantiating Configuration again. - * Below code demonstrates usage of multiple configuration sources. For instance a setting say 'setting1' is found in both the registered sources, - * then the later source will win. By this way a Local config can be overridden by a different setting while deployed remotely. - */ + //Below code demonstrates usage of multiple configuration sources. For instance a setting say 'setting1' is found in both the registered sources, + //then the later source will win. By this way a Local config can be overridden by a different setting while deployed remotely. var configuration = new Configuration(); configuration.AddJsonFile("LocalConfig.json"); configuration.AddEnvironmentVariables(); //All environment variables in the process's context flow in as configuration values. - /* Error page middleware displays a nice formatted HTML page for any unhandled exceptions in the request pipeline. - * Note: ErrorPageOptions.ShowAll to be used only at development time. Not recommended for production. - */ + //Error page middleware displays a nice formatted HTML page for any unhandled exceptions in the request pipeline. + //Note: ErrorPageOptions.ShowAll to be used only at development time. Not recommended for production. app.UseErrorPage(ErrorPageOptions.ShowAll); app.UseServices(services => @@ -87,8 +84,8 @@ namespace MusicStore app.UseMvc(routes => { routes.MapRoute( - name: "areaRoute", - template: "{area:exists}/{controller}/{action}", + name: "areaRoute", + template: "{area:exists}/{controller}/{action}", defaults: new { action = "Index" }); routes.MapRoute( diff --git a/src/MusicStore/project.json b/src/MusicStore/project.json index 3fc574b830..ac33f12227 100644 --- a/src/MusicStore/project.json +++ b/src/MusicStore/project.json @@ -4,6 +4,7 @@ ], "description": "Music store application on K", "version": "1.0.0-*", + "compilationOptions": { "warningsAsErrors": true }, "dependencies": { "Kestrel": "1.0.0-*", "Microsoft.AspNet.Server.IIS": "1.0.0-*", @@ -16,7 +17,7 @@ "Microsoft.AspNet.StaticFiles": "1.0.0-*", "EntityFramework.SqlServer": "7.0.0-*", /*For Mono*/ - "EntityFramework.InMemory": "7.0.0-*", + "EntityFramework.InMemory": "7.0.0-*", "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*", "Microsoft.Framework.OptionsModel": "1.0.0-*" }, @@ -29,4 +30,4 @@ "net451": { }, "aspnetcore50": { } } -} +} \ No newline at end of file diff --git a/test/E2ETests/DeploymentUtility.cs b/test/E2ETests/DeploymentUtility.cs index 79c37ef1bc..d1e29262c9 100644 --- a/test/E2ETests/DeploymentUtility.cs +++ b/test/E2ETests/DeploymentUtility.cs @@ -91,6 +91,14 @@ namespace E2ETests private static Process StartMonoHost(ServerType hostType, string applicationPath) { + //Mono needs this as GetFullPath does not work if it has \ + applicationPath = Path.GetFullPath(applicationPath.Replace('\\', '/')); + + //Mono does not have a way to pass in a --appbase switch. So it will be an environment variable. + //https://github.com/aspnet/KRuntime/issues/580 + Environment.SetEnvironmentVariable("APP_BASE", applicationPath); + Console.WriteLine("Setting the APP_BASE to {0}", applicationPath); + var path = Environment.GetEnvironmentVariable("PATH"); var kreBin = path.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries).Where(c => c.Contains("KRE-mono45")).FirstOrDefault(); @@ -103,13 +111,12 @@ namespace E2ETests var klrMonoManaged = Path.Combine(kreBin, "klr.mono.managed.dll"); var applicationHost = Path.Combine(kreBin, "Microsoft.Framework.ApplicationHost"); - Console.WriteLine(string.Format("Executing command: {0} {1} --appbase {2} {3} {4}", monoPath, klrMonoManaged, applicationPath, applicationHost, hostType.ToString())); + Console.WriteLine(string.Format("Executing command: {0} {1} {3} {4}", monoPath, klrMonoManaged, applicationPath, applicationHost, hostType.ToString())); - //https://github.com/aspnet/KRuntime/issues/580 var startInfo = new ProcessStartInfo { FileName = monoPath, - Arguments = string.Format("{0} --appbase {1} {2} {3}", klrMonoManaged, applicationPath, applicationHost, hostType.ToString()), + Arguments = string.Format("{0} {1} {2}", klrMonoManaged, applicationHost, hostType.ToString()), UseShellExecute = true, CreateNoWindow = true }; @@ -118,6 +125,8 @@ namespace E2ETests Console.WriteLine("Started {0}. Process Id : {1}", hostProcess.MainModule.FileName, hostProcess.Id); Thread.Sleep(5 * 1000); + //Clear the appbase so that it does not create issues with successive runs + Environment.SetEnvironmentVariable("APP_BASE", string.Empty); return hostProcess; } @@ -159,7 +168,7 @@ namespace E2ETests { Console.WriteLine("Started {0}. Process Id : {1}", hostProcess.MainModule.FileName, hostProcess.Id); } - catch (Win32Exception ex) + catch (Win32Exception) { Console.WriteLine("Cannot access 64 bit modules from a 32 bit process"); } @@ -230,5 +239,33 @@ namespace E2ETests dbWatch.Dispose(); } } + + public static void CleanUpApplication(Process hostProcess, string musicStoreDbName) + { + if (hostProcess != null && !hostProcess.HasExited) + { + //Shutdown the host process + hostProcess.Kill(); + hostProcess.WaitForExit(5 * 1000); + if (!hostProcess.HasExited) + { + Console.WriteLine("Unable to terminate the host process with process Id '{0}", hostProcess.Id); + } + else + { + Console.WriteLine("Successfully terminated host process with process Id '{0}'", hostProcess.Id); + } + } + else + { + Console.WriteLine("Host process already exited or never started successfully."); + } + + if (!Helpers.RunningOnMono) + { + //Mono uses InMemoryStore + DbUtils.DropDatabase(musicStoreDbName); + } + } } } \ No newline at end of file diff --git a/test/E2ETests/Helpers.cs b/test/E2ETests/Helpers.cs new file mode 100644 index 0000000000..f2b971b4a6 --- /dev/null +++ b/test/E2ETests/Helpers.cs @@ -0,0 +1,15 @@ +using System; + +namespace E2ETests +{ + public class Helpers + { + public static bool RunningOnMono + { + get + { + return Type.GetType("Mono.Runtime") != null; + } + } + } +} \ No newline at end of file diff --git a/test/E2ETests/Scenarios.cs b/test/E2ETests/Scenarios.cs new file mode 100644 index 0000000000..301874e08c --- /dev/null +++ b/test/E2ETests/Scenarios.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using Xunit; + +namespace E2ETests +{ + public partial class SmokeTests + { + private void VerifyStaticContentServed() + { + Console.WriteLine("Validating if static contents are served.."); + Console.WriteLine("Fetching favicon.ico.."); + var response = httpClient.GetAsync("favicon.ico").Result; + ThrowIfResponseStatusNotOk(response); + Console.WriteLine("Etag received: {0}", response.Headers.ETag.Tag); + Console.WriteLine("Etag received: {0}", response.Headers.ETag.IsWeak); + + //Check if you receive a NotModified on sending an etag + Console.WriteLine("Sending an IfNoneMatch header with e-tag"); + httpClient.DefaultRequestHeaders.IfNoneMatch.Add(response.Headers.ETag); + response = httpClient.GetAsync("favicon.ico").Result; + Assert.Equal(HttpStatusCode.NotModified, response.StatusCode); + httpClient.DefaultRequestHeaders.IfNoneMatch.Clear(); + Console.WriteLine("Successfully received a NotModified status"); + + Console.WriteLine("Fetching /Content/bootstrap.css.."); + response = httpClient.GetAsync("Content/bootstrap.css").Result; + ThrowIfResponseStatusNotOk(response); + Console.WriteLine("Verified static contents are served successfully"); + } + + private void VerifyHomePage(HttpResponseMessage response, string responseContent) + { + Console.WriteLine("Home page content : {0}", responseContent); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + ValidateLayoutPage(responseContent); + Assert.Contains("Home Page – 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."); + } + + private void ValidateLayoutPage(string responseContent) + { + Assert.Contains("ASP.NET MVC Music Store", responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("
  • Home
  • ", responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Store ", responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("
      ", responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("
    • ", responseContent, StringComparison.OrdinalIgnoreCase); + } + + private void AccessStoreWithoutPermissions(string email = null) + { + Console.WriteLine("Trying to access StoreManager that needs ManageStore claim with the current user : {0}", email ?? "Anonymous"); + var response = httpClient.GetAsync("Admin/StoreManager/").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + ValidateLayoutPage(responseContent); + Assert.Contains("Log in – MVC Music Store", responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("

      Use a local account to log in.

      ", responseContent, StringComparison.OrdinalIgnoreCase); + + if (!Helpers.RunningOnMono) + { + //Bug in Mono HttpClient that it does not automatically change the RequestMessage uri in case of a 302. + Assert.Equal(ApplicationBaseUrl + "Account/Login?ReturnUrl=%2FAdmin%2FStoreManager%2F", response.RequestMessage.RequestUri.AbsoluteUri); + } + + Console.WriteLine("Redirected to login page as expected."); + } + + private void AccessStoreWithPermissions() + { + Console.WriteLine("Trying to access the store inventory.."); + var response = httpClient.GetAsync("Admin/StoreManager/").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Equal(ApplicationBaseUrl + "Admin/StoreManager/", response.RequestMessage.RequestUri.AbsoluteUri); + Console.WriteLine("Successfully acccessed the store inventory"); + } + + private void RegisterUserWithNonMatchingPasswords() + { + Console.WriteLine("Trying to create user with not matching password and confirm password"); + var response = httpClient.GetAsync("Account/Register").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + ValidateLayoutPage(responseContent); + + var generatedEmail = Guid.NewGuid().ToString().Replace("-", string.Empty) + "@test.com"; + Console.WriteLine("Creating a new user with name '{0}'", generatedEmail); + var formParameters = new List> + { + new KeyValuePair("Email", generatedEmail), + new KeyValuePair("Password", "Password~1"), + new KeyValuePair("ConfirmPassword", "Password~2"), + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Account/Register", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); + Assert.Contains("
      • The password and confirmation password do not match.
      • ", responseContent, StringComparison.OrdinalIgnoreCase); + Console.WriteLine("Server side model validator rejected the user '{0}''s registration as passwords do not match.", generatedEmail); + } + + private string RegisterValidUser() + { + var response = httpClient.GetAsync("Account/Register").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + ValidateLayoutPage(responseContent); + + var generatedEmail = Guid.NewGuid().ToString().Replace("-", string.Empty) + "@test.com"; + Console.WriteLine("Creating a new user with name '{0}'", generatedEmail); + var formParameters = new List> + { + new KeyValuePair("Email", generatedEmail), + new KeyValuePair("Password", "Password~1"), + new KeyValuePair("ConfirmPassword", "Password~1"), + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Account/Register", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains(string.Format("Hello {0}!", generatedEmail), responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); + //Verify cookie sent + Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); + Console.WriteLine("Successfully registered user '{0}' and signed in", generatedEmail); + return generatedEmail; + } + + private void RegisterExistingUser(string email) + { + Console.WriteLine("Trying to register a user with name '{0}' again", email); + var response = httpClient.GetAsync("Account/Register").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + Console.WriteLine("Creating a new user with name '{0}'", email); + var formParameters = new List> + { + new KeyValuePair("Email", email), + new KeyValuePair("Password", "Password~1"), + new KeyValuePair("ConfirmPassword", "Password~1"), + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Account/Register", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains(string.Format("Name {0} is already taken.", email), responseContent, StringComparison.OrdinalIgnoreCase); + Console.WriteLine("Identity threw a valid exception that user '{0}' already exists in the system", email); + } + + private void SignOutUser(string email) + { + Console.WriteLine("Signing out from '{0}''s session", email); + var response = httpClient.GetAsync(string.Empty).Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + ValidateLayoutPage(responseContent); + var formParameters = new List> + { + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff")), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Account/LogOff", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + + if (!Helpers.RunningOnMono) + { + 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(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); + Console.WriteLine("Successfully signed out of '{0}''s session", email); + } + else + { + //Bug in Mono - on logout the cookie is not cleared in the cookie container and not redirected. Work around by reinstantiating the httpClient. + httpClientHandler = new HttpClientHandler(); + httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(ApplicationBaseUrl) }; + } + } + + private void SignInWithInvalidPassword(string email, string invalidPassword) + { + var response = httpClient.GetAsync("Account/Login").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + Console.WriteLine("Signing in with user '{0}'", email); + var formParameters = new List> + { + new KeyValuePair("Email", email), + new KeyValuePair("Password", invalidPassword), + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Login")), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Account/Login", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains("
        • Invalid login attempt.
        • ", responseContent, StringComparison.OrdinalIgnoreCase); + //Verify cookie not sent + Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); + Console.WriteLine("Identity successfully prevented an invalid user login."); + } + + private void SignInWithUser(string email, string password) + { + var response = httpClient.GetAsync("Account/Login").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + Console.WriteLine("Signing in with user '{0}'", email); + var formParameters = new List> + { + new KeyValuePair("Email", email), + new KeyValuePair("Password", password), + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Login")), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Account/Login", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains(string.Format("Hello {0}!", email), responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); + //Verify cookie sent + Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); + Console.WriteLine("Successfully signed in with user '{0}'", email); + } + + private void ChangePassword(string email) + { + var response = httpClient.GetAsync("Manage/ChangePassword").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + var formParameters = new List> + { + new KeyValuePair("OldPassword", "Password~1"), + new KeyValuePair("NewPassword", "Password~2"), + new KeyValuePair("ConfirmPassword", "Password~2"), + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Manage/ChangePassword")), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Manage/ChangePassword", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains("Your password has been changed.", responseContent, StringComparison.OrdinalIgnoreCase); + Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); + Console.WriteLine("Successfully changed the password for user '{0}'", email); + } + + private string CreateAlbum() + { + var albumName = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 12); + Console.WriteLine("Trying to create an album with name '{0}'", albumName); + var response = httpClient.GetAsync("Admin/StoreManager/create").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + var formParameters = new List> + { + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Admin/StoreManager/create")), + new KeyValuePair("GenreId", "1"), + new KeyValuePair("ArtistId", "1"), + new KeyValuePair("Title", albumName), + new KeyValuePair("Price", "9.99"), + new KeyValuePair("AlbumArtUrl", "http://myapp/testurl"), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Admin/StoreManager/create", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + + if (!Helpers.RunningOnMono) + { + //Bug in mono Httpclient - RequestMessage not automatically changed on 302 + Assert.Equal(ApplicationBaseUrl + "Admin/StoreManager", response.RequestMessage.RequestUri.AbsoluteUri); + } + + Assert.Contains(albumName, responseContent); + Console.WriteLine("Successfully created an album with name '{0}' in the store", albumName); + return albumName; + } + + private string FetchAlbumIdFromName(string albumName) + { + Console.WriteLine("Fetching the album id of '{0}'", albumName); + var response = httpClient.GetAsync(string.Format("Admin/StoreManager/GetAlbumIdFromName?albumName={0}", albumName)).Result; + ThrowIfResponseStatusNotOk(response); + var albumId = response.Content.ReadAsStringAsync().Result; + Console.WriteLine("Album id for album '{0}' is '{1}'", albumName, albumId); + return albumId; + } + + private void VerifyAlbumDetails(string albumId, string albumName) + { + Console.WriteLine("Getting details of album with Id '{0}'", albumId); + var response = httpClient.GetAsync(string.Format("Admin/StoreManager/Details?id={0}", albumId)).Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains(albumName, responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("http://myapp/testurl", responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains(string.Format("Edit", albumId), responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Back to List", responseContent, StringComparison.OrdinalIgnoreCase); + } + + private void AddAlbumToCart(string albumId, string albumName) + { + Console.WriteLine("Adding album id '{0}' to the cart", albumId); + var response = httpClient.GetAsync(string.Format("ShoppingCart/AddToCart?id={0}", albumId)).Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains(albumName, responseContent, StringComparison.OrdinalIgnoreCase); + Assert.Contains("", responseContent, StringComparison.OrdinalIgnoreCase); + Console.WriteLine("Verified that album is added to cart"); + } + + private void CheckOutCartItems() + { + Console.WriteLine("Checking out the cart contents..."); + var response = httpClient.GetAsync("Checkout/AddressAndPayment").Result; + ThrowIfResponseStatusNotOk(response); + var responseContent = response.Content.ReadAsStringAsync().Result; + + var formParameters = new List> + { + new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Checkout/AddressAndPayment")), + new KeyValuePair("FirstName", "FirstNameValue"), + new KeyValuePair("LastName", "LastNameValue"), + new KeyValuePair("Address", "AddressValue"), + new KeyValuePair("City", "Redmond"), + new KeyValuePair("State", "WA"), + new KeyValuePair("PostalCode", "98052"), + new KeyValuePair("Country", "USA"), + new KeyValuePair("Phone", "PhoneValue"), + new KeyValuePair("Email", "email@email.com"), + new KeyValuePair("PromoCode", "FREE"), + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + response = httpClient.PostAsync("Checkout/AddressAndPayment", content).Result; + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains("

          Checkout Complete

          ", responseContent, StringComparison.OrdinalIgnoreCase); + if (!Helpers.RunningOnMono) + { + //Bug in Mono HttpClient that it does not automatically change the RequestMessage uri in case of a 302. + Assert.StartsWith(ApplicationBaseUrl + "Checkout/Complete/", response.RequestMessage.RequestUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase); + } + } + + private void DeleteAlbum(string albumId, string albumName) + { + Console.WriteLine("Deleting album '{0}' from the store..", albumName); + + var formParameters = new List> + { + new KeyValuePair("id", albumId) + }; + + var content = new FormUrlEncodedContent(formParameters.ToArray()); + var response = httpClient.PostAsync("Admin/StoreManager/RemoveAlbum", content).Result; + ThrowIfResponseStatusNotOk(response); + + Console.WriteLine("Verifying if the album '{0}' is deleted from store", albumName); + response = httpClient.GetAsync(string.Format("Admin/StoreManager/GetAlbumIdFromName?albumName={0}", albumName)).Result; + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Console.WriteLine("Album is successfully deleted from the store.", albumName, albumId); + } + + private void ThrowIfResponseStatusNotOk(HttpResponseMessage response) + { + if (response.StatusCode != HttpStatusCode.OK) + { + Console.WriteLine(response.Content.ReadAsStringAsync().Result); + throw new Exception(string.Format("Received the above response with status code : {0}", response.StatusCode.ToString())); + } + } + } +} \ No newline at end of file diff --git a/test/E2ETests/SmokeTests.cs b/test/E2ETests/SmokeTests.cs index bf0911b2b6..c75d89c4a0 100644 --- a/test/E2ETests/SmokeTests.cs +++ b/test/E2ETests/SmokeTests.cs @@ -1,25 +1,17 @@ using System; using System.Diagnostics; -using System.Collections.Generic; -using System.Net; using System.Net.Http; using Xunit; namespace E2ETests { - public class SmokeTests + public partial class SmokeTests { private const string Connection_string_Format = "Server=(localdb)\\MSSQLLocalDB;Database={0};Trusted_Connection=True;MultipleActiveResultSets=true"; private string ApplicationBaseUrl; private HttpClient httpClient; private HttpClientHandler httpClientHandler; - public static bool RunningOnMono; - - public SmokeTests() - { - RunningOnMono = Type.GetType("Mono.Runtime") != null; - } [Theory] [InlineData(ServerType.Helios, KreFlavor.DesktopClr, KreArchitecture.x86, "http://localhost:5001/", false)] @@ -36,7 +28,7 @@ namespace E2ETests { Console.WriteLine("Variation Details : HostType = {0}, KreFlavor = {1}, Architecture = {2}, applicationBaseUrl = {3}", hostType, kreFlavor, architecture, applicationBaseUrl); - if (RunTestOnMono && !RunningOnMono) + if (RunTestOnMono && !Helpers.RunningOnMono) { //Skip Mono variations on Windows Console.WriteLine("Skipping mono variation on .NET"); @@ -44,7 +36,7 @@ namespace E2ETests return; } - if (!RunTestOnMono && RunningOnMono) + if (!RunTestOnMono && Helpers.RunningOnMono) { //Skip .net variations on mono Console.WriteLine("Skipping .NET variation on mono"); @@ -160,397 +152,7 @@ namespace E2ETests Console.WriteLine("Some tests failed. Proceeding with cleanup."); } - if (!RunningOnMono) - { - if (hostProcess != null && !hostProcess.HasExited) - { - //Shutdown the host process - hostProcess.Kill(); - hostProcess.WaitForExit(5 * 1000); - if (!hostProcess.HasExited) - { - Console.WriteLine("Unable to terminate the host process with process Id '{0}", hostProcess.Id); - } - else - { - Console.WriteLine("Successfully terminated host process with process Id '{0}'", hostProcess.Id); - } - } - else - { - Console.WriteLine("Host process already exited or never started successfully."); - } - - DbUtils.DropDatabase(musicStoreDbName); - } - } - } - - private void VerifyStaticContentServed() - { - Console.WriteLine("Validating if static contents are served.."); - Console.WriteLine("Fetching favicon.ico.."); - var response = httpClient.GetAsync("favicon.ico").Result; - ThrowIfResponseStatusNotOk(response); - - Console.WriteLine("Fetching /Content/bootstrap.css.."); - response = httpClient.GetAsync("Content/bootstrap.css").Result; - ThrowIfResponseStatusNotOk(response); - Console.WriteLine("Verified static contents are served successfully"); - } - - private void VerifyHomePage(HttpResponseMessage response, string responseContent) - { - Console.WriteLine("Home page content : {0}", responseContent); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - ValidateLayoutPage(responseContent); - Assert.Contains("Home Page – 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."); - } - - private void ValidateLayoutPage(string responseContent) - { - Assert.Contains("ASP.NET MVC Music Store", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("
        • Home
        • ", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("Store ", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("
            ", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("
          • ", responseContent, StringComparison.OrdinalIgnoreCase); - } - - private void AccessStoreWithoutPermissions(string email = null) - { - Console.WriteLine("Trying to access StoreManager that needs ManageStore claim with the current user : {0}", email ?? "Anonymous"); - var response = httpClient.GetAsync("Admin/StoreManager/").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - ValidateLayoutPage(responseContent); - Assert.Contains("Log in – MVC Music Store", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("

            Use a local account to log in.

            ", responseContent, StringComparison.OrdinalIgnoreCase); - - if (!RunningOnMono) - { - //Bug in Mono HttpClient that it does not automatically change the RequestMessage uri in case of a 302. - Assert.Equal(ApplicationBaseUrl + "Account/Login?ReturnUrl=%2FAdmin%2FStoreManager%2F", response.RequestMessage.RequestUri.AbsoluteUri); - } - - Console.WriteLine("Redirected to login page as expected."); - } - - private void AccessStoreWithPermissions() - { - Console.WriteLine("Trying to access the store inventory.."); - var response = httpClient.GetAsync("Admin/StoreManager/").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Equal(ApplicationBaseUrl + "Admin/StoreManager/", response.RequestMessage.RequestUri.AbsoluteUri); - Console.WriteLine("Successfully acccessed the store inventory"); - } - - private void RegisterUserWithNonMatchingPasswords() - { - Console.WriteLine("Trying to create user with not matching password and confirm password"); - var response = httpClient.GetAsync("Account/Register").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - ValidateLayoutPage(responseContent); - - var generatedEmail = Guid.NewGuid().ToString().Replace("-", string.Empty) + "@test.com"; - Console.WriteLine("Creating a new user with name '{0}'", generatedEmail); - var formParameters = new List> - { - new KeyValuePair("Email", generatedEmail), - new KeyValuePair("Password", "Password~1"), - new KeyValuePair("ConfirmPassword", "Password~2"), - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Account/Register", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); - Assert.Contains("
            • The password and confirmation password do not match.
            • ", responseContent, StringComparison.OrdinalIgnoreCase); - Console.WriteLine("Server side model validator rejected the user '{0}''s registration as passwords do not match.", generatedEmail); - } - - private string RegisterValidUser() - { - var response = httpClient.GetAsync("Account/Register").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - ValidateLayoutPage(responseContent); - - var generatedEmail = Guid.NewGuid().ToString().Replace("-", string.Empty) + "@test.com"; - Console.WriteLine("Creating a new user with name '{0}'", generatedEmail); - var formParameters = new List> - { - new KeyValuePair("Email", generatedEmail), - new KeyValuePair("Password", "Password~1"), - new KeyValuePair("ConfirmPassword", "Password~1"), - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Account/Register", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains(string.Format("Hello {0}!", generatedEmail), responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); - //Verify cookie sent - Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); - Console.WriteLine("Successfully registered user '{0}' and signed in", generatedEmail); - return generatedEmail; - } - - private void RegisterExistingUser(string email) - { - Console.WriteLine("Trying to register a user with name '{0}' again", email); - var response = httpClient.GetAsync("Account/Register").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - Console.WriteLine("Creating a new user with name '{0}'", email); - var formParameters = new List> - { - new KeyValuePair("Email", email), - new KeyValuePair("Password", "Password~1"), - new KeyValuePair("ConfirmPassword", "Password~1"), - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Account/Register", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains(string.Format("Name {0} is already taken.", email), responseContent, StringComparison.OrdinalIgnoreCase); - Console.WriteLine("Identity threw a valid exception that user '{0}' already exists in the system", email); - } - - private void SignOutUser(string email) - { - Console.WriteLine("Signing out from '{0}''s session", email); - var response = httpClient.GetAsync(string.Empty).Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - ValidateLayoutPage(responseContent); - var formParameters = new List> - { - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/LogOff")), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Account/LogOff", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - - if (!RunningOnMono) - { - 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(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); - Console.WriteLine("Successfully signed out of '{0}''s session", email); - } - else - { - //Bug in Mono - on logout the cookie is not cleared in the cookie container and not redirected. Work around by reinstantiating the httpClient. - httpClientHandler = new HttpClientHandler(); - httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(ApplicationBaseUrl) }; - } - } - - private void SignInWithInvalidPassword(string email, string invalidPassword) - { - var response = httpClient.GetAsync("Account/Login").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - Console.WriteLine("Signing in with user '{0}'", email); - var formParameters = new List> - { - new KeyValuePair("Email", email), - new KeyValuePair("Password", invalidPassword), - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Login")), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Account/Login", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains("
              • Invalid login attempt.
              • ", responseContent, StringComparison.OrdinalIgnoreCase); - //Verify cookie not sent - Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); - Console.WriteLine("Identity successfully prevented an invalid user login."); - } - - private void SignInWithUser(string email, string password) - { - var response = httpClient.GetAsync("Account/Login").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - Console.WriteLine("Signing in with user '{0}'", email); - var formParameters = new List> - { - new KeyValuePair("Email", email), - new KeyValuePair("Password", password), - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Login")), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Account/Login", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains(string.Format("Hello {0}!", email), responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); - //Verify cookie sent - Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); - Console.WriteLine("Successfully signed in with user '{0}'", email); - } - - private void ChangePassword(string email) - { - var response = httpClient.GetAsync("Manage/ChangePassword").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - var formParameters = new List> - { - new KeyValuePair("OldPassword", "Password~1"), - new KeyValuePair("NewPassword", "Password~2"), - new KeyValuePair("ConfirmPassword", "Password~2"), - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Manage/ChangePassword")), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Manage/ChangePassword", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains("Your password has been changed.", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); - Console.WriteLine("Successfully changed the password for user '{0}'", email); - } - - private string CreateAlbum() - { - var albumName = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 12); - Console.WriteLine("Trying to create an album with name '{0}'", albumName); - var response = httpClient.GetAsync("Admin/StoreManager/create").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - var formParameters = new List> - { - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Admin/StoreManager/create")), - new KeyValuePair("GenreId", "1"), - new KeyValuePair("ArtistId", "1"), - new KeyValuePair("Title", albumName), - new KeyValuePair("Price", "9.99"), - new KeyValuePair("AlbumArtUrl", "http://myapp/testurl"), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Admin/StoreManager/create", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - - if (!RunningOnMono) - { - //Bug in mono Httpclient - RequestMessage not automatically changed on 302 - Assert.Equal(ApplicationBaseUrl + "Admin/StoreManager", response.RequestMessage.RequestUri.AbsoluteUri); - } - - Assert.Contains(albumName, responseContent); - Console.WriteLine("Successfully created an album with name '{0}' in the store", albumName); - return albumName; - } - - private string FetchAlbumIdFromName(string albumName) - { - Console.WriteLine("Fetching the album id of '{0}'", albumName); - var response = httpClient.GetAsync(string.Format("Admin/StoreManager/GetAlbumIdFromName?albumName={0}", albumName)).Result; - ThrowIfResponseStatusNotOk(response); - var albumId = response.Content.ReadAsStringAsync().Result; - Console.WriteLine("Album id for album '{0}' is '{1}'", albumName, albumId); - return albumId; - } - - private void VerifyAlbumDetails(string albumId, string albumName) - { - Console.WriteLine("Getting details of album with Id '{0}'", albumId); - var response = httpClient.GetAsync(string.Format("Admin/StoreManager/Details?id={0}", albumId)).Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains(albumName, responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("http://myapp/testurl", responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains(string.Format("Edit", albumId), responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("Back to List", responseContent, StringComparison.OrdinalIgnoreCase); - } - - private void AddAlbumToCart(string albumId, string albumName) - { - Console.WriteLine("Adding album id '{0}' to the cart", albumId); - var response = httpClient.GetAsync(string.Format("ShoppingCart/AddToCart?id={0}", albumId)).Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains(albumName, responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("", responseContent, StringComparison.OrdinalIgnoreCase); - Console.WriteLine("Verified that album is added to cart"); - } - - private void CheckOutCartItems() - { - Console.WriteLine("Checking out the cart contents..."); - var response = httpClient.GetAsync("Checkout/AddressAndPayment").Result; - ThrowIfResponseStatusNotOk(response); - var responseContent = response.Content.ReadAsStringAsync().Result; - - var formParameters = new List> - { - new KeyValuePair("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Checkout/AddressAndPayment")), - new KeyValuePair("FirstName", "FirstNameValue"), - new KeyValuePair("LastName", "LastNameValue"), - new KeyValuePair("Address", "AddressValue"), - new KeyValuePair("City", "Redmond"), - new KeyValuePair("State", "WA"), - new KeyValuePair("PostalCode", "98052"), - new KeyValuePair("Country", "USA"), - new KeyValuePair("Phone", "PhoneValue"), - new KeyValuePair("Email", "email@email.com"), - new KeyValuePair("PromoCode", "FREE"), - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - response = httpClient.PostAsync("Checkout/AddressAndPayment", content).Result; - responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains("

                Checkout Complete

                ", responseContent, StringComparison.OrdinalIgnoreCase); - if (!RunningOnMono) - { - //Bug in Mono HttpClient that it does not automatically change the RequestMessage uri in case of a 302. - Assert.StartsWith(ApplicationBaseUrl + "Checkout/Complete/", response.RequestMessage.RequestUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase); - } - } - - private void DeleteAlbum(string albumId, string albumName) - { - Console.WriteLine("Deleting album '{0}' from the store..", albumName); - - var formParameters = new List> - { - new KeyValuePair("id", albumId) - }; - - var content = new FormUrlEncodedContent(formParameters.ToArray()); - var response = httpClient.PostAsync("Admin/StoreManager/RemoveAlbum", content).Result; - ThrowIfResponseStatusNotOk(response); - - Console.WriteLine("Verifying if the album '{0}' is deleted from store", albumName); - response = httpClient.GetAsync(string.Format("Admin/StoreManager/GetAlbumIdFromName?albumName={0}", albumName)).Result; - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - Console.WriteLine("Album is successfully deleted from the store.", albumName, albumId); - } - - private void ThrowIfResponseStatusNotOk(HttpResponseMessage response) - { - if (response.StatusCode != HttpStatusCode.OK) - { - Console.WriteLine(response.Content.ReadAsStringAsync().Result); - throw new Exception(string.Format("Received the above response with status code : {0}", response.StatusCode.ToString())); + DeploymentUtility.CleanUpApplication(hostProcess, musicStoreDbName); } } } diff --git a/test/E2ETests/project.json b/test/E2ETests/project.json index 7757a4c7b8..b975222e74 100644 --- a/test/E2ETests/project.json +++ b/test/E2ETests/project.json @@ -1,9 +1,10 @@ { + "compilationOptions": { "warningsAsErrors": true }, "commands": { "test": "Xunit.KRunner" }, "dependencies": { - "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*", "Xunit.KRunner": "1.0.0-*" },