feat: add presence-gated session leases (#7)
quality-gate / quality (push) Successful in 55s

Closes #7
This commit is contained in:
KyuubiYoru
2026-07-16 05:58:47 +02:00
parent 02ca502a76
commit 49564c7e7e
25 changed files with 2069 additions and 46 deletions
@@ -5,7 +5,10 @@ namespace FinalFactory.Rendezvous.Tests.State;
internal sealed class ManualRendezvousClock : IWallClock, IMonotonicClock
{
public DateTimeOffset UtcNow { get; private set; } = new(2026, 7, 16, 0, 0, 0, TimeSpan.Zero);
public ManualRendezvousClock(DateTimeOffset? utcNow = null) =>
UtcNow = utcNow ?? new DateTimeOffset(2026, 7, 16, 0, 0, 0, TimeSpan.Zero);
public DateTimeOffset UtcNow { get; private set; }
public TimeSpan Elapsed { get; private set; }
public void Advance(TimeSpan duration)
@@ -58,6 +61,7 @@ internal sealed class EphemeralStateFixture
LeaseFingerprint = Fingerprint($"lease-{sequence}"),
HostPresenceHandle = NewHandle(),
HostPresenceFingerprint = Fingerprint($"presence-{sequence}"),
CapabilityDerivationSalt = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
});
}
@@ -5,6 +5,16 @@ namespace FinalFactory.Rendezvous.Tests.State;
public sealed class InMemoryEphemeralRendezvousStoreTests
{
[Fact]
public void IdempotencyRetentionMustCoverResourceLifetimes()
{
ManualRendezvousClock clock = new();
Assert.Throws<ArgumentOutOfRangeException>(() => new InMemoryEphemeralRendezvousStore(
new EphemeralStoreOptions { IdempotencyLifetime = TimeSpan.FromSeconds(5) },
clock,
clock));
}
[Fact]
public void DuplicateRegistrationIsIdempotentButChangedRequestConflicts()
{
@@ -49,6 +59,7 @@ public sealed class InMemoryEphemeralRendezvousStoreTests
listing.Definition.ListingId,
listing.Definition.LeaseId,
listing.Definition.LeaseFingerprint,
listing.Definition.OwnerSubject,
listing.Version));
Assert.Equal(new DateTimeOffset(2026, 7, 16, 0, 1, 1, TimeSpan.Zero), renewed.Value!.LeaseExpiresAt);
fixture.Clock.MoveWall(TimeSpan.FromDays(-60));
@@ -72,6 +83,7 @@ public sealed class InMemoryEphemeralRendezvousStoreTests
listing.Definition.ListingId,
listing.Definition.LeaseId,
listing.Definition.LeaseFingerprint,
listing.Definition.OwnerSubject,
listing.Version));
});
Task<StoreResult<bool>> delete = Task.Run(() =>
@@ -80,7 +92,8 @@ public sealed class InMemoryEphemeralRendezvousStoreTests
return fixture.Store.DeleteListing(new(
command.Listing.ListingId,
command.Listing.LeaseId,
command.Listing.LeaseFingerprint));
command.Listing.LeaseFingerprint,
command.Listing.OwnerSubject));
});
start.Set();
@@ -102,6 +115,7 @@ public sealed class InMemoryEphemeralRendezvousStoreTests
listing.Definition.ListingId,
listing.Definition.LeaseId,
listing.Definition.LeaseFingerprint,
listing.Definition.OwnerSubject,
listing.Version);
StoreResult<StoredListing> first = fixture.Store.RenewLease(command);