Files
Rendezvous/tests/FinalFactory.Rendezvous.Tests/State/InMemoryEphemeralRendezvousStoreTests.cs
KyuubiYoru be732de7c9
quality-gate / quality (push) Failing after 1m1s
feat(server): add observability and operator controls (#16)
2026-07-16 13:22:16 +02:00

513 lines
23 KiB
C#

using FinalFactory.Rendezvous.Contracts;
using FinalFactory.Rendezvous.Server.State;
namespace FinalFactory.Rendezvous.Tests.State;
public sealed class InMemoryEphemeralRendezvousStoreTests
{
[Fact]
public void DeterministicHostileStateTransitionsStayTypedAndCapacityBounded()
{
const int seed = 0x15_57A7E;
Random random = new(seed);
EphemeralStateFixture fixture = new(new EphemeralStoreOptions
{
MaxJoinAttempts = 32,
});
StoredListing listing = fixture.CreateVisibleListing(out _);
for (int iteration = 0; iteration < 1_000; iteration++)
{
CreateJoinAttemptCommand command = fixture.AttemptCommand(listing);
command = random.Next(4) switch
{
0 => command with
{
Scope = new(new GameId("other-game"), new EnvironmentId("test")),
},
1 => command with { ProtocolVersion = command.ProtocolVersion + 1 },
_ => command,
};
StoreResult<StoredJoinAttempt> created = fixture.Store.CreateJoinAttempt(command);
Assert.True(Enum.IsDefined(created.Code));
if (!created.Succeeded || created.Value is null)
{
continue;
}
SecretFingerprint supplied = random.Next(3) == 0
? EphemeralStateFixture.Fingerprint($"wrong-{iteration}")
: created.Value.ClientCapabilityFingerprint;
StoreResult<StoredJoinAttempt> bound = fixture.Store.BindAttemptEndpoint(new(
created.Value.MediationHandle,
AttemptPeerRole.Client,
supplied,
EphemeralStateFixture.OtherPublicEndpoint(20_000 + iteration),
null));
Assert.True(Enum.IsDefined(bound.Code));
}
StoreResult<IReadOnlyList<StoredJoinAttempt>> attempts =
fixture.Store.BrowseHostJoinAttempts(new(
listing.Definition.ListingId,
listing.Definition.LeaseFingerprint,
100));
Assert.True(attempts.Succeeded);
Assert.InRange(attempts.Value!.Count, 1, 32);
}
[Fact]
public void IdempotencyRetentionMustCoverResourceLifetimes()
{
ManualRendezvousClock clock = new();
Assert.Throws<ArgumentOutOfRangeException>(() => new InMemoryEphemeralRendezvousStore(
new EphemeralStoreOptions { IdempotencyLifetime = TimeSpan.FromSeconds(5) },
clock,
clock));
}
[Fact]
public void DuplicateRegistrationIsIdempotentButChangedRequestConflicts()
{
EphemeralStateFixture fixture = new();
CreateListingCommand command = fixture.ListingCommand();
StoreResult<StoredListing> first = fixture.Store.CreateListing(command);
StoreResult<StoredListing> duplicate = fixture.Store.CreateListing(command);
StoreResult<StoredListing> changed = fixture.Store.CreateListing(command with { RequestFingerprint = "different" });
Assert.True(first.Succeeded);
Assert.True(duplicate.Succeeded);
Assert.True(duplicate.IsIdempotentReplay);
Assert.Equal(first.Value!.Definition.ListingId, duplicate.Value!.Definition.ListingId);
Assert.Equal(StoreResultCode.Conflict, changed.Code);
}
[Fact]
public void LeaseAndPresenceExpiryUseMonotonicTime()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out _);
fixture.Clock.Advance(TimeSpan.FromSeconds(20));
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(listing.Definition.ListingId, true).Code);
Assert.True(fixture.Store.GetListing(listing.Definition.ListingId, false).Succeeded);
fixture.Clock.Advance(TimeSpan.FromSeconds(40));
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(listing.Definition.ListingId, false).Code);
}
[Fact]
public void WallClockMovementDoesNotExpireOrExtendLease()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out CreateListingCommand command);
fixture.Clock.MoveWall(TimeSpan.FromDays(30));
Assert.True(fixture.Store.GetListing(listing.Definition.ListingId, false).Succeeded);
fixture.Clock.Advance(TimeSpan.FromSeconds(1));
StoreResult<StoredListing> renewed = fixture.Store.RenewLease(new(
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));
fixture.Clock.Advance(TimeSpan.FromSeconds(59));
Assert.True(fixture.Store.GetListing(listing.Definition.ListingId, false).Succeeded);
fixture.Clock.Advance(TimeSpan.FromSeconds(1));
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(command.Listing.ListingId, false).Code);
}
[Fact]
public async Task RenewDeleteRaceIsAtomicAndDeleteAlwaysWinsEventually()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out CreateListingCommand command);
using ManualResetEventSlim start = new(false);
Task<StoreResult<StoredListing>> renew = Task.Run(() =>
{
start.Wait();
return fixture.Store.RenewLease(new(
listing.Definition.ListingId,
listing.Definition.LeaseId,
listing.Definition.LeaseFingerprint,
listing.Definition.OwnerSubject,
listing.Version));
});
Task<StoreResult<bool>> delete = Task.Run(() =>
{
start.Wait();
return fixture.Store.DeleteListing(new(
command.Listing.ListingId,
command.Listing.LeaseId,
command.Listing.LeaseFingerprint,
command.Listing.OwnerSubject));
});
start.Set();
await Task.WhenAll(renew, delete);
StoreResult<StoredListing> renewResult = await renew;
StoreResult<bool> deleteResult = await delete;
Assert.True(deleteResult.Succeeded);
Assert.Contains(renewResult.Code, new[] { StoreResultCode.Success, StoreResultCode.NotFound });
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(command.Listing.ListingId, false).Code);
}
[Fact]
public void CompareAndSwapPreventsStaleRenewal()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out _);
RenewLeaseCommand command = new(
listing.Definition.ListingId,
listing.Definition.LeaseId,
listing.Definition.LeaseFingerprint,
listing.Definition.OwnerSubject,
listing.Version);
StoreResult<StoredListing> first = fixture.Store.RenewLease(command);
StoreResult<StoredListing> stale = fixture.Store.RenewLease(command);
Assert.Equal(2, first.Value!.Version);
Assert.Equal(StoreResultCode.Conflict, stale.Code);
Assert.Equal(2, stale.Value!.Version);
}
[Fact]
public void JoinRequiresExactScopeProtocolAndFreshHostPresence()
{
EphemeralStateFixture fixture = new();
CreateListingCommand listingCommand = fixture.ListingCommand();
StoredListing listing = fixture.Store.CreateListing(listingCommand).Value!;
CreateJoinAttemptCommand attempt = fixture.AttemptCommand(listing);
Assert.Equal(StoreResultCode.StaleHost, fixture.Store.CreateJoinAttempt(attempt).Code);
fixture.Store.BindHostPresence(new(
listingCommand.Listing.HostPresenceHandle,
listingCommand.Listing.HostPresenceFingerprint,
EphemeralStateFixture.PublicEndpoint(40_000),
null));
Assert.Equal(StoreResultCode.IncompatibleProtocol, fixture.Store.CreateJoinAttempt(attempt with { ProtocolVersion = 8 }).Code);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.CreateJoinAttempt(attempt with
{
Scope = new(new("other-game"), new("test")),
}).Code);
Assert.True(fixture.Store.CreateJoinAttempt(attempt).Succeeded);
}
[Fact]
public void DuplicateJoinIsIdempotentAndDoesNotAllocateTwice()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out _);
CreateJoinAttemptCommand command = fixture.AttemptCommand(listing);
StoreResult<StoredJoinAttempt> first = fixture.Store.CreateJoinAttempt(command);
StoreResult<StoredJoinAttempt> duplicate = fixture.Store.CreateJoinAttempt(command);
Assert.True(first.Succeeded);
Assert.True(duplicate.Succeeded);
Assert.True(duplicate.IsIdempotentReplay);
Assert.Equal(first.Value!.AttemptId, duplicate.Value!.AttemptId);
}
[Fact]
public void BrowseReturnsOnlyFreshPublicCompatibleListingsInStableOrder()
{
EphemeralStateFixture fixture = new();
StoredListing visible = fixture.CreateVisibleListing(out _);
CreateListingCommand staleCommand = fixture.ListingCommand();
fixture.Store.CreateListing(staleCommand);
CreateListingCommand unlistedCommand = fixture.ListingCommand();
unlistedCommand = unlistedCommand with
{
Listing = unlistedCommand.Listing with { Visibility = ListingVisibility.Unlisted },
};
fixture.Store.CreateListing(unlistedCommand);
fixture.Store.BindHostPresence(new(
unlistedCommand.Listing.HostPresenceHandle,
unlistedCommand.Listing.HostPresenceFingerprint,
EphemeralStateFixture.PublicEndpoint(40_099),
null));
StoreResult<IReadOnlyList<StoredListing>> result = fixture.Store.BrowseVisibleListings(new(
fixture.Scope,
visible.Definition.ProtocolVersion,
visible.Definition.RegionId));
Assert.True(result.Succeeded);
Assert.Collection(result.Value!, item => Assert.Equal(visible.Definition.ListingId, item.Definition.ListingId));
}
[Fact]
public async Task ConcurrentEndpointBindingAcceptsOneCompleteEndpointOnly()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out _);
CreateJoinAttemptCommand command = fixture.AttemptCommand(listing);
fixture.Store.CreateJoinAttempt(command);
BindAttemptEndpointCommand first = new(
command.MediationHandle,
AttemptPeerRole.Client,
command.ClientCapabilityFingerprint,
EphemeralStateFixture.PublicEndpoint(40_001),
EphemeralStateFixture.LocalEndpoint(40_001));
BindAttemptEndpointCommand second = first with
{
PublicEndpoint = EphemeralStateFixture.OtherPublicEndpoint(50_001),
LocalEndpoint = null,
};
using ManualResetEventSlim start = new(false);
Task<StoreResult<StoredJoinAttempt>> left = Task.Run(() => { start.Wait(); return fixture.Store.BindAttemptEndpoint(first); });
Task<StoreResult<StoredJoinAttempt>> right = Task.Run(() => { start.Wait(); return fixture.Store.BindAttemptEndpoint(second); });
start.Set();
await Task.WhenAll(left, right);
StoreResult<StoredJoinAttempt> leftResult = await left;
StoreResult<StoredJoinAttempt> rightResult = await right;
Assert.Equal(1, new[] { leftResult, rightResult }.Count(static result => result.Succeeded));
Assert.Equal(1, new[] { leftResult, rightResult }.Count(static result => result.Code == StoreResultCode.ReplayRejected));
}
[Fact]
public void AttemptCapabilitiesAndIntroductionAreOneTime()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out _);
CreateJoinAttemptCommand command = fixture.AttemptCommand(listing);
fixture.Store.CreateJoinAttempt(command);
BindAttemptEndpointCommand host = new(
command.MediationHandle,
AttemptPeerRole.Host,
command.HostCapabilityFingerprint,
EphemeralStateFixture.PublicEndpoint(40_010),
null);
BindAttemptEndpointCommand client = new(
command.MediationHandle,
AttemptPeerRole.Client,
command.ClientCapabilityFingerprint,
EphemeralStateFixture.OtherPublicEndpoint(40_020),
null);
Assert.True(fixture.Store.BindAttemptEndpoint(host).Succeeded);
Assert.True(fixture.Store.BindAttemptEndpoint(client).Succeeded);
Assert.True(fixture.Store.BindAttemptEndpoint(client).IsIdempotentReplay);
Assert.True(fixture.Store.ConsumeIntroduction(command.MediationHandle).Succeeded);
Assert.Equal(StoreResultCode.ReplayRejected, fixture.Store.ConsumeIntroduction(command.MediationHandle).Code);
Assert.Equal(StoreResultCode.ReplayRejected, fixture.Store.BindAttemptEndpoint(client with
{
PublicEndpoint = EphemeralStateFixture.OtherPublicEndpoint(40_021),
}).Code);
}
[Fact]
public void ExpiredAttemptCannotBeObservedBoundOrConsumed()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out _);
CreateJoinAttemptCommand command = fixture.AttemptCommand(listing);
fixture.Store.CreateJoinAttempt(command);
fixture.Clock.Advance(TimeSpan.FromSeconds(30));
Assert.Equal(StoreResultCode.NotFound, fixture.Store.BindAttemptEndpoint(new(
command.MediationHandle,
AttemptPeerRole.Client,
command.ClientCapabilityFingerprint,
EphemeralStateFixture.PublicEndpoint(40_050),
null)).Code);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.ConsumeIntroduction(command.MediationHandle).Code);
}
[Fact]
public void GenericReplayConsumptionIsBoundedAndExpires()
{
EphemeralStoreOptions options = new() { MaxReplayEntries = 1 };
EphemeralStateFixture fixture = new(options);
Assert.True(fixture.Store.ConsumeReplay(new("ticket", "one")).Succeeded);
Assert.Equal(StoreResultCode.ReplayRejected, fixture.Store.ConsumeReplay(new("ticket", "one")).Code);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.ConsumeReplay(new("ticket", "two")).Code);
fixture.Clock.Advance(options.ReplayLifetime);
Assert.True(fixture.Store.ConsumeReplay(new("ticket", "two")).Succeeded);
}
[Fact]
public void PresenceAttemptAndRevocationPoolsShedWithoutPartialMutation()
{
EphemeralStoreOptions options = new()
{
MaxPresenceBindings = 1,
MaxJoinAttempts = 1,
MaxRevocations = 1,
};
EphemeralStateFixture fixture = new(options);
StoredListing first = fixture.CreateVisibleListing(out CreateListingCommand firstCommand);
CreateListingCommand secondCommand = fixture.ListingCommand(owner: "publisher-2");
fixture.Store.CreateListing(secondCommand);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.BindHostPresence(new(
secondCommand.Listing.HostPresenceHandle,
secondCommand.Listing.HostPresenceFingerprint,
EphemeralStateFixture.OtherPublicEndpoint(42_000),
null)).Code);
Assert.True(fixture.Store.CreateJoinAttempt(fixture.AttemptCommand(first)).Succeeded);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.CreateJoinAttempt(fixture.AttemptCommand(first, "client-2")).Code);
Assert.True(fixture.Store.RevokePrincipal("unrelated", TimeSpan.FromMinutes(1)).Succeeded);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.RevokePrincipal(firstCommand.Listing.OwnerSubject, TimeSpan.FromMinutes(1)).Code);
Assert.True(fixture.Store.GetListing(first.Definition.ListingId, true).Succeeded);
Assert.True(fixture.Store.GetListing(secondCommand.Listing.ListingId, false).Succeeded);
}
[Fact]
public void ExhaustionShedsNewListingWithoutMutatingExistingState()
{
EphemeralStoreOptions options = new() { MaxListings = 1 };
EphemeralStateFixture fixture = new(options);
CreateListingCommand first = fixture.ListingCommand();
CreateListingCommand second = fixture.ListingCommand();
Assert.True(fixture.Store.CreateListing(first).Succeeded);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.CreateListing(second).Code);
Assert.True(fixture.Store.GetListing(first.Listing.ListingId, false).Succeeded);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(second.Listing.ListingId, false).Code);
}
[Fact]
public void IdempotencyPoolExhaustionDoesNotCreateUntrackedResource()
{
EphemeralStoreOptions options = new() { MaxListings = 2, MaxIdempotencyEntries = 1 };
EphemeralStateFixture fixture = new(options);
CreateListingCommand first = fixture.ListingCommand();
CreateListingCommand second = fixture.ListingCommand();
Assert.True(fixture.Store.CreateListing(first).Succeeded);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.CreateListing(second).Code);
Assert.True(fixture.Store.GetListing(first.Listing.ListingId, false).Succeeded);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(second.Listing.ListingId, false).Code);
}
[Fact]
public void PolicyQuotasAreCheckedInsideAtomicCreation()
{
EphemeralStateFixture fixture = new();
CreateListingCommand first = fixture.ListingCommand(owner: "publisher-quota") with { OwnerListingLimit = 1 };
CreateListingCommand second = fixture.ListingCommand(owner: "publisher-quota") with { OwnerListingLimit = 1 };
Assert.True(fixture.Store.CreateListing(first).Succeeded);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.CreateListing(second).Code);
fixture.Store.BindHostPresence(new(
first.Listing.HostPresenceHandle,
first.Listing.HostPresenceFingerprint,
EphemeralStateFixture.PublicEndpoint(42_100),
null));
StoredListing listing = fixture.Store.GetListing(first.Listing.ListingId, true).Value!;
CreateJoinAttemptCommand attempt = fixture.AttemptCommand(listing) with { ScopeAttemptLimit = 1 };
Assert.True(fixture.Store.CreateJoinAttempt(attempt).Succeeded);
Assert.Equal(StoreResultCode.CapacityExceeded, fixture.Store.CreateJoinAttempt(
fixture.AttemptCommand(listing, "client-quota-2") with { ScopeAttemptLimit = 1 }).Code);
}
[Fact]
public void InvalidDefaultSecurityValuesCannotEnterStore()
{
EphemeralStateFixture fixture = new();
CreateListingCommand command = fixture.ListingCommand();
Assert.Throws<ArgumentException>(() => fixture.Store.CreateListing(command with
{
Listing = command.Listing with { LeaseFingerprint = default },
}));
}
[Fact]
public void RevocationRemovesEveryPathAndBlocksNewWorkAtomically()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out CreateListingCommand command);
CreateJoinAttemptCommand attempt = fixture.AttemptCommand(listing, command.Listing.OwnerSubject);
fixture.Store.CreateJoinAttempt(attempt);
StoreResult<int> revoked = fixture.Store.RevokePrincipal(command.Listing.OwnerSubject, TimeSpan.FromMinutes(1));
Assert.True(revoked.Succeeded);
Assert.Equal(4, revoked.Value);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(listing.Definition.ListingId, false).Code);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.BindAttemptEndpoint(new(
attempt.MediationHandle,
AttemptPeerRole.Client,
attempt.ClientCapabilityFingerprint,
EphemeralStateFixture.PublicEndpoint(40_030),
null)).Code);
Assert.Equal(StoreResultCode.Revoked, fixture.Store.CreateListing(fixture.ListingCommand(owner: command.Listing.OwnerSubject)).Code);
}
[Fact]
public void RestartHasNewGenerationAndNoEphemeralState()
{
EphemeralStateFixture before = new();
StoredListing listing = before.CreateVisibleListing(out _);
EphemeralStateFixture after = new();
Assert.NotEqual(before.Store.InstanceId, after.Store.InstanceId);
Assert.Equal(StoreResultCode.NotFound, after.Store.GetListing(listing.Definition.ListingId, false).Code);
}
[Fact]
public void DrainRejectsNewWorkAllowsInflightCompletionThenClearsState()
{
EphemeralStoreOptions options = new() { GracefulDrainLifetime = TimeSpan.FromSeconds(5) };
EphemeralStateFixture fixture = new(options);
StoredListing listing = fixture.CreateVisibleListing(out _);
CreateJoinAttemptCommand attempt = fixture.AttemptCommand(listing);
fixture.Store.CreateJoinAttempt(attempt);
fixture.Store.BeginDrain();
Assert.Equal(StoreResultCode.Draining, fixture.Store.CreateListing(fixture.ListingCommand()).Code);
Assert.Equal(StoreResultCode.Draining, fixture.Store.CreateJoinAttempt(fixture.AttemptCommand(listing)).Code);
Assert.True(fixture.Store.BindAttemptEndpoint(new(
attempt.MediationHandle,
AttemptPeerRole.Client,
attempt.ClientCapabilityFingerprint,
EphemeralStateFixture.PublicEndpoint(41_000),
null)).Succeeded);
fixture.Clock.Advance(options.GracefulDrainLifetime);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(listing.Definition.ListingId, false).Code);
Assert.Equal(StoreResultCode.NotFound, fixture.Store.BindAttemptEndpoint(new(
attempt.MediationHandle,
AttemptPeerRole.Host,
attempt.HostCapabilityFingerprint,
EphemeralStateFixture.PublicEndpoint(41_001),
null)).Code);
}
[Fact]
public void PrecancelledOperationHasNoPartialEffect()
{
EphemeralStateFixture fixture = new();
CreateListingCommand command = fixture.ListingCommand();
using CancellationTokenSource cancellation = new();
cancellation.Cancel();
Assert.Throws<OperationCanceledException>(() => fixture.Store.CreateListing(command, cancellation.Token));
Assert.Equal(StoreResultCode.NotFound, fixture.Store.GetListing(command.Listing.ListingId, false).Code);
}
[Fact]
public void UnavailableStoreFailsNewAuthorizationClosedAndErasesActiveState()
{
EphemeralStateFixture fixture = new();
StoredListing listing = fixture.CreateVisibleListing(out _);
fixture.Store.MarkUnavailable();
Assert.Equal(StoreResultCode.ServiceUnavailable, fixture.Store.GetListing(listing.Definition.ListingId, false).Code);
Assert.Equal(StoreResultCode.ServiceUnavailable, fixture.Store.CreateJoinAttempt(fixture.AttemptCommand(listing)).Code);
}
}