using Bunit; using Bunit.TestDoubles; using Microsoft.Extensions.DependencyInjection; using ZB.MOM.WW.Audit; using ZB.MOM.WW.Secrets.Abstractions; using ZB.MOM.WW.Secrets.Ui.Components; using ZB.MOM.WW.Secrets.Ui.Tests.Fakes; namespace ZB.MOM.WW.Secrets.Ui.Tests; /// /// Secrets-014: "Add secret" on a name that already exists must not silently overwrite it (value, /// description, content type) and audit the overwrite as secret.add. A live row is refused; /// a tombstoned one is restored only on a second, deliberate submit of the same name. /// public class SecretEditorAddExistingTests : TestContext { private (RecordingSecretStore Store, CapturingAuditWriter Audit, FakeCipher Cipher) Setup() { var store = new RecordingSecretStore(); var audit = new CapturingAuditWriter(); var cipher = new FakeCipher(); Services.AddSingleton(store); Services.AddSingleton(audit); Services.AddSingleton(cipher); var auth = this.AddTestAuthorization(); auth.SetAuthorized("carol"); auth.SetPolicies(SecretsAuthorization.ManagePolicy); return (store, audit, cipher); } private static StoredSecret Existing(FakeCipher cipher, string name, bool deleted) => cipher.Encrypt(new SecretName(name), "live-credential", SecretContentType.ConnectionString) with { Description = "central db", Revision = 4, IsDeleted = deleted, CreatedUtc = DateTimeOffset.UnixEpoch, UpdatedUtc = DateTimeOffset.UnixEpoch, }; private IRenderedComponent FillAdd(string name, string value) { var cut = RenderComponent(p => p.Add(c => c.OnSaved, () => { })); cut.Find("[data-testid=secret-name]").Change(name); cut.Find("[data-testid=secret-value]").Change(value); return cut; } [Fact] public void Adding_an_existing_live_name_is_refused_and_writes_nothing() { var (store, audit, cipher) = Setup(); store.Seed(Existing(cipher, "db/central", deleted: false)); var cut = FillAdd("db/central", "pasted-from-other-env"); cut.Find("[data-testid=editor-submit]").Click(); Assert.Empty(store.Upserts); Assert.Empty(audit.Events); Assert.Contains("already exists", cut.Find("[data-testid=editor-error]").TextContent); // A second click does not get round the refusal. cut.Find("[data-testid=editor-submit]").Click(); Assert.Empty(store.Upserts); } [Fact] public void Adding_a_tombstoned_name_needs_a_second_submit_to_restore() { var (store, audit, cipher) = Setup(); store.Seed(Existing(cipher, "db/retired", deleted: true)); var cut = FillAdd("db/retired", "fresh-value"); cut.Find("[data-testid=editor-submit]").Click(); Assert.Empty(store.Upserts); Assert.Contains("deleted secret", cut.Find("[data-testid=editor-error]").TextContent); cut.Find("[data-testid=editor-submit]").Click(); StoredSecret row = Assert.Single(store.Upserts); Assert.Equal("fresh-value", cipher.Decrypt(row)); Assert.Equal("secret.add", Assert.Single(audit.Events).Action); } [Fact] public void Adding_a_new_name_still_works_in_one_submit() { var (store, _, _) = Setup(); var cut = FillAdd("db/new", "v"); cut.Find("[data-testid=editor-submit]").Click(); Assert.Single(store.Upserts); } }