fix(secrets-cli): audit flow — cipher guard, shared actor helper, summary + branch coverage

This commit is contained in:
Joseph Doherty
2026-07-19 09:55:16 -04:00
parent d1ec0fe5da
commit e8b34be255
2 changed files with 52 additions and 6 deletions
@@ -160,6 +160,50 @@ public sealed class ReferenceAuditFlowTests : IDisposable
Assert.DoesNotContain("MISSING-SEED-SENTINEL", console.Output, StringComparison.Ordinal);
Assert.DoesNotContain("TOMB-SEED-SENTINEL", console.Output, StringComparison.Ordinal);
// The closing re-audit summary reflects the now-all-Ok state.
Assert.Contains("Audit complete: 3 Ok.", console.Output, StringComparison.Ordinal);
}
[Fact]
public async Task Undecryptable_reference_is_offered_as_overwrite_and_reseeded()
{
KeyValuePair<string, string?>[] config = [new("Api:Bad", "${secret:svc/bad}")];
// Seal svc/bad under a DIFFERENT KEK on the same store, so the audited session cannot decrypt it.
SecretsSession writer = await NewSessionAsync(config);
await SeedAsync(writer, "svc/bad", "OLD-UNREADABLE-VALUE");
SecretsSession session = await NewSessionAsync(config); // fresh random KEK → svc/bad is Undecryptable
TestConsole console = NewConsole();
console.Input.PushTextWithEnter("y"); // overwrite confirm (defaults false for Undecryptable)
console.Input.PushTextWithEnter("NEW-READABLE-SENTINEL"); // fresh value (masked)
console.Input.PushKey(ConsoleKey.Enter); // content-type Text
console.Input.PushKey(ConsoleKey.Enter); // description empty
await new ReferenceAuditFlow().RunAsync(console, session, CancellationToken.None);
Assert.Contains("Undecryptable", console.Output, StringComparison.Ordinal); // rendered in the table
StoredSecret? row = await session.Store.GetAsync(new SecretName("svc/bad"), CancellationToken.None);
Assert.Equal("NEW-READABLE-SENTINEL", session.Cipher!.Decrypt(row!)); // now decrypts under the session KEK
Assert.DoesNotContain("NEW-READABLE-SENTINEL", console.Output, StringComparison.Ordinal);
}
[Fact]
public async Task Invalid_name_reference_renders_hint_and_is_not_prompted()
{
KeyValuePair<string, string?>[] config = [new("Api:Broken", "${secret:REPLACE ME}")];
SecretsSession session = await NewSessionAsync(config);
TestConsole console = NewConsole(); // push NO input: an InvalidName reference must never prompt
await new ReferenceAuditFlow().RunAsync(console, session, CancellationToken.None);
Assert.Contains("InvalidName", console.Output, StringComparison.Ordinal); // table status
Assert.Contains("not a valid secret name", console.Output, StringComparison.Ordinal); // fix-the-token hint
Assert.Equal(0, CountOccurrences(console.Output, "Seed '")); // no seed prompt
Assert.Contains("Audit complete: 1 InvalidName.", console.Output, StringComparison.Ordinal);
}
[Fact]