diff --git a/Core/Core/Api/ServerLimits.cs b/Core/Core/Api/ServerLimits.cs new file mode 100644 index 0000000000..ce5a2f26bf --- /dev/null +++ b/Core/Core/Api/ServerLimits.cs @@ -0,0 +1,13 @@ +namespace Speckle.Core.Api; + +/// +/// Defines the limits for specific API calls on the Speckle Server. +/// These are magic numbers! Should be aligned with server always. +/// +/// +/// ⚠️ Not all limits are reflected here! +/// +public static class ServerLimits +{ + public const int BRANCH_GET_LIMIT = 500; +} diff --git a/Core/IntegrationTests/Api.cs b/Core/IntegrationTests/Api.cs index f060cc11c0..72d17798f3 100644 --- a/Core/IntegrationTests/Api.cs +++ b/Core/IntegrationTests/Api.cs @@ -108,7 +108,6 @@ public async Task IsStreamAccessible() Assert.True(res); } - [Test, Order(13)] public async Task StreamSearch() { @@ -275,6 +274,54 @@ public async Task StreamGetBranches() Assert.That(res[0].name, Is.EqualTo("main")); } + [Test, Order(51)] + public async Task StreamGetBranches_Throws_WhenRequestingOverLimit() + { + Assert.ThrowsAsync>( + async () => await myClient.StreamGetBranches(streamId, ServerLimits.BRANCH_GET_LIMIT + 1).ConfigureAwait(false) + ); + var res = await myClient.StreamGetBranches(streamId, ServerLimits.BRANCH_GET_LIMIT).ConfigureAwait(false); + + Assert.That(res, Is.Not.Null); + } + + [Test, Order(52)] + public async Task StreamGetBranches_WithManyBranches() + { + var newStreamId = await myClient.StreamCreate(new StreamCreateInput { name = "Many branches stream" }); + + await CreateEmptyBranches(myClient, newStreamId, ServerLimits.BRANCH_GET_LIMIT); + + var res = await myClient.StreamGetBranches(newStreamId, ServerLimits.BRANCH_GET_LIMIT); + + Assert.That(res, Is.Not.Null); + Assert.That(res, Has.Count.EqualTo(ServerLimits.BRANCH_GET_LIMIT)); + } + + public async Task CreateEmptyBranches( + Client client, + string streamId, + int branchCount, + string branchPrefix = "Test branch" + ) + { + // now let's send HTTP requests to each of these URLs in parallel + var options = new ParallelOptions { MaxDegreeOfParallelism = 2 }; + + // now let's send HTTP requests to each of these URLs in parallel + await Parallel.ForEachAsync( + Enumerable.Range(0, branchCount), + options, + async (i, cancellationToken) => + { + await client.BranchCreate( + new BranchCreateInput { name = $"{branchPrefix} {i}", streamId = streamId }, + cancellationToken + ); + } + ); + } + #region commit [Test, Order(43)] diff --git a/Core/IntegrationTests/TestsIntegration.csproj b/Core/IntegrationTests/TestsIntegration.csproj index 2f1c6a6336..4d425df603 100644 --- a/Core/IntegrationTests/TestsIntegration.csproj +++ b/Core/IntegrationTests/TestsIntegration.csproj @@ -6,6 +6,7 @@ enable false + $(NoWarn);CA2007 diff --git a/Core/docker-compose.yml b/Core/docker-compose.yml index 23c4cdb817..e22c793b59 100644 --- a/Core/docker-compose.yml +++ b/Core/docker-compose.yml @@ -98,6 +98,7 @@ services: S3_CREATE_BUCKET: "true" FILE_SIZE_LIMIT_MB: 100 + MAX_PROJECT_MODELS_PER_PAGE: 500 # TODO: Change this to a unique secret for this server SESSION_SECRET: "TODO:ReplaceWithLongString" diff --git a/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs index f683a5b33d..985b8cd751 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/StreamSelectorViewModel.cs @@ -98,7 +98,9 @@ private async void GetBranches() { using var client = new Client(SelectedStream.Account); - Branches = (await client.StreamGetBranches(SelectedStream.Stream.id, 100, 1).ConfigureAwait(true)) + Branches = ( + await client.StreamGetBranches(SelectedStream.Stream.id, ServerLimits.BRANCH_GET_LIMIT, 1).ConfigureAwait(true) + ) .Where(x => x.commits.totalCount > 0) .ToList(); diff --git a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs index 74e3f255a6..7bc93eeb8d 100644 --- a/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs +++ b/DesktopUI2/DesktopUI2/ViewModels/StreamViewModel.cs @@ -248,7 +248,7 @@ internal async void GetBranchesAndRestoreState() AvailableFilters = new List(Bindings.GetSelectionFilters().Select(x => new FilterViewModel(x))); SelectedFilter = AvailableFilters[0]; - Branches = await Client.StreamGetBranches(Stream.id, 100, 0).ConfigureAwait(true); + Branches = await Client.StreamGetBranches(Stream.id, ServerLimits.BRANCH_GET_LIMIT, 0).ConfigureAwait(true); //TODO: Core's API calls and the StreamWrapper class need to be updated to properly support FE2 links //this is a temporary workaround @@ -430,7 +430,7 @@ private async Task GetBranches() try { var prevBranchName = SelectedBranch != null ? SelectedBranch.Branch.name : StreamState.BranchName; - Branches = await Client.StreamGetBranches(Stream.id, 500, 0).ConfigureAwait(true); + Branches = await Client.StreamGetBranches(Stream.id, ServerLimits.BRANCH_GET_LIMIT, 0).ConfigureAwait(true); var index = Branches.FindIndex(x => x.name == prevBranchName); if (index != -1)