[英]Why HttpClient uses an incorrect requestUri in post request?
[英]C# NetCore WebAPI integration testing: HttpClient uses HTTPS for GET request, but then uses only HTTP for the POST request, and fails with Forbidden
我正在使用 Net Core 6 开发一些非常简单的 API 端点。与这些端点一起,我想为它们实现集成测试。
对于集成测试,我将xunit与Microsoft.AspNetCore.Mvc.Testing一起使用,特别是WebApplicationFactory来设置 SUT。
到目前为止,我有两个 API 端点:
问题:Ping 请求测试成功。 SUT 似乎设置正确,HttpClient 确实请求,我得到了预期的结果“pong”。 但是,当我为 SignIn 运行测试时,SUT 似乎运行正常,但POST 请求永远不会到达后端。 请求的响应是 403 Forbidden,而不是预期的 401 Unauthorized。
到目前为止我的调试:
app.UseHttpsRedirection()
。 但是,在执行 SignUp 测试时,RequestUri 不会更改为 https。 我认为这是 403 响应的原因,但到目前为止我还没有解决这个问题。PingTest 的 HTTP 响应
response
{StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: text/plain; charset=utf-8
}}
Content: {System.Net.Http.StreamContent}
Headers: {}
IsSuccessStatusCode: true
ReasonPhrase: "OK"
RequestMessage: {Method: GET, RequestUri: 'https://localhost/api/template/ping', Version: 1.1, Content: <null>, Headers:
{
}}
StatusCode: OK
TrailingHeaders: {}
Version: {1.1}
SigInTest 的 HTTP 响应:
response
{StatusCode: 403, ReasonPhrase: 'Forbidden', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: application/problem+json; charset=utf-8
}}
Content: {System.Net.Http.StreamContent}
Headers: {}
IsSuccessStatusCode: false
ReasonPhrase: "Forbidden"
RequestMessage: {Method: POST, RequestUri: 'http://localhost/api/template/sign-in', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: application/json; charset=utf-8
Content-Length: 35
}}
StatusCode: Forbidden
TrailingHeaders: {}
Version: {1.1}
_content: {System.Net.Http.StreamContent}
_disposed: false
_headers: {}
_reasonPhrase: null
_requestMessage: {Method: POST, RequestUri: 'http://localhost/api/template/sign-in', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Content-Type: application/json; charset=utf-8
Content-Length: 35
}}
_statusCode: Forbidden
_trailingHeaders: {}
_version: {1.1}
我很感激你,我的电脑伙伴,可以提供的任何帮助。
我的代码:
程序.cs
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var services = builder.Services;
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection(); // <-- IMPORTANT
app.UseAuthorization();
app.UseAuthentication();
app.MapControllers();
app.Run();
public partial class Program { }
TemplateController.cs(API 端点)
namespace foo;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Route("api/template")]
[RequireHttps]
[ApiController]
public class TemplateController : ControllerBase
{
public TemplateController(){ }
[Route("sign-in")]
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public ActionResult<User> SignIn(AuthRequestDto credentials)
{
if (credentials == null) return Unauthorized("Missing credentials."); //Integration tests never reach this line. If I reach this line, I am good.
try
{
//Call to auth library
var (user, tokens) = _authService.SignIn(credentials.UserName, credentials.Password);
//Handle library's response.
return Ok();
}
catch (ArgumentException e) //<-- Thrown when auth fails. Replies with a 401, not a 403
{
return Unauthorized(e.Message);
}
catch (InvalidOperationException e)
{
return Problem(detail: e.Message, title: "Server error");
}
}
[Route("ping")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<string> Ping()
{
return Ok("pong");
}
}
AuthRequestDto.cs
using System.ComponentModel.DataAnnotations;
namespace foo;
public sealed class AuthRequestDto
{
[Required]
public string UserName { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
}
TemplateControllerTest.cs(集成测试)
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net;
using Xunit;
namespace AuthWebTemplateTest.Controllers;
public class TemplateControllerTest
{
[Fact]
public async void SignInTest()
{
var app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => { });
var client = app.CreateClient(); //<-- Get the HttpClient
string userName = "foo";
string password = "bar";
var body = new JsonContent(new { userName, password }); //<-- JsonContent is a custom class. Definition is below
var resource = "/api/template/sign-in";
var response = await client.PostAsync(resource, body);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); //<-- response.StatusCode is 403 Forbidden instead
}
[Fact]
public async void PingTest()
{
var app = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder => { });
var client = app.CreateClient();
var response = await client.GetAsync("/api/template/ping");
Assert.Equal(HttpStatusCode.OK, response.StatusCode); //Good!
Assert.Equal("pong", await response.Content.ReadAsStringAsync()); //Also Good!
}
}
json内容.cs
using Newtonsoft.Json;
using System.Net.Http;
using System.Text;
namespace AuthWebTemplateTest.util;
internal class JsonContent : StringContent
{
public JsonContent(object obj)
: base(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json")
{ }
}
我最终放弃并通过创建带有一些额外配置的 httpClient 来解决它,即
private const string HTTPS_BASE_URL = "https://localhost";
...
var client = _factory.CreateClient(new() { BaseAddress = new Uri(HTTPS_BASE_URL) });
现在所有测试都按预期工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.