first pass at adding framework tests that cover the release tests

This commit is contained in:
E M 2026-06-04 12:53:13 +10:00
parent e8b2ad014b
commit b5e8a155dc
No known key found for this signature in database
10 changed files with 792 additions and 11 deletions

5
.gitignore vendored
View File

@ -1,7 +1,8 @@
.vs
.vs
obj
bin
.vscode
Tools/AutoClient/datapath
.editorconfig
.DS_Store
.DS_Store
dotnet-tools.json

View File

@ -0,0 +1,31 @@
using Logging;
using Utils;
namespace LogosStorageClient
{
public interface ILogosStorageAccess
{
string GetName();
string GetImageName();
DateTime GetStartUtc();
DebugInfo GetDebugInfo();
void SetLogLevel(string logLevel);
string GetSpr();
DebugPeer GetDebugPeer(string peerId);
void ConnectToPeer(string peerId, string[] peerMultiAddresses);
string UploadFile(UploadInput uploadInput);
Stream DownloadFile(string contentId);
LocalDataset DownloadStreamless(ContentId cid);
LocalDataset DownloadManifestOnly(ContentId cid);
LocalDatasetList LocalFiles();
LogosStorageSpace Space();
Address GetDiscoveryEndpoint();
Address GetApiEndpoint();
Address GetListenEndpoint();
Address? GetMetricsEndpoint();
bool HasCrashed();
void DeleteDataDirFolder();
void Stop(bool waitTillStopped);
IDownloadedLog DownloadLog(string additionalName = "");
}
}

View File

@ -7,7 +7,7 @@ using WebUtils;
namespace LogosStorageClient
{
public class LogosStorageAccess
public class LogosStorageAccess : ILogosStorageAccess
{
public const string API_BASE_URL = "/api/storage/v1";

View File

@ -53,10 +53,10 @@ namespace LogosStorageClient
private readonly TransferSpeeds transferSpeeds;
private string peerId = string.Empty;
private string nodeId = string.Empty;
private readonly LogosStorageAccess logosStorageAccess;
private readonly ILogosStorageAccess logosStorageAccess;
private readonly IFileManager fileManager;
public StorageNode(ILog log, LogosStorageAccess logosStorageAccess, IFileManager fileManager, IStorageNodeHooks hooks)
public StorageNode(ILog log, ILogosStorageAccess logosStorageAccess, IFileManager fileManager, IStorageNodeHooks hooks)
{
this.logosStorageAccess = logosStorageAccess;
this.fileManager = fileManager;
@ -226,13 +226,11 @@ namespace LogosStorageClient
public void ConnectToPeer(IStorageNode node)
{
var peer = (StorageNode)node;
Log($"Connecting to peer {peer.GetName()}...");
Log($"Connecting to peer {node.GetName()}...");
var peerInfo = node.GetDebugInfo();
logosStorageAccess.ConnectToPeer(peerInfo.Id, GetPeerMultiAddresses(peer, peerInfo));
logosStorageAccess.ConnectToPeer(peerInfo.Id, GetPeerMultiAddresses(node, peerInfo));
Log($"Successfully connected to peer {peer.GetName()}.");
Log($"Successfully connected to peer {node.GetName()}.");
}
public void DeleteDataDirFolder()
@ -307,7 +305,7 @@ namespace LogosStorageClient
log.AddStringReplace(LogosStorageUtils.ToShortId(nodeId), nodeName);
}
private string[] GetPeerMultiAddresses(StorageNode peer, DebugInfo peerInfo)
private string[] GetPeerMultiAddresses(IStorageNode peer, DebugInfo peerInfo)
{
var peerId = peer.GetDiscoveryEndpoint().Host
.Replace("http://", "")

View File

@ -0,0 +1,125 @@
using Moq;
using NUnit.Framework;
using LogosStorageClient;
using LogosStorageClient.Hooks;
using Utils;
namespace FrameworkTests.LogosStorageClient
{
[TestFixture]
public class MuxingStorageNodeHooksTests
{
private Mock<IStorageNodeHooks> hookA = null!;
private Mock<IStorageNodeHooks> hookB = null!;
private MuxingStorageNodeHooks mux = null!;
[SetUp]
public void SetUp()
{
hookA = new Mock<IStorageNodeHooks>();
hookB = new Mock<IStorageNodeHooks>();
mux = new MuxingStorageNodeHooks(new[] { hookA.Object, hookB.Object });
}
[Test]
public void OnNodeStarting_DispatchesToAllHooks()
{
var start = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
mux.OnNodeStarting(start, "image:tag");
hookA.Verify(h => h.OnNodeStarting(start, "image:tag"), Times.Once);
hookB.Verify(h => h.OnNodeStarting(start, "image:tag"), Times.Once);
}
[Test]
public void OnNodeStarted_DispatchesToAllHooks()
{
var mockNode = new Mock<IStorageNode>();
mux.OnNodeStarted(mockNode.Object, "peerId-abc", "nodeId-xyz");
hookA.Verify(h => h.OnNodeStarted(mockNode.Object, "peerId-abc", "nodeId-xyz"), Times.Once);
hookB.Verify(h => h.OnNodeStarted(mockNode.Object, "peerId-abc", "nodeId-xyz"), Times.Once);
}
[Test]
public void OnNodeStopping_DispatchesToAllHooks()
{
mux.OnNodeStopping();
hookA.Verify(h => h.OnNodeStopping(), Times.Once);
hookB.Verify(h => h.OnNodeStopping(), Times.Once);
}
[Test]
public void OnFileUploading_DispatchesToAllHooks()
{
var size = new ByteSize(1024);
mux.OnFileUploading("uid-1", size);
hookA.Verify(h => h.OnFileUploading("uid-1", size), Times.Once);
hookB.Verify(h => h.OnFileUploading("uid-1", size), Times.Once);
}
[Test]
public void OnFileUploaded_DispatchesToAllHooks()
{
var size = new ByteSize(1024);
var cid = new ContentId("bafybeicid1");
mux.OnFileUploaded("uid-1", size, cid);
hookA.Verify(h => h.OnFileUploaded("uid-1", size, cid), Times.Once);
hookB.Verify(h => h.OnFileUploaded("uid-1", size, cid), Times.Once);
}
[Test]
public void OnFileDownloading_DispatchesToAllHooks()
{
var cid = new ContentId("bafybeicid1");
mux.OnFileDownloading(cid);
hookA.Verify(h => h.OnFileDownloading(cid), Times.Once);
hookB.Verify(h => h.OnFileDownloading(cid), Times.Once);
}
[Test]
public void OnFileDownloaded_DispatchesToAllHooks()
{
var size = new ByteSize(2048);
var cid = new ContentId("bafybeicid2");
mux.OnFileDownloaded(size, cid);
hookA.Verify(h => h.OnFileDownloaded(size, cid), Times.Once);
hookB.Verify(h => h.OnFileDownloaded(size, cid), Times.Once);
}
}
[TestFixture]
public class LogosStorageHooksFactoryTests
{
[Test]
public void CreateHooks_NoProviders_ReturnsDoNothingHooks()
{
var factory = new LogosStorageHooksFactory();
var hooks = factory.CreateHooks("test-node");
Assert.That(hooks, Is.InstanceOf<DoNothingLogosStorageHooks>());
}
[Test]
public void CreateHooks_OneProvider_ReturnsMuxingHooks()
{
var mockHooks = new Mock<IStorageNodeHooks>();
var mockProvider = new Mock<ILogosStorageHooksProvider>();
mockProvider.Setup(p => p.CreateHooks("my-node")).Returns(mockHooks.Object);
var factory = new LogosStorageHooksFactory();
factory.Providers.Add(mockProvider.Object);
var result = factory.CreateHooks("my-node");
Assert.That(result, Is.InstanceOf<MuxingStorageNodeHooks>());
}
[Test]
public void CreateHooks_MultipleProviders_ReturnsMuxingHooks()
{
var factory = new LogosStorageHooksFactory();
factory.Providers.Add(new DoNothingHooksProvider());
factory.Providers.Add(new DoNothingHooksProvider());
var result = factory.CreateHooks("my-node");
Assert.That(result, Is.InstanceOf<MuxingStorageNodeHooks>());
}
}
}

View File

@ -0,0 +1,95 @@
using NUnit.Framework;
using LogosStorageClient;
namespace FrameworkTests.LogosStorageClient
{
[TestFixture]
public class LogosStorageLogLineTests
{
[Test]
public void Parse_NullOrEmpty_ReturnsNull()
{
Assert.That(LogosStorageLogLine.Parse(string.Empty), Is.Null);
Assert.That(LogosStorageLogLine.Parse(null!), Is.Null);
}
[Test]
public void Parse_TooShort_ReturnsNull()
{
Assert.That(LogosStorageLogLine.Parse("INF 2024-01-01 00:00:00.000"), Is.Null);
}
[Test]
public void Parse_ValidLine_ReturnsCorrectFields()
{
// Real Nim log format: LVL yyyy-MM-dd HH:mm:ss.fff+00:00 message attrs
// position 3 = space after level, position 33 = space before message
var line = "INF 2024-03-15 12:34:56.789+00:00 Node started successfully topic=storage";
var result = LogosStorageLogLine.Parse(line);
Assert.That(result, Is.Not.Null);
Assert.That(result!.LogLevel, Is.EqualTo("INF"));
Assert.That(result.TimestampUtc, Is.EqualTo(new DateTime(2024, 3, 15, 12, 34, 56, 789, DateTimeKind.Utc)));
Assert.That(result.Message, Is.EqualTo("Node started successfully"));
}
[Test]
public void Parse_SimpleAttribute_ParsedCorrectly()
{
var line = "ERR 2024-03-15 12:34:56.789+00:00 Upload failed reason=timeout";
var result = LogosStorageLogLine.Parse(line);
Assert.That(result, Is.Not.Null);
Assert.That(result!.Attributes.ContainsKey("reason"), Is.True);
Assert.That(result.Attributes["reason"], Is.EqualTo("timeout"));
}
[Test]
public void Parse_QuotedAttributeValue_ParsedCorrectly()
{
var line = "WRN 2024-03-15 12:34:56.789+00:00 Slow download path=\"/data dir/file.bin\"";
var result = LogosStorageLogLine.Parse(line);
Assert.That(result, Is.Not.Null);
Assert.That(result!.Attributes["path"], Is.EqualTo("/data dir/file.bin"));
}
[Test]
public void Parse_MultipleAttributes_AllParsed()
{
var line = "DBG 2024-03-15 12:34:56.789+00:00 Peer discovered peerId=abc nodeId=xyz seen=true";
var result = LogosStorageLogLine.Parse(line);
Assert.That(result, Is.Not.Null);
Assert.That(result!.Attributes["peerId"], Is.EqualTo("abc"));
Assert.That(result.Attributes["nodeId"], Is.EqualTo("xyz"));
Assert.That(result.Attributes["seen"], Is.EqualTo("true"));
}
[Test]
public void Parse_InvalidTimestamp_ReturnsNull()
{
// Passes position-3 and position-33 space checks, but the 23-char timestamp
// substring (positions 4-26) is not a parseable datetime → returns null.
// 29 X's fill positions 4-32, space at 33, then message with attributes.
var line = "INF XXXXXXXXXXXXXXXXXXXXXXXXXXXXX message key=value";
Assert.That(LogosStorageLogLine.Parse(line), Is.Null);
}
[Test]
[TestCase("INF")]
[TestCase("ERR")]
[TestCase("WRN")]
[TestCase("DBG")]
public void Parse_LogLevel_IsPreserved(string level)
{
var line = $"{level} 2024-03-15 12:34:56.789+00:00 Message key=value";
var result = LogosStorageLogLine.Parse(line);
Assert.That(result?.LogLevel, Is.EqualTo(level));
}
}
}

View File

@ -0,0 +1,134 @@
using NUnit.Framework;
using LogosStorageClient;
using StorageOpenApi;
namespace FrameworkTests.LogosStorageClient
{
[TestFixture]
public class MapperTests
{
private Mapper mapper = null!;
[SetUp]
public void SetUp()
{
mapper = new Mapper();
}
[Test]
public void Map_DebugInfo_MapsAllFields()
{
var openApiDebugInfo = new StorageOpenApi.DebugInfo
{
Id = "peer-123",
Spr = "spr-abc",
Addrs = new List<string> { "/ip4/1.2.3.4/tcp/8080" },
AnnounceAddresses = new List<string> { "/ip4/5.6.7.8/tcp/8080" },
Storage = new StorageVersion { Version = "1.0.0", Revision = "abc123" },
Table = new PeersTable
{
LocalNode = new Node { NodeId = "nodeId-1", PeerId = "peerId-1" },
Nodes = new List<Node>()
}
};
var result = mapper.Map(openApiDebugInfo);
Assert.That(result.Id, Is.EqualTo("peer-123"));
Assert.That(result.Spr, Is.EqualTo("spr-abc"));
Assert.That(result.Addrs, Is.EqualTo(new[] { "/ip4/1.2.3.4/tcp/8080" }));
Assert.That(result.AnnounceAddresses, Is.EqualTo(new[] { "/ip4/5.6.7.8/tcp/8080" }));
Assert.That(result.Version.Version, Is.EqualTo("1.0.0"));
Assert.That(result.Version.Revision, Is.EqualTo("abc123"));
Assert.That(result.Table.LocalNode.NodeId, Is.EqualTo("nodeId-1"));
}
[Test]
public void Map_Space_MapsAllBytes()
{
var openApiSpace = new StorageOpenApi.Space
{
QuotaMaxBytes = 10_000_000,
QuotaUsedBytes = 3_000_000,
QuotaReservedBytes = 1_000_000,
TotalBlocks = 42
};
var result = mapper.Map(openApiSpace);
Assert.That(result.QuotaMaxBytes, Is.EqualTo(10_000_000));
Assert.That(result.QuotaUsedBytes, Is.EqualTo(3_000_000));
Assert.That(result.QuotaReservedBytes, Is.EqualTo(1_000_000));
Assert.That(result.TotalBlocks, Is.EqualTo(42));
}
[Test]
public void Map_DataList_MapsContent()
{
var openApiDataList = new StorageOpenApi.DataList
{
Content = new List<DataItem>
{
new DataItem
{
Cid = "bafybeicid1",
Manifest = new ManifestItem
{
TreeCid = "rootHashXYZ",
DatasetSize = 1024,
BlockSize = 512
}
}
}
};
var result = mapper.Map(openApiDataList);
Assert.That(result.Content.Length, Is.EqualTo(1));
Assert.That(result.Content[0].Cid.Id, Is.EqualTo("bafybeicid1"));
Assert.That(result.Content[0].Manifest.RootHash, Is.EqualTo("rootHashXYZ"));
Assert.That(result.Content[0].Manifest.DatasetSize.SizeInBytes, Is.EqualTo(1024));
Assert.That(result.Content[0].Manifest.BlockSize.SizeInBytes, Is.EqualTo(512));
}
[Test]
public void Map_DebugInfoTable_WithPeers_MapsNodes()
{
var openApiDebugInfo = new StorageOpenApi.DebugInfo
{
Id = "x",
Spr = "x",
Addrs = new List<string>(),
AnnounceAddresses = new List<string>(),
Storage = new StorageVersion { Version = "1", Revision = "r" },
Table = new PeersTable
{
LocalNode = new Node { NodeId = "local-nodeId", PeerId = "local-peerId" },
Nodes = new List<Node>
{
new Node { NodeId = "node-1", PeerId = "peer-1", Seen = true }
}
}
};
var result = mapper.Map(openApiDebugInfo);
Assert.That(result.Table.Nodes.Length, Is.EqualTo(1));
Assert.That(result.Table.Nodes[0].PeerId, Is.EqualTo("peer-1"));
Assert.That(result.Table.Nodes[0].Seen, Is.True);
}
[Test]
public void Map_DataList_EmptyContent_ReturnsEmptyArray()
{
var openApiDataList = new StorageOpenApi.DataList
{
Content = new List<DataItem>()
};
var result = mapper.Map(openApiDataList);
Assert.That(result.Content, Is.Empty);
}
}
}

View File

@ -0,0 +1,207 @@
using Moq;
using NUnit.Framework;
using LogosStorageClient;
using LogosStorageClient.Hooks;
using FileUtils;
using Logging;
using Utils;
namespace FrameworkTests.LogosStorageClient
{
[TestFixture]
public class StorageNodeTests
{
private Mock<ILogosStorageAccess> mockAccess = null!;
private Mock<IStorageNodeHooks> mockHooks = null!;
private Mock<IFileManager> mockFileManager = null!;
private StorageNode node = null!;
private NullLog nullLog = null!;
[SetUp]
public void SetUp()
{
mockAccess = new Mock<ILogosStorageAccess>();
mockHooks = new Mock<IStorageNodeHooks>();
mockFileManager = new Mock<IFileManager>();
nullLog = new NullLog();
mockAccess.Setup(a => a.GetName()).Returns("test-node");
mockAccess.Setup(a => a.GetStartUtc()).Returns(DateTime.UtcNow);
mockAccess.Setup(a => a.GetImageName()).Returns("test-image");
node = new StorageNode(nullLog, mockAccess.Object, mockFileManager.Object, mockHooks.Object);
}
// --- GetName ---
[Test]
public void GetName_DelegatesToAccess()
{
Assert.That(node.GetName(), Is.EqualTo("test-node"));
}
// --- UploadFile ---
[Test]
public void UploadFile_ValidResponse_ReturnsCid()
{
using var tmpFile = new TempFile();
File.WriteAllText(tmpFile.Path, "hello world");
var trackedFile = new TrackedFile(nullLog, tmpFile.Path, "test-file");
mockAccess.Setup(a => a.UploadFile(It.IsAny<UploadInput>())).Returns("bafybeicid123");
var cid = node.UploadFile(trackedFile);
Assert.That(cid.Id, Is.EqualTo("bafybeicid123"));
}
[Test]
public void UploadFile_FiresOnFileUploadingAndOnFileUploaded()
{
using var tmpFile = new TempFile();
File.WriteAllText(tmpFile.Path, "data");
var trackedFile = new TrackedFile(nullLog, tmpFile.Path, "test-file");
mockAccess.Setup(a => a.UploadFile(It.IsAny<UploadInput>())).Returns("bafybeicid999");
node.UploadFile(trackedFile);
mockHooks.Verify(h => h.OnFileUploading(It.IsAny<string>(), It.IsAny<ByteSize>()), Times.Once);
mockHooks.Verify(h => h.OnFileUploaded(
It.IsAny<string>(),
It.IsAny<ByteSize>(),
It.Is<ContentId>(c => c.Id == "bafybeicid999")), Times.Once);
}
[Test]
public void UploadFile_EmptyResponse_Throws()
{
using var tmpFile = new TempFile();
File.WriteAllText(tmpFile.Path, "data");
var trackedFile = new TrackedFile(nullLog, tmpFile.Path, "test-file");
mockAccess.Setup(a => a.UploadFile(It.IsAny<UploadInput>())).Returns(string.Empty);
Assert.Throws<Exception>(() => node.UploadFile(trackedFile));
}
[Test]
public void UploadFile_StoreBlockFailure_Throws()
{
using var tmpFile = new TempFile();
File.WriteAllText(tmpFile.Path, "data");
var trackedFile = new TrackedFile(nullLog, tmpFile.Path, "test-file");
mockAccess.Setup(a => a.UploadFile(It.IsAny<UploadInput>())).Returns("Unable to store block");
Assert.Throws<Exception>(() => node.UploadFile(trackedFile));
}
// --- Stop ---
[Test]
public void Stop_FiresOnNodeStoppingAndCallsAccessStop()
{
node.Stop(waitTillStopped: true);
mockHooks.Verify(h => h.OnNodeStopping(), Times.Once);
mockAccess.Verify(a => a.Stop(true), Times.Once);
}
// --- Initialize ---
[Test]
public void Initialize_ValidDebugInfo_SetsVersion()
{
mockAccess.Setup(a => a.GetDebugInfo()).Returns(new DebugInfo
{
Id = "peer-abc",
Table = new DebugInfoTable { LocalNode = new DebugInfoTableNode { NodeId = "node-xyz" } },
Version = new DebugInfoVersion { Version = "1.0.0", Revision = "rev123" }
});
node.Initialize();
Assert.That(node.Version.Version, Is.EqualTo("1.0.0"));
Assert.That(node.Version.Revision, Is.EqualTo("rev123"));
Assert.That(node.GetPeerId(), Is.EqualTo("peer-abc"));
}
[Test]
public void Initialize_FiresOnNodeStarted()
{
mockAccess.Setup(a => a.GetDebugInfo()).Returns(new DebugInfo
{
Id = "peer-abc",
Table = new DebugInfoTable { LocalNode = new DebugInfoTableNode { NodeId = "node-xyz" } },
Version = new DebugInfoVersion { Version = "1.0.0", Revision = "rev123" }
});
node.Initialize();
mockHooks.Verify(h => h.OnNodeStarted(node, "peer-abc", "node-xyz"), Times.Once);
}
// --- WaitUntilQuotaUsedIncreased ---
[Test]
public void WaitUntilQuotaUsedIncreased_QuotaAlreadyMet_DoesNotThrow()
{
var startSpace = new LogosStorageSpace { QuotaUsedBytes = 0, QuotaMaxBytes = 1_000_000, QuotaReservedBytes = 0 };
var afterSpace = new LogosStorageSpace { QuotaUsedBytes = 500, QuotaMaxBytes = 1_000_000, QuotaReservedBytes = 0 };
mockAccess.Setup(a => a.Space()).Returns(afterSpace);
Assert.DoesNotThrow(() =>
node.WaitUntilQuotaUsedIncreased(startSpace, new ByteSize(400), TimeSpan.FromSeconds(5)));
}
// --- ConnectToPeer ---
[Test]
public void ConnectToPeer_ReplacesZeroIPWithPeerDiscoveryHost()
{
var mockPeerNode = new Mock<IStorageNode>();
mockPeerNode.Setup(n => n.GetName()).Returns("peer-node");
mockPeerNode.Setup(n => n.GetDiscoveryEndpoint())
.Returns(new Address("peer-node", "http://10.0.0.2", 1234));
mockPeerNode.Setup(n => n.GetDebugInfo(It.IsAny<bool>())).Returns(new DebugInfo
{
Id = "peer-id-xyz",
Addrs = new[] { "/ip4/0.0.0.0/tcp/8080", "/ip4/0.0.0.0/tcp/9090" },
Table = new DebugInfoTable { LocalNode = new DebugInfoTableNode() },
Version = new DebugInfoVersion { Version = "1.0", Revision = "r1" }
});
node.ConnectToPeer(mockPeerNode.Object);
mockAccess.Verify(a => a.ConnectToPeer(
"peer-id-xyz",
It.Is<string[]>(addrs =>
addrs[0] == "/ip4/10.0.0.2/tcp/8080" &&
addrs[1] == "/ip4/10.0.0.2/tcp/9090")),
Times.Once);
}
// --- TransferSpeeds ---
[Test]
public void TransferSpeeds_InitiallyEmpty()
{
Assert.That(node.TransferSpeeds.GetUploadSpeed(), Is.Null);
Assert.That(node.TransferSpeeds.GetDownloadSpeed(), Is.Null);
}
// --- HasCrashed / Space / GetPeerId ---
[Test]
public void HasCrashed_DelegatesToAccess()
{
mockAccess.Setup(a => a.HasCrashed()).Returns(true);
Assert.That(node.HasCrashed(), Is.True);
}
}
internal class TempFile : IDisposable
{
public string Path { get; } = System.IO.Path.GetTempFileName();
public void Dispose() => File.Delete(Path);
}
}

View File

@ -0,0 +1,105 @@
using NUnit.Framework;
using LogosStorageClient;
namespace FrameworkTests.LogosStorageClient
{
[TestFixture]
public class DebugInfoVersionTests
{
[Test]
public void IsValid_BothEmpty_ReturnsFalse()
{
var v = new DebugInfoVersion();
Assert.That(v.IsValid(), Is.False);
}
[Test]
public void IsValid_VersionEmptyRevisionSet_ReturnsFalse()
{
var v = new DebugInfoVersion { Version = string.Empty, Revision = "abc123" };
Assert.That(v.IsValid(), Is.False);
}
[Test]
public void IsValid_RevisionEmptyVersionSet_ReturnsFalse()
{
var v = new DebugInfoVersion { Version = "1.0.0", Revision = string.Empty };
Assert.That(v.IsValid(), Is.False);
}
[Test]
public void IsValid_BothSet_ReturnsTrue()
{
var v = new DebugInfoVersion { Version = "1.0.0", Revision = "abc123" };
Assert.That(v.IsValid(), Is.True);
}
}
[TestFixture]
public class LogosStorageSpaceTests
{
[Test]
public void FreeBytes_IsQuotaMax_MinusUsedAndReserved()
{
var space = new LogosStorageSpace
{
QuotaMaxBytes = 1000,
QuotaUsedBytes = 300,
QuotaReservedBytes = 100
};
Assert.That(space.FreeBytes, Is.EqualTo(600));
}
[Test]
public void FreeBytes_NothingUsed_EqualToMax()
{
var space = new LogosStorageSpace { QuotaMaxBytes = 5000, QuotaUsedBytes = 0, QuotaReservedBytes = 0 };
Assert.That(space.FreeBytes, Is.EqualTo(5000));
}
[Test]
public void FreeBytes_ReservedAloneReducesFreeSpace()
{
var space = new LogosStorageSpace { QuotaMaxBytes = 1000, QuotaUsedBytes = 0, QuotaReservedBytes = 400 };
Assert.That(space.FreeBytes, Is.EqualTo(600));
}
}
[TestFixture]
public class LogosStorageUtilsTests
{
[Test]
public void ToShortId_ShortString_ReturnedUnchanged()
{
Assert.That(LogosStorageUtils.ToShortId("abc"), Is.EqualTo("abc"));
}
[Test]
public void ToShortId_ExactlyTenChars_ReturnedUnchanged()
{
Assert.That(LogosStorageUtils.ToShortId("abcde12345"), Is.EqualTo("abcde12345"));
}
[Test]
public void ToShortId_LongString_ReturnsFirst3StarLast6()
{
// "abcde12345678xyz" (16 chars): id[..3]="abc", id[^6..]="678xyz"
var result = LogosStorageUtils.ToShortId("abcde12345678xyz");
Assert.That(result, Is.EqualTo("abc*678xyz"));
}
[Test]
public void ToNodeIdShortId_ShortString_ReturnedUnchanged()
{
Assert.That(LogosStorageUtils.ToNodeIdShortId("abc"), Is.EqualTo("abc"));
}
[Test]
public void ToNodeIdShortId_LongString_ReturnsFirst2StarLast6()
{
// "abcde12345678xyz" (16 chars): id[..2]="ab", id[^6..]="678xyz"
var result = LogosStorageUtils.ToNodeIdShortId("abcde12345678xyz");
Assert.That(result, Is.EqualTo("ab*678xyz"));
}
}
}

View File

@ -0,0 +1,85 @@
using NUnit.Framework;
using LogosStorageClient;
using Utils;
namespace FrameworkTests.LogosStorageClient
{
[TestFixture]
public class TransferSpeedsTests
{
private TransferSpeeds speeds;
[SetUp]
public void SetUp()
{
speeds = new TransferSpeeds();
}
[Test]
public void GetUploadSpeed_NoSamples_ReturnsNull()
{
Assert.That(speeds.GetUploadSpeed(), Is.Null);
}
[Test]
public void GetDownloadSpeed_NoSamples_ReturnsNull()
{
Assert.That(speeds.GetDownloadSpeed(), Is.Null);
}
[Test]
public void GetUploadSpeed_OneSample_ReturnsCorrectSpeed()
{
// 1000 bytes in 2 seconds = 500 bytes/sec
speeds.AddUploadSample(new ByteSize(1000), TimeSpan.FromSeconds(2));
Assert.That(speeds.GetUploadSpeed()!.SizeInBytes, Is.EqualTo(500));
}
[Test]
public void GetDownloadSpeed_OneSample_ReturnsCorrectSpeed()
{
// 3000 bytes in 3 seconds = 1000 bytes/sec
speeds.AddDownloadSample(new ByteSize(3000), TimeSpan.FromSeconds(3));
Assert.That(speeds.GetDownloadSpeed()!.SizeInBytes, Is.EqualTo(1000));
}
[Test]
public void GetUploadSpeed_MultipleSamples_ReturnsAverage()
{
// 1000/1s = 1000, 3000/3s = 1000 → avg = 1000
speeds.AddUploadSample(new ByteSize(1000), TimeSpan.FromSeconds(1));
speeds.AddUploadSample(new ByteSize(3000), TimeSpan.FromSeconds(3));
Assert.That(speeds.GetUploadSpeed()!.SizeInBytes, Is.EqualTo(1000));
}
[Test]
public void UploadAndDownload_Independent()
{
speeds.AddUploadSample(new ByteSize(2000), TimeSpan.FromSeconds(1));
speeds.AddDownloadSample(new ByteSize(500), TimeSpan.FromSeconds(1));
Assert.That(speeds.GetUploadSpeed()!.SizeInBytes, Is.EqualTo(2000));
Assert.That(speeds.GetDownloadSpeed()!.SizeInBytes, Is.EqualTo(500));
}
[Test]
public void Combine_WithNull_ReturnsSelf()
{
speeds.AddUploadSample(new ByteSize(1000), TimeSpan.FromSeconds(1));
var combined = speeds.Combine(null);
Assert.That(combined.GetUploadSpeed()!.SizeInBytes, Is.EqualTo(1000));
}
[Test]
public void Combine_TwoSpeeds_MergesAndAverages()
{
speeds.AddUploadSample(new ByteSize(1000), TimeSpan.FromSeconds(1)); // 1000 b/s
var other = new TransferSpeeds();
other.AddUploadSample(new ByteSize(3000), TimeSpan.FromSeconds(1)); // 3000 b/s
var combined = speeds.Combine(other);
// avg(1000, 3000) = 2000
Assert.That(combined.GetUploadSpeed()!.SizeInBytes, Is.EqualTo(2000));
}
}
}