Identity/MusicStore/ErrorPage test re-anabling

Fixes #10672 #7925 #10670

Also, moves MusicStore tests to SQLite for better reliability
This commit is contained in:
Arthur Vickers 2019-06-01 17:09:04 -07:00
parent 400835e0b4
commit 173cf1786e
21 changed files with 403 additions and 375 deletions

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Data.SqlClient;
using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
@ -24,59 +23,73 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
services services
.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build()) .AddSingleton<IConfiguration>(new ConfigurationBuilder().Build())
.AddDbContext<IdentityDbContext>(o => .AddDbContext<VerstappenDbContext>(o =>
o.UseSqlite(fixture.Connection) o.UseSqlite(fixture.Connection)
.ConfigureWarnings(b => b.Log(CoreEventId.ManyServiceProvidersCreatedWarning))) .ConfigureWarnings(b => b.Log(CoreEventId.ManyServiceProvidersCreatedWarning)))
.AddIdentity<IdentityUser, IdentityRole>(o => o.Stores.MaxLengthForKeys = 128) .AddIdentity<IdentityUser, IdentityRole>(o => o.Stores.MaxLengthForKeys = 128)
.AddEntityFrameworkStores<IdentityDbContext>(); .AddEntityFrameworkStores<VerstappenDbContext>();
services.AddLogging(); services.AddLogging();
var provider = services.BuildServiceProvider(); _builder = new ApplicationBuilder(services.BuildServiceProvider());
_builder = new ApplicationBuilder(provider);
using (var scoped = provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) using (var scope = _builder.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
using (var db = scoped.ServiceProvider.GetRequiredService<IdentityDbContext>())
{ {
db.Database.EnsureCreated(); scope.ServiceProvider.GetRequiredService<VerstappenDbContext>().Database.EnsureCreated();
} }
} }
[ConditionalFact(Skip = "https://github.com/aspnet/AspNetCore/issues/7925")] // Need a different context type here since EF model is changing by having MaxLengthForKeys
public class VerstappenDbContext : IdentityDbContext<IdentityUser, IdentityRole, string>
{
public VerstappenDbContext(DbContextOptions options)
: base(options)
{
}
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)] [FrameworkSkipCondition(RuntimeFrameworks.Mono)]
[OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.MacOSX)]
public void EnsureDefaultSchema() public void EnsureDefaultSchema()
{ {
var db = _builder.ApplicationServices.GetRequiredService<IdentityDbContext>(); using (var scope = _builder.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
VerifyDefaultSchema(db); {
var db = scope.ServiceProvider.GetRequiredService<VerstappenDbContext>();
VerifyDefaultSchema(db);
}
} }
private static void VerifyDefaultSchema(IdentityDbContext dbContext) private static void VerifyDefaultSchema(VerstappenDbContext dbContext)
{ {
var sqlConn = dbContext.Database.GetDbConnection(); var sqlConn = (SqliteConnection)dbContext.Database.GetDbConnection();
using (var db = new SqliteConnection(sqlConn.ConnectionString)) try
{ {
db.Open(); sqlConn.Open();
Assert.True(DbUtil.VerifyColumns(db, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp", Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUsers", "Id", "UserName", "Email", "PasswordHash", "SecurityStamp",
"EmailConfirmed", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnabled", "EmailConfirmed", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnabled",
"LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail")); "LockoutEnd", "AccessFailedCount", "ConcurrencyStamp", "NormalizedUserName", "NormalizedEmail"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp")); Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetRoles", "Id", "Name", "NormalizedName", "ConcurrencyStamp"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserRoles", "UserId", "RoleId")); Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserRoles", "UserId", "RoleId"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue")); Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserClaims", "Id", "UserId", "ClaimType", "ClaimValue"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName")); Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserLogins", "UserId", "ProviderKey", "LoginProvider", "ProviderDisplayName"));
Assert.True(DbUtil.VerifyColumns(db, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value")); Assert.True(DbUtil.VerifyColumns(sqlConn, "AspNetUserTokens", "UserId", "LoginProvider", "Name", "Value"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUsers", 256, "UserName", "Email", "NormalizedUserName", "NormalizedEmail")); Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUsers", 256, "UserName", "Email", "NormalizedUserName", "NormalizedEmail"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetRoles", 256, "Name", "NormalizedName")); Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetRoles", 256, "Name", "NormalizedName"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUserLogins", 128, "LoginProvider", "ProviderKey")); Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUserLogins", 128, "LoginProvider", "ProviderKey"));
Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUserTokens", 128, "LoginProvider", "Name")); Assert.True(DbUtil.VerifyMaxLength(dbContext, "AspNetUserTokens", 128, "LoginProvider", "Name"));
DbUtil.VerifyIndex(db, "AspNetRoles", "RoleNameIndex", isUnique: true); DbUtil.VerifyIndex(sqlConn, "AspNetRoles", "RoleNameIndex", isUnique: true);
DbUtil.VerifyIndex(db, "AspNetUsers", "UserNameIndex", isUnique: true); DbUtil.VerifyIndex(sqlConn, "AspNetUsers", "UserNameIndex", isUnique: true);
DbUtil.VerifyIndex(db, "AspNetUsers", "EmailIndex"); DbUtil.VerifyIndex(sqlConn, "AspNetUsers", "EmailIndex");
db.Close(); }
finally
{
sqlConn.Close();
} }
} }
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Data.Common; using System.Data.Common;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.Test; using Microsoft.AspNetCore.Identity.Test;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Xunit; using Xunit;
@ -105,10 +106,10 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
public InkProtector() { } public InkProtector() { }
public string Unprotect(string keyId, string data) public string Unprotect(string keyId, string data)
=> "ink"; => data?.Substring(4);
public string Protect(string keyId, string data) public string Protect(string keyId, string data)
=> "ink"; => "ink:" + data;
} }
private class CustomUser : IdentityUser private class CustomUser : IdentityUser
@ -133,8 +134,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
{ {
if (reader.Read()) if (reader.Read())
{ {
Assert.Equal("Default:ink", reader.GetString(0)); var value = reader.GetString(0);
return reader.GetString(0) == "Default:ink"; return value.StartsWith("Default:ink:");
} }
} }
} }
@ -155,8 +156,8 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
{ {
if (reader.Read()) if (reader.Read())
{ {
Assert.Equal("Default:ink", reader.GetString(0)); var value = reader.GetString(0);
return reader.GetString(0) == "Default:ink"; return value.StartsWith("Default:ink:");
} }
} }
} }
@ -167,75 +168,103 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
/// Test. /// Test.
/// </summary> /// </summary>
/// <returns>Task</returns> /// <returns>Task</returns>
[Theory(Skip = "https://github.com/aspnet/AspNetCore/issues/7925")] [Fact]
[InlineData(true)] public Task CustomPersonalDataPropertiesCanBeProtected()
[InlineData(false)] => CustomPersonalDataPropertiesAreProtected<ProtectedIdentityDbContext>(true);
public async Task CustomPersonalDataPropertiesAreProtected(bool protect)
/// <summary>
/// Test.
/// </summary>
/// <returns>Task</returns>
[Fact]
public Task CustomPersonalDataPropertiesCanBeNotProtected()
=> CustomPersonalDataPropertiesAreProtected<UnprotectedIdentityDbContext>(false);
private async Task CustomPersonalDataPropertiesAreProtected<TContext>(bool protect)
where TContext : DbContext
{ {
using (var scratch = new ScratchDatabaseFixture()) using (var connection = new SqliteConnection($"DataSource=D{Guid.NewGuid()}.db"))
{ {
var services = new ServiceCollection().AddLogging(); var services = new ServiceCollection().AddLogging();
services.AddIdentity<CustomUser, IdentityRole>(options => services.AddIdentity<CustomUser, IdentityRole>(options =>
{ {
options.Stores.ProtectPersonalData = protect; options.Stores.ProtectPersonalData = protect;
}) })
.AddEntityFrameworkStores<IdentityDbContext<CustomUser>>() .AddEntityFrameworkStores<TContext>()
.AddPersonalDataProtection<InkProtector, DefaultKeyRing>(); .AddPersonalDataProtection<InkProtector, DefaultKeyRing>();
var dbOptions = new DbContextOptionsBuilder().UseSqlite(scratch.Connection) services.AddDbContext<TContext>(b => b.UseSqlite(connection));
.UseApplicationServiceProvider(services.BuildServiceProvider())
.Options;
var dbContext = new IdentityDbContext<CustomUser>(dbOptions);
services.AddSingleton(dbContext);
dbContext.Database.EnsureCreated();
var sp = services.BuildServiceProvider(); var applicationServiceProvider = services.BuildServiceProvider();
var manager = sp.GetService<UserManager<CustomUser>>();
var guid = Guid.NewGuid().ToString(); using (var scope = applicationServiceProvider.CreateScope())
var user = new CustomUser(); {
user.Id = guid; var dbContext = scope.ServiceProvider.GetRequiredService<TContext>();
user.UserName = guid; dbContext.Database.EnsureCreated();
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
user.Email = "test@test.com";
user.PersonalData1 = "p1";
user.PersonalData2 = "p2";
user.NonPersonalData1 = "np1";
user.NonPersonalData2 = "np2";
user.SafePersonalData = "safe";
user.PhoneNumber = "12345678";
IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user));
IdentityResultAssert.IsSuccess(await manager.ResetAuthenticatorKeyAsync(user)); var manager = scope.ServiceProvider.GetService<UserManager<CustomUser>>();
IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "loginProvider", "token", "value"));
var conn = dbContext.Database.GetDbConnection(); var guid = Guid.NewGuid().ToString();
conn.Open(); var user = new CustomUser();
if (protect) user.Id = guid;
{ user.UserName = guid;
Assert.True(FindInk(conn, "PhoneNumber", guid)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
Assert.True(FindInk(conn, "Email", guid)); user.Email = "test@test.com";
Assert.True(FindInk(conn, "UserName", guid)); user.PersonalData1 = "p1";
Assert.True(FindInk(conn, "PersonalData1", guid)); user.PersonalData2 = "p2";
Assert.True(FindInk(conn, "PersonalData2", guid)); user.NonPersonalData1 = "np1";
Assert.True(FindAuthenticatorKeyInk(conn, guid)); user.NonPersonalData2 = "np2";
Assert.True(FindTokenInk(conn, guid, "loginProvider", "token")); user.SafePersonalData = "safe";
user.PhoneNumber = "12345678";
IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user));
IdentityResultAssert.IsSuccess(await manager.ResetAuthenticatorKeyAsync(user));
IdentityResultAssert.IsSuccess(await manager.SetAuthenticationTokenAsync(user, "loginProvider", "token", "value"));
connection.Open();
if (protect)
{
Assert.True(FindInk(connection, "PhoneNumber", guid));
Assert.True(FindInk(connection, "Email", guid));
Assert.True(FindInk(connection, "UserName", guid));
Assert.True(FindInk(connection, "PersonalData1", guid));
Assert.True(FindInk(connection, "PersonalData2", guid));
Assert.True(FindAuthenticatorKeyInk(connection, guid));
Assert.True(FindTokenInk(connection, guid, "loginProvider", "token"));
}
else
{
Assert.False(FindInk(connection, "PhoneNumber", guid));
Assert.False(FindInk(connection, "Email", guid));
Assert.False(FindInk(connection, "UserName", guid));
Assert.False(FindInk(connection, "PersonalData1", guid));
Assert.False(FindInk(connection, "PersonalData2", guid));
Assert.False(FindAuthenticatorKeyInk(connection, guid));
Assert.False(FindTokenInk(connection, guid, "loginProvider", "token"));
}
Assert.False(FindInk(connection, "NonPersonalData1", guid));
Assert.False(FindInk(connection, "NonPersonalData2", guid));
Assert.False(FindInk(connection, "SafePersonalData", guid));
connection.Close();
} }
else }
{ }
Assert.False(FindInk(conn, "PhoneNumber", guid));
Assert.False(FindInk(conn, "Email", guid));
Assert.False(FindInk(conn, "UserName", guid));
Assert.False(FindInk(conn, "PersonalData1", guid));
Assert.False(FindInk(conn, "PersonalData2", guid));
Assert.False(FindAuthenticatorKeyInk(conn, guid));
Assert.False(FindTokenInk(conn, guid, "loginProvider", "token"));
}
Assert.False(FindInk(conn, "NonPersonalData1", guid));
Assert.False(FindInk(conn, "NonPersonalData2", guid));
Assert.False(FindInk(conn, "SafePersonalData", guid));
conn.Close(); private class ProtectedIdentityDbContext : IdentityDbContext<CustomUser>
{
public ProtectedIdentityDbContext(DbContextOptions<ProtectedIdentityDbContext> options)
: base(options)
{
}
}
private class UnprotectedIdentityDbContext : IdentityDbContext<CustomUser>
{
public UnprotectedIdentityDbContext(DbContextOptions<UnprotectedIdentityDbContext> options)
: base(options)
{
} }
} }

View File

@ -258,7 +258,7 @@ namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.Test
} }
} }
[ConditionalFact(Skip = "TODO: Fix for new EF. Issue #10670")] [ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.Mono)] [FrameworkSkipCondition(RuntimeFrameworks.Mono)]
[OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.MacOSX)]

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Diagnostics.FunctionalTests
public HttpClient Client { get; } public HttpClient Client { get; }
[ConditionalFact(Skip = "TODO: EF query interception for opening connection. Issue #10672")] [ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux)] [OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)] [OSSkipCondition(OperatingSystems.MacOSX)]
public async Task DatabaseErrorPage_ShowsError() public async Task DatabaseErrorPage_ShowsError()

View File

@ -36,7 +36,6 @@ namespace MusicStore.Controllers
{ {
// Retrieve Genre genre and its Associated associated Albums albums from database // Retrieve Genre genre and its Associated associated Albums albums from database
var genreModel = await DbContext.Genres var genreModel = await DbContext.Genres
.Include(g => g.Albums)
.Where(g => g.Name == genre) .Where(g => g.Name == genre)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
@ -45,6 +44,8 @@ namespace MusicStore.Controllers
return NotFound(); return NotFound();
} }
await DbContext.Entry(genreModel).Collection(g => g.Albums).LoadAsync();
return View(genreModel); return View(genreModel);
} }
@ -83,4 +84,4 @@ namespace MusicStore.Controllers
return View(album); return View(album);
} }
} }
} }

View File

@ -3,16 +3,13 @@ using System.Globalization;
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using MusicStore.Components; using MusicStore.Components;
using MusicStore.Mocks.Common; using MusicStore.Mocks.Common;
@ -47,16 +44,9 @@ namespace MusicStore
services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
// Add EF services to the services container // Add EF services to the services container
if (_platform.UseInMemoryStore) // Add EF services to the services container
{ services.AddDbContext<MusicStoreContext>(options =>
services.AddDbContext<MusicStoreContext>(options => options.UseSqlite("Data Source=MusicStore.db"));
options.UseInMemoryDatabase("Scratch"));
}
else
{
services.AddDbContext<MusicStoreContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
}
// Add Identity services to the services container // Add Identity services to the services container
services.AddIdentity<ApplicationUser, IdentityRole>() services.AddIdentity<ApplicationUser, IdentityRole>()

View File

@ -1,20 +1,15 @@
using System;
using System.Globalization; using System.Globalization;
using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Authentication.Twitter; using Microsoft.AspNetCore.Authentication.Twitter;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MusicStore.Components; using MusicStore.Components;
using MusicStore.Mocks.Common; using MusicStore.Mocks.Common;
using MusicStore.Mocks.Facebook; using MusicStore.Mocks.Facebook;
@ -50,16 +45,8 @@ namespace MusicStore
services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
// Add EF services to the services container // Add EF services to the services container
if (_platform.UseInMemoryStore) services.AddDbContext<MusicStoreContext>(options =>
{ options.UseSqlite("Data Source=MusicStore.db"));
services.AddDbContext<MusicStoreContext>(options =>
options.UseInMemoryDatabase("Scratch"));
}
else
{
services.AddDbContext<MusicStoreContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
}
// Add Identity services to the services container // Add Identity services to the services container
services.AddIdentity<ApplicationUser, IdentityRole>() services.AddIdentity<ApplicationUser, IdentityRole>()

View File

@ -115,17 +115,18 @@ namespace MusicStore.Models
.SumAsync(); .SumAsync();
} }
public Task<decimal> GetTotal() public async Task<decimal> GetTotal()
{ {
// Multiply album price by count of that album to get // Multiply album price by count of that album to get
// the current price for each of those albums in the cart // the current price for each of those albums in the cart
// sum all album price totals to get the cart total // sum all album price totals to get the cart total
return _dbContext return (await _dbContext
.CartItems .CartItems
.Where(c => c.CartId == _shoppingCartId) .Where(c => c.CartId == _shoppingCartId)
.Select(c => c.Album.Price * c.Count) .Select(c => c.Album.Price * c.Count)
.SumAsync(); .ToListAsync())
.Sum();
} }
public async Task CreateOrder(Order order) public async Task CreateOrder(Order order)

View File

@ -31,8 +31,7 @@
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" /> <Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
<Reference Include="Microsoft.AspNetCore.Session" /> <Reference Include="Microsoft.AspNetCore.Session" />
<Reference Include="Microsoft.AspNetCore.StaticFiles" /> <Reference Include="Microsoft.AspNetCore.StaticFiles" />
<Reference Include="Microsoft.EntityFrameworkCore.InMemory" /> <Reference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<Reference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<Reference Include="Microsoft.Extensions.Configuration.CommandLine" /> <Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
</ItemGroup> </ItemGroup>

View File

@ -3,12 +3,10 @@ using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MusicStore.Components; using MusicStore.Components;
using MusicStore.Models; using MusicStore.Models;
@ -40,16 +38,8 @@ namespace MusicStore
services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
// Add EF services to the services container // Add EF services to the services container
if (_platform.UseInMemoryStore) services.AddDbContext<MusicStoreContext>(options =>
{ options.UseSqlite("Data Source=MusicStore.db"));
services.AddDbContext<MusicStoreContext>(options =>
options.UseInMemoryDatabase("Scratch"));
}
else
{
services.AddDbContext<MusicStoreContext>(options =>
options.UseSqlServer(Configuration[StoreConfig.ConnectionStringKey.Replace("__", ":")]));
}
// Add Identity services to the services container // Add Identity services to the services container
services.AddIdentity<ApplicationUser, IdentityRole>() services.AddIdentity<ApplicationUser, IdentityRole>()

View File

@ -57,7 +57,7 @@ namespace MusicStore
// Add EF services to the services container // Add EF services to the services container
services.AddDbContext<MusicStoreContext>(options => services.AddDbContext<MusicStoreContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); options.UseSqlite("Data Source=MusicStore.db"));
// Add Identity services to the services container // Add Identity services to the services container
services.AddIdentity<ApplicationUser, IdentityRole>() services.AddIdentity<ApplicationUser, IdentityRole>()

View File

@ -2,12 +2,10 @@ using System.Globalization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using MusicStore.Components; using MusicStore.Components;
using MusicStore.Models; using MusicStore.Models;
@ -57,16 +55,8 @@ namespace MusicStore
services.Configure<AppSettings>(Configuration.GetSection("AppSettings")); services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
// Add EF services to the services container // Add EF services to the services container
if (_platform.UseInMemoryStore) services.AddDbContext<MusicStoreContext>(options =>
{ options.UseSqlite("Data Source=MusicStore.db"));
services.AddDbContext<MusicStoreContext>(options =>
options.UseInMemoryDatabase("Scratch"));
}
else
{
services.AddDbContext<MusicStoreContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
}
// Add Identity services to the services container // Add Identity services to the services container
services.AddIdentity<ApplicationUser, IdentityRole>() services.AddIdentity<ApplicationUser, IdentityRole>()

View File

@ -1,33 +1,22 @@
using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MusicStore.Controllers; using MusicStore.Controllers;
using MusicStore.Models; using MusicStore.Models;
using Xunit; using Xunit;
namespace MusicStore.Components namespace MusicStore.Components
{ {
public class CartSummaryComponentTest public class CartSummaryComponentTest : IClassFixture<SqliteInMemoryFixture>
{ {
private readonly IServiceProvider _serviceProvider; private readonly SqliteInMemoryFixture _fixture;
public CartSummaryComponentTest() public CartSummaryComponentTest(SqliteInMemoryFixture fixture)
{ {
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); _fixture = fixture;
_fixture.CreateDatabase();
var services = new ServiceCollection();
services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
_serviceProvider = services.BuildServiceProvider();
} }
[Fact] [Fact]
@ -45,7 +34,7 @@ namespace MusicStore.Components
viewContext.HttpContext.Session.SetString("Session", cartId); viewContext.HttpContext.Session.SetString("Session", cartId);
// DbContext initialization // DbContext initialization
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
PopulateData(dbContext, cartId, albumTitle: "AlbumA", itemCount: 10); PopulateData(dbContext, cartId, albumTitle: "AlbumA", itemCount: 10);
// CartSummaryComponent initialization // CartSummaryComponent initialization
@ -68,10 +57,20 @@ namespace MusicStore.Components
private static void PopulateData(MusicStoreContext context, string cartId, string albumTitle, int itemCount) private static void PopulateData(MusicStoreContext context, string cartId, string albumTitle, int itemCount)
{ {
var album = new Album() var album = new Album
{ {
AlbumId = 1, AlbumId = 1,
Title = albumTitle, Title = albumTitle,
Artist = new Artist
{
ArtistId = 1,
Name = "Kung Fu Kenny"
},
Genre = new Genre
{
GenreId = 1,
Name = "Rap"
}
}; };
var cartItems = Enumerable.Range(1, itemCount).Select(n => var cartItems = Enumerable.Range(1, itemCount).Select(n =>

View File

@ -6,7 +6,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -15,29 +14,21 @@ using Xunit;
namespace MusicStore.Controllers namespace MusicStore.Controllers
{ {
public class CheckoutControllerTest public class CheckoutControllerTest : IClassFixture<SqliteInMemoryFixture>
{ {
private readonly IServiceProvider _serviceProvider; private readonly SqliteInMemoryFixture _fixture;
public CheckoutControllerTest() public CheckoutControllerTest(SqliteInMemoryFixture fixture)
{ {
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); _fixture = fixture;
_fixture.CreateDatabase();
var services = new ServiceCollection();
services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
_serviceProvider = services.BuildServiceProvider();
} }
[Fact] [Fact]
public void AddressAndPayment_ReturnsDefaultView() public void AddressAndPayment_ReturnsDefaultView()
{ {
// Arrange // Arrange
var controller = new CheckoutController(_serviceProvider.GetService<ILogger<CheckoutController>>()); var controller = new CheckoutController(_fixture.ServiceProvider.GetService<ILogger<CheckoutController>>());
// Act // Act
var result = controller.AddressAndPayment(); var result = controller.AddressAndPayment();
@ -54,10 +45,7 @@ namespace MusicStore.Controllers
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var orderId = 10; var orderId = 10;
var order = new Order() var order = CreateOrder(10);
{
OrderId = orderId,
};
// Session initialization // Session initialization
var cartId = "CartId_A"; var cartId = "CartId_A";
@ -76,7 +64,7 @@ namespace MusicStore.Controllers
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims)); httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims));
// DbContext initialization // DbContext initialization
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var cartItems = CreateTestCartItems( var cartItems = CreateTestCartItems(
cartId, cartId,
itemPrice: 10, itemPrice: 10,
@ -85,7 +73,7 @@ namespace MusicStore.Controllers
dbContext.AddRange(cartItems); dbContext.AddRange(cartItems);
dbContext.SaveChanges(); dbContext.SaveChanges();
var controller = new CheckoutController(_serviceProvider.GetService<ILogger<CheckoutController>>()); var controller = new CheckoutController(_fixture.ServiceProvider.GetService<ILogger<CheckoutController>>());
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -105,17 +93,17 @@ namespace MusicStore.Controllers
{ {
// Arrange // Arrange
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
// AddressAndPayment action reads the Promo code from FormCollection. // AddressAndPayment action reads the Promo code from FormCollection.
context.Request.Form = context.Request.Form =
new FormCollection(new Dictionary<string, StringValues>()); new FormCollection(new Dictionary<string, StringValues>());
var controller = new CheckoutController(_serviceProvider.GetService<ILogger<CheckoutController>>()); var controller = new CheckoutController(_fixture.ServiceProvider.GetService<ILogger<CheckoutController>>());
controller.ControllerContext.HttpContext = context; controller.ControllerContext.HttpContext = context;
// Do not need actual data for Order; the Order object will be checked for the reference equality. // Do not need actual data for Order; the Order object will be checked for the reference equality.
var order = new Order(); var order = CreateOrder();
// Act // Act
var result = await controller.AddressAndPayment(dbContext, order, CancellationToken.None); var result = await controller.AddressAndPayment(dbContext, order, CancellationToken.None);
@ -135,12 +123,12 @@ namespace MusicStore.Controllers
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
context.Request.Form = context.Request.Form =
new FormCollection(new Dictionary<string, StringValues>()); new FormCollection(new Dictionary<string, StringValues>());
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var controller = new CheckoutController(_serviceProvider.GetService<ILogger<CheckoutController>>()); var controller = new CheckoutController(_fixture.ServiceProvider.GetService<ILogger<CheckoutController>>());
controller.ControllerContext.HttpContext = context; controller.ControllerContext.HttpContext = context;
var order = new Order(); var order = CreateOrder();
// Act // Act
var result = await controller.AddressAndPayment(dbContext, order, new CancellationToken(true)); var result = await controller.AddressAndPayment(dbContext, order, new CancellationToken(true));
@ -157,11 +145,11 @@ namespace MusicStore.Controllers
public async Task AddressAndPayment_ReturnsOrderIfInvalidOrderModel() public async Task AddressAndPayment_ReturnsOrderIfInvalidOrderModel()
{ {
// Arrange // Arrange
var controller = new CheckoutController(_serviceProvider.GetService<ILogger<CheckoutController>>()); var controller = new CheckoutController(_fixture.ServiceProvider.GetService<ILogger<CheckoutController>>());
controller.ModelState.AddModelError("a", "ModelErrorA"); controller.ModelState.AddModelError("a", "ModelErrorA");
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var order = new Order(); var order = CreateOrder();
// Act // Act
var result = await controller.AddressAndPayment(dbContext, order, CancellationToken.None); var result = await controller.AddressAndPayment(dbContext, order, CancellationToken.None);
@ -174,6 +162,22 @@ namespace MusicStore.Controllers
Assert.Same(order, viewResult.ViewData.Model); Assert.Same(order, viewResult.ViewData.Model);
} }
private Order CreateOrder(int orderId = 100, string userName = "TestUserA")
=> new Order
{
OrderId = orderId,
Username = userName,
FirstName = "Macavity",
LastName = "Clark",
Address = "11 Meadow Drive",
City = "Healing",
State = "IA",
PostalCode = "DN37 7RU",
Country = "USK",
Phone = "555 887876",
Email = "mc@sample.com"
};
[Fact] [Fact]
public async Task Complete_ReturnsOrderIdIfValid() public async Task Complete_ReturnsOrderIdIfValid()
{ {
@ -187,16 +191,11 @@ namespace MusicStore.Controllers
User = new ClaimsPrincipal(new ClaimsIdentity(claims)), User = new ClaimsPrincipal(new ClaimsIdentity(claims)),
}; };
var dbContext = var dbContext = _fixture.Context;
_serviceProvider.GetRequiredService<MusicStoreContext>(); dbContext.Add(CreateOrder(orderId, userName));
dbContext.Add(new Order()
{
OrderId = orderId,
Username = userName
});
dbContext.SaveChanges(); dbContext.SaveChanges();
var controller = new CheckoutController(_serviceProvider.GetService<ILogger<CheckoutController>>()); var controller = new CheckoutController(_fixture.ServiceProvider.GetService<ILogger<CheckoutController>>());
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -215,10 +214,9 @@ namespace MusicStore.Controllers
{ {
// Arrange // Arrange
var invalidOrderId = 100; var invalidOrderId = 100;
var dbContext = var dbContext = _fixture.Context;
_serviceProvider.GetRequiredService<MusicStoreContext>();
var controller = new CheckoutController(_serviceProvider.GetService<ILogger<CheckoutController>>()); var controller = new CheckoutController(_fixture.ServiceProvider.GetService<ILogger<CheckoutController>>());
controller.ControllerContext.HttpContext = new DefaultHttpContext(); controller.ControllerContext.HttpContext = new DefaultHttpContext();
// Act // Act
@ -233,10 +231,21 @@ namespace MusicStore.Controllers
private static CartItem[] CreateTestCartItems(string cartId, decimal itemPrice, int numberOfItem) private static CartItem[] CreateTestCartItems(string cartId, decimal itemPrice, int numberOfItem)
{ {
var albums = Enumerable.Range(1, 10).Select(n => var albums = Enumerable.Range(1, 10).Select(n =>
new Album() new Album
{ {
AlbumId = n, AlbumId = n,
Price = itemPrice, Price = itemPrice,
Title = "Greatest Hits",
Artist = new Artist
{
ArtistId = 1,
Name = "Kung Fu Kenny"
},
Genre = new Genre
{
GenreId = 1,
Name = "Rap"
}
}).ToArray(); }).ToArray();
var cartItems = Enumerable.Range(1, numberOfItem).Select(n => var cartItems = Enumerable.Range(1, numberOfItem).Select(n =>

View File

@ -1,38 +1,27 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.AspNetCore.Mvc.ViewComponents;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MusicStore.Models; using MusicStore.Models;
using Xunit; using Xunit;
namespace MusicStore.Components namespace MusicStore.Components
{ {
public class GenreMenuComponentTest public class GenreMenuComponentTest : IClassFixture<SqliteInMemoryFixture>
{ {
private readonly IServiceProvider _serviceProvider; private readonly SqliteInMemoryFixture _fixture;
public GenreMenuComponentTest() public GenreMenuComponentTest(SqliteInMemoryFixture fixture)
{ {
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); _fixture = fixture;
_fixture.CreateDatabase();
var services = new ServiceCollection();
services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
_serviceProvider = services.BuildServiceProvider();
} }
[Fact] [Fact]
public async Task GenreMenuComponent_Returns_NineGenres() public async Task GenreMenuComponent_Returns_NineGenres()
{ {
// Arrange // Arrange
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var genreMenuComponent = new GenreMenuComponent(dbContext); var genreMenuComponent = new GenreMenuComponent(dbContext);
PopulateData(dbContext); PopulateData(dbContext);
@ -50,7 +39,7 @@ namespace MusicStore.Components
private static void PopulateData(MusicStoreContext context) private static void PopulateData(MusicStoreContext context)
{ {
var genres = Enumerable.Range(1, 10).Select(n => new Genre { GenreId = n }); var genres = Enumerable.Range(1, 10).Select(n => new Genre { GenreId = n, Name = $"G{n}" });
context.AddRange(genres); context.AddRange(genres);
context.SaveChanges(); context.SaveChanges();

View File

@ -12,22 +12,14 @@ using Xunit;
namespace MusicStore.Controllers namespace MusicStore.Controllers
{ {
public class HomeControllerTest public class HomeControllerTest : IClassFixture<SqliteInMemoryFixture>
{ {
private readonly IServiceProvider _serviceProvider; private readonly SqliteInMemoryFixture _fixture;
public HomeControllerTest() public HomeControllerTest(SqliteInMemoryFixture fixture)
{ {
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); _fixture = fixture;
_fixture.CreateDatabase();
var services = new ServiceCollection();
services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
_serviceProvider = services.BuildServiceProvider();
} }
[Fact] [Fact]
@ -50,8 +42,8 @@ namespace MusicStore.Controllers
public async Task Index_GetsSixTopAlbums() public async Task Index_GetsSixTopAlbums()
{ {
// Arrange // Arrange
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var cache = _serviceProvider.GetRequiredService<IMemoryCache>(); var cache = _fixture.ServiceProvider.GetRequiredService<IMemoryCache>();
var controller = new HomeController(new TestAppSettings()); var controller = new HomeController(new TestAppSettings());
PopulateData(dbContext); PopulateData(dbContext);
@ -116,12 +108,13 @@ namespace MusicStore.Controllers
}).ToArray(); }).ToArray();
var albums = Enumerable.Range(1, 10).Select(n => var albums = Enumerable.Range(1, 10).Select(n =>
new Album() new Album
{ {
Artist = artists[n - 1], Artist = artists[n - 1],
ArtistId = n, ArtistId = n,
Genre = generes[n - 1], Genre = generes[n - 1],
GenreId = n, GenreId = n,
Title = "Greatest Hits",
}).ToArray(); }).ToArray();
return albums; return albums;

View File

@ -5,9 +5,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MusicStore.Models; using MusicStore.Models;
@ -15,37 +13,14 @@ using Xunit;
namespace MusicStore.Controllers namespace MusicStore.Controllers
{ {
public class ManageControllerTest public class ManageControllerTest : IClassFixture<ManageControllerTest.Fixture>
{ {
private readonly IServiceProvider _serviceProvider; private readonly Fixture _fixture;
public ManageControllerTest() public ManageControllerTest(Fixture fixture)
{ {
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); _fixture = fixture;
_fixture.CreateDatabase();
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddOptions();
services
.AddMemoryCache()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MusicStoreContext>();
services.AddMvc();
services.AddSingleton<IAuthenticationService, NoOpAuth>();
services.AddLogging();
// IHttpContextAccessor is required for SignInManager, and UserManager
var context = new DefaultHttpContext();
services.AddSingleton<IHttpContextAccessor>(
new HttpContextAccessor()
{
HttpContext = context,
});
_serviceProvider = services.BuildServiceProvider();
} }
[Fact] [Fact]
@ -56,19 +31,19 @@ namespace MusicStore.Controllers
var phone = "abcdefg"; var phone = "abcdefg";
var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, userId) }; var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, userId) };
var userManager = _serviceProvider.GetRequiredService<UserManager<ApplicationUser>>(); var userManager = _fixture.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var userManagerResult = await userManager.CreateAsync( var userManagerResult = await userManager.CreateAsync(
new ApplicationUser { Id = userId, UserName = "Test", TwoFactorEnabled = true, PhoneNumber = phone }, new ApplicationUser { Id = userId, UserName = "Test", TwoFactorEnabled = true, PhoneNumber = phone },
"Pass@word1"); "Pass@word1");
Assert.True(userManagerResult.Succeeded); Assert.True(userManagerResult.Succeeded);
var signInManager = _serviceProvider.GetRequiredService<SignInManager<ApplicationUser>>(); var signInManager = _fixture.ServiceProvider.GetRequiredService<SignInManager<ApplicationUser>>();
var httpContext = _serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext; var httpContext = _fixture.ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims)); httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims));
httpContext.RequestServices = _serviceProvider; httpContext.RequestServices = _fixture.ServiceProvider;
var schemeProvider = _serviceProvider.GetRequiredService<IAuthenticationSchemeProvider>(); var schemeProvider = _fixture.ServiceProvider.GetRequiredService<IAuthenticationSchemeProvider>();
var controller = new ManageController(userManager, signInManager, schemeProvider); var controller = new ManageController(userManager, signInManager, schemeProvider);
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
@ -117,5 +92,34 @@ namespace MusicStore.Controllers
} }
} }
public class Fixture : SqliteInMemoryFixture
{
public override IServiceCollection ConfigureServices(IServiceCollection services)
{
services = base.ConfigureServices(services);
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddOptions();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MusicStoreContext>();
services.AddMvc();
services.AddSingleton<IAuthenticationService, NoOpAuth>();
services.AddLogging();
// IHttpContextAccessor is required for SignInManager, and UserManager
var context = new DefaultHttpContext();
services.AddSingleton<IHttpContextAccessor>(
new HttpContextAccessor()
{
HttpContext = context,
});
return services;
}
}
} }
} }

View File

@ -3,62 +3,49 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MusicStore.Models; using MusicStore.Models;
using Xunit; using Xunit;
namespace MusicStore.Test namespace MusicStore.Test
{ {
public class ShoppingCartTest : IClassFixture<ShoppingCartFixture> public class ShoppingCartTest : IClassFixture<SqliteInMemoryFixture>
{ {
private readonly ShoppingCartFixture _fixture; private readonly SqliteInMemoryFixture _fixture;
public ShoppingCartTest(ShoppingCartFixture fixture) public ShoppingCartTest(SqliteInMemoryFixture fixture)
{ {
_fixture = fixture; _fixture = fixture;
_fixture.CreateDatabase();
} }
[Fact] [Fact]
public async Task ComputesTotal() public async Task ComputesTotal()
{ {
var cartId = Guid.NewGuid().ToString(); var cartId = Guid.NewGuid().ToString();
using (var db = _fixture.CreateContext()) var db = _fixture.Context;
{ var a = db.Albums.Add(
var a = db.Albums.Add( new Album
new Album {
AlbumId = 1,
Title = "Greatest Hits",
Price = 15.99m,
Artist = new Artist
{ {
Price = 15.99m ArtistId = 1,
}).Entity; Name = "Kung Fu Kenny"
},
Genre = new Genre
{
GenreId = 1,
Name = "Rap"
}
}).Entity;
db.CartItems.Add(new CartItem { Album = a, Count = 2, CartId = cartId }); db.CartItems.Add(new CartItem { Album = a, Count = 2, CartId = cartId });
db.SaveChanges(); db.SaveChanges();
Assert.Equal(31.98m, await ShoppingCart.GetCart(db, cartId).GetTotal()); Assert.Equal(31.98m, await ShoppingCart.GetCart(db, cartId).GetTotal());
}
} }
} }
public class ShoppingCartFixture
{
private readonly IServiceProvider _serviceProvider;
public ShoppingCartFixture()
{
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider();
var services = new ServiceCollection();
services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
_serviceProvider = services.BuildServiceProvider();
}
public virtual MusicStoreContext CreateContext()
=> _serviceProvider.GetRequiredService<MusicStoreContext>();
}
} }

View File

@ -7,10 +7,8 @@ using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using MusicStore.Models; using MusicStore.Models;
using MusicStore.ViewModels; using MusicStore.ViewModels;
@ -18,23 +16,14 @@ using Xunit;
namespace MusicStore.Controllers namespace MusicStore.Controllers
{ {
public class ShoppingCartControllerTest public class ShoppingCartControllerTest : IClassFixture<ShoppingCartControllerTest.Fixture>
{ {
private readonly IServiceProvider _serviceProvider; private readonly Fixture _fixture;
public ShoppingCartControllerTest() public ShoppingCartControllerTest(Fixture fixture)
{ {
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); _fixture = fixture;
_fixture.CreateDatabase();
var services = new ServiceCollection();
services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
services.AddMvc();
_serviceProvider = services.BuildServiceProvider();
} }
[Fact] [Fact]
@ -45,8 +34,8 @@ namespace MusicStore.Controllers
httpContext.Session = new TestSession(); httpContext.Session = new TestSession();
var controller = new ShoppingCartController( var controller = new ShoppingCartController(
_serviceProvider.GetRequiredService<MusicStoreContext>(), _fixture.Context,
_serviceProvider.GetService<ILogger<ShoppingCartController>>()); _fixture.ServiceProvider.GetService<ILogger<ShoppingCartController>>());
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -71,8 +60,8 @@ namespace MusicStore.Controllers
httpContext.Session.SetString("Session", "CartId_A"); httpContext.Session.SetString("Session", "CartId_A");
var controller = new ShoppingCartController( var controller = new ShoppingCartController(
_serviceProvider.GetRequiredService<MusicStoreContext>(), _fixture.Context,
_serviceProvider.GetService<ILogger<ShoppingCartController>>()); _fixture.ServiceProvider.GetService<ILogger<ShoppingCartController>>());
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -97,7 +86,7 @@ namespace MusicStore.Controllers
httpContext.Session = new TestSession(); httpContext.Session = new TestSession();
httpContext.Session.SetString("Session", cartId); httpContext.Session.SetString("Session", cartId);
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var cartItems = CreateTestCartItems( var cartItems = CreateTestCartItems(
cartId, cartId,
itemPrice: 10, itemPrice: 10,
@ -108,7 +97,7 @@ namespace MusicStore.Controllers
var controller = new ShoppingCartController( var controller = new ShoppingCartController(
dbContext, dbContext,
_serviceProvider.GetService<ILogger<ShoppingCartController>>()); _fixture.ServiceProvider.GetService<ILogger<ShoppingCartController>>());
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -134,14 +123,14 @@ namespace MusicStore.Controllers
httpContext.Session.SetString("Session", "CartId_A"); httpContext.Session.SetString("Session", "CartId_A");
// Creates the albums of AlbumId = 1 ~ 10. // Creates the albums of AlbumId = 1 ~ 10.
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var albums = CreateTestAlbums(itemPrice: 10); var albums = CreateTestAlbums(itemPrice: 10);
dbContext.AddRange(albums); dbContext.AddRange(albums);
dbContext.SaveChanges(); dbContext.SaveChanges();
var controller = new ShoppingCartController( var controller = new ShoppingCartController(
dbContext, dbContext,
_serviceProvider.GetService<ILogger<ShoppingCartController>>()); _fixture.ServiceProvider.GetService<ILogger<ShoppingCartController>>());
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -172,7 +161,7 @@ namespace MusicStore.Controllers
httpContext.Session.SetString("Session", cartId); httpContext.Session.SetString("Session", cartId);
// DbContext initialization // DbContext initialization
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var cartItems = CreateTestCartItems(cartId, unitPrice, numberOfItem); var cartItems = CreateTestCartItems(cartId, unitPrice, numberOfItem);
dbContext.AddRange(cartItems.Select(n => n.Album).Distinct()); dbContext.AddRange(cartItems.Select(n => n.Album).Distinct());
dbContext.AddRange(cartItems); dbContext.AddRange(cartItems);
@ -183,7 +172,7 @@ namespace MusicStore.Controllers
httpContext.Features.Set<IServiceProvidersFeature>(serviceProviderFeature); httpContext.Features.Set<IServiceProvidersFeature>(serviceProviderFeature);
// AntiForgery initialization // AntiForgery initialization
serviceProviderFeature.RequestServices = _serviceProvider; serviceProviderFeature.RequestServices = _fixture.ServiceProvider;
var antiForgery = serviceProviderFeature.RequestServices.GetRequiredService<IAntiforgery>(); var antiForgery = serviceProviderFeature.RequestServices.GetRequiredService<IAntiforgery>();
var tokens = antiForgery.GetTokens(httpContext); var tokens = antiForgery.GetTokens(httpContext);
@ -196,7 +185,7 @@ namespace MusicStore.Controllers
// Cotroller initialization // Cotroller initialization
var controller = new ShoppingCartController( var controller = new ShoppingCartController(
dbContext, dbContext,
_serviceProvider.GetService<ILogger<ShoppingCartController>>()); _fixture.ServiceProvider.GetService<ILogger<ShoppingCartController>>());
controller.ControllerContext.HttpContext = httpContext; controller.ControllerContext.HttpContext = httpContext;
// Act // Act
@ -207,7 +196,7 @@ namespace MusicStore.Controllers
var viewModel = Assert.IsType<ShoppingCartRemoveViewModel>(jsonResult.Value); var viewModel = Assert.IsType<ShoppingCartRemoveViewModel>(jsonResult.Value);
Assert.Equal(numberOfItem - 1, viewModel.CartCount); Assert.Equal(numberOfItem - 1, viewModel.CartCount);
Assert.Equal((numberOfItem - 1) * 10, viewModel.CartTotal); Assert.Equal((numberOfItem - 1) * 10, viewModel.CartTotal);
Assert.Equal(" has been removed from your shopping cart.", viewModel.Message); Assert.Equal("Greatest Hits has been removed from your shopping cart.", viewModel.Message);
var cart = ShoppingCart.GetCart(dbContext, httpContext); var cart = ShoppingCart.GetCart(dbContext, httpContext);
Assert.DoesNotContain((await cart.GetCartItems()), c => c.CartItemId == cartItemId); Assert.DoesNotContain((await cart.GetCartItems()), c => c.CartItemId == cartItemId);
@ -232,11 +221,34 @@ namespace MusicStore.Controllers
private static Album[] CreateTestAlbums(decimal itemPrice) private static Album[] CreateTestAlbums(decimal itemPrice)
{ {
return Enumerable.Range(1, 10).Select(n => return Enumerable.Range(1, 10).Select(n =>
new Album() new Album
{ {
Title = "Greatest Hits",
AlbumId = n, AlbumId = n,
Price = itemPrice, Price = itemPrice,
Artist = new Artist
{
ArtistId = 1,
Name = "Kung Fu Kenny"
},
Genre = new Genre
{
GenreId = 1,
Name = "Rap"
}
}).ToArray(); }).ToArray();
} }
public class Fixture : SqliteInMemoryFixture
{
public override IServiceCollection ConfigureServices(IServiceCollection services)
{
services = base.ConfigureServices(services);
services.AddMvc();
return services;
}
}
} }
} }

View File

@ -0,0 +1,52 @@
using System;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MusicStore.Models;
namespace MusicStore
{
public class SqliteInMemoryFixture : IDisposable
{
private IServiceScope _serviceScope;
private SqliteConnection _connection;
public virtual void Dispose()
{
_connection?.Close();
_connection?.Dispose();
_serviceScope?.Dispose();
_serviceScope= null;
}
public virtual IServiceCollection ConfigureServices(IServiceCollection services)
=> services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseSqlite(_connection));
public virtual IServiceProvider ServiceProvider
{
get
{
if (_serviceScope == null)
{
_serviceScope = ConfigureServices(new ServiceCollection()).BuildServiceProvider().CreateScope();
}
return _serviceScope.ServiceProvider;
}
}
public virtual MusicStoreContext Context
=> ServiceProvider.GetRequiredService<MusicStoreContext>();
public virtual void CreateDatabase()
{
Dispose();
_connection = new SqliteConnection("Data Source=:memory:");
_connection.Open();
Context.Database.EnsureCreated();
}
}
}

View File

@ -12,29 +12,21 @@ using Xunit;
namespace MusicStore.Controllers namespace MusicStore.Controllers
{ {
public class StoreControllerTest public class StoreControllerTest : IClassFixture<SqliteInMemoryFixture>
{ {
private readonly IServiceProvider _serviceProvider; private readonly SqliteInMemoryFixture _fixture;
public StoreControllerTest() public StoreControllerTest(SqliteInMemoryFixture fixture)
{ {
var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); _fixture = fixture;
_fixture.CreateDatabase();
var services = new ServiceCollection();
services
.AddMemoryCache()
.AddLogging()
.AddDbContext<MusicStoreContext>(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider));
_serviceProvider = services.BuildServiceProvider();
} }
[Fact] [Fact]
public async Task Index_CreatesViewWithGenres() public async Task Index_CreatesViewWithGenres()
{ {
// Arrange // Arrange
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
CreateTestGenres(numberOfGenres: 10, numberOfAlbums: 1, dbContext: dbContext); CreateTestGenres(numberOfGenres: 10, numberOfAlbums: 1, dbContext: dbContext);
var controller = new StoreController(dbContext, new TestAppSettings()); var controller = new StoreController(dbContext, new TestAppSettings());
@ -56,7 +48,7 @@ namespace MusicStore.Controllers
{ {
// Arrange // Arrange
var controller = new StoreController( var controller = new StoreController(
_serviceProvider.GetRequiredService<MusicStoreContext>(), _fixture.Context,
new TestAppSettings()); new TestAppSettings());
// Act // Act
@ -72,7 +64,7 @@ namespace MusicStore.Controllers
// Arrange // Arrange
var genreName = "Genre 1"; var genreName = "Genre 1";
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
CreateTestGenres(numberOfGenres: 3, numberOfAlbums: 3, dbContext: dbContext); CreateTestGenres(numberOfGenres: 3, numberOfAlbums: 3, dbContext: dbContext);
var controller = new StoreController(dbContext, new TestAppSettings()); var controller = new StoreController(dbContext, new TestAppSettings());
@ -97,11 +89,11 @@ namespace MusicStore.Controllers
// Arrange // Arrange
var albumId = int.MinValue; var albumId = int.MinValue;
var controller = new StoreController( var controller = new StoreController(
_serviceProvider.GetRequiredService<MusicStoreContext>(), _fixture.Context,
new TestAppSettings()); new TestAppSettings());
// Act // Act
var result = await controller.Details(_serviceProvider.GetRequiredService<IMemoryCache>(), albumId); var result = await controller.Details(_fixture.ServiceProvider.GetRequiredService<IMemoryCache>(), albumId);
// Assert // Assert
Assert.IsType<NotFoundResult>(result); Assert.IsType<NotFoundResult>(result);
@ -113,10 +105,10 @@ namespace MusicStore.Controllers
// Arrange // Arrange
var albumId = 1; var albumId = 1;
var dbContext = _serviceProvider.GetRequiredService<MusicStoreContext>(); var dbContext = _fixture.Context;
var genres = CreateTestGenres(numberOfGenres: 3, numberOfAlbums: 3, dbContext: dbContext); var genres = CreateTestGenres(numberOfGenres: 3, numberOfAlbums: 3, dbContext: dbContext);
var cache = _serviceProvider.GetRequiredService<IMemoryCache>(); var cache = _fixture.ServiceProvider.GetRequiredService<IMemoryCache>();
var controller = new StoreController(dbContext, new TestAppSettings()); var controller = new StoreController(dbContext, new TestAppSettings());
@ -148,31 +140,22 @@ namespace MusicStore.Controllers
artist.Name = "Artist1"; artist.Name = "Artist1";
var albums = Enumerable.Range(1, numberOfAlbums * numberOfGenres).Select(n => var albums = Enumerable.Range(1, numberOfAlbums * numberOfGenres).Select(n =>
new Album() new Album
{ {
AlbumId = n, AlbumId = n,
Artist = artist, Artist = artist,
ArtistId = artist.ArtistId ArtistId = artist.ArtistId,
Title = "Greatest Hits",
}).ToList(); }).ToList();
var generes = Enumerable.Range(1, numberOfGenres).Select(n => var generes = Enumerable.Range(1, numberOfGenres).Select(n =>
new Genre() new Genre
{ {
Albums = albums.Where(i => i.AlbumId % numberOfGenres == n - 1).ToList(), Albums = albums.Where(i => i.AlbumId % numberOfGenres == n - 1).ToList(),
GenreId = n, GenreId = n,
Name = "Genre " + n Name = "Genre " + n
}); });
var artis = Enumerable.Range(1, numberOfGenres).Select(n =>
new Genre()
{
Albums = albums.Where(i => i.AlbumId % numberOfGenres == n - 1).ToList(),
GenreId = n,
Name = "Genre " + n,
});
dbContext.Add(artist);
dbContext.AddRange(albums);
dbContext.AddRange(generes); dbContext.AddRange(generes);
dbContext.SaveChanges(); dbContext.SaveChanges();