757 lines
24 KiB
C#
757 lines
24 KiB
C#
// -----------------------------------------------------------------------------
|
|
// RoutingDecisionPropertyTests.cs
|
|
// Sprint: SPRINT_5100_0007_0001_testing_strategy_2026
|
|
// Task: TEST-STRAT-5100-004 - Property-based tests for routing/decision logic
|
|
// Description: FsCheck property tests for DefaultRoutingPlugin routing invariants
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using FluentAssertions;
|
|
using FsCheck;
|
|
using FsCheck.Xunit;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Router.Common.Enums;
|
|
using StellaOps.Router.Common.Models;
|
|
using StellaOps.Router.Gateway.Configuration;
|
|
using StellaOps.Router.Gateway.Routing;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Router.Gateway.Tests.Properties;
|
|
|
|
/// <summary>
|
|
/// Property-based tests for routing decision logic using FsCheck.
|
|
/// Tests verify invariants of the DefaultRoutingPlugin routing algorithm.
|
|
/// </summary>
|
|
public sealed class RoutingDecisionPropertyTests
|
|
{
|
|
#region Generators
|
|
|
|
/// <summary>
|
|
/// Generates a random ConnectionState with valid values.
|
|
/// </summary>
|
|
private static Gen<ConnectionState> GenerateConnection(
|
|
string? forcedRegion = null,
|
|
InstanceHealthStatus? forcedStatus = null,
|
|
string? forcedVersion = null)
|
|
{
|
|
return from connectionId in Gen.Elements("conn-1", "conn-2", "conn-3", "conn-4", "conn-5")
|
|
from serviceName in Gen.Constant("test-service")
|
|
from version in forcedVersion != null
|
|
? Gen.Constant(forcedVersion)
|
|
: Gen.Elements("1.0.0", "1.1.0", "2.0.0")
|
|
from region in forcedRegion != null
|
|
? Gen.Constant(forcedRegion)
|
|
: Gen.Elements("eu1", "eu2", "us1", "us2", "ap1")
|
|
from status in forcedStatus.HasValue
|
|
? Gen.Constant(forcedStatus.Value)
|
|
: Gen.Elements(InstanceHealthStatus.Healthy, InstanceHealthStatus.Degraded, InstanceHealthStatus.Unhealthy)
|
|
from pingMs in Gen.Choose(1, 500)
|
|
select new ConnectionState
|
|
{
|
|
ConnectionId = $"{connectionId}-{region}",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = $"{connectionId}-{region}",
|
|
ServiceName = serviceName,
|
|
Version = version,
|
|
Region = region
|
|
},
|
|
Status = status,
|
|
AveragePingMs = pingMs,
|
|
LastHeartbeatUtc = DateTimeOffset.UtcNow.AddSeconds(-pingMs % 60)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a list of connection candidates.
|
|
/// </summary>
|
|
private static Gen<List<ConnectionState>> GenerateCandidates(
|
|
int minCount = 1,
|
|
int maxCount = 10,
|
|
string? forcedRegion = null,
|
|
InstanceHealthStatus? forcedStatus = null)
|
|
{
|
|
return from count in Gen.Choose(minCount, maxCount)
|
|
from connections in Gen.ListOf(count, GenerateConnection(forcedRegion, forcedStatus))
|
|
select connections.DistinctBy(c => c.ConnectionId).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates RoutingOptions with valid combinations.
|
|
/// </summary>
|
|
private static Gen<RoutingOptions> GenerateRoutingOptions()
|
|
{
|
|
return from preferLocal in Arb.Generate<bool>()
|
|
from allowDegraded in Arb.Generate<bool>()
|
|
from strictVersion in Arb.Generate<bool>()
|
|
from tieBreaker in Gen.Elements(TieBreakerMode.Random, TieBreakerMode.RoundRobin, TieBreakerMode.LowestLatency)
|
|
select new RoutingOptions
|
|
{
|
|
PreferLocalRegion = preferLocal,
|
|
AllowDegradedInstances = allowDegraded,
|
|
StrictVersionMatching = strictVersion,
|
|
TieBreaker = tieBreaker,
|
|
RoutingTimeoutMs = 5000,
|
|
DefaultVersion = null
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property Tests - Determinism
|
|
|
|
[Property(MaxTest = 100, Arbitrary = new[] { typeof(ConnectionArbitrary) })]
|
|
public void SameInputs_ProduceDeterministicDecisions()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = true,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = true,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
var candidates = CreateFixedCandidates();
|
|
|
|
// Act - Run routing multiple times
|
|
var decisions = new List<string?>();
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext("1.0.0", candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
decisions.Add(decision?.Connection?.ConnectionId);
|
|
}
|
|
|
|
// Assert - All decisions should be identical
|
|
decisions.All(d => d == decisions[0]).Should().BeTrue(
|
|
"same inputs with deterministic tie-breaker should produce same routing decision");
|
|
}
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void EmptyCandidates_AlwaysReturnsNull()
|
|
{
|
|
// Arrange
|
|
var optionsGen = GenerateRoutingOptions();
|
|
var options = optionsGen.Sample(1, 1).First();
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext("1.0.0", []),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().BeNull("empty candidates should always return null");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property Tests - Health Preference
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void HealthyPreferred_WhenHealthyExists_NeverChoosesDegraded()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
// Create mixed candidates with both healthy and degraded
|
|
var healthy = new ConnectionState
|
|
{
|
|
ConnectionId = "healthy-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "healthy-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 100 // Higher latency but healthy
|
|
};
|
|
|
|
var degraded = new ConnectionState
|
|
{
|
|
ConnectionId = "degraded-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "degraded-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Degraded,
|
|
AveragePingMs = 1 // Lower latency but degraded
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { degraded, healthy };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull();
|
|
decision!.Connection.Status.Should().Be(InstanceHealthStatus.Healthy,
|
|
"healthy instances should always be preferred over degraded");
|
|
}
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void WhenOnlyDegraded_AndAllowDegradedTrue_SelectsDegraded()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
var degraded1 = new ConnectionState
|
|
{
|
|
ConnectionId = "degraded-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "degraded-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Degraded,
|
|
AveragePingMs = 10
|
|
};
|
|
|
|
var degraded2 = new ConnectionState
|
|
{
|
|
ConnectionId = "degraded-2",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "degraded-2",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Degraded,
|
|
AveragePingMs = 20
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { degraded1, degraded2 };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull("degraded instances should be selected when no healthy available and AllowDegradedInstances=true");
|
|
decision!.Connection.Status.Should().Be(InstanceHealthStatus.Degraded);
|
|
}
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void WhenOnlyDegraded_AndAllowDegradedFalse_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = false,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
var degraded = new ConnectionState
|
|
{
|
|
ConnectionId = "degraded-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "degraded-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Degraded,
|
|
AveragePingMs = 10
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { degraded };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().BeNull("degraded instances should not be selected when AllowDegradedInstances=false");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property Tests - Region Tier Preference
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void LocalRegion_AlwaysPreferred_WhenAvailable()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = true,
|
|
AllowDegradedInstances = false,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var gatewayRegion = "eu1";
|
|
var plugin = CreatePlugin(gatewayRegion, options);
|
|
|
|
var localInstance = new ConnectionState
|
|
{
|
|
ConnectionId = "local-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "local-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1" // Same as gateway
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 100 // Higher latency
|
|
};
|
|
|
|
var remoteInstance = new ConnectionState
|
|
{
|
|
ConnectionId = "remote-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "remote-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "us1" // Different region
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 1 // Lower latency
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { remoteInstance, localInstance };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates, gatewayRegion),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull();
|
|
decision!.Connection.Instance.Region.Should().Be(gatewayRegion,
|
|
"local region should always be preferred when PreferLocalRegion=true");
|
|
}
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void WhenNoLocalRegion_FallsBackToRemote()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = true,
|
|
AllowDegradedInstances = false,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var gatewayRegion = "eu1";
|
|
var plugin = CreatePlugin(gatewayRegion, options);
|
|
|
|
var remoteInstance = new ConnectionState
|
|
{
|
|
ConnectionId = "remote-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "remote-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "us1" // Different region
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 10
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { remoteInstance };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates, gatewayRegion),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull("should fallback to remote region when no local available");
|
|
decision!.Connection.Instance.Region.Should().Be("us1");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property Tests - Version Matching
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void StrictVersionMatching_RejectsNonMatchingVersions()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = true,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
var v1Instance = new ConnectionState
|
|
{
|
|
ConnectionId = "v1-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "v1-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 10
|
|
};
|
|
|
|
var v2Instance = new ConnectionState
|
|
{
|
|
ConnectionId = "v2-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "v2-1",
|
|
ServiceName = "test-service",
|
|
Version = "2.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 10
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { v1Instance, v2Instance };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext("2.0.0", candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull();
|
|
decision!.Connection.Instance.Version.Should().Be("2.0.0",
|
|
"strict version matching should only select matching version");
|
|
}
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void RequestedVersion_NotAvailable_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = true,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
var v1Instance = new ConnectionState
|
|
{
|
|
ConnectionId = "v1-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "v1-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 10
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { v1Instance };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext("3.0.0", candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().BeNull("requested version not available should return null");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property Tests - Tie-Breaker Behavior
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void LowestLatency_TieBreaker_SelectsLowestPing()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = false,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
var highLatency = new ConnectionState
|
|
{
|
|
ConnectionId = "high-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "high-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 100
|
|
};
|
|
|
|
var lowLatency = new ConnectionState
|
|
{
|
|
ConnectionId = "low-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "low-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 10
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { highLatency, lowLatency };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull();
|
|
decision!.Connection.ConnectionId.Should().Be("low-1",
|
|
"lowest latency tie-breaker should select instance with lowest ping");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property Tests - Invariants
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void DecisionAlwaysIncludesEndpoint()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
var candidates = CreateFixedCandidates();
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().NotBeNull();
|
|
decision!.Endpoint.Should().NotBeNull("decision should always include endpoint");
|
|
decision.Connection.Should().NotBeNull("decision should always include connection");
|
|
}
|
|
|
|
[Property(MaxTest = 100)]
|
|
public void UnhealthyInstances_NeverSelected()
|
|
{
|
|
// Arrange
|
|
var options = new RoutingOptions
|
|
{
|
|
PreferLocalRegion = false,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var plugin = CreatePlugin("eu1", options);
|
|
|
|
var unhealthy = new ConnectionState
|
|
{
|
|
ConnectionId = "unhealthy-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "unhealthy-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Unhealthy,
|
|
AveragePingMs = 1 // Even with lowest latency
|
|
};
|
|
|
|
var candidates = new List<ConnectionState> { unhealthy };
|
|
|
|
// Act
|
|
var decision = plugin.ChooseInstanceAsync(
|
|
CreateContext(null, candidates),
|
|
CancellationToken.None).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
decision.Should().BeNull("unhealthy instances should never be selected");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helpers
|
|
|
|
private static DefaultRoutingPlugin CreatePlugin(string gatewayRegion, RoutingOptions? options = null)
|
|
{
|
|
options ??= new RoutingOptions
|
|
{
|
|
PreferLocalRegion = true,
|
|
AllowDegradedInstances = true,
|
|
StrictVersionMatching = false,
|
|
TieBreaker = TieBreakerMode.LowestLatency,
|
|
RoutingTimeoutMs = 5000
|
|
};
|
|
|
|
var gatewayConfig = new RouterNodeConfig
|
|
{
|
|
Region = gatewayRegion,
|
|
NeighborRegions = ["eu2", "eu3"]
|
|
};
|
|
|
|
return new DefaultRoutingPlugin(
|
|
Options.Create(options),
|
|
Options.Create(gatewayConfig));
|
|
}
|
|
|
|
private static RoutingContext CreateContext(
|
|
string? requestedVersion,
|
|
List<ConnectionState> candidates,
|
|
string gatewayRegion = "eu1")
|
|
{
|
|
return new RoutingContext
|
|
{
|
|
Method = "GET",
|
|
Path = "/test",
|
|
Headers = new Dictionary<string, string>(),
|
|
Endpoint = new EndpointDescriptor
|
|
{
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Method = "GET",
|
|
Path = "/test"
|
|
},
|
|
AvailableConnections = candidates,
|
|
GatewayRegion = gatewayRegion,
|
|
RequestedVersion = requestedVersion,
|
|
CancellationToken = CancellationToken.None
|
|
};
|
|
}
|
|
|
|
private static List<ConnectionState> CreateFixedCandidates()
|
|
{
|
|
return
|
|
[
|
|
new ConnectionState
|
|
{
|
|
ConnectionId = "conn-1",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "conn-1",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 10
|
|
},
|
|
new ConnectionState
|
|
{
|
|
ConnectionId = "conn-2",
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = "conn-2",
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
},
|
|
Status = InstanceHealthStatus.Healthy,
|
|
AveragePingMs = 20
|
|
}
|
|
];
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Custom Arbitrary for generating ConnectionState instances.
|
|
/// </summary>
|
|
public class ConnectionArbitrary
|
|
{
|
|
public static Arbitrary<ConnectionState> ConnectionState()
|
|
{
|
|
return Arb.From(Gen.Elements(
|
|
CreateConn("c1", "eu1", InstanceHealthStatus.Healthy, 10),
|
|
CreateConn("c2", "eu1", InstanceHealthStatus.Healthy, 20),
|
|
CreateConn("c3", "eu2", InstanceHealthStatus.Healthy, 30),
|
|
CreateConn("c4", "us1", InstanceHealthStatus.Degraded, 5)));
|
|
}
|
|
|
|
private static ConnectionState CreateConn(string id, string region, InstanceHealthStatus status, int pingMs)
|
|
{
|
|
return new ConnectionState
|
|
{
|
|
ConnectionId = id,
|
|
Instance = new ServiceInstance
|
|
{
|
|
InstanceId = id,
|
|
ServiceName = "test-service",
|
|
Version = "1.0.0",
|
|
Region = region
|
|
},
|
|
Status = status,
|
|
AveragePingMs = pingMs
|
|
};
|
|
}
|
|
}
|