sprints completion. new product advisories prepared

This commit is contained in:
master
2026-01-16 16:30:03 +02:00
parent a927d924e3
commit 4ca3ce8fb4
255 changed files with 42434 additions and 1020 deletions

View File

@@ -0,0 +1,431 @@
// -----------------------------------------------------------------------------
// BinaryIndexOpsModelsTests.cs
// Sprint: SPRINT_20260112_007_BINIDX_binaryindex_user_config
// Task: BINIDX-TEST-04 — Tests for ops endpoint response models
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Text.Json;
using StellaOps.BinaryIndex.Core.Configuration;
using Xunit;
namespace StellaOps.BinaryIndex.WebService.Tests;
public sealed class BinaryIndexOpsModelsTests
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
#region BinaryIndexOpsHealthResponse Tests
[Fact]
public void BinaryIndexOpsHealthResponse_SerializesCorrectly()
{
var response = CreateSampleHealthResponse();
var json = JsonSerializer.Serialize(response, JsonOptions);
var deserialized = JsonSerializer.Deserialize<BinaryIndexOpsHealthResponse>(json, JsonOptions);
Assert.NotNull(deserialized);
Assert.Equal(response.Status, deserialized.Status);
Assert.Equal(response.Timestamp, deserialized.Timestamp);
Assert.Equal(response.Version, deserialized.Version);
}
[Fact]
public void BinaryIndexOpsHealthResponse_ContainsDeterministicOrdering()
{
var response1 = CreateSampleHealthResponse();
var response2 = CreateSampleHealthResponse();
var json1 = JsonSerializer.Serialize(response1, JsonOptions);
var json2 = JsonSerializer.Serialize(response2, JsonOptions);
// Same data should produce identical JSON
Assert.Equal(json1, json2);
}
[Fact]
public void ComponentHealthStatus_ValidStatuses()
{
var healthyStatus = new ComponentHealthStatus { Status = "healthy", Message = "OK", ResponseTimeMs = 5 };
var degradedStatus = new ComponentHealthStatus { Status = "degraded", Message = "Slow" };
var unhealthyStatus = new ComponentHealthStatus { Status = "unhealthy", Message = "Unavailable" };
Assert.Equal("healthy", healthyStatus.Status);
Assert.Equal("degraded", degradedStatus.Status);
Assert.Equal("unhealthy", unhealthyStatus.Status);
}
[Fact]
public void BinaryIndexLifterWarmness_HandlesMultipleIsas()
{
var warmness = new BinaryIndexLifterWarmness
{
WarmPreloadEnabled = true,
Isas = new Dictionary<string, IsaWarmness>
{
["intel-64"] = new IsaWarmness { Warm = true, AvailableCount = 4, MaxCount = 4 },
["armv8-64"] = new IsaWarmness { Warm = false, AvailableCount = 0, MaxCount = 4 }
}.ToImmutableDictionary()
};
Assert.Equal(2, warmness.Isas.Count);
Assert.True(warmness.Isas["intel-64"].Warm);
Assert.False(warmness.Isas["armv8-64"].Warm);
}
#endregion
#region BinaryIndexBenchResponse Tests
[Fact]
public void BinaryIndexBenchResponse_SerializesLatencyStats()
{
var response = CreateSampleBenchResponse();
var json = JsonSerializer.Serialize(response, JsonOptions);
Assert.Contains("latencySummary", json);
Assert.Contains("p50", json);
Assert.Contains("p95", json);
Assert.Contains("p99", json);
}
[Fact]
public void BenchLatencySummary_ContainsAllPercentiles()
{
var summary = new BenchLatencySummary
{
Min = 1.0,
Max = 100.0,
Mean = 25.0,
P50 = 20.0,
P95 = 80.0,
P99 = 95.0
};
Assert.Equal(1.0, summary.Min);
Assert.Equal(100.0, summary.Max);
Assert.True(summary.P50 <= summary.P95);
Assert.True(summary.P95 <= summary.P99);
}
[Fact]
public void BenchOperationResult_TracksOperationType()
{
var lifterAcquire = new BenchOperationResult
{
Operation = "lifter_acquire",
LatencyMs = 2.5,
Success = true
};
var cacheLookup = new BenchOperationResult
{
Operation = "cache_lookup",
LatencyMs = 0.8,
Success = true
};
Assert.Equal("lifter_acquire", lifterAcquire.Operation);
Assert.Equal("cache_lookup", cacheLookup.Operation);
}
#endregion
#region BinaryIndexFunctionCacheStats Tests
[Fact]
public void BinaryIndexFunctionCacheStats_CalculatesHitRate()
{
var stats = new BinaryIndexFunctionCacheStats
{
Enabled = true,
Backend = "valkey",
Hits = 800,
Misses = 200,
Evictions = 50,
HitRate = 0.8,
KeyPrefix = "binidx:fn:",
CacheTtlSeconds = 3600
};
Assert.Equal(0.8, stats.HitRate);
Assert.Equal(800, stats.Hits);
Assert.Equal(200, stats.Misses);
}
[Fact]
public void BinaryIndexFunctionCacheStats_HandlesDisabledCache()
{
var stats = new BinaryIndexFunctionCacheStats
{
Enabled = false,
Backend = "none",
Hits = 0,
Misses = 0,
Evictions = 0,
HitRate = 0.0,
KeyPrefix = "",
CacheTtlSeconds = 0
};
Assert.False(stats.Enabled);
Assert.Equal(0.0, stats.HitRate);
}
[Fact]
public void BinaryIndexFunctionCacheStats_SerializesMemoryBytes()
{
var stats = new BinaryIndexFunctionCacheStats
{
Enabled = true,
Backend = "valkey",
Hits = 100,
Misses = 10,
Evictions = 5,
HitRate = 0.909,
KeyPrefix = "test:",
CacheTtlSeconds = 3600,
EstimatedEntries = 1000,
EstimatedMemoryBytes = 52428800 // 50 MB
};
var json = JsonSerializer.Serialize(stats, JsonOptions);
Assert.Contains("estimatedMemoryBytes", json);
Assert.Contains("52428800", json);
}
#endregion
#region BinaryIndexEffectiveConfig Tests
[Fact]
public void BinaryIndexEffectiveConfig_DoesNotContainSecrets()
{
var config = CreateSampleEffectiveConfig();
var json = JsonSerializer.Serialize(config, JsonOptions);
// Should not contain sensitive fields
Assert.DoesNotContain("password", json.ToLowerInvariant());
Assert.DoesNotContain("secret", json.ToLowerInvariant());
Assert.DoesNotContain("connectionString", json.ToLowerInvariant());
}
[Fact]
public void BinaryIndexEffectiveConfig_ContainsVersions()
{
var config = CreateSampleEffectiveConfig();
Assert.NotNull(config.Versions);
Assert.NotNull(config.Versions.BinaryIndex);
Assert.NotNull(config.Versions.B2R2);
}
[Fact]
public void B2R2PoolConfigView_ContainsPoolSettings()
{
var view = new B2R2PoolConfigView
{
MaxPoolSizePerIsa = 4,
WarmPreload = true,
AcquireTimeoutMs = 5000,
EnableMetrics = true
};
Assert.Equal(4, view.MaxPoolSizePerIsa);
Assert.True(view.WarmPreload);
}
[Fact]
public void FunctionCacheConfigView_ContainsCacheTtl()
{
var view = new FunctionCacheConfigView
{
Enabled = true,
Backend = "valkey",
KeyPrefix = "binidx:fn:",
CacheTtlSeconds = 3600,
MaxTtlSeconds = 86400,
EarlyExpiryPercent = 10,
MaxEntrySizeBytes = 1048576
};
Assert.Equal(3600, view.CacheTtlSeconds);
Assert.Equal(86400, view.MaxTtlSeconds);
}
[Fact]
public void BackendVersions_TracksAllComponents()
{
var versions = new BackendVersions
{
BinaryIndex = "1.0.0",
B2R2 = "0.9.1",
Valkey = "7.0.0",
Postgresql = "16.1"
};
Assert.NotNull(versions.BinaryIndex);
Assert.NotNull(versions.B2R2);
Assert.NotNull(versions.Valkey);
Assert.NotNull(versions.Postgresql);
}
#endregion
#region Offline Mode Tests
[Fact]
public void BinaryIndexOpsHealthResponse_IndicatesOfflineStatus()
{
var offlineResponse = new BinaryIndexOpsHealthResponse
{
Status = "degraded",
Timestamp = "2026-01-16T10:00:00Z",
Version = "1.0.0",
Components = new BinaryIndexComponentHealth
{
Valkey = new ComponentHealthStatus { Status = "unhealthy", Message = "Offline mode - Valkey unavailable" },
Postgresql = new ComponentHealthStatus { Status = "healthy" },
LifterPool = new ComponentHealthStatus { Status = "healthy" }
},
LifterWarmness = new BinaryIndexLifterWarmness
{
WarmPreloadEnabled = true,
Isas = ImmutableDictionary<string, IsaWarmness>.Empty
}
};
Assert.Equal("degraded", offlineResponse.Status);
Assert.Equal("unhealthy", offlineResponse.Components.Valkey.Status);
Assert.Contains("Offline", offlineResponse.Components.Valkey.Message);
}
[Fact]
public void BinaryIndexFunctionCacheStats_HandlesValkeyUnavailable()
{
var unavailableStats = new BinaryIndexFunctionCacheStats
{
Enabled = true,
Backend = "valkey",
Hits = 0,
Misses = 0,
Evictions = 0,
HitRate = 0.0,
KeyPrefix = "binidx:fn:",
CacheTtlSeconds = 3600,
ErrorMessage = "Valkey connection failed"
};
Assert.NotNull(unavailableStats.ErrorMessage);
}
#endregion
#region Helper Methods
private static BinaryIndexOpsHealthResponse CreateSampleHealthResponse()
{
return new BinaryIndexOpsHealthResponse
{
Status = "healthy",
Timestamp = "2026-01-16T10:00:00Z",
Version = "1.0.0",
Components = new BinaryIndexComponentHealth
{
Valkey = new ComponentHealthStatus { Status = "healthy", ResponseTimeMs = 2 },
Postgresql = new ComponentHealthStatus { Status = "healthy", ResponseTimeMs = 5 },
LifterPool = new ComponentHealthStatus { Status = "healthy" }
},
LifterWarmness = new BinaryIndexLifterWarmness
{
WarmPreloadEnabled = true,
Isas = new Dictionary<string, IsaWarmness>
{
["intel-64"] = new IsaWarmness { Warm = true, AvailableCount = 4, MaxCount = 4 }
}.ToImmutableDictionary()
}
};
}
private static BinaryIndexBenchResponse CreateSampleBenchResponse()
{
return new BinaryIndexBenchResponse
{
Timestamp = "2026-01-16T10:05:00Z",
SampleSize = 10,
LatencySummary = new BenchLatencySummary
{
Min = 1.2,
Max = 15.8,
Mean = 5.4,
P50 = 4.5,
P95 = 12.3,
P99 = 14.9
},
Operations = new[]
{
new BenchOperationResult { Operation = "lifter_acquire", LatencyMs = 2.1, Success = true },
new BenchOperationResult { Operation = "cache_lookup", LatencyMs = 0.8, Success = true }
}.ToImmutableArray()
};
}
private static BinaryIndexEffectiveConfig CreateSampleEffectiveConfig()
{
return new BinaryIndexEffectiveConfig
{
B2R2Pool = new B2R2PoolConfigView
{
MaxPoolSizePerIsa = 4,
WarmPreload = true,
AcquireTimeoutMs = 5000,
EnableMetrics = true
},
SemanticLifting = new SemanticLiftingConfigView
{
B2R2Version = "0.9.1",
NormalizationRecipeVersion = "1.0.0",
MaxInstructionsPerFunction = 10000,
MaxFunctionsPerBinary = 5000,
FunctionLiftTimeoutMs = 30000,
EnableDeduplication = true
},
FunctionCache = new FunctionCacheConfigView
{
Enabled = true,
Backend = "valkey",
KeyPrefix = "binidx:fn:",
CacheTtlSeconds = 3600,
MaxTtlSeconds = 86400,
EarlyExpiryPercent = 10,
MaxEntrySizeBytes = 1048576
},
Persistence = new PersistenceConfigView
{
Schema = "binary_index",
MinPoolSize = 2,
MaxPoolSize = 10,
CommandTimeoutSeconds = 30,
RetryOnFailure = true,
BatchSize = 100
},
Versions = new BackendVersions
{
BinaryIndex = "1.0.0",
B2R2 = "0.9.1",
Valkey = "7.0.0",
Postgresql = "16.1"
}
};
}
#endregion
}

View File

@@ -0,0 +1,209 @@
// -----------------------------------------------------------------------------
// BinaryIndexOptionsTests.cs
// Sprint: SPRINT_20260112_007_BINIDX_binaryindex_user_config
// Task: BINIDX-TEST-04 — Tests for config binding and ops endpoints
// -----------------------------------------------------------------------------
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using StellaOps.BinaryIndex.Core.Configuration;
using Xunit;
namespace StellaOps.BinaryIndex.WebService.Tests;
public sealed class BinaryIndexOptionsTests
{
[Fact]
public void BinaryIndexOptions_DefaultValues_AreValid()
{
var options = new BinaryIndexOptions();
// B2R2Pool defaults
Assert.Equal(4, options.B2R2Pool.MaxPoolSizePerIsa);
Assert.True(options.B2R2Pool.EnableWarmPreload);
Assert.Equal(TimeSpan.FromSeconds(5), options.B2R2Pool.AcquireTimeout);
Assert.True(options.B2R2Pool.EnableMetrics);
// SemanticLifting defaults
Assert.True(options.SemanticLifting.Enabled);
Assert.Equal("0.9.1", options.SemanticLifting.B2R2Version);
// FunctionCache defaults
Assert.True(options.FunctionCache.Enabled);
Assert.Equal("binidx:fn:", options.FunctionCache.KeyPrefix);
// Persistence defaults
Assert.Equal("binary_index", options.Persistence.Schema);
Assert.True(options.Persistence.RetryOnFailure);
// Ops defaults
Assert.True(options.Ops.EnableHealthEndpoint);
Assert.True(options.Ops.EnableBenchEndpoint);
}
[Fact]
public void B2R2PoolOptions_MaxPoolSizePerIsa_Validation()
{
var validationResults = new List<ValidationResult>();
var validOptions = new B2R2PoolOptions { MaxPoolSizePerIsa = 32 };
var invalidLow = new B2R2PoolOptions { MaxPoolSizePerIsa = 0 };
var invalidHigh = new B2R2PoolOptions { MaxPoolSizePerIsa = 100 };
// Valid value
Assert.True(Validator.TryValidateObject(
validOptions,
new ValidationContext(validOptions),
validationResults,
true));
// Invalid low value
validationResults.Clear();
Assert.False(Validator.TryValidateObject(
invalidLow,
new ValidationContext(invalidLow),
validationResults,
true));
// Invalid high value
validationResults.Clear();
Assert.False(Validator.TryValidateObject(
invalidHigh,
new ValidationContext(invalidHigh),
validationResults,
true));
}
[Fact]
public void BinaryIndexOptions_BindsFromConfiguration()
{
var configData = new Dictionary<string, string?>
{
["StellaOps:BinaryIndex:B2R2Pool:MaxPoolSizePerIsa"] = "8",
["StellaOps:BinaryIndex:B2R2Pool:EnableWarmPreload"] = "false",
["StellaOps:BinaryIndex:SemanticLifting:Enabled"] = "false",
["StellaOps:BinaryIndex:SemanticLifting:B2R2Version"] = "1.0.0",
["StellaOps:BinaryIndex:FunctionCache:Enabled"] = "true",
["StellaOps:BinaryIndex:FunctionCache:KeyPrefix"] = "test:fn:",
["StellaOps:BinaryIndex:Persistence:Schema"] = "test_schema",
["StellaOps:BinaryIndex:Ops:EnableBenchEndpoint"] = "false",
};
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(configData)
.Build();
var services = new ServiceCollection();
services.Configure<BinaryIndexOptions>(
configuration.GetSection(BinaryIndexOptions.SectionName));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<BinaryIndexOptions>>().Value;
Assert.Equal(8, options.B2R2Pool.MaxPoolSizePerIsa);
Assert.False(options.B2R2Pool.EnableWarmPreload);
Assert.False(options.SemanticLifting.Enabled);
Assert.Equal("1.0.0", options.SemanticLifting.B2R2Version);
Assert.True(options.FunctionCache.Enabled);
Assert.Equal("test:fn:", options.FunctionCache.KeyPrefix);
Assert.Equal("test_schema", options.Persistence.Schema);
Assert.False(options.Ops.EnableBenchEndpoint);
}
[Fact]
public void BinaryIndexOptions_MissingSection_UsesDefaults()
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>())
.Build();
var services = new ServiceCollection();
services.Configure<BinaryIndexOptions>(
configuration.GetSection(BinaryIndexOptions.SectionName));
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<BinaryIndexOptions>>().Value;
// Should use defaults
Assert.Equal(4, options.B2R2Pool.MaxPoolSizePerIsa);
Assert.True(options.SemanticLifting.Enabled);
Assert.True(options.FunctionCache.Enabled);
}
[Fact]
public void FunctionCacheOptions_Validation()
{
var validationResults = new List<ValidationResult>();
var validOptions = new FunctionCacheOptions
{
CacheTtl = TimeSpan.FromMinutes(30),
MaxTtl = TimeSpan.FromHours(2),
};
Assert.True(Validator.TryValidateObject(
validOptions,
new ValidationContext(validOptions),
validationResults,
true));
}
[Fact]
public void BinaryIndexPersistenceOptions_DefaultPoolSizes()
{
var options = new BinaryIndexPersistenceOptions();
Assert.Equal(2, options.MinPoolSize);
Assert.Equal(10, options.MaxPoolSize);
Assert.Equal(TimeSpan.FromSeconds(30), options.CommandTimeout);
}
[Fact]
public void BinaryIndexOpsOptions_RedactedKeys_ContainsSecrets()
{
var options = new BinaryIndexOpsOptions();
Assert.Contains("ConnectionString", options.RedactedKeys);
Assert.Contains("Password", options.RedactedKeys);
}
[Fact]
public void BinaryIndexOpsOptions_BenchRateLimit_IsReasonable()
{
var options = new BinaryIndexOpsOptions();
// Should not allow more than 60 bench runs per minute
Assert.InRange(options.BenchRateLimitPerMinute, 1, 60);
}
[Fact]
public void SemanticLiftingOptions_Limits_AreReasonable()
{
var options = new SemanticLiftingOptions();
// Max instructions should prevent runaway analysis
Assert.InRange(options.MaxInstructionsPerFunction, 1000, 100000);
// Max functions should prevent large binary overload
Assert.InRange(options.MaxFunctionsPerBinary, 100, 50000);
// Timeout should be reasonable
Assert.InRange(options.FunctionLiftTimeout.TotalSeconds, 1, 300);
}
[Fact]
public void B2R2PoolOptions_WarmPreloadIsas_ContainsCommonArchitectures()
{
var options = new B2R2PoolOptions();
Assert.Contains("intel-64", options.WarmPreloadIsas);
Assert.Contains("armv8-64", options.WarmPreloadIsas);
}
[Fact]
public void BinaryIndexOptions_SectionName_IsCorrect()
{
Assert.Equal("StellaOps:BinaryIndex", BinaryIndexOptions.SectionName);
}
}