Print an empty unlocker or vault list as [] in JSON (closes #148)
check / check (push) Waiting to run
check / check (push) Waiting to run
`secret unlocker list --json` printed "unlockers": null for a vault with no unlockers, and `secret vault list --json` printed "vaults": null when vaults.d held no vaults. Both lists were nil slices, which encoding/json writes as null. Each is now made as an empty slice before it is filled, so an empty list prints as []. vault.ListVaults therefore returns an empty slice instead of nil for an empty vaults.d; its other callers only range over the result or check its length. Model: opus-5-5
This commit was merged in pull request #178.
This commit is contained in:
@@ -314,7 +314,8 @@ func (cli *Instance) UnlockersList(jsonOutput bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var unlockers []UnlockerInfo
|
// Not nil, so that a vault with no unlockers prints [] in JSON, not null
|
||||||
|
unlockers := make([]UnlockerInfo, 0, len(unlockerMetadata))
|
||||||
|
|
||||||
for _, unlockerID := range slices.Sorted(maps.Keys(unlockerMetadata)) {
|
for _, unlockerID := range slices.Sorted(maps.Keys(unlockerMetadata)) {
|
||||||
metadata := unlockerMetadata[unlockerID]
|
metadata := unlockerMetadata[unlockerID]
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// Unlocker List Tests
|
// Unlocker List Tests
|
||||||
//
|
//
|
||||||
// Tests for `secret unlocker list` behavior when an unlocker's metadata
|
// Tests for `secret unlocker list`:
|
||||||
// cannot be read or used:
|
|
||||||
//
|
//
|
||||||
// - TestUnlockersListToleratesCorruptMetadata: one unlocker's corrupt
|
// - TestUnlockersListToleratesCorruptMetadata: one unlocker's corrupt
|
||||||
// metadata does not stop the others from being listed.
|
// metadata does not stop the others from being listed.
|
||||||
// - TestUnlockersListSkipsUnreadableMetadata: an unlocker whose metadata
|
// - TestUnlockersListSkipsUnreadableMetadata: an unlocker whose metadata
|
||||||
// file cannot be checked for or read is left out, and the other is
|
// file cannot be checked for or read is left out, and the other is
|
||||||
// still listed.
|
// still listed.
|
||||||
|
// - TestUnlockersListEmptyJSON: a vault with no unlockers lists them as
|
||||||
|
// an empty JSON list.
|
||||||
|
|
||||||
//nolint:testpackage // white-box test of unexported internals
|
//nolint:testpackage // white-box test of unexported internals
|
||||||
package cli
|
package cli
|
||||||
@@ -321,3 +322,36 @@ func TestUnlockersListSkipsUnreadableMetadata(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUnlockersListEmptyJSON is a regression test for
|
||||||
|
// https://git.eeqj.de/sneak/secret/issues/148: listing the unlockers of a
|
||||||
|
// vault that has none, as after its last unlocker is removed, printed
|
||||||
|
// "unlockers": null in JSON form instead of an empty list.
|
||||||
|
func TestUnlockersListEmptyJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
vaultDir := filepath.Join(listTestStateDir, "vaults.d", listTestVaultName)
|
||||||
|
|
||||||
|
require.NoError(t, afero.WriteFile(
|
||||||
|
fs, filepath.Join(listTestStateDir, "currentvault"),
|
||||||
|
[]byte(listTestVaultName), listTestFilePerm,
|
||||||
|
))
|
||||||
|
require.NoError(t, fs.MkdirAll(
|
||||||
|
filepath.Join(vaultDir, listTestUnlockersDirName), listTestDirPerm,
|
||||||
|
))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
cmd := &cobra.Command{}
|
||||||
|
cmd.SetOut(&buf)
|
||||||
|
|
||||||
|
instance := &Instance{fs: fs, stateDir: listTestStateDir, cmd: cmd}
|
||||||
|
require.NoError(t, instance.UnlockersList(true))
|
||||||
|
|
||||||
|
var decoded map[string]any
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal(buf.Bytes(), &decoded))
|
||||||
|
assert.Equal(t, []any{}, decoded["unlockers"],
|
||||||
|
"no unlockers must be printed as [], not %s", buf.String())
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"sneak.berlin/go/secret/internal/cli"
|
||||||
|
"sneak.berlin/go/secret/internal/secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestVaultListEmptyJSON is a regression test for
|
||||||
|
// https://git.eeqj.de/sneak/secret/issues/148: listing the vaults when
|
||||||
|
// vaults.d holds none printed "vaults": null in JSON form instead of an
|
||||||
|
// empty list.
|
||||||
|
func TestVaultListEmptyJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
require.NoError(t, fs.MkdirAll(
|
||||||
|
filepath.Join(testStateDir, "vaults.d"), secret.DirPerms,
|
||||||
|
))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
cmd := &cobra.Command{}
|
||||||
|
cmd.SetOut(&buf)
|
||||||
|
|
||||||
|
c := cli.NewCLIInstanceWithStateDir(fs, testStateDir)
|
||||||
|
require.NoError(t, c.ListVaults(cmd, true))
|
||||||
|
|
||||||
|
var decoded map[string]any
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal(buf.Bytes(), &decoded))
|
||||||
|
assert.Equal(t, []any{}, decoded["vaults"],
|
||||||
|
"no vaults must be printed as [], not %s", buf.String())
|
||||||
|
}
|
||||||
@@ -140,8 +140,9 @@ func ListVaults(fs afero.Fs, stateDir string) ([]string, error) {
|
|||||||
return nil, fmt.Errorf("failed to read vaults directory: %w", err)
|
return nil, fmt.Errorf("failed to read vaults directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract vault names
|
// Extract vault names. Not nil, so that no vaults prints [] in JSON,
|
||||||
var vaults []string
|
// not null.
|
||||||
|
vaults := make([]string, 0, len(entries))
|
||||||
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
|
|||||||
Reference in New Issue
Block a user