Public Access
Keep an open editor's pickers in step with the vault
The host editor's four pickers were snapshots taken when it opened, and the comment on EditorAuthenticationChoices said why: a picker whose contents move under somebody halfway through a form is worse than a list a minute stale, and only one editor could be open at a time anyway, so the only way to add a key was to close this one. The second half of that stopped being true when AHostEditorIsInTheWay was split from AVaultEditorIsInTheWay. The host editor is the Hosts screen's business and the keychain's editors are the Vault screen's; neither refuses the other now, which was the right split — it stopped three quarters of a screen going inert over an editor the user was not looking at — but it left the assumption those snapshots rested on false and nothing to notice. So the ordinary way of using the feature was the broken one. Somebody starts editing a host, finds there is no key to bind it to, goes to KEYS, makes one, and comes back to a picker that does not have it — with the fix being to throw the form away and start again. The same for a password, a tag, a group, and for a whole vault made on the Teams screen because the host being typed belongs to the team rather than to the person typing it: the vault they had just made for it was the one place they could not file it. RefreshOpenEditors refills whichever editor is open, and it hangs off ReloadAsync rather than off the twenty-odd commands that write to the vault. That is the choice worth stating, because it is what makes a sync count as well as a save: a key pulled from another machine reaches the open editor by the same path a key typed here does, and a place that wrote to the vault without refreshing the editor would be a bug nobody would find for months. What the old comment was protecting against is real, so every picker is put back onto what it was already showing, by id, and not one typed field is touched. An editor that reset its own bindings because a background sync landed would be a worse bug than the stale list this fixes — it would rebind a host as a side effect of somebody else's work. The placeholder entries go back too, which is the case 3.4 measures: a group deleted on another machine mid-edit still cannot unfile the host when the form is saved. The group editor gets the same treatment for the same reasons; it shares the drawer, and its default binding is lent to every host under it. The snippet editor's vault picker was the same copy of the same list and went stale the same way. It watches TargetVaults rather than the reload, because that screen has always been a wrapper over the vault's collections and has no reload of its own to hang off — which is how it already follows Snippets. The move panels are deliberately left alone. A vault arriving from a sync while one is open still will not appear in it, but a move panel is opened by the act that fills it and its picker resets its selection to the first entry on every rebuild, so refreshing it would move a destination somebody had chosen. Same class of bug, different answer, and not this change. Five tests, and four of them were checked failing with the RefreshOpenEditors call commented out: a key reaching the open host editor and binding when chosen, an item arriving without moving a selection that was already made, a tag arriving as an unworn chip, a key reaching the group editor, and a vault reaching the host and snippet editors without moving either. Manual check 7.12 sits beside 7.11, which is this same bug on the files screen's picker, and says what the worse failure would look like: a picker that moves rather than one that does not notice.
This commit is contained in:
@@ -319,6 +319,95 @@ public sealed class VaultSharingTests : IAsyncLifetime
|
||||
shell.Vault.HasVaultChoice.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// And it reaches a host editor that was already open, which is very nearly the only way a vault gets made:
|
||||
/// somebody starts adding a host, realises it belongs to the team rather than to them, and goes to make
|
||||
/// somewhere to put it. The editor's picker is its own — see <c>VaultViewModel.EditorVaultChoices</c> — and
|
||||
/// it was filled when the editor opened, so the vault they had just made for this host was the one place
|
||||
/// they could not file it without throwing the form away.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The second half is what stops the fix being worse than the bug: the refill adds the entry and leaves the
|
||||
/// selection alone, so a vault appearing cannot move a half-typed host into it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task AVaultCreatedWithAHostEditorOpen_IsOfferedAsSomewhereToFileThatHost()
|
||||
{
|
||||
await UnlockedAsync();
|
||||
|
||||
var vault = shell.Vault!;
|
||||
|
||||
vault.NewHostCommand.Execute(null);
|
||||
vault.EditorLabel = "prod-db";
|
||||
vault.EditorHostname = "db.internal";
|
||||
|
||||
await CreateVaultAsync(shell.Vaults, "Platform secrets");
|
||||
|
||||
var vaultId = shell.Vaults.SelectedVault!.VaultId;
|
||||
|
||||
vault.IsEditing.ShouldBeTrue("making a vault must not close the host editor");
|
||||
vault.EditorLabel.ShouldBe("prod-db", "nor discard what has been typed into it");
|
||||
|
||||
vault.ShowsEditorVaultChoice.ShouldBeTrue(
|
||||
"a second writable vault is what makes the picker worth drawing at all");
|
||||
|
||||
vault.EditorVaultChoices.Select(choice => choice.VaultId).ShouldContain(vaultId);
|
||||
|
||||
vault.EditorSelectedVault.ShouldNotBeNull().IsPersonal
|
||||
.ShouldBeTrue("the refill must not move the host into the vault that has just appeared");
|
||||
|
||||
// Choosing it, on the other hand, files the host there — which is what the picker was for.
|
||||
vault.EditorSelectedVault = vault.EditorVaultChoices.Single(
|
||||
choice => choice.VaultId == vaultId);
|
||||
|
||||
await vault.SaveHostCommand.ExecuteAsync(null);
|
||||
|
||||
vault.Hosts.ShouldHaveSingleItem().VaultId.ShouldBe(vaultId, vault.Status);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The snippet editor's picker is a copy of the same list — see
|
||||
/// <c>SnippetsViewModel.BuildEditorVaultChoices</c> — and went stale in exactly the same way. It watches the
|
||||
/// list rather than the reload, because this screen has always been a wrapper over the vault's collections
|
||||
/// and has no reload of its own to hang off.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task AVaultCreatedWithASnippetEditorOpen_IsOfferedAsSomewhereToFileThatSnippet()
|
||||
{
|
||||
await UnlockedAsync();
|
||||
|
||||
var vault = shell.Vault!;
|
||||
var snippets = SnippetsOver(vault);
|
||||
|
||||
snippets.NewCommand.Execute(null);
|
||||
snippets.EditorLabel = "restart the api";
|
||||
snippets.EditorCommand = "sudo systemctl restart dodossh-api";
|
||||
|
||||
await CreateVaultAsync(shell.Vaults, "Platform secrets");
|
||||
|
||||
var vaultId = shell.Vaults.SelectedVault!.VaultId;
|
||||
|
||||
snippets.IsEditing.ShouldBeTrue("making a vault must not close the snippet editor");
|
||||
snippets.EditorCommand.ShouldBe(
|
||||
"sudo systemctl restart dodossh-api",
|
||||
"nor discard what has been typed into it");
|
||||
|
||||
snippets.ShowsEditorVaultChoice.ShouldBeTrue("there are two vaults to choose between now");
|
||||
snippets.EditorVaultChoices.Select(choice => choice.VaultId).ShouldContain(vaultId);
|
||||
|
||||
snippets.EditorSelectedVault.ShouldNotBeNull().IsPersonal
|
||||
.ShouldBeTrue("the refill must not move the snippet into the vault that has just appeared");
|
||||
|
||||
snippets.EditorSelectedVault = snippets.EditorVaultChoices.Single(
|
||||
choice => choice.VaultId == vaultId);
|
||||
|
||||
await snippets.SaveCommand.ExecuteAsync(null);
|
||||
|
||||
Snippet(snippets, "restart the api").VaultId.ShouldBe(vaultId, snippets.Status);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Making a vault makes exactly one membership list, and this is the assertion that the two-step create
|
||||
/// has not started leaking them: the screen no longer offers to make one on its own, so a second one
|
||||
|
||||
Reference in New Issue
Block a user