save progress
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
// <copyright file="SchedulerChainLinkingTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
using FluentAssertions;
|
||||
using StellaOps.HybridLogicalClock;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scheduler.Persistence.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class SchedulerChainLinkingTests
|
||||
{
|
||||
[Fact]
|
||||
public void ComputeLink_WithNullPrevLink_UsesZeroLink()
|
||||
{
|
||||
// Arrange
|
||||
var jobId = Guid.Parse("12345678-1234-1234-1234-123456789012");
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1000000000000L, NodeId = "node1", LogicalCounter = 1 };
|
||||
var payloadHash = new byte[32];
|
||||
payloadHash[0] = 0xAB;
|
||||
|
||||
// Act
|
||||
var link1 = SchedulerChainLinking.ComputeLink(null, jobId, hlc, payloadHash);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(SchedulerChainLinking.ZeroLink, jobId, hlc, payloadHash);
|
||||
|
||||
// Assert
|
||||
link1.Should().HaveCount(32);
|
||||
link1.Should().BeEquivalentTo(link2, "null prev_link should be treated as zero link");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeLink_IsDeterministic_SameInputsSameOutput()
|
||||
{
|
||||
// Arrange
|
||||
var prevLink = new byte[32];
|
||||
prevLink[0] = 0x01;
|
||||
var jobId = Guid.Parse("AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE");
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "scheduler-1", LogicalCounter = 42 };
|
||||
var payloadHash = new byte[32];
|
||||
for (int i = 0; i < 32; i++) payloadHash[i] = (byte)i;
|
||||
|
||||
// Act
|
||||
var link1 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payloadHash);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payloadHash);
|
||||
var link3 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payloadHash);
|
||||
|
||||
// Assert
|
||||
link1.Should().BeEquivalentTo(link2);
|
||||
link2.Should().BeEquivalentTo(link3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeLink_DifferentJobIds_ProduceDifferentLinks()
|
||||
{
|
||||
// Arrange
|
||||
var prevLink = new byte[32];
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "node1", LogicalCounter = 1 };
|
||||
var payloadHash = new byte[32];
|
||||
|
||||
var jobId1 = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||||
var jobId2 = Guid.Parse("22222222-2222-2222-2222-222222222222");
|
||||
|
||||
// Act
|
||||
var link1 = SchedulerChainLinking.ComputeLink(prevLink, jobId1, hlc, payloadHash);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(prevLink, jobId2, hlc, payloadHash);
|
||||
|
||||
// Assert
|
||||
link1.Should().NotBeEquivalentTo(link2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeLink_DifferentHlcTimestamps_ProduceDifferentLinks()
|
||||
{
|
||||
// Arrange
|
||||
var prevLink = new byte[32];
|
||||
var jobId = Guid.NewGuid();
|
||||
var payloadHash = new byte[32];
|
||||
|
||||
var hlc1 = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "node1", LogicalCounter = 1 };
|
||||
var hlc2 = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "node1", LogicalCounter = 2 }; // Different counter
|
||||
var hlc3 = new HlcTimestamp { PhysicalTime = 1704067200001L, NodeId = "node1", LogicalCounter = 1 }; // Different physical time
|
||||
|
||||
// Act
|
||||
var link1 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc1, payloadHash);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc2, payloadHash);
|
||||
var link3 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc3, payloadHash);
|
||||
|
||||
// Assert
|
||||
link1.Should().NotBeEquivalentTo(link2);
|
||||
link1.Should().NotBeEquivalentTo(link3);
|
||||
link2.Should().NotBeEquivalentTo(link3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeLink_DifferentPrevLinks_ProduceDifferentLinks()
|
||||
{
|
||||
// Arrange
|
||||
var jobId = Guid.NewGuid();
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "node1", LogicalCounter = 1 };
|
||||
var payloadHash = new byte[32];
|
||||
|
||||
var prevLink1 = new byte[32];
|
||||
var prevLink2 = new byte[32];
|
||||
prevLink2[0] = 0xFF;
|
||||
|
||||
// Act
|
||||
var link1 = SchedulerChainLinking.ComputeLink(prevLink1, jobId, hlc, payloadHash);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(prevLink2, jobId, hlc, payloadHash);
|
||||
|
||||
// Assert
|
||||
link1.Should().NotBeEquivalentTo(link2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeLink_DifferentPayloadHashes_ProduceDifferentLinks()
|
||||
{
|
||||
// Arrange
|
||||
var prevLink = new byte[32];
|
||||
var jobId = Guid.NewGuid();
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "node1", LogicalCounter = 1 };
|
||||
|
||||
var payload1 = new byte[32];
|
||||
var payload2 = new byte[32];
|
||||
payload2[31] = 0x01;
|
||||
|
||||
// Act
|
||||
var link1 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payload1);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payload2);
|
||||
|
||||
// Assert
|
||||
link1.Should().NotBeEquivalentTo(link2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeLink_WithStringHlc_ProducesSameResultAsParsedHlc()
|
||||
{
|
||||
// Arrange
|
||||
var prevLink = new byte[32];
|
||||
var jobId = Guid.NewGuid();
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "node1", LogicalCounter = 42 };
|
||||
var hlcString = hlc.ToSortableString();
|
||||
var payloadHash = new byte[32];
|
||||
|
||||
// Act
|
||||
var link1 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payloadHash);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlcString, payloadHash);
|
||||
|
||||
// Assert
|
||||
link1.Should().BeEquivalentTo(link2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyLink_ValidLink_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var prevLink = new byte[32];
|
||||
prevLink[0] = 0xDE;
|
||||
var jobId = Guid.NewGuid();
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "verifier", LogicalCounter = 100 };
|
||||
var payloadHash = new byte[32];
|
||||
payloadHash[15] = 0xAD;
|
||||
|
||||
var computedLink = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payloadHash);
|
||||
|
||||
// Act
|
||||
var isValid = SchedulerChainLinking.VerifyLink(computedLink, prevLink, jobId, hlc, payloadHash);
|
||||
|
||||
// Assert
|
||||
isValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyLink_TamperedLink_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var prevLink = new byte[32];
|
||||
var jobId = Guid.NewGuid();
|
||||
var hlc = new HlcTimestamp { PhysicalTime = 1704067200000L, NodeId = "node1", LogicalCounter = 1 };
|
||||
var payloadHash = new byte[32];
|
||||
|
||||
var computedLink = SchedulerChainLinking.ComputeLink(prevLink, jobId, hlc, payloadHash);
|
||||
|
||||
// Tamper with the link
|
||||
var tamperedLink = (byte[])computedLink.Clone();
|
||||
tamperedLink[0] ^= 0xFF;
|
||||
|
||||
// Act
|
||||
var isValid = SchedulerChainLinking.VerifyLink(tamperedLink, prevLink, jobId, hlc, payloadHash);
|
||||
|
||||
// Assert
|
||||
isValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputePayloadHash_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var payload = new { Id = 123, Name = "Test", Values = new[] { 1, 2, 3 } };
|
||||
|
||||
// Act
|
||||
var hash1 = SchedulerChainLinking.ComputePayloadHash(payload);
|
||||
var hash2 = SchedulerChainLinking.ComputePayloadHash(payload);
|
||||
|
||||
// Assert
|
||||
hash1.Should().HaveCount(32);
|
||||
hash1.Should().BeEquivalentTo(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputePayloadHash_DifferentPayloads_ProduceDifferentHashes()
|
||||
{
|
||||
// Arrange
|
||||
var payload1 = new { Id = 1, Name = "First" };
|
||||
var payload2 = new { Id = 2, Name = "Second" };
|
||||
|
||||
// Act
|
||||
var hash1 = SchedulerChainLinking.ComputePayloadHash(payload1);
|
||||
var hash2 = SchedulerChainLinking.ComputePayloadHash(payload2);
|
||||
|
||||
// Assert
|
||||
hash1.Should().NotBeEquivalentTo(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputePayloadHash_ByteArray_ProducesConsistentHash()
|
||||
{
|
||||
// Arrange
|
||||
var bytes = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
|
||||
|
||||
// Act
|
||||
var hash1 = SchedulerChainLinking.ComputePayloadHash(bytes);
|
||||
var hash2 = SchedulerChainLinking.ComputePayloadHash(bytes);
|
||||
|
||||
// Assert
|
||||
hash1.Should().HaveCount(32);
|
||||
hash1.Should().BeEquivalentTo(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToHex_NullLink_ReturnsNullString()
|
||||
{
|
||||
// Act
|
||||
var result = SchedulerChainLinking.ToHex(null);
|
||||
|
||||
// Assert
|
||||
result.Should().Be("(null)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToHex_EmptyLink_ReturnsNullString()
|
||||
{
|
||||
// Act
|
||||
var result = SchedulerChainLinking.ToHex(Array.Empty<byte>());
|
||||
|
||||
// Assert
|
||||
result.Should().Be("(null)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToHex_ValidLink_ReturnsLowercaseHex()
|
||||
{
|
||||
// Arrange
|
||||
var link = new byte[] { 0xAB, 0xCD, 0xEF };
|
||||
|
||||
// Act
|
||||
var result = SchedulerChainLinking.ToHex(link);
|
||||
|
||||
// Assert
|
||||
result.Should().Be("abcdef");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChainIntegrity_SequentialLinks_FormValidChain()
|
||||
{
|
||||
// Arrange - Simulate a chain of 5 entries
|
||||
var jobIds = Enumerable.Range(1, 5).Select(i => Guid.NewGuid()).ToList();
|
||||
var payloads = jobIds.Select(id => SchedulerChainLinking.ComputePayloadHash(new { JobId = id })).ToList();
|
||||
|
||||
var links = new List<byte[]>();
|
||||
byte[]? prevLink = null;
|
||||
long baseTime = 1704067200000L;
|
||||
|
||||
// Act - Build chain
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var hlc = new HlcTimestamp { PhysicalTime = baseTime + i, NodeId = "node1", LogicalCounter = i };
|
||||
var link = SchedulerChainLinking.ComputeLink(prevLink, jobIds[i], hlc, payloads[i]);
|
||||
links.Add(link);
|
||||
prevLink = link;
|
||||
}
|
||||
|
||||
// Assert - Verify chain integrity
|
||||
byte[]? expectedPrev = null;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var hlc = new HlcTimestamp { PhysicalTime = baseTime + i, NodeId = "node1", LogicalCounter = i };
|
||||
var isValid = SchedulerChainLinking.VerifyLink(links[i], expectedPrev, jobIds[i], hlc, payloads[i]);
|
||||
isValid.Should().BeTrue($"Link {i} should be valid");
|
||||
expectedPrev = links[i];
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChainIntegrity_TamperedMiddleLink_BreaksChain()
|
||||
{
|
||||
// Arrange - Build a chain of 3 entries
|
||||
var jobIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var payloads = jobIds.Select(id => SchedulerChainLinking.ComputePayloadHash(new { JobId = id })).ToArray();
|
||||
var hlcs = new[]
|
||||
{
|
||||
new HlcTimestamp { PhysicalTime = 1000L, NodeId = "node1", LogicalCounter = 0 },
|
||||
new HlcTimestamp { PhysicalTime = 1001L, NodeId = "node1", LogicalCounter = 0 },
|
||||
new HlcTimestamp { PhysicalTime = 1002L, NodeId = "node1", LogicalCounter = 0 }
|
||||
};
|
||||
|
||||
var link0 = SchedulerChainLinking.ComputeLink(null, jobIds[0], hlcs[0], payloads[0]);
|
||||
var link1 = SchedulerChainLinking.ComputeLink(link0, jobIds[1], hlcs[1], payloads[1]);
|
||||
var link2 = SchedulerChainLinking.ComputeLink(link1, jobIds[2], hlcs[2], payloads[2]);
|
||||
|
||||
// Tamper with middle link
|
||||
var tamperedLink1 = (byte[])link1.Clone();
|
||||
tamperedLink1[0] ^= 0xFF;
|
||||
|
||||
// Act & Assert - First link is still valid
|
||||
SchedulerChainLinking.VerifyLink(link0, null, jobIds[0], hlcs[0], payloads[0])
|
||||
.Should().BeTrue("First link should be valid");
|
||||
|
||||
// Middle link verification fails
|
||||
SchedulerChainLinking.VerifyLink(tamperedLink1, link0, jobIds[1], hlcs[1], payloads[1])
|
||||
.Should().BeFalse("Tampered middle link should fail verification");
|
||||
|
||||
// Third link verification fails because prev_link is wrong
|
||||
SchedulerChainLinking.VerifyLink(link2, tamperedLink1, jobIds[2], hlcs[2], payloads[2])
|
||||
.Should().BeFalse("Third link should fail with tampered prev_link");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user