sprints completion. new product advisories prepared
This commit is contained in:
@@ -0,0 +1,494 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BinaryIndexOpsModels.cs
|
||||
// Sprint: SPRINT_20260112_007_BINIDX_binaryindex_user_config
|
||||
// Task: BINIDX-OPS-02
|
||||
// Description: Response models for BinaryIndex ops endpoints.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.BinaryIndex.Core.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Response for GET /api/v1/ops/binaryindex/health
|
||||
/// </summary>
|
||||
public sealed record BinaryIndexOpsHealthResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Overall health status.
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public required string Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp of the health check (ISO-8601).
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public required string Timestamp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Component health details.
|
||||
/// </summary>
|
||||
[JsonPropertyName("components")]
|
||||
public required BinaryIndexComponentHealth Components { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Lifter pool warmness status.
|
||||
/// </summary>
|
||||
[JsonPropertyName("lifterWarmness")]
|
||||
public required BinaryIndexLifterWarmness LifterWarmness { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Service version.
|
||||
/// </summary>
|
||||
[JsonPropertyName("version")]
|
||||
public required string Version { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Health status for individual components.
|
||||
/// </summary>
|
||||
public sealed record BinaryIndexComponentHealth
|
||||
{
|
||||
/// <summary>
|
||||
/// Valkey cache health.
|
||||
/// </summary>
|
||||
[JsonPropertyName("valkey")]
|
||||
public required ComponentHealthStatus Valkey { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL persistence health.
|
||||
/// </summary>
|
||||
[JsonPropertyName("postgresql")]
|
||||
public required ComponentHealthStatus Postgresql { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// B2R2 lifter pool health.
|
||||
/// </summary>
|
||||
[JsonPropertyName("lifterPool")]
|
||||
public required ComponentHealthStatus LifterPool { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Health status for a single component.
|
||||
/// </summary>
|
||||
public sealed record ComponentHealthStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Status: "healthy", "degraded", "unhealthy", or "unknown".
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
public required string Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional message with details.
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Response time in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("responseTimeMs")]
|
||||
public long? ResponseTimeMs { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lifter warmness status per ISA.
|
||||
/// </summary>
|
||||
public sealed record BinaryIndexLifterWarmness
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether warm preload is enabled.
|
||||
/// </summary>
|
||||
[JsonPropertyName("warmPreloadEnabled")]
|
||||
public required bool WarmPreloadEnabled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Warmness status by ISA.
|
||||
/// </summary>
|
||||
[JsonPropertyName("isas")]
|
||||
public required ImmutableDictionary<string, IsaWarmness> Isas { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Warmness status for a single ISA.
|
||||
/// </summary>
|
||||
public sealed record IsaWarmness
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the ISA is warmed up.
|
||||
/// </summary>
|
||||
[JsonPropertyName("isWarm")]
|
||||
public required bool IsWarm { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of pooled lifters available.
|
||||
/// </summary>
|
||||
[JsonPropertyName("pooledCount")]
|
||||
public required int PooledCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum pool size for this ISA.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxPoolSize")]
|
||||
public required int MaxPoolSize { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for POST /api/v1/ops/binaryindex/bench/run
|
||||
/// </summary>
|
||||
public sealed record BinaryIndexBenchResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Bench run timestamp (ISO-8601).
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public required string Timestamp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sample size used.
|
||||
/// </summary>
|
||||
[JsonPropertyName("sampleSize")]
|
||||
public required int SampleSize { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Latency summary.
|
||||
/// </summary>
|
||||
[JsonPropertyName("latency")]
|
||||
public required BenchLatencySummary Latency { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Per-operation breakdown.
|
||||
/// </summary>
|
||||
[JsonPropertyName("operations")]
|
||||
public required ImmutableArray<BenchOperationResult> Operations { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the bench completed successfully.
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public required bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if bench failed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Latency summary statistics.
|
||||
/// </summary>
|
||||
public sealed record BenchLatencySummary
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum latency in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("minMs")]
|
||||
public required double MinMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum latency in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("maxMs")]
|
||||
public required double MaxMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Mean latency in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("meanMs")]
|
||||
public required double MeanMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// P50 (median) latency in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("p50Ms")]
|
||||
public required double P50Ms { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// P95 latency in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("p95Ms")]
|
||||
public required double P95Ms { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// P99 latency in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("p99Ms")]
|
||||
public required double P99Ms { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result for a single bench operation.
|
||||
/// </summary>
|
||||
public sealed record BenchOperationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Operation name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("operation")]
|
||||
public required string Operation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Latency in milliseconds.
|
||||
/// </summary>
|
||||
[JsonPropertyName("latencyMs")]
|
||||
public required double LatencyMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the operation succeeded.
|
||||
/// </summary>
|
||||
[JsonPropertyName("success")]
|
||||
public required bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// ISA used for the operation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("isa")]
|
||||
public string? Isa { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for GET /api/v1/ops/binaryindex/cache
|
||||
/// </summary>
|
||||
public sealed record BinaryIndexFunctionCacheStats
|
||||
{
|
||||
/// <summary>
|
||||
/// Timestamp of stats collection (ISO-8601).
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public required string Timestamp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether caching is enabled.
|
||||
/// </summary>
|
||||
[JsonPropertyName("enabled")]
|
||||
public required bool Enabled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Backend type (e.g., "Valkey", "Redis", "InMemory").
|
||||
/// </summary>
|
||||
[JsonPropertyName("backend")]
|
||||
public required string Backend { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total cache hits.
|
||||
/// </summary>
|
||||
[JsonPropertyName("hits")]
|
||||
public required long Hits { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total cache misses.
|
||||
/// </summary>
|
||||
[JsonPropertyName("misses")]
|
||||
public required long Misses { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total evictions.
|
||||
/// </summary>
|
||||
[JsonPropertyName("evictions")]
|
||||
public required long Evictions { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Hit rate (0.0 to 1.0).
|
||||
/// </summary>
|
||||
[JsonPropertyName("hitRate")]
|
||||
public required double HitRate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Key prefix used.
|
||||
/// </summary>
|
||||
[JsonPropertyName("keyPrefix")]
|
||||
public required string KeyPrefix { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Configured TTL.
|
||||
/// </summary>
|
||||
[JsonPropertyName("cacheTtl")]
|
||||
public required string CacheTtl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Estimated entry count (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("estimatedEntries")]
|
||||
public long? EstimatedEntries { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Estimated memory usage in bytes (if available).
|
||||
/// </summary>
|
||||
[JsonPropertyName("estimatedMemoryBytes")]
|
||||
public long? EstimatedMemoryBytes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for GET /api/v1/ops/binaryindex/config
|
||||
/// </summary>
|
||||
public sealed record BinaryIndexEffectiveConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Timestamp of config snapshot (ISO-8601).
|
||||
/// </summary>
|
||||
[JsonPropertyName("timestamp")]
|
||||
public required string Timestamp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// B2R2 pool configuration (sanitized).
|
||||
/// </summary>
|
||||
[JsonPropertyName("b2r2Pool")]
|
||||
public required B2R2PoolConfigView B2R2Pool { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Semantic lifting configuration.
|
||||
/// </summary>
|
||||
[JsonPropertyName("semanticLifting")]
|
||||
public required SemanticLiftingConfigView SemanticLifting { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Function cache configuration (sanitized).
|
||||
/// </summary>
|
||||
[JsonPropertyName("functionCache")]
|
||||
public required FunctionCacheConfigView FunctionCache { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Persistence configuration (sanitized).
|
||||
/// </summary>
|
||||
[JsonPropertyName("persistence")]
|
||||
public required PersistenceConfigView Persistence { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Backend versions.
|
||||
/// </summary>
|
||||
[JsonPropertyName("versions")]
|
||||
public required BackendVersions Versions { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitized view of B2R2 pool config.
|
||||
/// </summary>
|
||||
public sealed record B2R2PoolConfigView
|
||||
{
|
||||
[JsonPropertyName("maxPoolSizePerIsa")]
|
||||
public required int MaxPoolSizePerIsa { get; init; }
|
||||
|
||||
[JsonPropertyName("warmPreloadEnabled")]
|
||||
public required bool WarmPreloadEnabled { get; init; }
|
||||
|
||||
[JsonPropertyName("warmPreloadIsas")]
|
||||
public required ImmutableArray<string> WarmPreloadIsas { get; init; }
|
||||
|
||||
[JsonPropertyName("acquireTimeoutSeconds")]
|
||||
public required double AcquireTimeoutSeconds { get; init; }
|
||||
|
||||
[JsonPropertyName("metricsEnabled")]
|
||||
public required bool MetricsEnabled { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitized view of semantic lifting config.
|
||||
/// </summary>
|
||||
public sealed record SemanticLiftingConfigView
|
||||
{
|
||||
[JsonPropertyName("enabled")]
|
||||
public required bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("b2r2Version")]
|
||||
public required string B2R2Version { get; init; }
|
||||
|
||||
[JsonPropertyName("normalizationRecipeVersion")]
|
||||
public required string NormalizationRecipeVersion { get; init; }
|
||||
|
||||
[JsonPropertyName("maxInstructionsPerFunction")]
|
||||
public required int MaxInstructionsPerFunction { get; init; }
|
||||
|
||||
[JsonPropertyName("maxFunctionsPerBinary")]
|
||||
public required int MaxFunctionsPerBinary { get; init; }
|
||||
|
||||
[JsonPropertyName("functionLiftTimeoutSeconds")]
|
||||
public required double FunctionLiftTimeoutSeconds { get; init; }
|
||||
|
||||
[JsonPropertyName("deduplicationEnabled")]
|
||||
public required bool DeduplicationEnabled { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitized view of function cache config.
|
||||
/// </summary>
|
||||
public sealed record FunctionCacheConfigView
|
||||
{
|
||||
[JsonPropertyName("enabled")]
|
||||
public required bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("backend")]
|
||||
public required string Backend { get; init; }
|
||||
|
||||
[JsonPropertyName("keyPrefix")]
|
||||
public required string KeyPrefix { get; init; }
|
||||
|
||||
[JsonPropertyName("cacheTtl")]
|
||||
public required string CacheTtl { get; init; }
|
||||
|
||||
[JsonPropertyName("maxTtl")]
|
||||
public required string MaxTtl { get; init; }
|
||||
|
||||
[JsonPropertyName("earlyExpiryEnabled")]
|
||||
public required bool EarlyExpiryEnabled { get; init; }
|
||||
|
||||
[JsonPropertyName("earlyExpiryFactor")]
|
||||
public required double EarlyExpiryFactor { get; init; }
|
||||
|
||||
[JsonPropertyName("maxEntrySizeBytes")]
|
||||
public required int MaxEntrySizeBytes { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sanitized view of persistence config.
|
||||
/// </summary>
|
||||
public sealed record PersistenceConfigView
|
||||
{
|
||||
[JsonPropertyName("enabled")]
|
||||
public required bool Enabled { get; init; }
|
||||
|
||||
[JsonPropertyName("schema")]
|
||||
public required string Schema { get; init; }
|
||||
|
||||
[JsonPropertyName("minPoolSize")]
|
||||
public required int MinPoolSize { get; init; }
|
||||
|
||||
[JsonPropertyName("maxPoolSize")]
|
||||
public required int MaxPoolSize { get; init; }
|
||||
|
||||
[JsonPropertyName("commandTimeoutSeconds")]
|
||||
public required double CommandTimeoutSeconds { get; init; }
|
||||
|
||||
[JsonPropertyName("retryOnFailureEnabled")]
|
||||
public required bool RetryOnFailureEnabled { get; init; }
|
||||
|
||||
[JsonPropertyName("maxRetryCount")]
|
||||
public required int MaxRetryCount { get; init; }
|
||||
|
||||
[JsonPropertyName("batchSize")]
|
||||
public required int BatchSize { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Backend version information.
|
||||
/// </summary>
|
||||
public sealed record BackendVersions
|
||||
{
|
||||
[JsonPropertyName("service")]
|
||||
public required string Service { get; init; }
|
||||
|
||||
[JsonPropertyName("b2r2")]
|
||||
public required string B2R2 { get; init; }
|
||||
|
||||
[JsonPropertyName("postgresql")]
|
||||
public string? Postgresql { get; init; }
|
||||
|
||||
[JsonPropertyName("valkey")]
|
||||
public string? Valkey { get; init; }
|
||||
|
||||
[JsonPropertyName("dotnet")]
|
||||
public required string Dotnet { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BinaryIndexOptions.cs
|
||||
// Sprint: SPRINT_20260112_007_BINIDX_binaryindex_user_config
|
||||
// Task: BINIDX-CONF-01
|
||||
// Description: Unified configuration options for BinaryIndex services.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StellaOps.BinaryIndex.Core.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Root configuration for BinaryIndex services.
|
||||
/// </summary>
|
||||
public sealed class BinaryIndexOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration section name.
|
||||
/// </summary>
|
||||
public const string SectionName = "StellaOps:BinaryIndex";
|
||||
|
||||
/// <summary>
|
||||
/// B2R2 lifter pool configuration.
|
||||
/// </summary>
|
||||
public B2R2PoolOptions B2R2Pool { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Semantic lifting configuration.
|
||||
/// </summary>
|
||||
public SemanticLiftingOptions SemanticLifting { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Function cache (Valkey) configuration.
|
||||
/// </summary>
|
||||
public FunctionCacheOptions FunctionCache { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL persistence configuration.
|
||||
/// </summary>
|
||||
public BinaryIndexPersistenceOptions Persistence { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Operational settings.
|
||||
/// </summary>
|
||||
public BinaryIndexOpsOptions Ops { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for B2R2 lifter pool.
|
||||
/// </summary>
|
||||
public sealed class B2R2PoolOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum pooled lifters per ISA.
|
||||
/// </summary>
|
||||
[Range(1, 64)]
|
||||
public int MaxPoolSizePerIsa { get; init; } = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to warm preload lifters at startup.
|
||||
/// </summary>
|
||||
public bool EnableWarmPreload { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// ISAs to warm preload at startup.
|
||||
/// </summary>
|
||||
public ImmutableArray<string> WarmPreloadIsas { get; init; } =
|
||||
[
|
||||
"intel-64",
|
||||
"intel-32",
|
||||
"armv8-64",
|
||||
"armv7-32"
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for acquiring a lifter from the pool.
|
||||
/// </summary>
|
||||
public TimeSpan AcquireTimeout { get; init; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Enable lifter pool metrics collection.
|
||||
/// </summary>
|
||||
public bool EnableMetrics { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for semantic lifting (LowUIR).
|
||||
/// </summary>
|
||||
public sealed class SemanticLiftingOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether semantic lifting is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// B2R2 LowUIR version string for cache keys.
|
||||
/// </summary>
|
||||
public string B2R2Version { get; init; } = "0.9.1";
|
||||
|
||||
/// <summary>
|
||||
/// Normalization recipe version for deterministic fingerprints.
|
||||
/// </summary>
|
||||
public string NormalizationRecipeVersion { get; init; } = "v1";
|
||||
|
||||
/// <summary>
|
||||
/// Maximum instructions per function to lift.
|
||||
/// </summary>
|
||||
[Range(100, 100000)]
|
||||
public int MaxInstructionsPerFunction { get; init; } = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum functions per binary to process.
|
||||
/// </summary>
|
||||
[Range(10, 50000)]
|
||||
public int MaxFunctionsPerBinary { get; init; } = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for lifting a single function.
|
||||
/// </summary>
|
||||
public TimeSpan FunctionLiftTimeout { get; init; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Enable IR statement deduplication.
|
||||
/// </summary>
|
||||
public bool EnableDeduplication { get; init; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for Valkey function cache.
|
||||
/// </summary>
|
||||
public sealed class FunctionCacheOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether caching is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Valkey connection string or service name.
|
||||
/// </summary>
|
||||
public string? ConnectionString { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Key prefix for cache entries.
|
||||
/// </summary>
|
||||
public string KeyPrefix { get; init; } = "stellaops:binidx:funccache:";
|
||||
|
||||
/// <summary>
|
||||
/// Default TTL for cached entries.
|
||||
/// </summary>
|
||||
public TimeSpan CacheTtl { get; init; } = TimeSpan.FromHours(4);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum TTL for any entry.
|
||||
/// </summary>
|
||||
public TimeSpan MaxTtl { get; init; } = TimeSpan.FromHours(24);
|
||||
|
||||
/// <summary>
|
||||
/// Enable early expiry jitter to prevent thundering herd.
|
||||
/// </summary>
|
||||
public bool EnableEarlyExpiry { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Early expiry jitter factor (0.0 to 0.5).
|
||||
/// </summary>
|
||||
[Range(0.0, 0.5)]
|
||||
public double EarlyExpiryFactor { get; init; } = 0.1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum cache entry size in bytes.
|
||||
/// </summary>
|
||||
[Range(1024, 10_000_000)]
|
||||
public int MaxEntrySizeBytes { get; init; } = 1_000_000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for PostgreSQL persistence.
|
||||
/// </summary>
|
||||
public sealed class BinaryIndexPersistenceOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether persistence is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL schema name for BinaryIndex tables.
|
||||
/// </summary>
|
||||
public string Schema { get; init; } = "binary_index";
|
||||
|
||||
/// <summary>
|
||||
/// Connection pool minimum size.
|
||||
/// </summary>
|
||||
[Range(1, 100)]
|
||||
public int MinPoolSize { get; init; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Connection pool maximum size.
|
||||
/// </summary>
|
||||
[Range(1, 500)]
|
||||
public int MaxPoolSize { get; init; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Command timeout for database operations.
|
||||
/// </summary>
|
||||
public TimeSpan CommandTimeout { get; init; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// Enable automatic retry on transient failures.
|
||||
/// </summary>
|
||||
public bool EnableRetryOnFailure { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum retry attempts.
|
||||
/// </summary>
|
||||
[Range(0, 10)]
|
||||
public int MaxRetryCount { get; init; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Batch size for bulk operations.
|
||||
/// </summary>
|
||||
[Range(10, 10000)]
|
||||
public int BatchSize { get; init; } = 500;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operational configuration.
|
||||
/// </summary>
|
||||
public sealed class BinaryIndexOpsOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enable health check endpoint.
|
||||
/// </summary>
|
||||
public bool EnableHealthEndpoint { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable bench sampling endpoint.
|
||||
/// </summary>
|
||||
public bool EnableBenchEndpoint { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable configuration visibility endpoint.
|
||||
/// </summary>
|
||||
public bool EnableConfigEndpoint { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable cache stats endpoint.
|
||||
/// </summary>
|
||||
public bool EnableCacheStatsEndpoint { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Rate limit for bench endpoint (calls per minute).
|
||||
/// </summary>
|
||||
[Range(1, 60)]
|
||||
public int BenchRateLimitPerMinute { get; init; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum bench sample size.
|
||||
/// </summary>
|
||||
[Range(1, 100)]
|
||||
public int MaxBenchSampleSize { get; init; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration keys to redact from visibility endpoint.
|
||||
/// </summary>
|
||||
public ImmutableArray<string> RedactedKeys { get; init; } =
|
||||
[
|
||||
"ConnectionString",
|
||||
"Password",
|
||||
"Secret",
|
||||
"Token",
|
||||
"ApiKey"
|
||||
];
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user