feat: add bounded compatible session browser (#8)
quality-gate / quality (push) Successful in 57s
quality-gate / quality (push) Successful in 57s
Closes #8
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
using System.Text.Json;
|
||||
using FinalFactory.Rendezvous.Contracts;
|
||||
using FinalFactory.Rendezvous.Server.Browser;
|
||||
using FinalFactory.Rendezvous.Server.State;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Tests.Browser;
|
||||
|
||||
public sealed class SessionBrowserServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void ListEnforcesTenantProtocolPresenceVisibilityAndAvailabilityFilters()
|
||||
{
|
||||
using SessionBrowserFixture fixture = new();
|
||||
StoredListing eligible = fixture.Add();
|
||||
fixture.Add(scope: new(new("other-game"), fixture.Scope.EnvironmentId));
|
||||
fixture.Add(scope: new(fixture.Scope.GameId, new("other-env")));
|
||||
fixture.Add(protocolVersion: 8);
|
||||
fixture.Add(regionId: new("us-east"));
|
||||
fixture.Add(visibility: ListingVisibility.Unlisted);
|
||||
fixture.Add(fresh: false);
|
||||
fixture.Add(currentPlayers: 8, maximumPlayers: 8);
|
||||
BrowseSessionsRequest request = fixture.Request();
|
||||
request.ExcludeFull = true;
|
||||
|
||||
BrowserServiceResult<BrowseSessionsResponse> result = fixture.Browser.Browse(request);
|
||||
|
||||
Assert.True(result.Succeeded);
|
||||
Assert.Collection(result.Value!.Items, item => Assert.Equal(eligible.Definition.ListingId, item.ListingId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnguessableIdRetrievalAllowsFreshUnlistedOnlyWithinExactScope()
|
||||
{
|
||||
using SessionBrowserFixture fixture = new();
|
||||
StoredListing unlisted = fixture.Add(visibility: ListingVisibility.Unlisted);
|
||||
|
||||
Assert.True(fixture.Browser.Get(
|
||||
unlisted.Definition.ListingId,
|
||||
fixture.Scope.GameId,
|
||||
fixture.Scope.EnvironmentId,
|
||||
7).Succeeded);
|
||||
Assert.Equal(RendezvousErrorCode.NotFound, fixture.Browser.Get(
|
||||
unlisted.Definition.ListingId,
|
||||
new("other-game"),
|
||||
fixture.Scope.EnvironmentId,
|
||||
7).Error);
|
||||
Assert.Equal(RendezvousErrorCode.NotFound, fixture.Browser.Get(
|
||||
unlisted.Definition.ListingId,
|
||||
fixture.Scope.GameId,
|
||||
fixture.Scope.EnvironmentId,
|
||||
8).Error);
|
||||
fixture.Clock.Advance(TimeSpan.FromSeconds(20));
|
||||
Assert.Equal(RendezvousErrorCode.NotFound, fixture.Browser.Get(
|
||||
unlisted.Definition.ListingId,
|
||||
fixture.Scope.GameId,
|
||||
fixture.Scope.EnvironmentId,
|
||||
7).Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeysetCursorReturnsStableRecordsOnceAndRejectsTamperingOrRescoping()
|
||||
{
|
||||
using SessionBrowserFixture fixture = new();
|
||||
for (int index = 0; index < 7; index++)
|
||||
{
|
||||
fixture.Add();
|
||||
}
|
||||
|
||||
BrowseSessionsRequest request = fixture.Request(pageSize: 2);
|
||||
List<SessionListingId> seen = [];
|
||||
do
|
||||
{
|
||||
BrowseSessionsResponse page = fixture.Browser.Browse(request).Value!;
|
||||
seen.AddRange(page.Items.Select(static item => item.ListingId));
|
||||
request.Cursor = page.NextCursor;
|
||||
}
|
||||
while (request.Cursor is not null);
|
||||
|
||||
Assert.Equal(7, seen.Count);
|
||||
Assert.Equal(7, seen.Distinct().Count());
|
||||
Assert.Equal(seen.OrderBy(static id => id.Value), seen);
|
||||
|
||||
BrowseSessionsRequest tampered = fixture.Request(pageSize: 2);
|
||||
tampered.Cursor = fixture.Browser.Browse(tampered).Value!.NextCursor + "A";
|
||||
Assert.Equal(RendezvousErrorCode.InvalidRequest, fixture.Browser.Browse(tampered).Error);
|
||||
BrowseSessionsRequest rescoped = fixture.Request(pageSize: 2);
|
||||
rescoped.Cursor = fixture.Browser.Browse(fixture.Request(pageSize: 2)).Value!.NextCursor;
|
||||
rescoped.ExcludeFull = true;
|
||||
Assert.Equal(RendezvousErrorCode.InvalidRequest, fixture.Browser.Browse(rescoped).Error);
|
||||
|
||||
BrowseSessionsRequest expired = fixture.Request(pageSize: 2);
|
||||
expired.Cursor = fixture.Browser.Browse(expired).Value!.NextCursor;
|
||||
fixture.Clock.Advance(TimeSpan.FromMinutes(5));
|
||||
Assert.Equal(RendezvousErrorCode.InvalidRequest, fixture.Browser.Browse(expired).Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResponseByteBudgetTrimsLargePagesAndContinuesWithCursor()
|
||||
{
|
||||
using SessionBrowserFixture fixture = new();
|
||||
Dictionary<string, string> metadata = Enumerable.Range(0, 14).ToDictionary(
|
||||
static index => $"key-{index}",
|
||||
static index => new string((char)('a' + index % 26), 256),
|
||||
EqualityComparer<string>.Default);
|
||||
for (int index = 0; index < 100; index++)
|
||||
{
|
||||
fixture.Add(metadata: metadata);
|
||||
}
|
||||
|
||||
BrowseSessionsResponse response = fixture.Browser.Browse(fixture.Request()).Value!;
|
||||
int encodedBytes = JsonSerializer.SerializeToUtf8Bytes(response, ContractJson.Options).Length;
|
||||
|
||||
Assert.InRange(encodedBytes, 1, ContractLimits.BrowserResponseMaxBytes);
|
||||
Assert.NotEmpty(response.Items);
|
||||
Assert.NotNull(response.NextCursor);
|
||||
Assert.True(response.Items.Count < 100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PresentationMetadataIsJsonEscapedAndResponseHasNoConnectionSecrets()
|
||||
{
|
||||
using SessionBrowserFixture fixture = new();
|
||||
fixture.Add(metadata: new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["mode"] = "co-op",
|
||||
["map"] = "<script>alert(1)</script>",
|
||||
});
|
||||
|
||||
BrowseSessionsResponse response = fixture.Browser.Browse(fixture.Request()).Value!;
|
||||
string json = JsonSerializer.Serialize(response, ContractJson.Options);
|
||||
|
||||
Assert.DoesNotContain("<script>", json, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("endpoint", json, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("token", json, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("capability", json, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Equal("<script>alert(1)</script>", Assert.Single(response.Items).Metadata["map"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RevokedListingDisappearsBeforeAnotherReadPathCanObserveIt()
|
||||
{
|
||||
using SessionBrowserFixture fixture = new();
|
||||
StoredListing listing = fixture.Add();
|
||||
fixture.Store.RevokeListing(listing.Definition.ListingId);
|
||||
|
||||
Assert.Empty(fixture.Browser.Browse(fixture.Request()).Value!.Items);
|
||||
Assert.Equal(RendezvousErrorCode.NotFound, fixture.Browser.Get(
|
||||
listing.Definition.ListingId,
|
||||
fixture.Scope.GameId,
|
||||
fixture.Scope.EnvironmentId,
|
||||
7).Error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user