// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Diagnostics.Entity.FunctionalTests.Helpers; using Microsoft.AspNet.Http; using Microsoft.AspNet.TestHost; using Microsoft.Data.Entity; using Microsoft.Data.Entity.Infrastructure; using Microsoft.Data.Entity.Migrations; using Microsoft.Data.Entity.Migrations.Infrastructure; using Microsoft.Data.Entity.Utilities; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Logging; using Xunit; using Microsoft.AspNet.Diagnostics.Entity.Tests.Helpers; namespace Microsoft.AspNet.Diagnostics.Entity.Tests { public class DatabaseErrorPageMiddlewareTest { [Fact] public async Task Successful_requests_pass_thru() { TestServer server = TestServer.Create(app => app .UseDatabaseErrorPage() .UseMiddleware()); HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/"); Assert.Equal("Request Handled", await response.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } class SuccessMiddleware { public SuccessMiddleware(RequestDelegate next) { } public virtual async Task Invoke(HttpContext context) { await context.Response.WriteAsync("Request Handled"); context.Response.StatusCode = (int)HttpStatusCode.OK; } } [Fact] public async Task Non_database_exceptions_pass_thru() { TestServer server = TestServer.Create(app => app .UseDatabaseErrorPage() .UseMiddleware()); var ex = await Assert.ThrowsAsync(async () => await server.CreateClient().GetAsync("http://localhost/")); Assert.Equal("Exception requested from TestMiddleware", ex.Message); } class ExceptionMiddleware { public ExceptionMiddleware(RequestDelegate next) { } public virtual Task Invoke(HttpContext context) { throw new InvalidOperationException("Exception requested from TestMiddleware"); } } [Fact] public async Task Error_page_displayed_no_migrations() { TestServer server = SetupTestServer(); HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_NoDbOrMigrationsTitle", typeof(BloggingContext).Name), content); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_AddMigrationCommand").Replace(">", ">"), content); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_ApplyMigrationsCommand").Replace(">", ">"), content); } class NoMigrationsMiddleware { public NoMigrationsMiddleware(RequestDelegate next) { } public virtual Task Invoke(HttpContext context) { using (var db = context.ApplicationServices.GetService()) { db.Blogs.Add(new Blog()); db.SaveChanges(); throw new Exception("SaveChanges should have thrown"); } } } [Fact] public async Task Error_page_displayed_pending_migrations() { TestServer server = SetupTestServer(); HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingMigrationsTitle", typeof(BloggingContextWithMigrations).Name), content); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_ApplyMigrationsCommand").Replace(">", ">"), content); Assert.Contains("
  • 111111111111111_MigrationOne
  • ", content); Assert.Contains("
  • 222222222222222_MigrationTwo
  • ", content); Assert.DoesNotContain(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_AddMigrationCommand").Replace(">", ">"), content); } class PendingMigrationsMiddleware { public PendingMigrationsMiddleware(RequestDelegate next) { } public virtual Task Invoke(HttpContext context) { using (var db = context.ApplicationServices.GetService()) { db.Blogs.Add(new Blog()); db.SaveChanges(); throw new Exception("SaveChanges should have thrown"); } } } [Fact] public async Task Error_page_displayed_pending_model_changes() { TestServer server = SetupTestServer(); HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_PendingChangesTitle", typeof(BloggingContextWithPendingModelChanges).Name), content); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_AddMigrationCommand").Replace(">", ">"), content); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_ApplyMigrationsCommand").Replace(">", ">"), content); } class PendingModelChangesMiddleware { public PendingModelChangesMiddleware(RequestDelegate next) { } public virtual Task Invoke(HttpContext context) { using (var db = context.ApplicationServices.GetService()) { var databaseInternals = (IMigrationsEnabledDatabaseInternals)db.Database; var migrator = databaseInternals.Migrator; migrator.ApplyMigrations(); db.Blogs.Add(new Blog()); db.SaveChanges(); throw new Exception("SaveChanges should have thrown"); } } } [Fact] public async Task Error_page_then_apply_migrations() { TestServer server = SetupTestServer(); var client = server.CreateClient(); var expectedMigrationsEndpoint = "/ApplyDatabaseMigrations"; var expectedContextType = typeof(BloggingContextWithMigrations).AssemblyQualifiedName; // Step One: Initial request with database failure HttpResponseMessage response = await client.GetAsync("http://localhost/"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); // Ensure the url we're going to test is what the page is using in it's JavaScript Assert.Contains("req.open(\"POST\", \"" + expectedMigrationsEndpoint + "\", true);", content); Assert.Contains("var params = \"context=\" + encodeURIComponent(\"" + expectedContextType + "\");", content); // Step Two: Request to migrations endpoint var formData = new FormUrlEncodedContent(new List> { new KeyValuePair("context", expectedContextType) }); response = await client.PostAsync("http://localhost" + expectedMigrationsEndpoint, formData); content = await response.Content.ReadAsStringAsync(); Console.WriteLine(content); Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); // Step Three: Successful request after migrations applied response = await client.GetAsync("http://localhost/"); content = await response.Content.ReadAsStringAsync(); Assert.Equal("Saved a Blog", content); } class ApplyMigrationsMiddleware { public ApplyMigrationsMiddleware(RequestDelegate next) { } public virtual async Task Invoke(HttpContext context) { using (var db = context.ApplicationServices.GetService()) { db.Blogs.Add(new Blog()); db.SaveChanges(); await context.Response.WriteAsync("Saved a Blog"); } } } [Fact] public async Task Customize_migrations_end_point() { var migrationsEndpoint = "/MyCustomEndPoints/ApplyMyMigrationsHere"; using (var database = SqlServerTestStore.CreateScratch()) { var server = TestServer.Create(app => { app.UseServices(services => { services.AddEntityFramework().AddSqlServer(); services.AddScoped(); services.AddInstance(new DbContextOptions().UseSqlServer(database.ConnectionString)); }); var options = DatabaseErrorPageOptions.ShowAll; options.MigrationsEndPointPath = new PathString(migrationsEndpoint); app.UseDatabaseErrorPage(options); app.UseMiddleware(); }); HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); Assert.Contains("req.open(\"POST\", \"" + migrationsEndpoint + "\", true);", content); } } [Fact] public async Task Pass_thru_when_context_not_in_services() { using (var database = SqlServerTestStore.CreateScratch()) { var logProvider = new TestLoggerProvider(); var server = TestServer.Create(app => { app.UseServices(services => { services.AddEntityFramework() .AddSqlServer(); services.AddInstance( new DbContextOptions() .UseSqlServer(database.ConnectionString)); }); app.UseDatabaseErrorPage(); app.UseMiddleware(); app.ApplicationServices.GetService().AddProvider(logProvider); }); var ex = await Assert.ThrowsAsync(async () => await server.CreateClient().GetAsync("http://localhost/")); Assert.True(logProvider.Logger.Messages.Any(m => m.StartsWith(StringsHelpers.GetResourceString("FormatDatabaseErrorPageMiddleware_ContextNotRegistered", typeof(BloggingContext))))); } } class ContextNotRegisteredInServicesMiddleware { public ContextNotRegisteredInServicesMiddleware(RequestDelegate next) { } public virtual Task Invoke(HttpContext context) { var options = context.ApplicationServices.GetService(); using (var db = new BloggingContext(context.ApplicationServices, options)) { db.Blogs.Add(new Blog()); db.SaveChanges(); throw new Exception("SaveChanges should have thrown"); } } } [Fact] public async Task Pass_thru_when_exception_in_logic() { using (var database = SqlServerTestStore.CreateScratch()) { var logProvider = new TestLoggerProvider(); var server = SetupTestServer(logProvider); var ex = await Assert.ThrowsAsync(async () => await server.CreateClient().GetAsync("http://localhost/")); Assert.True(logProvider.Logger.Messages.Any(m => m.StartsWith(StringsHelpers.GetResourceString("FormatDatabaseErrorPageMiddleware_Exception")))); } } class ExceptionInLogicMiddleware { public ExceptionInLogicMiddleware(RequestDelegate next) { } public virtual Task Invoke(HttpContext context) { using (var db = context.ApplicationServices.GetService()) { db.Blogs.Add(new Blog()); db.SaveChanges(); throw new Exception("SaveChanges should have thrown"); } } } [Fact] public async Task Error_page_displayed_when_exception_wrapped() { TestServer server = SetupTestServer(); HttpResponseMessage response = await server.CreateClient().GetAsync("http://localhost/"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); Assert.Contains("I wrapped your exception", content); Assert.Contains(StringsHelpers.GetResourceString("FormatDatabaseErrorPage_NoDbOrMigrationsTitle", typeof(BloggingContext).Name), content); } class WrappedExceptionMiddleware { public WrappedExceptionMiddleware(RequestDelegate next) { } public virtual Task Invoke(HttpContext context) { using (var db = context.ApplicationServices.GetService()) { db.Blogs.Add(new Blog()); try { db.SaveChanges(); throw new Exception("SaveChanges should have thrown"); } catch (Exception ex) { throw new Exception("I wrapped your exception", ex); } } } } private static TestServer SetupTestServer(ILoggerProvider logProvider = null) where TContext : DbContext { using (var database = SqlServerTestStore.CreateScratch()) { return TestServer.Create(app => { app.UseServices(services => { services.AddEntityFramework() .AddSqlServer(); services.AddScoped(); services.AddInstance( new DbContextOptions() .UseSqlServer(database.ConnectionString)); }); app.UseDatabaseErrorPage(); app.UseMiddleware(); if (logProvider != null) { app.ApplicationServices.GetService().AddProvider(logProvider); } }); } } } }