diff --git a/.testcoverage.yml b/.testcoverage.yml index d3c4bd2..3e1bfb4 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -3,10 +3,8 @@ profile: cover.out threshold: file: 70 package: 80 - total: 80 + total: 85 exclude: paths: - - internal/style/style_handlers.go - main\.go$ - - cmd/main\.go$ diff --git a/cmd/config_test.go b/cmd/config_test.go index 9a7a934..b651cf7 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -12,7 +12,7 @@ import ( var emptyString = "" -func TestNewHTTPSWrenchConfig(t *testing.T) { +func TestConfig_NewHTTPSWrenchConfig(t *testing.T) { t.Run("new HTTPSWrenchConfig", func(t *testing.T) { var mc requests.RequestsMetaConfig diff --git a/cmd/man_test.go b/cmd/man_test.go new file mode 100644 index 0000000..2d19a23 --- /dev/null +++ b/cmd/man_test.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "bytes" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMan(t *testing.T) { + t.Run("Run manCmd", func(t *testing.T) { + destDir := t.TempDir() + manBuf := new(bytes.Buffer) + rootCmd.SetOut(manBuf) + rootCmd.SetErr(manBuf) + rootCmd.SetArgs([]string{"man", "--dest-dir", destDir}) + + err := rootCmd.Execute() + require.NoError(t, err) + + rootCmd.SetArgs([]string{"man", "--dest-dir", "fake-dir"}) + errWrongDir := rootCmd.Execute() + require.NoError(t, errWrongDir) + require.FileExists(t, filepath.Join(destDir, "https-wrench.1")) + // WARN stdout does not get into the buffer + // require.Contains(t, manBuf.String(), "no such file or directory--- FAIL") + }) +} diff --git a/cmd/root.go b/cmd/root.go index 6641747..72337e2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -80,11 +80,13 @@ https://github.com/xenOs76/https-wrench`, }, } -func Execute() { +func Execute() error { err := rootCmd.Execute() if err != nil { - return + return err } + + return nil } func init() { diff --git a/cmd/root_test.go b/cmd/root_test.go index afa7180..e7e69aa 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -2,13 +2,93 @@ package cmd import ( "bytes" + "crypto/x509" _ "embed" "testing" _ "github.com/breml/rootcerts" + "github.com/google/go-cmp/cmp" + "github.com/spf13/viper" "github.com/stretchr/testify/require" + "github.com/xenos76/https-wrench/internal/requests" ) +func TestRootCmd_LoadConfig(t *testing.T) { + t.Run("LoadConfig no config file", func(t *testing.T) { + oldCfg := cfgFile + + t.Cleanup(func() { + cfgFile = oldCfg + + viper.Reset() + }) + + var mc requests.RequestsMetaConfig + + config, err := LoadConfig() + require.NoError(t, err) + require.False(t, config.Debug) + require.False(t, config.Verbose) + require.Empty(t, config.CaBundle) + + if diff := cmp.Diff(mc, config.RequestsMetaConfig); diff != "" { + t.Errorf( + "NewHTTPSWrenchConfig: RequestsMetaConfig mismatch (-want +got):\n%s", + diff, + ) + } + }) + + t.Run("LoadConfig embedded config file", func(t *testing.T) { + oldCfg := cfgFile + + t.Cleanup(func() { + cfgFile = oldCfg + + viper.Reset() + }) + + var expectedCaCertsPool *x509.CertPool + + var expectedRequestsConfigs []requests.RequestConfig + + cfgFile = "./embedded/config-example.yaml" + + initConfig() + + config, err := LoadConfig() + require.NoError(t, err) + require.False(t, config.Debug) + require.True(t, config.Verbose) + require.Empty(t, config.CaBundle) + + // testing mapstructure squash/embedding of requests.RequestsMetaConfig + // into HTTPSWrenchConfig + require.False(t, config.RequestDebug) + require.False(t, config.RequestVerbose) + require.IsType(t, expectedCaCertsPool, config.CACertsPool) + require.IsType(t, expectedRequestsConfigs, config.Requests) + + // testing against the current values of the embedded config + require.Equal(t, "httpBunComGet", config.Requests[0].Name) + require.Equal(t, "https://cat.httpbun.com:443", config.Requests[0].TransportOverrideURL) + }) +} + +func TestRootCmd_Execute(t *testing.T) { + t.Run("Execute empty config", func(t *testing.T) { + cfgFile = "" + + initConfig() + + _, err := LoadConfig() + + require.NoError(t, err) + err = Execute() + require.EqualError(t, err, "flag needs an argument: --config") + }) +} + func TestRootCmd(t *testing.T) { tests := []struct { name string @@ -33,12 +113,6 @@ func TestRootCmd(t *testing.T) { }, }, - { - name: "config flag not arg", - args: []string{"--config"}, - expectError: true, - expected: []string{"flag needs an argument: --config"}, - }, { name: "config flag valid arg", args: []string{"--config", "./embedded/config-example.yaml"}, @@ -46,6 +120,14 @@ func TestRootCmd(t *testing.T) { // Unable to intercept the output expected: []string{}, }, + + { + name: "config flag not arg", + args: []string{"--config"}, + expectError: true, + expected: []string{"flag needs an argument: --config"}, + }, + { name: "version", args: []string{"--version"}, @@ -57,6 +139,14 @@ func TestRootCmd(t *testing.T) { for _, tc := range tests { tt := tc t.Run(tt.name, func(t *testing.T) { + oldCfg := cfgFile + + t.Cleanup(func() { + cfgFile = oldCfg + + viper.Reset() + }) + buf := new(bytes.Buffer) rootCmd.SetOut(buf) rootCmd.SetErr(buf) diff --git a/internal/style/style_handlers_test.go b/internal/style/style_handlers_test.go new file mode 100644 index 0000000..dc36d43 --- /dev/null +++ b/internal/style/style_handlers_test.go @@ -0,0 +1,234 @@ +package style + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func RSAGenerateKey(bits int) (*rsa.PrivateKey, error) { + priv, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, fmt.Errorf("failed to generate RSA private key: %w", err) + } + + return priv, nil +} + +func ECDSAGenerateKey(curve elliptic.Curve) (*ecdsa.PrivateKey, error) { + priv, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate ECDSA private key: %w", err) + } + + return priv, nil +} + +func ED25519GenerateKey() (ed25519.PrivateKey, error) { + _, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate ED25519 private key: %w", err) + } + + return priv, nil +} + +func TestPrintKeyInfoStyle(t *testing.T) { + rsaKey, rsaErr := RSAGenerateKey(2048) + require.NoError(t, rsaErr) + + ecdsaKey, ecdsaErr := ECDSAGenerateKey(elliptic.P256()) + require.NoError(t, ecdsaErr) + + ed25519Key, edErr := ED25519GenerateKey() + require.NoError(t, edErr) + + var fakeKey crypto.PrivateKey + + tests := []struct { + name string + key crypto.PrivateKey + expectedType string + }{ + { + name: "rsa private key", + key: rsaKey, + expectedType: "RSA", + }, + { + name: "ecdsa private key", + key: ecdsaKey, + expectedType: "ECDSA", + }, + + { + name: "ed25519 private key", + key: ed25519Key, + expectedType: "ED25519", + }, + + { + name: "fake private key", + key: fakeKey, + expectedType: "Unknown", + }, + } + for _, tc := range tests { + tt := tc + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var buffer bytes.Buffer + + PrintKeyInfoStyle(&buffer, tt.key) + + got := buffer.String() + + require.Contains(t, got, tt.expectedType) + }) + } +} + +func TestLgSprintf(t *testing.T) { + tests := []string{ + "a string", + } + for _, tc := range tests { + tt := tc + t.Run(tt, func(t *testing.T) { + t.Parallel() + + simple := LgSprintf(Status, "%s", tt) + require.Equal(t, tt, simple) + + pattern := LgSprintf(Status, "%s - lorem ipsum", tt) + require.Equal(t, tt+" - lorem ipsum", pattern) + }) + } +} + +func TestStatusCodeParse(t *testing.T) { + tests := []struct { + statusInt int + statusString string + }{ + {0, "0"}, + {200, "200"}, + {300, "300"}, + {400, "400"}, + {500, "500"}, + } + for _, tc := range tests { + tt := tc + t.Run(tt.statusString, func(t *testing.T) { + t.Parallel() + + s := StatusCodeParse(tt.statusInt) + require.Equal(t, tt.statusString, s) + }) + } +} + +func TestBoolStyle(t *testing.T) { + tests := []struct { + statusBool bool + statusString string + }{ + {true, "true"}, + {false, "false"}, + } + for _, tc := range tests { + tt := tc + t.Run(tt.statusString, func(t *testing.T) { + t.Parallel() + + s := BoolStyle(tt.statusBool) + require.Equal(t, tt.statusString, s) + }) + } +} + +func TestCodeSyntaxHighlight(t *testing.T) { + tests := []struct { + lang string + code string + expect []string + }{ + { + lang: "html", + code: ` + +
+