Files
Rendezvous/tests/FinalFactory.Rendezvous.Tests/State/EphemeralStateTestData.cs
KyuubiYoru 49564c7e7e
quality-gate / quality (push) Successful in 55s
feat: add presence-gated session leases (#7)
Closes #7
2026-07-16 05:58:47 +02:00

110 lines
4.4 KiB
C#

using FinalFactory.Rendezvous.Contracts;
using FinalFactory.Rendezvous.Server.State;
namespace FinalFactory.Rendezvous.Tests.State;
internal sealed class ManualRendezvousClock : IWallClock, IMonotonicClock
{
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)
{
Elapsed += duration;
UtcNow += duration;
}
public void MoveWall(TimeSpan duration) => UtcNow += duration;
}
internal sealed class EphemeralStateFixture
{
private int _sequence;
public EphemeralStateFixture(EphemeralStoreOptions? options = null)
{
Clock = new();
Store = new(options ?? new EphemeralStoreOptions(), Clock, Clock);
}
public ManualRendezvousClock Clock { get; }
public InMemoryEphemeralRendezvousStore Store { get; }
public TenantScope Scope { get; } = new(new GameId("space-game"), new EnvironmentId("test"));
public CreateListingCommand ListingCommand(
string owner = "publisher-1",
string? idempotencyKey = null,
string? requestFingerprint = null)
{
int sequence = Interlocked.Increment(ref _sequence);
return new(
idempotencyKey ?? $"register-{sequence}",
requestFingerprint ?? $"request-{sequence}",
new ListingDefinition
{
ListingId = NewListingId(),
LeaseId = NewLeaseId(),
Scope = Scope,
OwnerSubject = owner,
RegionId = new RegionId("eu-central"),
ProtocolVersion = 7,
BuildVersion = "1.2.3",
DisplayName = "Test host",
Visibility = ListingVisibility.Public,
TrustMode = PublisherTrustMode.ManagedDedicated,
CurrentPlayers = 1,
MaximumPlayers = 8,
Metadata = new Dictionary<string, string>(StringComparer.Ordinal) { ["mode"] = "coop" },
LeaseFingerprint = Fingerprint($"lease-{sequence}"),
HostPresenceHandle = NewHandle(),
HostPresenceFingerprint = Fingerprint($"presence-{sequence}"),
CapabilityDerivationSalt = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
});
}
public StoredListing CreateVisibleListing(out CreateListingCommand command)
{
command = ListingCommand();
StoreResult<StoredListing> created = Store.CreateListing(command);
Assert.True(created.Succeeded);
StoreResult<StoredListing> bound = Store.BindHostPresence(new(
command.Listing.HostPresenceHandle,
command.Listing.HostPresenceFingerprint,
PublicEndpoint(40_000),
LocalEndpoint(40_000)));
Assert.True(bound.Succeeded);
return bound.Value!;
}
public CreateJoinAttemptCommand AttemptCommand(StoredListing listing, string owner = "client-1")
{
int sequence = Interlocked.Increment(ref _sequence);
return new()
{
IdempotencyOwner = owner,
IdempotencyKey = $"join-{sequence}",
RequestFingerprint = $"join-request-{sequence}",
ClientSubject = owner,
AttemptId = NewAttemptId(),
MediationHandle = NewHandle(),
Scope = listing.Definition.Scope,
ListingId = listing.Definition.ListingId,
ProtocolVersion = listing.Definition.ProtocolVersion,
HostCapabilityFingerprint = Fingerprint($"host-{sequence}"),
ClientCapabilityFingerprint = Fingerprint($"client-{sequence}"),
};
}
public static SecretFingerprint Fingerprint(string value) => new(value);
public static ObservedEndpoint PublicEndpoint(int port) => new(AddressFamilyKind.Ipv4, "203.0.113.10", port);
public static ObservedEndpoint OtherPublicEndpoint(int port) => new(AddressFamilyKind.Ipv4, "198.51.100.20", port);
public static ObservedEndpoint LocalEndpoint(int port) => new(AddressFamilyKind.Ipv4, "192.168.1.20", port);
public static SessionListingId NewListingId() => new(Guid.NewGuid());
public static LeaseId NewLeaseId() => new(Guid.NewGuid());
public static JoinAttemptId NewAttemptId() => new(Guid.NewGuid());
public static MediationHandle NewHandle() => new(Guid.NewGuid());
}