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)