fix(cli): tighten chain command validation
Some checks are pending
Security Scan / security (push) Waiting to run
Test / Test (push) Waiting to run

Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
Virgil 2026-04-04 20:22:48 +00:00
parent 41f2d52979
commit d6f31dbe57
4 changed files with 91 additions and 0 deletions

View file

@ -6,6 +6,8 @@
package blockchain
import (
"fmt"
"net"
"os"
"path/filepath"
@ -75,3 +77,16 @@ func ensureChainDataDirExists(dataDir string) error {
}
return nil
}
func validateChainOptions(dataDir, seed string) error {
if dataDir == "" {
return coreerr.E("validateChainOptions", "data dir is required", nil)
}
if seed == "" {
return coreerr.E("validateChainOptions", "seed is required", nil)
}
if _, _, err := net.SplitHostPort(seed); err != nil {
return coreerr.E("validateChainOptions", fmt.Sprintf("seed %q must be host:port", seed), err)
}
return nil
}

View file

@ -46,3 +46,68 @@ func TestAddChainCommands_Good_PersistentFlags(t *testing.T) {
assert.NotNil(t, chainCmd.PersistentFlags().Lookup("seed"))
assert.NotNil(t, chainCmd.PersistentFlags().Lookup("testnet"))
}
func TestValidateChainOptions_Good(t *testing.T) {
err := validateChainOptions("/tmp/lethean", "seed.example:36942")
require.NoError(t, err)
}
func TestValidateChainOptions_Bad(t *testing.T) {
tests := []struct {
name string
dataDir string
seed string
want string
}{
{name: "missing data dir", dataDir: "", seed: "seed.example:36942", want: "data dir is required"},
{name: "missing seed", dataDir: "/tmp/lethean", seed: "", want: "seed is required"},
{name: "malformed seed", dataDir: "/tmp/lethean", seed: "seed.example", want: "must be host:port"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateChainOptions(tt.dataDir, tt.seed)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.want)
})
}
}
func TestChainSyncCommand_BadMutuallyExclusiveFlags(t *testing.T) {
dataDir := t.TempDir()
seed := "seed.example:36942"
testnet := false
cmd := newChainSyncCommand(&dataDir, &seed, &testnet)
cmd.SetArgs([]string{"--daemon", "--stop"})
err := cmd.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "cannot be combined")
}
func TestChainSyncCommand_BadArgsRejected(t *testing.T) {
dataDir := t.TempDir()
seed := "seed.example:36942"
testnet := false
cmd := newChainSyncCommand(&dataDir, &seed, &testnet)
cmd.SetArgs([]string{"extra"})
err := cmd.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "unknown command")
}
func TestChainExplorerCommand_BadSeedRejected(t *testing.T) {
dataDir := t.TempDir()
seed := "bad-seed"
testnet := false
cmd := newChainExplorerCommand(&dataDir, &seed, &testnet)
cmd.SetArgs(nil)
err := cmd.Execute()
require.Error(t, err)
assert.Contains(t, err.Error(), "must be host:port")
}

View file

@ -34,6 +34,10 @@ func newChainExplorerCommand(dataDir, seed *string, testnet *bool) *cobra.Comman
Use: "explorer",
Short: "TUI block explorer",
Long: "Interactive terminal block explorer with live sync status.",
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
return validateChainOptions(*dataDir, *seed)
},
RunE: func(cmd *cobra.Command, args []string) error {
return runChainExplorer(*dataDir, *seed, *testnet)
},

View file

@ -42,6 +42,13 @@ func newChainSyncCommand(dataDir, seed *string, testnet *bool) *cobra.Command {
Use: "sync",
Short: "Headless P2P chain sync",
Long: "Sync the blockchain from P2P peers without the TUI explorer.",
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
if daemon && stop {
return coreerr.E("newChainSyncCommand", "flags --daemon and --stop cannot be combined", nil)
}
return validateChainOptions(*dataDir, *seed)
},
RunE: func(cmd *cobra.Command, args []string) error {
if stop {
return stopChainSyncDaemon(*dataDir)