//nolint:testpackage // white-box test of unexported internals package cli import ( "errors" "testing" "github.com/awnumar/memguard" "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/stretchr/testify/require" "sneak.berlin/go/secret/internal/vault" ) // errRenameFailed is returned by renameFailFs in place of a successful // rename. var errRenameFailed = errors.New("permission denied") // renameFailFs fails every Rename of one path, as a read-only parent // directory does. type renameFailFs struct { afero.Fs path string } func (f *renameFailFs) Rename(oldname, newname string) error { if oldname == f.path { return errRenameFailed } return f.Fs.Rename(oldname, newname) } // TestCrossVaultMoveFailsWhenSourceRemains is a regression test for // https://git.eeqj.de/sneak/secret/issues/159, where a move between two // vaults whose source could not be removed reported the secret moved and // succeeded. It must fail, and the copy and the source must both be there. // The state directory lock refuses the wrapped filesystem, so the test // calls moveSecret, which MoveSecret runs once it holds the lock. func TestCrossVaultMoveFailsWhenSourceRemains(t *testing.T) { t.Parallel() base := afero.NewMemMapFs() _, err := vault.CreateVault(base, testStateDir, "work", testMnemonicBuffer(t), nil) require.NoError(t, err) source, err := vault.CreateVault(base, testStateDir, "default", testMnemonicBuffer(t), nil) require.NoError(t, err) err = source.AddSecret("x", memguard.NewBufferFromBytes([]byte("value")), false) require.NoError(t, err) instance := NewCLIInstanceWithStateDir(&renameFailFs{ Fs: base, path: testStateDir + "/vaults.d/default/secrets.d/x", }, testStateDir) instance.Mnemonic = testMnemonicBuffer(t) err = instance.moveSecret(&cobra.Command{}, "default:x", "work:y", false) require.ErrorIs(t, err, errRenameFailed) require.ErrorContains(t, err, "secret 'default:x' is copied with all "+ "1 version(s) to 'work:y', but the source remains") // requireValue requires that the vault vaultName holds secretName with // the value stored above. requireValue := func(vaultName, secretName string) { vlt := vault.NewVault(base, testStateDir, vaultName) vlt.Mnemonic = testMnemonicBuffer(t) value, err := vlt.GetSecret(secretName) require.NoError(t, err, vaultName+":"+secretName) defer value.Destroy() require.Equal(t, []byte("value"), value.Bytes()) } requireValue("default", "x") requireValue("work", "y") }