package cli_test import ( "testing" "git.eeqj.de/sneak/secret/internal/cli" "git.eeqj.de/sneak/secret/internal/secret" "git.eeqj.de/sneak/secret/internal/vault" "github.com/awnumar/memguard" "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/stretchr/testify/require" ) // TestCreateExistingVaultChangesNothing is a regression test for // https://git.eeqj.de/sneak/secret/issues/74, where running `secret init` // a second time, or `secret vault create` with the name of an existing // vault, replaced that vault's keys, so that none of its secrets could be // decrypted any more. Each must refuse, change nothing, and leave every // vault's secret readable through its passphrase unlocker. // //nolint:paralleltest // t.Setenv forbids parallel subtests func TestCreateExistingVaultChangesNothing(t *testing.T) { t.Setenv(secret.EnvMnemonic, testMnemonic) t.Setenv(secret.EnvUnlockPassphrase, testPassphrase) // `secret init`, `secret vault create work`, `secret vault select // default`, and the secret "x" in each vault. "work" is then not the // current vault, which creating it again must not change. fs := afero.NewMemMapFs() c := cli.NewCLIInstanceWithStateDir(fs, testStateDir) cmd := &cobra.Command{} require.NoError(t, c.Init(cmd)) require.NoError(t, c.CreateVault(cmd, "work")) require.NoError(t, c.SelectVault(cmd, "default")) vaults, err := vault.ListVaults(fs, testStateDir) require.NoError(t, err) require.Len(t, vaults, 2) for _, name := range vaults { value := memguard.NewBufferFromBytes([]byte("value")) err := vault.NewVault(fs, testStateDir, name).AddSecret("x", value, false) require.NoError(t, err) } before := snapshotStateDir(t, fs) tests := []struct { command string want string run func(c *cli.Instance) error }{ { "init", "failed to create default vault: vault default already exists", func(c *cli.Instance) error { return c.Init(cmd) }, }, { "vault create default", "vault default already exists", func(c *cli.Instance) error { return c.CreateVault(cmd, "default") }, }, { "vault create work", "vault work already exists", func(c *cli.Instance) error { return c.CreateVault(cmd, "work") }, }, } for _, tt := range tests { t.Run(tt.command, func(t *testing.T) { fs := newFsFromSnapshot(t, before) err := tt.run(cli.NewCLIInstanceWithStateDir(fs, testStateDir)) require.EqualError(t, err, tt.want) require.Equal(t, before, snapshotStateDir(t, fs)) }) } // Every case left the state directory exactly as recorded in before, so // reading each vault's secret once from it shows that it still decrypts // after each case. Without the mnemonic, reading a secret goes through // the vault's passphrase unlocker, which is slow. t.Setenv(secret.EnvMnemonic, "") for _, name := range vaults { value, err := vault.NewVault(fs, testStateDir, name).GetSecret("x") require.NoError(t, err) require.Equal(t, "value", string(value)) } } // TestStopAtPassphrasePromptLeavesNothing is a regression test for the // review of https://git.eeqj.de/sneak/secret/pulls/82: `secret init` or // `secret vault create` stopped at the passphrase prompt left a vault with // no unlocker, which neither command would then create again. Each must ask // for the passphrase before writing anything. // //nolint:paralleltest // t.Setenv forbids parallel subtests func TestStopAtPassphrasePromptLeavesNothing(t *testing.T) { t.Setenv(secret.EnvMnemonic, testMnemonic) // Without the passphrase in the environment, both commands prompt for // it, which fails because the tests do not run in a terminal. t.Setenv(secret.EnvUnlockPassphrase, "") // An empty state directory for `secret init`, and one holding the vault // "default" for `secret vault create work`. empty := afero.NewMemMapFs() require.NoError(t, empty.MkdirAll(testStateDir, secret.DirPerms)) withDefault := afero.NewMemMapFs() _, err := vault.CreateVault(withDefault, testStateDir, "default") require.NoError(t, err) cmd := &cobra.Command{} tests := []struct { command string fs afero.Fs run func(c *cli.Instance) error }{ { "init", empty, func(c *cli.Instance) error { return c.Init(cmd) }, }, { "vault create work", withDefault, func(c *cli.Instance) error { return c.CreateVault(cmd, "work") }, }, } for _, tt := range tests { t.Run(tt.command, func(t *testing.T) { before := snapshotStateDir(t, tt.fs) err := tt.run(cli.NewCLIInstanceWithStateDir(tt.fs, testStateDir)) require.ErrorContains(t, err, "failed to read passphrase") require.Equal(t, before, snapshotStateDir(t, tt.fs)) }) } }