fix(secrets-cli): CRUD flows — escaped validation markup, shared prompt helpers, delete existence check

This commit is contained in:
Joseph Doherty
2026-07-19 09:47:10 -04:00
parent 6ad710bdbd
commit e5a4d0276a
5 changed files with 98 additions and 50 deletions
@@ -122,6 +122,28 @@ public sealed class CrudFlowTests : IDisposable
Assert.DoesNotContain("TOPSECRET-PLAINTEXT", console.Output, StringComparison.Ordinal); // never echoed
}
[Fact]
public async Task Invalid_name_reprompts_then_completes()
{
SecretsSession session = await NewFullSessionAsync();
TestConsole console = NewConsole();
console.Input.PushTextWithEnter("bad name"); // invalid (space) → must re-prompt, not throw
console.Input.PushTextWithEnter("good/name"); // valid retry
console.Input.PushTextWithEnter("VALUE-AFTER-REPROMPT"); // value
console.Input.PushKey(ConsoleKey.Enter); // content-type: Text
console.Input.PushKey(ConsoleKey.Enter); // description: empty
// The bug this pins: SecretName's error text embeds "[a-z0-9._/-]", which Spectre parses as
// markup and throws InvalidOperationException on unless the validator escapes it. Reaching the
// assertion at all proves the re-prompt happened cleanly.
await new SetSecretFlow().RunAsync(console, session, CancellationToken.None);
StoredSecret? row = await session.Store.GetAsync(new SecretName("good/name"), CancellationToken.None);
Assert.NotNull(row);
Assert.Equal("VALUE-AFTER-REPROMPT", session.Cipher!.Decrypt(row!));
}
[Fact]
public async Task Existing_name_confirms_overwrite()
{
@@ -201,6 +223,21 @@ public sealed class CrudFlowTests : IDisposable
Assert.True(row!.IsDeleted);
}
[Fact]
public async Task Unknown_name_reports_not_found_without_confirming()
{
SecretsSession session = await NewFullSessionAsync();
TestConsole console = NewConsole();
console.Input.PushTextWithEnter("never/existed"); // name only — no confirm input is scripted
// If the flow prompted for a confirm before checking existence, the missing input would fail.
await new DeleteSecretFlow().RunAsync(console, session, CancellationToken.None);
Assert.Contains("No secret named", console.Output, StringComparison.Ordinal);
StoredSecret? row = await session.Store.GetAsync(new SecretName("never/existed"), CancellationToken.None);
Assert.Null(row);
}
[Fact]
public async Task Decline_leaves_row()
{