![](/img/trans.png)
[英]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.