check / check (push) Failing after 2s
vault.CreateVault takes the unlocker passphrase and writes the vault directory, its metadata, long-term public key and passphrase unlocker into a temporary directory, renames that into vaults.d once complete, and only then makes the vault current. secret init and secret vault create call it once instead of adding the unlocker afterwards, so a kill part-way leaves either no vault, whose temporary directory the next command that takes the lock deletes, or a complete one. A test records the state directory before every change the call makes and checks each state, and the command run again from it. Model: opus-5-5
75 lines
2.5 KiB
Go
75 lines
2.5 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/secret/internal/cli"
|
|
"git.eeqj.de/sneak/secret/internal/secret"
|
|
"git.eeqj.de/sneak/secret/internal/vault"
|
|
"github.com/spf13/afero"
|
|
"github.com/spf13/cobra"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestLeftoversRemovedByNextChangingCommand is a regression test for
|
|
// https://git.eeqj.de/sneak/secret/issues/75. It plants what a command
|
|
// killed part-way leaves in each directory where secret.TempDirFor and
|
|
// secret.WriteFileAtomic make temporary entries: a temporary directory
|
|
// holding a vault, secret, unlocker or version being added or removed, and
|
|
// a temporary file beside a file being replaced. `secret list` must leave
|
|
// them all, and the next command that takes the state directory lock, here
|
|
// `secret vault select` of the vault already current, must delete exactly
|
|
// them: a vault named like a temporary directory stays. The copy has no
|
|
// lock file yet, so that command, as after a killed one, finds no mark that
|
|
// the last holder of the lock finished.
|
|
func TestLeftoversRemovedByNextChangingCommand(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fs := newTwoVaultFs(t)
|
|
|
|
_, err := vault.CreateVault(fs, testStateDir, ".tmp-0", nil, nil)
|
|
require.NoError(t, err)
|
|
require.NoError(t, vault.SelectVault(fs, testStateDir, "default"))
|
|
|
|
before := snapshotStateDir(t, fs)
|
|
|
|
vaultDir := testStateDir + "/vaults.d/default"
|
|
secretDir := vaultDir + "/secrets.d/x"
|
|
|
|
versions, err := secret.ListVersions(fs, secretDir)
|
|
require.NoError(t, err)
|
|
require.Len(t, versions, 1)
|
|
|
|
for _, dir := range []string{
|
|
testStateDir + "/.tmp-1/default",
|
|
vaultDir + "/.tmp-2/x",
|
|
secretDir + "/.tmp-3/" + testVersion,
|
|
} {
|
|
require.NoError(t, fs.MkdirAll(dir, secret.DirPerms))
|
|
require.NoError(t, afero.WriteFile(fs, dir+"/value.age",
|
|
[]byte("encrypted"), secret.FilePerms))
|
|
}
|
|
|
|
for _, file := range []string{
|
|
testStateDir + "/.currentvault.tmp-4",
|
|
vaultDir + "/.current-unlocker.tmp-5",
|
|
secretDir + "/.current.tmp-6",
|
|
secretDir + "/versions/" + versions[0] + "/.metadata.age.tmp-7",
|
|
} {
|
|
require.NoError(t, afero.WriteFile(fs, file,
|
|
[]byte("partial"), secret.FilePerms))
|
|
}
|
|
|
|
planted := snapshotStateDir(t, fs)
|
|
c := cli.NewCLIInstanceWithStateDir(fs, testStateDir)
|
|
cmd := &cobra.Command{}
|
|
cmd.SetOut(io.Discard)
|
|
|
|
require.NoError(t, c.ListSecrets(cmd, false, false, ""))
|
|
require.Equal(t, planted, snapshotStateDir(t, fs))
|
|
|
|
require.NoError(t, c.SelectVault(cmd, "default"))
|
|
require.Equal(t, before, snapshotStateDir(t, fs))
|
|
}
|