package cli_test import ( "bytes" "errors" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/x/internal/cli" "sneak.berlin/go/x/internal/pwgen" ) const testVersion = "v1.2.3-test" func TestVersionCommand(t *testing.T) { t.Parallel() output, err := execute(t, "version") require.NoError(t, err) assert.Equal(t, testVersion+"\n", output) } func TestVersionFlag(t *testing.T) { t.Parallel() output, err := execute(t, "--version") require.NoError(t, err) assert.Contains(t, output, testVersion) } func TestInfoCommand(t *testing.T) { t.Parallel() output, err := execute(t, "info") require.NoError(t, err) assert.Contains(t, output, "version: "+testVersion+"\n") assert.Contains(t, output, "go: go") assert.Contains(t, output, "module: sneak.berlin/go/x\n") assert.Contains(t, output, "config: (none)\n") } func TestConfigFileDiscoveredInConfigDir(t *testing.T) { t.Parallel() configDir := t.TempDir() path := filepath.Join(configDir, ".sneak.yaml") require.NoError(t, os.WriteFile(path, []byte("example: 1\n"), 0o600)) params := cli.Params{Version: testVersion, ConfigDir: configDir} output, err := executeWithParams(t, params, "info") require.NoError(t, err) assert.Contains(t, output, "Using config file: "+path+"\n") assert.Contains(t, output, "config: "+path+"\n") } func TestConfigFlagLoadsNamedFile(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "elsewhere.yaml") require.NoError(t, os.WriteFile(path, []byte("example: 1\n"), 0o600)) output, err := execute(t, "--config", path, "info") require.NoError(t, err) assert.Contains(t, output, "Using config file: "+path+"\n") assert.Contains(t, output, "config: "+path+"\n") } func TestConfigFlagRejectsMissingFile(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "missing.yaml") _, err := execute(t, "--config", path, "version") require.Error(t, err) } func TestConfigRejectsMalformedFile(t *testing.T) { t.Parallel() configDir := t.TempDir() path := filepath.Join(configDir, ".sneak.yaml") require.NoError(t, os.WriteFile(path, []byte("not: [valid\n"), 0o600)) params := cli.Params{Version: testVersion, ConfigDir: configDir} _, err := executeWithParams(t, params, "version") require.Error(t, err) } func TestGenerateRandomSeededIsDeterministic(t *testing.T) { t.Parallel() first, err := execute(t, "generate", "random", "--count", "4096", "seed") require.NoError(t, err) assert.Len(t, first, 4096) second, err := execute(t, "generate", "random", "--count", "4096", "seed") require.NoError(t, err) assert.Equal(t, first, second) other, err := execute(t, "gen", "random", "-c", "4096", "other seed") require.NoError(t, err) assert.NotEqual(t, first, other) } func TestGenerateRandomUnseeded(t *testing.T) { t.Parallel() first, err := execute(t, "generate", "random", "--count", "1024") require.NoError(t, err) assert.Len(t, first, 1024) second, err := execute(t, "generate", "random", "--count", "1024") require.NoError(t, err) assert.NotEqual(t, first, second) } func TestGenerateRandomRejectsExtraArguments(t *testing.T) { t.Parallel() _, err := execute(t, "generate", "random", "one", "two") require.Error(t, err) } func TestGeneratePassword(t *testing.T) { t.Parallel() _, err := pwgen.New(pwgen.Params{Words: nil, MinWordLength: 0, MaxWordLength: 0}) if errors.Is(err, pwgen.ErrNoUsableWords) { t.Skip("this system has no word list") } require.NoError(t, err) output, err := execute(t, "generate", "password") require.NoError(t, err) password := strings.TrimSuffix(output, "\n") assert.NotContains(t, password, "\n") assert.GreaterOrEqual(t, len(password), 3*(pwgen.DefaultMinWordLength+1)+2) assert.True(t, strings.ContainsAny(password, "0123456789"), password) assert.True(t, strings.ContainsAny(password, pwgen.Delimiters), password) } func TestUnknownCommandFails(t *testing.T) { t.Parallel() _, err := execute(t, "cbk") require.Error(t, err) } // execute runs sutil with the given arguments, looking for config in an // empty temporary directory, and returns everything it wrote to stdout and // stderr. func execute(t *testing.T, args ...string) (string, error) { t.Helper() params := cli.Params{Version: testVersion, ConfigDir: t.TempDir()} return executeWithParams(t, params, args...) } // executeWithParams is execute with explicit Params. func executeWithParams( t *testing.T, params cli.Params, args ...string, ) (string, error) { t.Helper() root := cli.New(params) var output bytes.Buffer root.SetOut(&output) root.SetErr(&output) root.SetArgs(args) err := root.Execute() return output.String(), err }