feat: add TLS certificate hot-reload for new connections (Gap 14.3)
Add ReloadTlsCertificates(oldOpts, newOpts) returning TlsReloadResult for path-based cert comparison and validation during config hot-reload. Add 10 targeted tests covering no-change, path detection, missing file, null transitions, and success cases.
This commit is contained in:
@@ -224,6 +224,153 @@ public class TlsReloadTests
|
||||
result.ChangeCount.ShouldBe(2);
|
||||
}
|
||||
|
||||
|
||||
// ─── ReloadTlsCertificates (TlsReloadResult) tests ─────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void No_cert_change_returns_no_change()
|
||||
{
|
||||
// Go parity: tlsConfigReload — identical cert path means no reload needed
|
||||
var oldOpts = new NatsOptions { TlsCert = "/same/cert.pem" };
|
||||
var newOpts = new NatsOptions { TlsCert = "/same/cert.pem" };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificateChanged.ShouldBeFalse();
|
||||
result.CertificateLoaded.ShouldBeFalse();
|
||||
result.Error.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cert_path_changed_detected()
|
||||
{
|
||||
// Go parity: tlsConfigReload — different cert path triggers reload
|
||||
var oldOpts = new NatsOptions { TlsCert = "/old/cert.pem" };
|
||||
var newOpts = new NatsOptions { TlsCert = "/new/cert.pem" };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificateChanged.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cert_path_set_returns_path()
|
||||
{
|
||||
// Verify CertificatePath reflects the new cert path when changed
|
||||
var oldOpts = new NatsOptions { TlsCert = "/old/cert.pem" };
|
||||
var newOpts = new NatsOptions { TlsCert = "/new/cert.pem" };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificatePath.ShouldBe("/new/cert.pem");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Missing_cert_file_returns_error()
|
||||
{
|
||||
// Go parity: tlsConfigReload — non-existent cert path returns an error
|
||||
var oldOpts = new NatsOptions { TlsCert = "/old/cert.pem" };
|
||||
var newOpts = new NatsOptions { TlsCert = "/nonexistent/path/cert.pem" };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.Error.ShouldNotBeNullOrWhiteSpace();
|
||||
result.Error!.ShouldContain("/nonexistent/path/cert.pem");
|
||||
result.CertificateLoaded.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Null_to_null_no_change()
|
||||
{
|
||||
// Both TlsCert null — no TLS configured on either side, no change
|
||||
var oldOpts = new NatsOptions { TlsCert = null };
|
||||
var newOpts = new NatsOptions { TlsCert = null };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificateChanged.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Null_to_value_detected()
|
||||
{
|
||||
// Go parity: TestConfigReloadEnableTLS — enabling TLS is detected as a change
|
||||
var oldOpts = new NatsOptions { TlsCert = null };
|
||||
var newOpts = new NatsOptions { TlsCert = "/new/cert.pem" };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificateChanged.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Value_to_null_detected()
|
||||
{
|
||||
// Go parity: TestConfigReloadDisableTLS — disabling TLS is a change, loads successfully
|
||||
var oldOpts = new NatsOptions { TlsCert = "/old/cert.pem" };
|
||||
var newOpts = new NatsOptions { TlsCert = null };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificateChanged.ShouldBeTrue();
|
||||
result.CertificateLoaded.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Valid_cert_path_loaded_true()
|
||||
{
|
||||
// Go parity: tlsConfigReload — file exists, so CertificateLoaded is true
|
||||
var tempFile = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
var oldOpts = new NatsOptions { TlsCert = "/old/cert.pem" };
|
||||
var newOpts = new NatsOptions { TlsCert = tempFile };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificateChanged.ShouldBeTrue();
|
||||
result.CertificateLoaded.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Error_null_on_success()
|
||||
{
|
||||
// Successful reload (file exists) must have Error as null
|
||||
var tempFile = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
var oldOpts = new NatsOptions { TlsCert = "/old/cert.pem" };
|
||||
var newOpts = new NatsOptions { TlsCert = tempFile };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.Error.ShouldBeNull();
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Same_empty_strings_no_change()
|
||||
{
|
||||
// Both TlsCert are empty string — treat as equal, no change
|
||||
var oldOpts = new NatsOptions { TlsCert = string.Empty };
|
||||
var newOpts = new NatsOptions { TlsCert = string.Empty };
|
||||
|
||||
var result = ConfigReloader.ReloadTlsCertificates(oldOpts, newOpts);
|
||||
|
||||
result.CertificateChanged.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to write a self-signed certificate to PEM files.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user