Check errors by identity, not by message text, in tests (closes #49)
check / check (push) Failing after 1s
check / check (push) Failing after 1s
Tests that asserted a failure by a fragment of its message now use errors.Is: a refactor returning the wrong error, or wrapping with %v instead of %w, now fails them. New tests return each exported error of internal/vault and pkg/bip85 that no test returned, and check wrapped causes (os.ErrNotExist, ErrMnemonicMismatch through GetSecret, ErrInvalidPathComponent through DeriveBIP85Entropy). The 999-versions test moves into package secret to name its unexported error. Checks of errors no test can name keep their text; they are listed on the issue. Model: opus-5-5
This commit was merged in pull request #112.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package vault_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"github.com/awnumar/memguard"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
// otherMnemonic is a valid BIP39 mnemonic other than testMnemonic.
|
||||
otherMnemonic = "legal winner thank year wave sausage worth useful " +
|
||||
"legal winner thank yellow"
|
||||
|
||||
// missingName names no vault, secret or unlocker.
|
||||
missingName = "missing"
|
||||
)
|
||||
|
||||
// newErrorTestVault creates the vault testVaultName, with the secret
|
||||
// testSecretName in it, on a new in-memory filesystem.
|
||||
func newErrorTestVault(t *testing.T) *vault.Vault {
|
||||
t.Helper()
|
||||
|
||||
vlt, err := vault.CreateVault(afero.NewMemMapFs(), testStateDir,
|
||||
testVaultName, testMnemonicBuffer(t), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
value := memguard.NewBufferFromBytes([]byte("value"))
|
||||
t.Cleanup(value.Destroy)
|
||||
|
||||
require.NoError(t, vlt.AddSecret(testSecretName, value, false))
|
||||
|
||||
return vlt
|
||||
}
|
||||
|
||||
// TestVaultErrors checks that each failure returns its exported error,
|
||||
// wrapped or not, so that errors.Is tells it apart from the others.
|
||||
func TestVaultErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
vaultDir := filepath.Join(testStateDir, "vaults.d", testVaultName)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
run func(vlt *vault.Vault) error
|
||||
want error
|
||||
}{
|
||||
{"create an existing vault", func(vlt *vault.Vault) error {
|
||||
_, err := vault.CreateVault(vlt.GetFilesystem(), testStateDir,
|
||||
testVaultName, nil, nil)
|
||||
|
||||
return err
|
||||
}, vault.ErrVaultExists},
|
||||
{"select a missing vault", func(vlt *vault.Vault) error {
|
||||
return vault.SelectVault(vlt.GetFilesystem(), testStateDir, missingName)
|
||||
}, vault.ErrVaultNotFound},
|
||||
{"add a nil value", func(vlt *vault.Vault) error {
|
||||
return vlt.AddSecret(missingName, nil, false)
|
||||
}, vault.ErrNilValueBuffer},
|
||||
{"get a missing secret", func(vlt *vault.Vault) error {
|
||||
_, err := vlt.GetSecret(missingName)
|
||||
|
||||
return err
|
||||
}, vault.ErrSecretNotFound},
|
||||
{"copy onto an existing secret", func(vlt *vault.Vault) error {
|
||||
return vlt.CopySecretAllVersions(vlt, testSecretName, testSecretName, false)
|
||||
}, vault.ErrSecretExists},
|
||||
{"copy a secret without versions", func(vlt *vault.Vault) error {
|
||||
const versionless = "versionless"
|
||||
|
||||
err := vlt.GetFilesystem().MkdirAll(
|
||||
filepath.Join(vaultDir, "secrets.d", versionless), secret.DirPerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return vlt.CopySecretAllVersions(vlt, versionless, "copy", false)
|
||||
}, vault.ErrNoVersions},
|
||||
{"remove a missing unlocker", func(vlt *vault.Vault) error {
|
||||
return vlt.RemoveUnlocker(missingName)
|
||||
}, vault.ErrUnlockerNotFound},
|
||||
{"select a missing unlocker", func(vlt *vault.Vault) error {
|
||||
return vlt.SelectUnlocker(missingName)
|
||||
}, vault.ErrUnlockerNotFound},
|
||||
{"unlocker of an unknown type", func(vlt *vault.Vault) error {
|
||||
fs := vlt.GetFilesystem()
|
||||
|
||||
err := afero.WriteFile(fs,
|
||||
filepath.Join(vaultDir, "unlockers.d", "odd", "unlocker-metadata.json"),
|
||||
[]byte(`{"type":"odd"}`), secret.FilePerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = afero.WriteFile(fs, filepath.Join(vaultDir, "current-unlocker"),
|
||||
[]byte("odd"), secret.FilePerms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = vlt.GetCurrentUnlocker()
|
||||
|
||||
return err
|
||||
}, vault.ErrUnsupportedUnlockerType},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.ErrorIs(t, tt.run(newErrorTestVault(t)), tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetSecretWithWrongMnemonic checks that getting a secret that exists,
|
||||
// from a vault the given mnemonic does not open, fails with
|
||||
// ErrMnemonicMismatch through GetSecret's wrapping, and not with
|
||||
// ErrSecretNotFound.
|
||||
func TestGetSecretWithWrongMnemonic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
created := newErrorTestVault(t)
|
||||
|
||||
mnemonic := memguard.NewBufferFromBytes([]byte(otherMnemonic))
|
||||
t.Cleanup(mnemonic.Destroy)
|
||||
|
||||
vlt := vault.NewVault(created.GetFilesystem(), testStateDir, testVaultName)
|
||||
vlt.SetMnemonic(mnemonic)
|
||||
|
||||
_, err := vlt.GetSecret(testSecretName)
|
||||
require.ErrorIs(t, err, vault.ErrMnemonicMismatch)
|
||||
require.NotErrorIs(t, err, vault.ErrSecretNotFound)
|
||||
}
|
||||
Reference in New Issue
Block a user