Behebt #568. Keine der Testdateien mit "EndpointsTests" im Namen hat je einen Endpunkt aufgerufen. CopyTradingAndResolutionFarmingEndpointsTests beruehrte gar keinen Produktionscode: Die Tests summierten Listen im Test und verglichen das Ergebnis mit sich selbst. Modules_KnownModules pruefte, ob ein Array seine eigenen Elemente enthaelt. Settings_UpdateRequest pruefte die Portbegrenzung an einer im Test nachgebauten Lambda. Solche Tests bleiben gruen, egal was der Endpunkt tut. Neu startet WebUiTestHost das WebUI in einem Testserver (Microsoft.AspNetCore.TestHost) und spricht es ueber HTTP an. Damit der Test die Kette aus dem Betrieb prueft und keine nachgebaute, liegt sie jetzt in UsePolyTraderWebPipeline(): statische Dateien, Token-Pruefung, alle Routen. ConfigurePolyTraderWeb ruft genau diese Methode. Ersetzt wird nur, was ausserhalb des Prozesses liegt - Kestrel durch den Testserver, MySQL durch den InMemory-Provider. Die acht CopyTrading/ResolutionFarming-Tests pruefen dieselben Absichten wie vorher, jetzt an der Antwort der Route. Neu hinzugekommen sind Tests fuer: - Zugangssperre: ohne Token, mit falschem Token, ohne konfiguriertes Token - die Modulliste - Job-Umschalten samt 404 - die Ablehnung einer leeren Supervisor-Frage Die beiden Tests, die schon echten Code prueften, bleiben. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
147 lines
5.6 KiB
C#
147 lines
5.6 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using PolyTrader.Core.Modularity;
|
|
using PolyTrader.Web.Configuration;
|
|
using PolyTrader.Web.Security;
|
|
using PolyTraderSharp.Models;
|
|
using PolyTraderSharp.Services;
|
|
using Xunit;
|
|
|
|
namespace PolyTrader.Tests.WebUi
|
|
{
|
|
/// <summary>
|
|
/// Zugangssperre, Module, Jobs und Supervisor ueber echte HTTP-Aufrufe (#568).
|
|
/// Ersetzt zwei Tests, die nicht fehlschlagen konnten: einer pruefte, ob ein Array seine eigenen
|
|
/// Elemente enthaelt, der andere die Portbegrenzung an einer im Test nachgebauten Lambda.
|
|
/// </summary>
|
|
public class ModulesAndSettingsEndpointsTests
|
|
{
|
|
private sealed class FakeModule : IPolyTraderModule
|
|
{
|
|
public FakeModule(string name) => Name = name;
|
|
public string Name { get; }
|
|
public string DbPrefix => "fake_";
|
|
public void RegisterServices(IServiceCollection services, IConfiguration configuration) { }
|
|
public void RegisterUi(IModuleUiHost host, IServiceProvider services) { }
|
|
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
}
|
|
|
|
// ---- Zugangssperre ---------------------------------------------------
|
|
|
|
[Fact]
|
|
public async Task Api_ohne_Token_wird_abgewiesen()
|
|
{
|
|
await using var web = await WebUiTestHost.StartAsync();
|
|
|
|
using var res = await web.Anonymous.GetAsync("/api/modules");
|
|
|
|
Assert.Equal(HttpStatusCode.Unauthorized, res.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Api_mit_falschem_Token_wird_abgewiesen()
|
|
{
|
|
await using var web = await WebUiTestHost.StartAsync();
|
|
|
|
using var req = new HttpRequestMessage(HttpMethod.Get, "/api/modules");
|
|
req.Headers.Add(TokenAuthMiddleware.TokenHeaderName, "falsch");
|
|
using var res = await web.Anonymous.SendAsync(req);
|
|
|
|
Assert.Equal(HttpStatusCode.Unauthorized, res.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Ohne_konfiguriertes_Token_ist_alles_gesperrt()
|
|
{
|
|
await using var web = await WebUiTestHost.StartAsync(configureOptions: o => o.AuthToken = "");
|
|
|
|
using var res = await web.Client.GetAsync("/api/modules");
|
|
|
|
Assert.Equal(HttpStatusCode.Unauthorized, res.StatusCode);
|
|
}
|
|
|
|
// ---- Module ----------------------------------------------------------
|
|
|
|
[Fact]
|
|
public async Task Module_zeigt_alle_bekannten_und_markiert_die_geladenen()
|
|
{
|
|
await using var web = await WebUiTestHost.StartAsync(s =>
|
|
s.AddSingleton<IPolyTraderModule>(new FakeModule("CopyTrading")));
|
|
|
|
var modules = await web.GetJsonAsync("/api/modules");
|
|
|
|
var byName = modules.EnumerateArray().ToDictionary(
|
|
m => m.GetProperty("name").GetString()!,
|
|
m => m.GetProperty("isActive").GetBoolean());
|
|
|
|
Assert.Equal(new[] { "Accounting", "CopyTrading", "ResolutionFarming", "Supervisor" },
|
|
byName.Keys.OrderBy(k => k).ToArray());
|
|
Assert.True(byName["CopyTrading"]);
|
|
Assert.False(byName["Supervisor"]);
|
|
}
|
|
|
|
// ---- Jobs ------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public async Task Job_umschalten_aendert_den_Zustand_und_unbekannter_Job_liefert_404()
|
|
{
|
|
await using var web = await WebUiTestHost.StartAsync();
|
|
var row = new JobStatusRow { JobName = "Trader Analytics", IsEnabled = true };
|
|
web.Jobs.Jobs.Add(row);
|
|
|
|
var (status, body) = await web.PostJsonAsync("/api/jobs/Trader%20Analytics/toggle");
|
|
Assert.Equal(200, status);
|
|
Assert.False(body.GetProperty("isEnabled").GetBoolean());
|
|
Assert.False(row.IsEnabled);
|
|
|
|
var (unknown, _) = await web.PostJsonAsync("/api/jobs/GibtEsNicht/toggle");
|
|
Assert.Equal(404, unknown);
|
|
}
|
|
|
|
// ---- Supervisor ------------------------------------------------------
|
|
|
|
[Fact]
|
|
public async Task Supervisor_lehnt_leere_Frage_ab_bevor_ein_Modell_gefragt_wird()
|
|
{
|
|
await using var web = await WebUiTestHost.StartAsync();
|
|
|
|
var (status, _) = await web.PostJsonAsync("/api/modules/supervisor/ask", new { question = " " });
|
|
|
|
Assert.Equal(400, status);
|
|
}
|
|
|
|
// ---- Hilfsfunktionen, die schon echten Code pruefen ------------------
|
|
|
|
[Fact]
|
|
public void Settings_GenerateSecureToken_ProducesCryptographicallyStrongString()
|
|
{
|
|
string token1 = WebUiConfigManager.GenerateSecureToken();
|
|
string token2 = WebUiConfigManager.GenerateSecureToken();
|
|
|
|
Assert.NotEmpty(token1);
|
|
Assert.NotEqual(token1, token2);
|
|
Assert.True(token1.Length >= 32);
|
|
}
|
|
|
|
[Fact]
|
|
public void Logs_TerminalLogger_FiltersHistoryByTimeSpan()
|
|
{
|
|
var logger = new TerminalLogger();
|
|
logger.Info("Testnachricht 1 vor 5 Minuten");
|
|
logger.Warning("Testnachricht 2");
|
|
|
|
var recentMsgs = logger.GetHistory(TimeSpan.FromMinutes(10)).Select(h => h.Message).ToList();
|
|
|
|
Assert.Contains("Testnachricht 1 vor 5 Minuten", recentMsgs);
|
|
Assert.Contains("Testnachricht 2", recentMsgs);
|
|
}
|
|
}
|
|
}
|