Paralleles WebGUI mit Kestrel, Token-Sicherung und SSE-Logs implementiert

This commit is contained in:
Richard
2026-09-07 21:55:51 +02:00
parent 28b3aedec7
commit 10f928eb4e
36 changed files with 3114 additions and 266 deletions
@@ -0,0 +1,178 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using PolyTrader.Web.Configuration;
using PolyTrader.Web.Security;
using Xunit;
namespace PolyTrader.Tests.WebUi
{
public class TokenAuthMiddlewareTests
{
[Fact]
public void ValidateToken_CorrectToken_ReturnsTrue()
{
Assert.True(TokenAuthMiddleware.ValidateToken("secret-token-123", "secret-token-123"));
}
[Fact]
public void ValidateToken_WrongToken_ReturnsFalse()
{
Assert.False(TokenAuthMiddleware.ValidateToken("wrong-token", "secret-token-123"));
}
[Theory]
[InlineData("", "secret")]
[InlineData("secret", "")]
[InlineData(null, "secret")]
[InlineData("secret", null)]
public void ValidateToken_NullOrEmpty_ReturnsFalse(string? candidate, string? expected)
{
Assert.False(TokenAuthMiddleware.ValidateToken(candidate!, expected!));
}
[Fact]
public async Task InvokeAsync_UnconfiguredToken_RedirectsToLoginWithNoTokenError()
{
bool nextCalled = false;
var middleware = new TokenAuthMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = new DefaultHttpContext();
context.Request.Path = "/";
var options = Options.Create(new WebUiOptions { AuthToken = "" });
await middleware.InvokeAsync(context, options);
Assert.False(nextCalled);
Assert.Equal(StatusCodes.Status302Found, context.Response.StatusCode);
Assert.Equal("/login?error=no_token", context.Response.Headers.Location);
}
[Fact]
public async Task InvokeAsync_UnconfiguredToken_ApiReturns401()
{
bool nextCalled = false;
var middleware = new TokenAuthMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = new DefaultHttpContext();
context.Request.Path = "/api/dashboard/summary";
context.Response.Body = new MemoryStream();
var options = Options.Create(new WebUiOptions { AuthToken = "" });
await middleware.InvokeAsync(context, options);
Assert.False(nextCalled);
Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode);
}
[Fact]
public async Task InvokeAsync_PublicPathLogin_PassesThroughEvenWithoutToken()
{
bool nextCalled = false;
var middleware = new TokenAuthMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = new DefaultHttpContext();
context.Request.Path = "/login";
var options = Options.Create(new WebUiOptions { AuthToken = "" });
await middleware.InvokeAsync(context, options);
Assert.True(nextCalled);
}
[Fact]
public async Task InvokeAsync_ValidTokenInCookie_PassesThrough()
{
bool nextCalled = false;
var middleware = new TokenAuthMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = new DefaultHttpContext();
context.Request.Path = "/";
context.Request.Headers.Cookie = $"{TokenAuthMiddleware.CookieName}=valid-secret";
var options = Options.Create(new WebUiOptions { AuthToken = "valid-secret" });
await middleware.InvokeAsync(context, options);
Assert.True(nextCalled);
}
[Fact]
public async Task InvokeAsync_ValidTokenInBearerHeader_PassesThrough()
{
bool nextCalled = false;
var middleware = new TokenAuthMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = new DefaultHttpContext();
context.Request.Path = "/api/accounts";
context.Request.Headers.Authorization = "Bearer valid-secret";
var options = Options.Create(new WebUiOptions { AuthToken = "valid-secret" });
await middleware.InvokeAsync(context, options);
Assert.True(nextCalled);
}
[Fact]
public async Task InvokeAsync_MissingAuth_HtmlPageRedirectsToLogin()
{
bool nextCalled = false;
var middleware = new TokenAuthMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = new DefaultHttpContext();
context.Request.Path = "/jobs";
var options = Options.Create(new WebUiOptions { AuthToken = "valid-secret" });
await middleware.InvokeAsync(context, options);
Assert.False(nextCalled);
Assert.Equal(StatusCodes.Status302Found, context.Response.StatusCode);
Assert.Equal("/login", context.Response.Headers.Location);
}
[Fact]
public async Task InvokeAsync_MissingAuth_ApiReturns401()
{
bool nextCalled = false;
var middleware = new TokenAuthMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = new DefaultHttpContext();
context.Request.Path = "/api/jobs";
context.Response.Body = new MemoryStream();
var options = Options.Create(new WebUiOptions { AuthToken = "valid-secret" });
await middleware.InvokeAsync(context, options);
Assert.False(nextCalled);
Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode);
}
}
}