513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
156 lines
6.3 KiB
C#
156 lines
6.3 KiB
C#
using Microsoft.Extensions.Options;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Transport.Tests;
|
|
|
|
/// <summary>
|
|
/// Arch-review 08 §1.5: <see cref="TransportOptions"/> feeds bundle size caps,
|
|
/// zip-bomb defences, unlock rate limits and — security-critically — the PBKDF2
|
|
/// iteration count used to derive the bundle encryption key. A zero/negative or
|
|
/// otherwise degenerate value must be rejected at startup with a clear,
|
|
/// key-naming message rather than silently weakening key derivation or crashing
|
|
/// deep inside the import pipeline.
|
|
/// </summary>
|
|
public class TransportOptionsValidatorTests
|
|
{
|
|
private static ValidateOptionsResult Validate(TransportOptions options) =>
|
|
new TransportOptionsValidator().Validate(Options.DefaultName, options);
|
|
|
|
/// <summary>Verifies that the default options are valid.</summary>
|
|
[Fact]
|
|
public void DefaultOptions_AreValid()
|
|
{
|
|
var result = Validate(new TransportOptions());
|
|
Assert.True(result.Succeeded, result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a zero bundle session TTL is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroBundleSessionTtlMinutes_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { BundleSessionTtlMinutes = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("BundleSessionTtlMinutes", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a zero maximum bundle size is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroMaxBundleSizeMb_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxBundleSizeMb = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxBundleSizeMb", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a zero maximum of concurrent import sessions is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroMaxConcurrentImportSessions_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxConcurrentImportSessions = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxConcurrentImportSessions", result.FailureMessage); // FAILS today: validates clean
|
|
}
|
|
|
|
/// <summary>Verifies that a negative maximum of concurrent import sessions is rejected.</summary>
|
|
[Fact]
|
|
public void NegativeMaxConcurrentImportSessions_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxConcurrentImportSessions = -1 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxConcurrentImportSessions", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that zero PBKDF2 iterations is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroPbkdf2Iterations_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { Pbkdf2Iterations = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("Pbkdf2Iterations", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a PBKDF2 iteration count below the security floor is rejected.</summary>
|
|
[Fact]
|
|
public void Pbkdf2Iterations_BelowSecurityFloor_IsRejected()
|
|
{
|
|
// A config typo (e.g. 6_000 instead of 600_000) must not silently weaken
|
|
// bundle key derivation: anything under 100_000 is rejected.
|
|
var result = Validate(new TransportOptions { Pbkdf2Iterations = 99_999 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("Pbkdf2Iterations", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a PBKDF2 iteration count at the security floor is valid.</summary>
|
|
[Fact]
|
|
public void Pbkdf2Iterations_AtSecurityFloor_IsValid()
|
|
{
|
|
var result = Validate(new TransportOptions { Pbkdf2Iterations = 100_000 });
|
|
Assert.True(result.Succeeded, result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that zero unlock attempts per session is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroMaxUnlockAttemptsPerSession_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxUnlockAttemptsPerSession = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxUnlockAttemptsPerSession", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a zero maximum decompressed bundle entry size is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroMaxBundleEntryDecompressedMb_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxBundleEntryDecompressedMb = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxBundleEntryDecompressedMb", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a zero maximum bundle entry count is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroMaxBundleEntryCount_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxBundleEntryCount = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxBundleEntryCount", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a zero maximum bundle entry compression ratio is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroMaxBundleEntryCompressionRatio_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxBundleEntryCompressionRatio = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxBundleEntryCompressionRatio", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that zero unlock attempts per IP per hour is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroMaxUnlockAttemptsPerIpPerHour_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { MaxUnlockAttemptsPerIpPerHour = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("MaxUnlockAttemptsPerIpPerHour", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a zero major schema version is rejected.</summary>
|
|
[Fact]
|
|
public void ZeroSchemaVersionMajor_IsRejected()
|
|
{
|
|
var result = Validate(new TransportOptions { SchemaVersionMajor = 0 });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("SchemaVersionMajor", result.FailureMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that a blank source environment is rejected.</summary>
|
|
/// <param name="value">The blank source environment value.</param>
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public void BlankSourceEnvironment_IsRejected(string value)
|
|
{
|
|
var result = Validate(new TransportOptions { SourceEnvironment = value });
|
|
Assert.True(result.Failed);
|
|
Assert.Contains("SourceEnvironment", result.FailureMessage);
|
|
}
|
|
}
|