You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
382 lines
9.6 KiB
382 lines
9.6 KiB
package simulation |
|
|
|
import ( |
|
"testing" |
|
) |
|
|
|
func TestThirdPartyServiceLogic_BasicProcessing(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
props := map[string]any{ |
|
"provider": "Stripe", |
|
"latency": 150.0, |
|
} |
|
|
|
requests := []*Request{ |
|
{ID: "1", Type: "POST", LatencyMS: 50, Path: []string{}}, |
|
{ID: "2", Type: "GET", LatencyMS: 30, Path: []string{}}, |
|
} |
|
|
|
output, healthy := logic.Tick(props, requests, 1) |
|
|
|
if !healthy { |
|
t.Error("Expected third party service to be healthy") |
|
} |
|
|
|
if len(output) != 2 { |
|
t.Errorf("Expected 2 processed requests, got %d", len(output)) |
|
} |
|
|
|
// Verify latency was added (should be around base latency with some variance) |
|
for i, req := range output { |
|
originalLatency := requests[i].LatencyMS |
|
if req.LatencyMS <= originalLatency { |
|
t.Errorf("Expected third party service latency to be added") |
|
} |
|
|
|
// Check that path was updated |
|
if len(req.Path) == 0 { |
|
t.Error("Expected path to be updated") |
|
} |
|
|
|
lastPathElement := req.Path[len(req.Path)-1] |
|
if lastPathElement != "third-party-success" && lastPathElement != "third-party-failed" { |
|
t.Errorf("Expected path to indicate success or failure, got %s", lastPathElement) |
|
} |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_ProviderCharacteristics(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
providers := []string{"Stripe", "AWS", "Slack", "Twilio"} |
|
|
|
for _, provider := range providers { |
|
t.Run(provider, func(t *testing.T) { |
|
props := map[string]any{ |
|
"provider": provider, |
|
"latency": 100.0, |
|
} |
|
|
|
requests := []*Request{{ID: "1", Type: "POST", LatencyMS: 0, Path: []string{}}} |
|
|
|
output, healthy := logic.Tick(props, requests, 1) |
|
|
|
if !healthy { |
|
t.Errorf("Expected %s service to be healthy", provider) |
|
} |
|
|
|
if len(output) != 1 { |
|
t.Errorf("Expected 1 processed request for %s", provider) |
|
} |
|
|
|
// Verify latency characteristics |
|
addedLatency := output[0].LatencyMS |
|
if addedLatency <= 0 { |
|
t.Errorf("Expected %s to add latency", provider) |
|
} |
|
|
|
// AWS and Google should be faster than Slack |
|
if provider == "AWS" && addedLatency > 200 { |
|
t.Errorf("Expected AWS to have lower latency, got %dms", addedLatency) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_RateLimiting(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
props := map[string]any{ |
|
"provider": "Slack", // Has low rate limit (30 RPS) |
|
"latency": 100.0, |
|
} |
|
|
|
// Send more requests than rate limit |
|
requests := make([]*Request, 50) // More than Slack's 30 RPS limit |
|
for i := range requests { |
|
requests[i] = &Request{ID: string(rune('1' + i)), Type: "POST", LatencyMS: 0} |
|
} |
|
|
|
output, healthy := logic.Tick(props, requests, 1) |
|
|
|
// Should only process up to rate limit |
|
if len(output) != 30 { |
|
t.Errorf("Expected 30 processed requests due to Slack rate limit, got %d", len(output)) |
|
} |
|
|
|
// Service should still be healthy with rate limiting |
|
if !healthy { |
|
t.Error("Expected service to be healthy despite rate limiting") |
|
} |
|
|
|
// Check that rate limit hits were recorded |
|
status, ok := props["_serviceStatus"].(ServiceStatus) |
|
if !ok { |
|
t.Error("Expected service status to be recorded") |
|
} |
|
|
|
if status.RateLimitHits != 1 { |
|
t.Errorf("Expected 1 rate limit hit, got %d", status.RateLimitHits) |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_ServiceFailure(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
props := map[string]any{ |
|
"provider": "Generic", |
|
"latency": 100.0, |
|
} |
|
|
|
// Set up service as already having failures |
|
status := ServiceStatus{ |
|
IsUp: false, |
|
LastCheck: 0, |
|
FailureCount: 6, |
|
} |
|
props["_serviceStatus"] = status |
|
|
|
requests := []*Request{{ID: "1", Type: "POST", LatencyMS: 50, Path: []string{}}} |
|
|
|
output, healthy := logic.Tick(props, requests, 1) |
|
|
|
if healthy { |
|
t.Error("Expected service to be unhealthy when external service is down") |
|
} |
|
|
|
if len(output) != 1 { |
|
t.Error("Expected request to be processed even when service is down") |
|
} |
|
|
|
// Should have very high latency due to timeout |
|
if output[0].LatencyMS < 5000 { |
|
t.Errorf("Expected high latency for service failure, got %dms", output[0].LatencyMS) |
|
} |
|
|
|
// Check path indicates timeout |
|
lastPath := output[0].Path[len(output[0].Path)-1] |
|
if lastPath != "third-party-timeout" { |
|
t.Errorf("Expected timeout path, got %s", lastPath) |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_ServiceRecovery(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
props := map[string]any{ |
|
"provider": "Stripe", |
|
"latency": 100.0, |
|
} |
|
|
|
// Set up service as down but with old timestamp (should recover) |
|
status := ServiceStatus{ |
|
IsUp: false, |
|
LastCheck: 0, // Very old timestamp |
|
FailureCount: 3, |
|
} |
|
props["_serviceStatus"] = status |
|
|
|
requests := []*Request{{ID: "1", Type: "POST", LatencyMS: 50, Path: []string{}}} |
|
|
|
// Run with current tick that's more than 30 seconds later |
|
_, healthy := logic.Tick(props, requests, 400) // 40 seconds later |
|
|
|
if !healthy { |
|
t.Error("Expected service to be healthy after recovery") |
|
} |
|
|
|
// Check that service recovered |
|
updatedStatus, ok := props["_serviceStatus"].(ServiceStatus) |
|
if !ok { |
|
t.Error("Expected updated service status") |
|
} |
|
|
|
if !updatedStatus.IsUp { |
|
t.Error("Expected service to have recovered") |
|
} |
|
|
|
if updatedStatus.FailureCount != 0 { |
|
t.Error("Expected failure count to be reset on recovery") |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_ReliabilityDifferences(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
// Test different reliability levels |
|
testCases := []struct { |
|
provider string |
|
expectedReliability float64 |
|
}{ |
|
{"AWS", 0.9995}, |
|
{"Google", 0.9999}, |
|
{"Stripe", 0.999}, |
|
{"Slack", 0.995}, |
|
{"Generic", 0.99}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
reliability := logic.getProviderReliability(tc.provider) |
|
if reliability != tc.expectedReliability { |
|
t.Errorf("Expected %s reliability %.4f, got %.4f", |
|
tc.provider, tc.expectedReliability, reliability) |
|
} |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_RateLimitDifferences(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
// Test different rate limits |
|
testCases := []struct { |
|
provider string |
|
expectedLimit int |
|
}{ |
|
{"AWS", 1000}, |
|
{"Stripe", 100}, |
|
{"Slack", 30}, |
|
{"SendGrid", 200}, |
|
{"Twilio", 50}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
rateLimit := logic.getProviderRateLimit(tc.provider) |
|
if rateLimit != tc.expectedLimit { |
|
t.Errorf("Expected %s rate limit %d, got %d", |
|
tc.provider, tc.expectedLimit, rateLimit) |
|
} |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_LatencyVariance(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
props := map[string]any{ |
|
"provider": "Stripe", |
|
"latency": 100.0, |
|
} |
|
|
|
requests := []*Request{{ID: "1", Type: "POST", LatencyMS: 0, Path: []string{}}} |
|
|
|
latencies := []int{} |
|
|
|
// Run multiple times to observe variance |
|
for i := 0; i < 10; i++ { |
|
output, _ := logic.Tick(props, requests, i) |
|
latencies = append(latencies, output[0].LatencyMS) |
|
} |
|
|
|
// Check that we have variance (not all latencies are the same) |
|
allSame := true |
|
firstLatency := latencies[0] |
|
for _, latency := range latencies[1:] { |
|
if latency != firstLatency { |
|
allSame = false |
|
break |
|
} |
|
} |
|
|
|
if allSame { |
|
t.Error("Expected latency variance, but all latencies were the same") |
|
} |
|
|
|
// All latencies should be reasonable (between 50ms and 300ms for Stripe) |
|
for _, latency := range latencies { |
|
if latency < 50 || latency > 300 { |
|
t.Errorf("Expected reasonable latency for Stripe, got %dms", latency) |
|
} |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_DefaultValues(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
// Empty props should use defaults |
|
props := map[string]any{} |
|
|
|
requests := []*Request{{ID: "1", Type: "POST", LatencyMS: 0, Path: []string{}}} |
|
|
|
output, healthy := logic.Tick(props, requests, 1) |
|
|
|
if !healthy { |
|
t.Error("Expected service to be healthy with default values") |
|
} |
|
|
|
if len(output) != 1 { |
|
t.Error("Expected 1 processed request with defaults") |
|
} |
|
|
|
// Should have reasonable default latency (around 200ms base) |
|
if output[0].LatencyMS < 100 || output[0].LatencyMS > 400 { |
|
t.Errorf("Expected reasonable default latency, got %dms", output[0].LatencyMS) |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_SuccessCountTracking(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
props := map[string]any{ |
|
"provider": "AWS", // High reliability |
|
"latency": 50.0, |
|
} |
|
|
|
requests := []*Request{{ID: "1", Type: "POST", LatencyMS: 0, Path: []string{}}} |
|
|
|
// Run multiple successful requests |
|
for i := 0; i < 5; i++ { |
|
logic.Tick(props, requests, i) |
|
} |
|
|
|
status, ok := props["_serviceStatus"].(ServiceStatus) |
|
if !ok { |
|
t.Error("Expected service status to be tracked") |
|
} |
|
|
|
// Should have accumulated success count |
|
if status.SuccessCount == 0 { |
|
t.Error("Expected success count to be tracked") |
|
} |
|
|
|
// Should be healthy |
|
if !status.IsUp { |
|
t.Error("Expected service to remain up with successful calls") |
|
} |
|
} |
|
|
|
func TestThirdPartyServiceLogic_FailureRecovery(t *testing.T) { |
|
logic := ThirdPartyServiceLogic{} |
|
|
|
props := map[string]any{ |
|
"provider": "Generic", |
|
"latency": 100.0, |
|
} |
|
|
|
// Set up service with some failures but still up |
|
status := ServiceStatus{ |
|
IsUp: true, |
|
FailureCount: 3, |
|
SuccessCount: 0, |
|
} |
|
props["_serviceStatus"] = status |
|
|
|
requests := []*Request{{ID: "1", Type: "POST", LatencyMS: 0, Path: []string{}}} |
|
|
|
// Simulate a successful call (with high probability for Generic service) |
|
// We'll run this multiple times to ensure we get at least one success |
|
successFound := false |
|
for i := 0; i < 10 && !successFound; i++ { |
|
output, _ := logic.Tick(props, requests, i) |
|
if len(output[0].Path) > 0 && output[0].Path[len(output[0].Path)-1] == "third-party-success" { |
|
successFound = true |
|
} |
|
} |
|
|
|
if successFound { |
|
updatedStatus, _ := props["_serviceStatus"].(ServiceStatus) |
|
// Failure count should have decreased |
|
if updatedStatus.FailureCount >= 3 { |
|
t.Error("Expected failure count to decrease after successful call") |
|
} |
|
} |
|
}
|
|
|