From 854a60e768c7717a88d93581bc1b900d945ab08f Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 17 Jan 2026 22:20:51 +0100 Subject: [PATCH 1/3] ci: complete tests add more tests for cmd add test for style handlers add test for main.go update go-testcoverag config --- .testcoverage.yml | 4 +- cmd/config_test.go | 2 +- cmd/man_test.go | 27 +++ cmd/root.go | 6 +- cmd/root_test.go | 104 +++++++++--- internal/style/style_handlers_test.go | 234 ++++++++++++++++++++++++++ main.go | 18 +- main_test.go | 14 ++ 8 files changed, 381 insertions(+), 28 deletions(-) create mode 100644 cmd/man_test.go create mode 100644 internal/style/style_handlers_test.go create mode 100644 main_test.go 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..bd0ef6b --- /dev/null +++ b/cmd/man_test.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "bytes" + "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) + // 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..39ff6d3 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -2,13 +2,75 @@ package cmd import ( "bytes" + "crypto/x509" _ "embed" "testing" _ "github.com/breml/rootcerts" + "github.com/google/go-cmp/cmp" "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) { + 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) { + 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) + Execute() + }) +} + func TestRootCmd(t *testing.T) { tests := []struct { name string @@ -16,21 +78,29 @@ func TestRootCmd(t *testing.T) { expectError bool expected []string }{ + // { + // name: "no args", + // args: []string{}, + // expectError: false, + // expected: []string{ + // "HTTPS Wrench", + // "Usage:", + // "Available Commands:", + // "Flags:", + // "certinfo", + // "requests", + // "--config", + // "--version", + // "--help", + // }, + // }, + // { - name: "no args", - args: []string{}, + name: "config flag valid arg", + args: []string{"--config", "./embedded/config-example.yaml"}, expectError: false, - expected: []string{ - "HTTPS Wrench", - "Usage:", - "Available Commands:", - "Flags:", - "certinfo", - "requests", - "--config", - "--version", - "--help", - }, + // Unable to intercept the output + expected: []string{}, }, { @@ -39,13 +109,7 @@ func TestRootCmd(t *testing.T) { expectError: true, expected: []string{"flag needs an argument: --config"}, }, - { - name: "config flag valid arg", - args: []string{"--config", "./embedded/config-example.yaml"}, - expectError: false, - // Unable to intercept the output - expected: []string{}, - }, + { name: "version", args: []string{"--version"}, diff --git a/internal/style/style_handlers_test.go b/internal/style/style_handlers_test.go new file mode 100644 index 0000000..4319482 --- /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: "ed21219 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: ` + + + Div Align Attribute + + +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua. +
+ + + `, + expect: []string{ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "sed do eiusmod tempor incididunt ut", + }, + }, + { + lang: "yaml", + code: ` + --- + # yaml document beginning + # comment syntax + + name: John Doe + age: 30 + + fruits: + - Apple + - Banana + - Cherry + + person: + name: John Doe + age: 30 + address: + street: '123 Main St' + city: Example City + `, + expect: []string{ + "123 Main St", + "John Doe", + "Apple", + "Banana", + }, + }, + + { + lang: "NoCode", + code: "test", + expect: []string{ + "test", + }, + }, + } + for _, tc := range tests { + tt := tc + t.Run(tt.lang, func(t *testing.T) { + t.Parallel() + + s := CodeSyntaxHighlight(tt.lang, tt.code) + for _, expect := range tt.expect { + require.Contains(t, s, expect) + } + }) + } +} diff --git a/main.go b/main.go index e4ee2c1..eddd0ad 100644 --- a/main.go +++ b/main.go @@ -21,8 +21,22 @@ THE SOFTWARE. */ package main -import "github.com/xenos76/https-wrench/cmd" +import ( + "fmt" + "os" + + "github.com/xenos76/https-wrench/cmd" +) func main() { - cmd.Execute() + err := Run() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func Run() error { + err := cmd.Execute() + return err } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..5830fd8 --- /dev/null +++ b/main_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMain(t *testing.T) { + t.Run("Run", func(t *testing.T) { + err := Run() + require.NoError(t, err) + }) +} From 7a825315baef3dfbf3793d8036c6549adbf4c9d4 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 17 Jan 2026 22:55:32 +0100 Subject: [PATCH 2/3] test: add cleanup add cleanup function to tests in cmd/root --- cmd/root_test.go | 59 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/cmd/root_test.go b/cmd/root_test.go index 39ff6d3..27126f8 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -8,12 +8,21 @@ import ( _ "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() @@ -31,6 +40,14 @@ func TestRootCmd_LoadConfig(t *testing.T) { }) 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 @@ -78,23 +95,23 @@ func TestRootCmd(t *testing.T) { expectError bool expected []string }{ - // { - // name: "no args", - // args: []string{}, - // expectError: false, - // expected: []string{ - // "HTTPS Wrench", - // "Usage:", - // "Available Commands:", - // "Flags:", - // "certinfo", - // "requests", - // "--config", - // "--version", - // "--help", - // }, - // }, - // + { + name: "no args", + args: []string{}, + expectError: false, + expected: []string{ + "HTTPS Wrench", + "Usage:", + "Available Commands:", + "Flags:", + "certinfo", + "requests", + "--config", + "--version", + "--help", + }, + }, + { name: "config flag valid arg", args: []string{"--config", "./embedded/config-example.yaml"}, @@ -121,6 +138,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) From 7a85b8923e75dc41ed324cc0afbe4bcb008165ae Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 17 Jan 2026 23:35:18 +0100 Subject: [PATCH 3/3] fix: tests fix error handling, typos, file checks --- cmd/man_test.go | 2 ++ cmd/root_test.go | 3 ++- internal/style/style_handlers_test.go | 2 +- main_test.go | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/man_test.go b/cmd/man_test.go index bd0ef6b..2d19a23 100644 --- a/cmd/man_test.go +++ b/cmd/man_test.go @@ -2,6 +2,7 @@ package cmd import ( "bytes" + "path/filepath" "testing" "github.com/stretchr/testify/require" @@ -21,6 +22,7 @@ func TestMan(t *testing.T) { 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_test.go b/cmd/root_test.go index 27126f8..e7e69aa 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -84,7 +84,8 @@ func TestRootCmd_Execute(t *testing.T) { _, err := LoadConfig() require.NoError(t, err) - Execute() + err = Execute() + require.EqualError(t, err, "flag needs an argument: --config") }) } diff --git a/internal/style/style_handlers_test.go b/internal/style/style_handlers_test.go index 4319482..dc36d43 100644 --- a/internal/style/style_handlers_test.go +++ b/internal/style/style_handlers_test.go @@ -70,7 +70,7 @@ func TestPrintKeyInfoStyle(t *testing.T) { }, { - name: "ed21219 private key", + name: "ed25519 private key", key: ed25519Key, expectedType: "ED25519", }, diff --git a/main_test.go b/main_test.go index 5830fd8..e8ca664 100644 --- a/main_test.go +++ b/main_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestMain(t *testing.T) { +func TestRun(t *testing.T) { t.Run("Run", func(t *testing.T) { err := Run() require.NoError(t, err)