Simulierten Testzugang für interne Wizard-Tests ohne echtes Gerät
DemoRouterOsClient simuliert ein RouterOS-Gerät komplett im Arbeitsspeicher (keine Netzwerkverbindung) und wird aktiviert, wenn exakt die Zugangsdaten 127.127.127.127 / admin / admin eingegeben werden. So lässt sich der komplette Interview-Ablauf durchklicken, ohne ein reales MikroTik-Gerät zu verändern oder überhaupt zu benötigen. Nur in Debug-Builds erreichbar (#if DEBUG in RouterOsClientFactory), damit dieser Testzugang nicht in der für Endnutzer veröffentlichten Version landet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ca21d7b8a0
commit
f23da2e981
3 files changed
+146
No files matched your search
@@ -0,0 +1,40 @@
|
||||
using MikrotikInstaller.Core.Connectivity;
|
||||
using MikrotikInstaller.Core.Connectivity.Demo;
|
||||
|
||||
namespace MikrotikInstaller.Core.Tests.Connectivity;
|
||||
|
||||
public class DemoRouterOsClientTests
|
||||
{
|
||||
[Fact]
|
||||
public void Matches_OnlyExactTestCredentials()
|
||||
{
|
||||
Assert.True(DemoRouterOsClient.Matches(new RouterOsCredentials("127.127.127.127", "admin", "admin")));
|
||||
Assert.False(DemoRouterOsClient.Matches(new RouterOsCredentials("192.168.88.1", "admin", "admin")));
|
||||
Assert.False(DemoRouterOsClient.Matches(new RouterOsCredentials("127.127.127.127", "admin", "falsch")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_ReturnsSimulatedInterfacesAndResource()
|
||||
{
|
||||
var client = new DemoRouterOsClient();
|
||||
|
||||
var interfaces = await client.GetAsync("/interface");
|
||||
var resource = await client.GetAsync("/system/resource");
|
||||
|
||||
Assert.NotEmpty(interfaces);
|
||||
Assert.Contains(interfaces, i => i["name"] == "ether1");
|
||||
Assert.Single(resource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAsync_StoresRowAndReturnsId()
|
||||
{
|
||||
var client = new DemoRouterOsClient();
|
||||
|
||||
var id = await client.AddAsync("/interface/bridge", new Dictionary<string, string> { ["name"] = "bridge-lan" });
|
||||
var rows = await client.GetAsync("/interface/bridge");
|
||||
|
||||
Assert.False(string.IsNullOrEmpty(id));
|
||||
Assert.Contains(rows, r => r["name"] == "bridge-lan" && r[".id"] == id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
namespace MikrotikInstaller.Core.Connectivity.Demo;
|
||||
|
||||
/// <summary>
|
||||
/// Simulierter RouterOS-Client für interne Tests des Assistenten, ganz ohne echtes Gerät.
|
||||
/// Wird ausschließlich aktiviert, wenn exakt die Test-Zugangsdaten verwendet werden
|
||||
/// (<see cref="Matches"/>) — es findet dabei keinerlei Netzwerkverbindung statt, alle Daten
|
||||
/// liegen nur im Arbeitsspeicher dieses Objekts. Nur in Debug-Builds erreichbar
|
||||
/// (siehe <see cref="RouterOsClientFactory"/>), damit dieser Testzugang nicht in der
|
||||
/// veröffentlichten Version landet.
|
||||
/// </summary>
|
||||
public sealed class DemoRouterOsClient : IRouterOsClient
|
||||
{
|
||||
public const string DemoHost = "127.127.127.127";
|
||||
public const string DemoUsername = "admin";
|
||||
public const string DemoPassword = "admin";
|
||||
|
||||
private readonly Dictionary<string, List<Dictionary<string, string>>> _data = BuildInitialData();
|
||||
private int _nextId = 1;
|
||||
|
||||
public static bool Matches(RouterOsCredentials credentials) =>
|
||||
credentials.Host == DemoHost && credentials.Username == DemoUsername && credentials.Password == DemoPassword;
|
||||
|
||||
public RouterOsProtocol Protocol => RouterOsProtocol.Rest;
|
||||
|
||||
public Task<IReadOnlyList<IReadOnlyDictionary<string, string>>> GetAsync(string path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var rows = _data.TryGetValue(Normalize(path), out var list) ? list : [];
|
||||
IReadOnlyList<IReadOnlyDictionary<string, string>> result = rows.Select(r => (IReadOnlyDictionary<string, string>)r).ToList();
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<string> AddAsync(string path, IReadOnlyDictionary<string, string> parameters, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var id = $"*{_nextId++}";
|
||||
var row = new Dictionary<string, string>(parameters) { [".id"] = id };
|
||||
|
||||
var key = Normalize(path);
|
||||
if (!_data.TryGetValue(key, out var list))
|
||||
{
|
||||
list = [];
|
||||
_data[key] = list;
|
||||
}
|
||||
|
||||
list.Add(row);
|
||||
return Task.FromResult(id);
|
||||
}
|
||||
|
||||
public Task SetAsync(string path, string id, IReadOnlyDictionary<string, string> parameters, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_data.TryGetValue(Normalize(path), out var list))
|
||||
{
|
||||
var row = list.FirstOrDefault(r => r.GetValueOrDefault(".id") == id);
|
||||
if (row is not null)
|
||||
{
|
||||
foreach (var (key, value) in parameters)
|
||||
{
|
||||
row[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RemoveAsync(string path, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_data.TryGetValue(Normalize(path), out var list))
|
||||
{
|
||||
list.RemoveAll(r => r.GetValueOrDefault(".id") == id);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(string path, IReadOnlyDictionary<string, string>? parameters = null, CancellationToken cancellationToken = default) =>
|
||||
Task.CompletedTask;
|
||||
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
|
||||
private static string Normalize(string path) => path.TrimEnd('/');
|
||||
|
||||
private static Dictionary<string, List<Dictionary<string, string>>> BuildInitialData() => new()
|
||||
{
|
||||
["/system/resource"] = [new() { ["board-name"] = "CHR (Simulator)", ["version"] = "7.15.2 (stable)", ["architecture-name"] = "x86_64" }],
|
||||
["/system/identity"] = [new() { ["name"] = "Test-Router" }],
|
||||
["/system/routerboard"] = [new() { ["routerboard"] = "no" }],
|
||||
["/interface"] =
|
||||
[
|
||||
new() { [".id"] = "*1", ["name"] = "ether1", ["type"] = "ether", ["running"] = "true", ["disabled"] = "false" },
|
||||
new() { [".id"] = "*2", ["name"] = "ether2", ["type"] = "ether", ["running"] = "true", ["disabled"] = "false" },
|
||||
new() { [".id"] = "*3", ["name"] = "ether3", ["type"] = "ether", ["running"] = "false", ["disabled"] = "false" },
|
||||
new() { [".id"] = "*4", ["name"] = "wlan1", ["type"] = "wlan", ["running"] = "false", ["disabled"] = "true" },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MikrotikInstaller.Core.Connectivity.Binary;
|
||||
using MikrotikInstaller.Core.Connectivity.Demo;
|
||||
using MikrotikInstaller.Core.Connectivity.Rest;
|
||||
|
||||
namespace MikrotikInstaller.Core.Connectivity;
|
||||
@@ -12,6 +13,16 @@ public static class RouterOsClientFactory
|
||||
{
|
||||
public static async Task<IRouterOsClient> ConnectAsync(RouterOsCredentials credentials, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#if DEBUG
|
||||
// Nur in Debug-Builds: interner Testzugang ohne echtes Gerät, siehe DemoRouterOsClient.
|
||||
// Bewusst per Compile-Flag ausgeschlossen, damit dieser Zugang nicht in der für
|
||||
// Endnutzer veröffentlichten Version landet.
|
||||
if (DemoRouterOsClient.Matches(credentials))
|
||||
{
|
||||
return new DemoRouterOsClient();
|
||||
}
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
return await RestRouterOsClient.ConnectAsync(credentials, cancellationToken);
|
||||
|
||||
Reference in new issue
Block a user