Reputation: 1192
I have the following AdminController tests in my C# MVC app. the file looks like this.
AdminControllerTests.cs:
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using AngleSharp.Html.Dom;
using Xunit;
using amaranth.Helpers;
using amaranth.Models;
using amaranth.Data;
using Microsoft.AspNetCore.Identity;
namespace amaranth.Tests
{
public class AdminControllerTests :
IClassFixture<CustomWebApplicationFactory<amaranth.Startup>>
{
private readonly CustomWebApplicationFactory<amaranth.Startup>
_factory;
public AdminControllerTests(
CustomWebApplicationFactory<amaranth.Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task Post_FirsttoRegisterClaimsAdmin()
{
var client = _factory.CreateClient(
new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true
});
var initResponse = await client.GetAsync("/Identity/Account/Register");
var antiForgeryValues = await AntiForgeryTokenExtractor.ExtractAntiForgeryValues(initResponse);
var postRequest = new HttpRequestMessage(HttpMethod.Post, "/Identity/Account/Register");
postRequest.Headers.Add("Cookie", new CookieHeaderValue(AntiForgeryTokenExtractor.AntiForgeryCookieName, antiForgeryValues.cookieValue).ToString());
var formModel = new Dictionary<string, string>
{
{ AntiForgeryTokenExtractor.AntiForgeryFieldName, antiForgeryValues.fieldValue },
{ "Email", "[email protected]" },
{ "Password", "pas3w0!rRd" },
{ "ConfirmPassword", "pas3w0!rRd" },
};
postRequest.Content = new FormUrlEncodedContent(formModel);
var response = await client.SendAsync(postRequest);
response.EnsureSuccessStatusCode();
var postRequest2 = new HttpRequestMessage(HttpMethod.Post, "/Home/SetAdminToUser");
postRequest2.Headers.Add("Cookie", new CookieHeaderValue(AntiForgeryTokenExtractor.AntiForgeryCookieName, antiForgeryValues.cookieValue).ToString());
var formModel2 = new Dictionary<string, string>
{
{ AntiForgeryTokenExtractor.AntiForgeryFieldName, antiForgeryValues.fieldValue },
{ "check1", "True" },
{ "check2", "True" },
{ "check3", "True" },
};
postRequest2.Content = new FormUrlEncodedContent(formModel2);
var response2 = await client.SendAsync(postRequest2);
response2.EnsureSuccessStatusCode();
Assert.Equal("http://localhost/Home/ClaimAdmin", response.RequestMessage.RequestUri.ToString());
Assert.Equal("http://localhost/Admin/MasterWalletList", response2.RequestMessage.RequestUri.ToString());
IdentityUserRole<string>? userRl;
IdentityUser? user;
using (var scope = _factory.Services.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
user = db.Users.FirstOrDefault();
userRl = db.UserRoles.FirstOrDefault();
}
Assert.NotNull(user);
Assert.True(userRl?.UserId == user?.Id);
}
[Fact]
public async Task CreateMasterWalletTest()
{
var client = _factory.CreateClient(
new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true
});
var initResponse = await client.GetAsync("/Admin/MasterWalletList");
var postRequest = new HttpRequestMessage(HttpMethod.Post, "/Admin/CreateMasterWallet");
var antiForgeryValues = await AntiForgeryTokenExtractor.ExtractAntiForgeryValues(initResponse);
postRequest.Headers.Add("Cookie", new CookieHeaderValue(AntiForgeryTokenExtractor.AntiForgeryCookieName, antiForgeryValues.cookieValue).ToString());
var formModel = new Dictionary<string, string>
{
{ AntiForgeryTokenExtractor.AntiForgeryFieldName, antiForgeryValues.fieldValue },
{ "label", "testWallet1t" },
{ "isTestNet", "True" }
};
postRequest.Content = new FormUrlEncodedContent(formModel);
var response = await client.SendAsync(postRequest);
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
IdentityUserRole<string>? userRl;
MasterWallet? wallet1;
IdentityUser user;
using (var scope = _factory.Services.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
wallet1 = db.MasterWallets.FirstOrDefault();
userRl = db.UserRoles.FirstOrDefault();
user = db.Users.FirstOrDefault();
}
Assert.Equal(user.Email, "[email protected]");
//Assert.Equal(userRl?.UserId, user?.Id);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(wallet1);
Assert.Equal(wallet1?.Address.Substring(0, 3), "tb1");
}
}
}
The second test CreateMasterWalletTest
fails with the following error:
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 6.0.5 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.InMemory:6.0.5' with options: StoreName=InMemoryDbForTesting
[xUnit.net 00:00:02.01] amaranth.Tests.AdminControllerTests.CreateMasterWalletTest [FAIL]
Failed amaranth.Tests.AdminControllerTests.CreateMasterWalletTest [38 ms]
Error Message:
Assert.NotNull() Failure
Stack Trace:
at amaranth.Tests.AdminControllerTests.CreateMasterWalletTest() in /path/to/dir/amaranth.Tests/IntegrationTests/AdminControllerTests.cs:line 128
--- End of stack trace from previous location ---
Failed! - Failed: 1, Passed: 5, Skipped: 0, Total: 6, Duration: 2 s - /path/to/dir/amaranth.Tests/bin/Debug/net6.0/amaranth.Tests.dll (net6.0)
I can see that the problem is that "/Admin/CreateMasterWallet" requires an authenticated user (via cookie). My debugger shows that when I call var response = await client.SendAsync(postRequest);
, response
is trying to redirect to the login page as it does with all non-authenticated users.
So how do I make my user authenticate and send an auth cookie to the Http request?
In case it's relevant, this is my CustomWebApplicationFactory file.
CustomWebApplicationFactory.cs
using System;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using amaranth.Data;
namespace amaranth.Tests
{
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
services.Remove(descriptor);
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
});
services.AddAntiforgery(t =>
{
t.Cookie.Name = AntiForgeryTokenExtractor.AntiForgeryCookieName;
t.FormFieldName = AntiForgeryTokenExtractor.AntiForgeryFieldName;
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
var logger = scopedServices
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
db.Database.EnsureCreated();
}
});
}
}
}
Upvotes: 1
Views: 1193
Reputation: 211
Also if the question is 2 years old now, the solution is to call with the client the sign in functionality as the user does (maybe you have to handle CSRF protection also). On the server side, during the sign the Session Cookie is added to the response like it is described here: https://docs.duendesoftware.com/identityserver/v6/ui/login/session/
You can then use the same client for the request that requires Cookie authentication. The controller can than be protected with [Authorize(AuthenticationSchemes = IdentityServerConstants.DefaultCookieAuthenticationScheme)]
or the "CookieAuthenticationDefaults.AuthenticationScheme".
I use this kind of solution to provide resources for a AuthorizeInteractionResponseGenerator
from IdentityServer to force the user to add some data, like his address or phone number before he gets redirected to the application.
Upvotes: 1