feat(audit): M5.5 per-channel retention overrides via purge-role bounded delete (T3)
This commit is contained in:
+103
@@ -50,4 +50,107 @@ public class AuditLogOptionsValidatorTests
|
||||
result.Failures!,
|
||||
f => f.Contains(nameof(AuditLogOptions.InboundMaxBytes), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// M5.5 (T3) per-channel retention overrides
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Validate_PerChannelRetention_ShorterThanGlobal_Passes()
|
||||
{
|
||||
// A per-channel window strictly shorter than the global window is the
|
||||
// sanctioned case — the purge actor expires those rows earlier via the
|
||||
// maintenance-path row DELETE.
|
||||
var validator = new AuditLogOptionsValidator();
|
||||
var opts = new AuditLogOptions
|
||||
{
|
||||
RetentionDays = 365,
|
||||
PerChannelRetentionDays = new Dictionary<string, int>
|
||||
{
|
||||
["ApiOutbound"] = 90,
|
||||
["Notification"] = 30, // floor (MinRetentionDays)
|
||||
},
|
||||
};
|
||||
|
||||
Assert.True(validator.Validate(null, opts).Succeeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PerChannelRetention_EqualToGlobal_Passes()
|
||||
{
|
||||
// Equal to global is allowed (the bound is [Min, RetentionDays] inclusive);
|
||||
// the purge actor simply treats it as a no-op since it is not SHORTER.
|
||||
var validator = new AuditLogOptionsValidator();
|
||||
var opts = new AuditLogOptions
|
||||
{
|
||||
RetentionDays = 200,
|
||||
PerChannelRetentionDays = new Dictionary<string, int> { ["DbOutbound"] = 200 },
|
||||
};
|
||||
|
||||
Assert.True(validator.Validate(null, opts).Succeeded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PerChannelRetention_LongerThanGlobal_Fails()
|
||||
{
|
||||
// A per-channel window LONGER than the global window is meaningless under
|
||||
// month-partition switch-out (governed by the global window) and is rejected.
|
||||
var validator = new AuditLogOptionsValidator();
|
||||
var opts = new AuditLogOptions
|
||||
{
|
||||
RetentionDays = 100,
|
||||
PerChannelRetentionDays = new Dictionary<string, int> { ["ApiInbound"] = 200 },
|
||||
};
|
||||
|
||||
var result = validator.Validate(null, opts);
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Contains(
|
||||
result.Failures!,
|
||||
f => f.Contains(nameof(AuditLogOptions.PerChannelRetentionDays), StringComparison.Ordinal)
|
||||
&& f.Contains("ApiInbound", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PerChannelRetention_BelowMinimum_Fails()
|
||||
{
|
||||
var validator = new AuditLogOptionsValidator();
|
||||
var opts = new AuditLogOptions
|
||||
{
|
||||
RetentionDays = 365,
|
||||
PerChannelRetentionDays = new Dictionary<string, int> { ["ApiOutbound"] = 29 },
|
||||
};
|
||||
|
||||
var result = validator.Validate(null, opts);
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Contains(
|
||||
result.Failures!,
|
||||
f => f.Contains(nameof(AuditLogOptions.PerChannelRetentionDays), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PerChannelRetention_UnknownChannelKey_Fails()
|
||||
{
|
||||
// Keys must be recognized AuditChannel names; a typo / unknown key is rejected
|
||||
// rather than silently ignored so a misconfiguration surfaces at boot.
|
||||
var validator = new AuditLogOptionsValidator();
|
||||
var opts = new AuditLogOptions
|
||||
{
|
||||
RetentionDays = 365,
|
||||
PerChannelRetentionDays = new Dictionary<string, int> { ["NotAChannel"] = 90 },
|
||||
};
|
||||
|
||||
var result = validator.Validate(null, opts);
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Contains(
|
||||
result.Failures!,
|
||||
f => f.Contains("NotAChannel", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_PerChannelRetention_DefaultEmpty_Passes()
|
||||
{
|
||||
// The default (no overrides) must pass — this is the common case.
|
||||
var validator = new AuditLogOptionsValidator();
|
||||
Assert.True(validator.Validate(null, new AuditLogOptions()).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user