135 lines
4.9 KiB
C#
135 lines
4.9 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using TermRemoteCtl.Agent.Configuration;
|
|
|
|
namespace TermRemoteCtl.Agent.IntegrationTests.Api;
|
|
|
|
public sealed class PairingEndpointsTests
|
|
{
|
|
[Fact]
|
|
public async Task Redeem_Returns_BadRequest_When_Request_Is_Incomplete()
|
|
{
|
|
await using var fixture = new PairingApiFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
using var response = await client.PostAsJsonAsync(
|
|
"/api/pairing/redeem",
|
|
new
|
|
{
|
|
code = "123456"
|
|
});
|
|
|
|
var payload = await response.Content.ReadFromJsonAsync<RedeemResponse>();
|
|
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
Assert.NotNull(payload);
|
|
Assert.False(payload.Success);
|
|
Assert.Equal("invalid_request", payload.ErrorCode);
|
|
Assert.Equal("rejected", payload.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Redeem_Returns_BadRequest_When_Json_Is_Malformed()
|
|
{
|
|
await using var fixture = new PairingApiFixture();
|
|
using var client = fixture.CreateClient();
|
|
using var content = new StringContent("{\"code\":", Encoding.UTF8, "application/json");
|
|
|
|
using var response = await client.PostAsync("/api/pairing/redeem", content);
|
|
var payload = await response.Content.ReadFromJsonAsync<RedeemResponse>();
|
|
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
Assert.NotNull(payload);
|
|
Assert.False(payload.Success);
|
|
Assert.Equal("invalid_request", payload.ErrorCode);
|
|
Assert.Equal("rejected", payload.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Redeem_Returns_BadRequest_When_Request_Body_Is_Empty()
|
|
{
|
|
await using var fixture = new PairingApiFixture();
|
|
using var client = fixture.CreateClient();
|
|
using var content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
|
|
|
|
using var response = await client.PostAsync("/api/pairing/redeem", content);
|
|
var payload = await response.Content.ReadFromJsonAsync<RedeemResponse>();
|
|
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
Assert.NotNull(payload);
|
|
Assert.False(payload.Success);
|
|
Assert.Equal("invalid_request", payload.ErrorCode);
|
|
Assert.Equal("rejected", payload.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateCode_Does_Not_Write_Secret_Code_To_Audit_Log()
|
|
{
|
|
await using var fixture = new PairingApiFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
var code = await client.PostFromJsonAsync<PairingCodeResponse>("/api/pairing/code");
|
|
var auditLog = await File.ReadAllTextAsync(fixture.GetAuditLogPath(), Encoding.UTF8);
|
|
|
|
Assert.NotNull(code);
|
|
Assert.DoesNotContain(code!.Value, auditLog, StringComparison.Ordinal);
|
|
}
|
|
|
|
private sealed class PairingApiFixture : WebApplicationFactory<Program>
|
|
{
|
|
private readonly string _dataRoot = Path.Combine(Path.GetTempPath(), "TermRemoteCtl.Tests", Guid.NewGuid().ToString("N"));
|
|
|
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
|
{
|
|
builder.UseEnvironment("Development");
|
|
builder.ConfigureAppConfiguration((_, configBuilder) =>
|
|
{
|
|
configBuilder.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Agent:DataRoot"] = _dataRoot,
|
|
["Agent:BindAddress"] = "127.0.0.1",
|
|
["Agent:HttpsPort"] = "9443",
|
|
["Agent:WebSocketFrameFlushMilliseconds"] = "33",
|
|
["Agent:RingBufferLineLimit"] = "4000"
|
|
});
|
|
});
|
|
}
|
|
|
|
public string GetAuditLogPath()
|
|
{
|
|
var options = Services.GetRequiredService<IOptions<AgentOptions>>().Value;
|
|
return Path.Combine(options.DataRoot, "audit.log.jsonl");
|
|
}
|
|
|
|
public new async ValueTask DisposeAsync()
|
|
{
|
|
await base.DisposeAsync();
|
|
if (Directory.Exists(_dataRoot))
|
|
{
|
|
Directory.Delete(_dataRoot, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed record RedeemResponse(bool Success, string ErrorCode, string Status);
|
|
|
|
private sealed record PairingCodeResponse(string Value, DateTimeOffset ExpiresAtUtc);
|
|
}
|
|
|
|
internal static class HttpClientJsonExtensions
|
|
{
|
|
public static async Task<T?> PostFromJsonAsync<T>(this HttpClient client, string requestUri)
|
|
{
|
|
using var response = await client.PostAsync(requestUri, JsonContent.Create(new { }));
|
|
response.EnsureSuccessStatusCode();
|
|
return await response.Content.ReadFromJsonAsync<T>();
|
|
}
|
|
}
|