From 282ec11dbca12e5e04491eab58fde6d2d37c776b Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Sun, 2 Nov 2025 16:42:56 +0530 Subject: [PATCH 1/7] feat: add fuzz testing for CLI --- Makefile | 40 ++++++++++++++++- e2e/fuzz_test.go | 115 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 e2e/fuzz_test.go diff --git a/Makefile b/Makefile index 9418059..9cc6403 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ LD_FLAGS := -X github.com/getoptimum/mump2p-cli/internal/config.Domain=$(DOMAIN) -X github.com/getoptimum/mump2p-cli/internal/config.Version=$(VERSION) \ -X github.com/getoptimum/mump2p-cli/internal/config.CommitHash=$(COMMIT_HASH) -.PHONY: all build run clean test help lint build tag release print-cli-name e2e-test e2e-quick +.PHONY: all build run clean test help lint build tag release print-cli-name e2e-test e2e-quick e2e-fuzz e2e-fuzz-quick all: lint build @@ -82,5 +82,43 @@ e2e-quick: ## Run quick smoke tests only @echo "Running quick smoke tests..." go test ./e2e -v -run TestCLISmokeCommands -timeout 2m +e2e-fuzz: ## Run fuzz tests against dist/ binary + @echo "Running fuzz tests..." + @if [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-linux" ] && [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-mac" ]; then \ + echo "Error: No binary found in $(BUILD_DIR)/"; \ + echo "Run 'make build' first with release credentials"; \ + exit 1; \ + fi + @echo "Fuzzing publish topic names..." + @go test ./e2e -run='^$$' -fuzz=FuzzPublishTopicName -fuzztime=1m -timeout=3m + @echo "Fuzzing publish messages..." + @go test ./e2e -run='^$$' -fuzz=FuzzPublishMessage -fuzztime=1m -timeout=3m + @echo "Fuzzing service URLs..." + @go test ./e2e -run='^$$' -fuzz=FuzzServiceURL -fuzztime=1m -timeout=3m + @echo "Fuzzing subscribe topic names..." + @go test ./e2e -run='^$$' -fuzz=FuzzSubscribeTopicName -fuzztime=1m -timeout=3m + @echo "Fuzzing file paths..." + @go test ./e2e -run='^$$' -fuzz=FuzzFilePath -fuzztime=1m -timeout=3m + @echo "✅ All fuzz tests passed!" + +e2e-fuzz-quick: ## Run quick fuzz tests + @echo "Running quick fuzz tests..." + @if [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-linux" ] && [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-mac" ]; then \ + echo "Error: No binary found in $(BUILD_DIR)/"; \ + echo "Run 'make build' first with release credentials"; \ + exit 1; \ + fi + @echo "Fuzzing publish topic names..." + @go test ./e2e -run='^$$' -fuzz=FuzzPublishTopicName -fuzztime=20s -timeout=1m + @echo "Fuzzing publish messages..." + @go test ./e2e -run='^$$' -fuzz=FuzzPublishMessage -fuzztime=20s -timeout=1m + @echo "Fuzzing service URLs..." + @go test ./e2e -run='^$$' -fuzz=FuzzServiceURL -fuzztime=20s -timeout=1m + @echo "Fuzzing subscribe topic names..." + @go test ./e2e -run='^$$' -fuzz=FuzzSubscribeTopicName -fuzztime=20s -timeout=1m + @echo "Fuzzing file paths..." + @go test ./e2e -run='^$$' -fuzz=FuzzFilePath -fuzztime=20s -timeout=1m + @echo "✅ All fuzz tests passed!" + help: ## Show help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' \ No newline at end of file diff --git a/e2e/fuzz_test.go b/e2e/fuzz_test.go new file mode 100644 index 0000000..6ce5d90 --- /dev/null +++ b/e2e/fuzz_test.go @@ -0,0 +1,115 @@ +package main + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +// FuzzPublishTopicName tests the publish command with a topic name + +func FuzzPublishTopicName(f *testing.F) { + require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") + + f.Add("") + f.Add("\x00") + f.Add("../../../etc/passwd") + f.Add(strings.Repeat("a", 1000)) + f.Add("test\n\r\t") + f.Add("test'; DROP TABLE") + f.Add("${HOME}") + + f.Fuzz(func(t *testing.T, topic string) { + if len(topic) > 5000 { + t.Skip() + } + + args := []string{"publish", "--topic=" + topic, "--message=test"} + _, _ = RunCommand(cliBinaryPath, args...) + }) +} + +// FuzzPublishMessage tests the publish command with a message + +func FuzzPublishMessage(f *testing.F) { + require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") + + f.Add("") + f.Add("\x00") + f.Add(strings.Repeat("a", 10000)) + f.Add("{\"test\": \"value\"}") + f.Add("test") + + f.Fuzz(func(t *testing.T, message string) { + if len(message) > 50000 { + t.Skip() + } + + args := []string{"publish", "--topic=fuzz-test", "--message=" + message} + _, _ = RunCommand(cliBinaryPath, args...) + }) +} + +// FuzzServiceURL tests the health command with a service URL + +func FuzzServiceURL(f *testing.F) { + require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") + + f.Add("not-a-url") + f.Add("://broken") + f.Add("http://") + f.Add("http://localhost:-8080") + f.Add("http://localhost:99999") + f.Add("javascript:alert(1)") + + f.Fuzz(func(t *testing.T, url string) { + if len(url) > 1000 { + t.Skip() + } + + args := []string{"health", "--service-url=" + url} + _, _ = RunCommand(cliBinaryPath, args...) + }) +} + +// FuzzSubscribeTopicName tests the subscribe command with a topic name +func FuzzSubscribeTopicName(f *testing.F) { + require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") + + f.Add("") + f.Add("\x00") + f.Add("../../../etc/passwd") + f.Add(strings.Repeat("a", 1000)) + f.Add("test\n\r\t") + + f.Fuzz(func(t *testing.T, topic string) { + if len(topic) > 5000 { + t.Skip() + } + + args := []string{"subscribe", "--topic=" + topic} + _, _ = RunCommand(cliBinaryPath, args...) + }) +} + +// FuzzFilePath tests the publish command with a file path +func FuzzFilePath(f *testing.F) { + require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") + + f.Add("") + f.Add("nonexistent.txt") + f.Add("../../../etc/passwd") + f.Add("/dev/null") + f.Add("test\x00file.txt") + f.Add(strings.Repeat("a", 500) + ".txt") + + f.Fuzz(func(t *testing.T, filepath string) { + if len(filepath) > 2000 { + t.Skip() + } + + args := []string{"publish", "--topic=fuzz-test", "--file=" + filepath} + _, _ = RunCommand(cliBinaryPath, args...) + }) +} From 02c7fad12301e3f2bef3ef31b95193eb2eae3069 Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Mon, 3 Nov 2025 19:29:22 +0530 Subject: [PATCH 2/7] fix --- .github/workflows/release.yml | 5 +++ Makefile | 6 +--- e2e/fuzz_test.go | 65 +++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7fe16d..fc302d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,6 +47,11 @@ jobs: MUMP2P_E2E_TOKEN_B64: ${{ secrets.MUMP2P_E2E_TOKEN_B64 }} run: make e2e-test + - name: Run Fuzz Tests + env: + MUMP2P_E2E_TOKEN_B64: ${{ secrets.MUMP2P_E2E_TOKEN_B64 }} + run: make e2e-fuzz-quick + - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: diff --git a/Makefile b/Makefile index 9cc6403..dbdbdc9 100644 --- a/Makefile +++ b/Makefile @@ -95,8 +95,6 @@ e2e-fuzz: ## Run fuzz tests against dist/ binary @go test ./e2e -run='^$$' -fuzz=FuzzPublishMessage -fuzztime=1m -timeout=3m @echo "Fuzzing service URLs..." @go test ./e2e -run='^$$' -fuzz=FuzzServiceURL -fuzztime=1m -timeout=3m - @echo "Fuzzing subscribe topic names..." - @go test ./e2e -run='^$$' -fuzz=FuzzSubscribeTopicName -fuzztime=1m -timeout=3m @echo "Fuzzing file paths..." @go test ./e2e -run='^$$' -fuzz=FuzzFilePath -fuzztime=1m -timeout=3m @echo "✅ All fuzz tests passed!" @@ -114,11 +112,9 @@ e2e-fuzz-quick: ## Run quick fuzz tests @go test ./e2e -run='^$$' -fuzz=FuzzPublishMessage -fuzztime=20s -timeout=1m @echo "Fuzzing service URLs..." @go test ./e2e -run='^$$' -fuzz=FuzzServiceURL -fuzztime=20s -timeout=1m - @echo "Fuzzing subscribe topic names..." - @go test ./e2e -run='^$$' -fuzz=FuzzSubscribeTopicName -fuzztime=20s -timeout=1m @echo "Fuzzing file paths..." @go test ./e2e -run='^$$' -fuzz=FuzzFilePath -fuzztime=20s -timeout=1m @echo "✅ All fuzz tests passed!" help: ## Show help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' \ No newline at end of file + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/e2e/fuzz_test.go b/e2e/fuzz_test.go index 6ce5d90..75c0fd3 100644 --- a/e2e/fuzz_test.go +++ b/e2e/fuzz_test.go @@ -8,7 +8,6 @@ import ( ) // FuzzPublishTopicName tests the publish command with a topic name - func FuzzPublishTopicName(f *testing.F) { require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") @@ -25,13 +24,23 @@ func FuzzPublishTopicName(f *testing.F) { t.Skip() } + // For obviously invalid input, verify graceful error handling + if strings.Contains(topic, "\x00") { + args := []string{"publish", "--topic=" + topic, "--message=test"} + _, err := RunCommand(cliBinaryPath, args...) + require.Error(t, err, "CLI should reject null bytes in topic name") + return + } + args := []string{"publish", "--topic=" + topic, "--message=test"} - _, _ = RunCommand(cliBinaryPath, args...) + out, err := RunCommand(cliBinaryPath, args...) + if err != nil { + t.Logf("Command failed (expected for invalid input): %v\nOutput: %s", err, out) + } }) } // FuzzPublishMessage tests the publish command with a message - func FuzzPublishMessage(f *testing.F) { require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") @@ -46,13 +55,23 @@ func FuzzPublishMessage(f *testing.F) { t.Skip() } + // For obviously invalid input, verify graceful error handling + if strings.Contains(message, "\x00") { + args := []string{"publish", "--topic=fuzz-test", "--message=" + message} + _, err := RunCommand(cliBinaryPath, args...) + require.Error(t, err, "CLI should reject null bytes in message") + return + } + args := []string{"publish", "--topic=fuzz-test", "--message=" + message} - _, _ = RunCommand(cliBinaryPath, args...) + out, err := RunCommand(cliBinaryPath, args...) + if err != nil { + t.Logf("Command failed (expected for invalid input): %v\nOutput: %s", err, out) + } }) } // FuzzServiceURL tests the health command with a service URL - func FuzzServiceURL(f *testing.F) { require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") @@ -69,27 +88,10 @@ func FuzzServiceURL(f *testing.F) { } args := []string{"health", "--service-url=" + url} - _, _ = RunCommand(cliBinaryPath, args...) - }) -} - -// FuzzSubscribeTopicName tests the subscribe command with a topic name -func FuzzSubscribeTopicName(f *testing.F) { - require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") - - f.Add("") - f.Add("\x00") - f.Add("../../../etc/passwd") - f.Add(strings.Repeat("a", 1000)) - f.Add("test\n\r\t") - - f.Fuzz(func(t *testing.T, topic string) { - if len(topic) > 5000 { - t.Skip() + out, err := RunCommand(cliBinaryPath, args...) + if err != nil { + t.Logf("Command failed (expected for invalid URL): %v\nOutput: %s", err, out) } - - args := []string{"subscribe", "--topic=" + topic} - _, _ = RunCommand(cliBinaryPath, args...) }) } @@ -109,7 +111,18 @@ func FuzzFilePath(f *testing.F) { t.Skip() } + // For obviously invalid input, verify graceful error handling + if strings.Contains(filepath, "\x00") { + args := []string{"publish", "--topic=fuzz-test", "--file=" + filepath} + _, err := RunCommand(cliBinaryPath, args...) + require.Error(t, err, "CLI should reject null bytes in file path") + return + } + args := []string{"publish", "--topic=fuzz-test", "--file=" + filepath} - _, _ = RunCommand(cliBinaryPath, args...) + out, err := RunCommand(cliBinaryPath, args...) + if err != nil { + t.Logf("Command failed (expected for invalid file path): %v\nOutput: %s", err, out) + } }) } From 2947c751df7f4581d51f24fa29b30023952b9c7b Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Thu, 18 Dec 2025 18:29:58 +0530 Subject: [PATCH 3/7] fix: address fuzz test review comments --- .github/workflows/ci.yml | 14 +++++++++ Makefile | 21 ++----------- e2e/fuzz_test.go | 67 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 79 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cffbe27..47c4f7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,3 +54,17 @@ jobs: # Optional: show only new issues if it's a pull request. The default value is `false`. only-new-issues: true working-directory: ./ + + fuzz: + name: fuzz tests + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.go-version }} + cache: true + - uses: actions/checkout@v4 + - name: Build CLI binary + run: make build + - name: Run fuzz tests + run: make e2e-fuzz diff --git a/Makefile b/Makefile index 08d7ec7..8694b00 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ LD_FLAGS := -X github.com/getoptimum/mump2p-cli/internal/config.Domain=$(DOMAIN) .DEFAULT_GOAL := help -.PHONY: all build run clean test help lint build tag release print-cli-name e2e-test e2e-quick e2e-fuzz e2e-fuzz-quick coverage +.PHONY: all build run clean test help lint build tag release print-cli-name e2e-test e2e-quick e2e-fuzz coverage all: lint build @@ -99,24 +99,7 @@ e2e-fuzz: ## Run fuzz tests against dist/ binary @go test ./e2e -run='^$$' -fuzz=FuzzServiceURL -fuzztime=1m -timeout=3m @echo "Fuzzing file paths..." @go test ./e2e -run='^$$' -fuzz=FuzzFilePath -fuzztime=1m -timeout=3m - @echo "✅ All fuzz tests passed!" - -e2e-fuzz-quick: ## Run quick fuzz tests - @echo "Running quick fuzz tests..." - @if [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-linux" ] && [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-mac" ]; then \ - echo "Error: No binary found in $(BUILD_DIR)/"; \ - echo "Run 'make build' first with release credentials"; \ - exit 1; \ - fi - @echo "Fuzzing publish topic names..." - @go test ./e2e -run='^$$' -fuzz=FuzzPublishTopicName -fuzztime=20s -timeout=1m - @echo "Fuzzing publish messages..." - @go test ./e2e -run='^$$' -fuzz=FuzzPublishMessage -fuzztime=20s -timeout=1m - @echo "Fuzzing service URLs..." - @go test ./e2e -run='^$$' -fuzz=FuzzServiceURL -fuzztime=20s -timeout=1m - @echo "Fuzzing file paths..." - @go test ./e2e -run='^$$' -fuzz=FuzzFilePath -fuzztime=20s -timeout=1m - @echo "✅ All fuzz tests passed!" + @echo "All fuzz tests passed!" help: ## Show help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/e2e/fuzz_test.go b/e2e/fuzz_test.go index 75c0fd3..c425279 100644 --- a/e2e/fuzz_test.go +++ b/e2e/fuzz_test.go @@ -27,20 +27,34 @@ func FuzzPublishTopicName(f *testing.F) { // For obviously invalid input, verify graceful error handling if strings.Contains(topic, "\x00") { args := []string{"publish", "--topic=" + topic, "--message=test"} - _, err := RunCommand(cliBinaryPath, args...) + out, err := RunCommand(cliBinaryPath, args...) require.Error(t, err, "CLI should reject null bytes in topic name") + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on topic with null byte: %v\nOutput: %s", err, out) + } return } args := []string{"publish", "--topic=" + topic, "--message=test"} out, err := RunCommand(cliBinaryPath, args...) + // For fuzzing, we need to detect failures - invalid topics should fail gracefully + // Valid topics might succeed (if subscribed) or fail (if not subscribed) + // The key is that the CLI should handle the input without panicking if err != nil { + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on topic %q: %v\nOutput: %s", topic, err, out) + } + // For invalid topics, errors are expected and acceptable t.Logf("Command failed (expected for invalid input): %v\nOutput: %s", err, out) } }) } -// FuzzPublishMessage tests the publish command with a message +// FuzzPublishMessage tests the publish command with a message. +// This is distinct from FuzzPublishTopicName which tests topic name validation; +// this test focuses on message content validation and handling. func FuzzPublishMessage(f *testing.F) { require.NotEmpty(f, cliBinaryPath, "CLI binary path must be set by TestMain") @@ -58,14 +72,26 @@ func FuzzPublishMessage(f *testing.F) { // For obviously invalid input, verify graceful error handling if strings.Contains(message, "\x00") { args := []string{"publish", "--topic=fuzz-test", "--message=" + message} - _, err := RunCommand(cliBinaryPath, args...) + out, err := RunCommand(cliBinaryPath, args...) require.Error(t, err, "CLI should reject null bytes in message") + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on message with null byte: %v\nOutput: %s", err, out) + } return } args := []string{"publish", "--topic=fuzz-test", "--message=" + message} out, err := RunCommand(cliBinaryPath, args...) + // For fuzzing, we need to detect failures - invalid messages should fail gracefully + // Valid messages might succeed (if topic is subscribed) or fail (if not subscribed) + // The key is that the CLI should handle the input without panicking if err != nil { + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on message %q: %v\nOutput: %s", message, err, out) + } + // For invalid messages, errors are expected and acceptable t.Logf("Command failed (expected for invalid input): %v\nOutput: %s", err, out) } }) @@ -81,15 +107,36 @@ func FuzzServiceURL(f *testing.F) { f.Add("http://localhost:-8080") f.Add("http://localhost:99999") f.Add("javascript:alert(1)") + f.Add("\x00") f.Fuzz(func(t *testing.T, url string) { if len(url) > 1000 { t.Skip() } + // For obviously invalid input, verify graceful error handling + if strings.Contains(url, "\x00") { + args := []string{"health", "--service-url=" + url} + out, err := RunCommand(cliBinaryPath, args...) + require.Error(t, err, "CLI should reject null bytes in service URL") + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on service URL with null byte: %v\nOutput: %s", err, out) + } + return + } + args := []string{"health", "--service-url=" + url} out, err := RunCommand(cliBinaryPath, args...) + // For fuzzing, we need to detect failures - invalid URLs should fail gracefully + // Valid URLs might succeed (if proxy is reachable) or fail (if not) + // The key is that the CLI should handle the input without panicking if err != nil { + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on URL %q: %v\nOutput: %s", url, err, out) + } + // For invalid URLs, errors are expected and acceptable t.Logf("Command failed (expected for invalid URL): %v\nOutput: %s", err, out) } }) @@ -114,14 +161,26 @@ func FuzzFilePath(f *testing.F) { // For obviously invalid input, verify graceful error handling if strings.Contains(filepath, "\x00") { args := []string{"publish", "--topic=fuzz-test", "--file=" + filepath} - _, err := RunCommand(cliBinaryPath, args...) + out, err := RunCommand(cliBinaryPath, args...) require.Error(t, err, "CLI should reject null bytes in file path") + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on file path with null byte: %v\nOutput: %s", err, out) + } return } args := []string{"publish", "--topic=fuzz-test", "--file=" + filepath} out, err := RunCommand(cliBinaryPath, args...) + // For fuzzing, we need to detect failures - invalid file paths should fail gracefully + // Valid file paths might succeed (if file exists and topic is subscribed) or fail (if not) + // The key is that the CLI should handle the input without panicking if err != nil { + // Verify error is handled gracefully (not a panic) + if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { + t.Fatalf("CLI panicked or crashed on file path %q: %v\nOutput: %s", filepath, err, out) + } + // For invalid file paths, errors are expected and acceptable t.Logf("Command failed (expected for invalid file path): %v\nOutput: %s", err, out) } }) From 9c3d517d72cf43ae6ca56a6ff232ca550437f3e7 Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Mon, 22 Dec 2025 09:10:39 +0400 Subject: [PATCH 4/7] fix: make token setup optional for fuzz tests --- e2e/setup.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/e2e/setup.go b/e2e/setup.go index e90a14d..478ac3b 100644 --- a/e2e/setup.go +++ b/e2e/setup.go @@ -9,14 +9,16 @@ import ( ) // PrepareCLI sets up the test environment and returns the CLI binary path +// Token setup is optional - if it fails, we continue without auth (useful for fuzz tests) func PrepareCLI() (cliPath string, cleanup func(), err error) { - tokenPath, err := SetupTokenFile() - if err != nil { - return "", nil, err - } - if err := os.Setenv("MUMP2P_AUTH_PATH", tokenPath); err != nil { - return "", nil, fmt.Errorf("failed to set MUMP2P_AUTH_PATH: %w", err) + tokenPath, tokenErr := SetupTokenFile() + if tokenErr == nil { + // Token setup succeeded, set it up + if err := os.Setenv("MUMP2P_AUTH_PATH", tokenPath); err != nil { + return "", nil, fmt.Errorf("failed to set MUMP2P_AUTH_PATH: %w", err) + } } + // If token setup failed, we continue without auth (for fuzz tests that don't need it) repoRoot, err := findRepoRoot() if err != nil { return "", nil, err @@ -46,7 +48,9 @@ func PrepareCLI() (cliPath string, cleanup func(), err error) { } cleanup = func() { - _ = os.RemoveAll(filepath.Dir(tokenPath)) + if tokenPath != "" { + _ = os.RemoveAll(filepath.Dir(tokenPath)) + } } return cli, cleanup, nil From 46164e110380cdfdfd5f8c4e97e32c172db187da Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Mon, 29 Dec 2025 10:36:49 +0530 Subject: [PATCH 5/7] fix --- Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 8694b00..e0adfe2 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ LD_FLAGS := -X github.com/getoptimum/mump2p-cli/internal/config.Domain=$(DOMAIN) .DEFAULT_GOAL := help -.PHONY: all build run clean test help lint build tag release print-cli-name e2e-test e2e-quick e2e-fuzz coverage +.PHONY: all build run clean test help lint build tag release print-cli-name e2e-test e2e-fuzz coverage all: lint build @@ -80,10 +80,6 @@ e2e-test: ## Run E2E tests against dist/ binary fi go test ./e2e -v -timeout 10m -e2e-quick: ## Run quick smoke tests only - @echo "Running quick smoke tests..." - go test ./e2e -v -run TestCLISmokeCommands -timeout 2m - e2e-fuzz: ## Run fuzz tests against dist/ binary @echo "Running fuzz tests..." @if [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-linux" ] && [ ! -f "$(BUILD_DIR)/$(CLI_NAME)-mac" ]; then \ From 47a1b9174688d9d2167b2667b2e83c6560912adc Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Mon, 29 Dec 2025 11:09:18 +0530 Subject: [PATCH 6/7] fix --- .github/workflows/release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc302d6..55b87e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,9 +48,7 @@ jobs: run: make e2e-test - name: Run Fuzz Tests - env: - MUMP2P_E2E_TOKEN_B64: ${{ secrets.MUMP2P_E2E_TOKEN_B64 }} - run: make e2e-fuzz-quick + run: make e2e-fuzz - name: Upload Build Artifacts uses: actions/upload-artifact@v4 From 4361ae8ae89daa2b579671dc11362d1dcfb57f6c Mon Sep 17 00:00:00 2001 From: swarnabhasinha Date: Tue, 27 Jan 2026 12:51:26 +0530 Subject: [PATCH 7/7] Improve fuzz test setup with timeouts and better error handling --- cmd/health.go | 5 ++++- cmd/tracer.go | 12 +++++++++--- e2e/cli_runner.go | 23 ++++++++++++++++++++++- e2e/fuzz_test.go | 8 ++++---- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/cmd/health.go b/cmd/health.go index 45e8c3b..4b07c3a 100644 --- a/cmd/health.go +++ b/cmd/health.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "time" "github.com/getoptimum/mump2p-cli/internal/config" "github.com/getoptimum/mump2p-cli/internal/formatter" @@ -42,7 +43,9 @@ var healthCmd = &cobra.Command{ return fmt.Errorf("failed to create request: %v", err) } - resp, err := http.DefaultClient.Do(req) + // Use HTTP client with timeout to prevent hanging during fuzzing + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) if err != nil { return fmt.Errorf("health check failed: %v", err) } diff --git a/cmd/tracer.go b/cmd/tracer.go index 59ab38b..e830d2c 100644 --- a/cmd/tracer.go +++ b/cmd/tracer.go @@ -592,7 +592,9 @@ func resetStats(ctx context.Context, base, jwt string) error { if jwt != "" { req.Header.Set("Authorization", "Bearer "+jwt) } - resp, err := http.DefaultClient.Do(req) + // Use HTTP client with timeout to prevent hanging during fuzzing + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) if err != nil { return err } @@ -677,7 +679,9 @@ func proxyPublishRandom(base, jwt, clientID, topic string, length uint64) error if !IsAuthDisabled() && jwt != "" { req.Header.Set("Authorization", "Bearer "+jwt) } - resp, err := http.DefaultClient.Do(req) + // Use HTTP client with timeout to prevent hanging during fuzzing + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) if err != nil { return err } @@ -708,7 +712,9 @@ func proxySubscribe(base, jwt, topic, clientID string, threshold int) error { if !IsAuthDisabled() && jwt != "" { req.Header.Set("Authorization", "Bearer "+jwt) } - resp, err := http.DefaultClient.Do(req) + // Use HTTP client with timeout to prevent hanging during fuzzing + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) if err != nil { return err } diff --git a/e2e/cli_runner.go b/e2e/cli_runner.go index a354699..0cd0156 100644 --- a/e2e/cli_runner.go +++ b/e2e/cli_runner.go @@ -2,22 +2,43 @@ package main import ( "bytes" + "context" "os" "os/exec" + "time" +) + +const ( + // DefaultCommandTimeout is the timeout for CLI commands in fuzz tests + // This prevents hanging when fuzzing creates valid URLs that don't respond + DefaultCommandTimeout = 10 * time.Second ) // RunCommand executes the CLI binary with given arguments and returns output +// It uses a timeout to prevent hanging during fuzz tests func RunCommand(bin string, args ...string) (string, error) { + return RunCommandWithTimeout(bin, DefaultCommandTimeout, args...) +} + +// RunCommandWithTimeout executes the CLI binary with a specific timeout +func RunCommandWithTimeout(bin string, timeout time.Duration, args ...string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + var out bytes.Buffer var stderr bytes.Buffer - cmd := exec.Command(bin, args...) + cmd := exec.CommandContext(ctx, bin, args...) cmd.Env = os.Environ() cmd.Stdout = &out cmd.Stderr = &stderr err := cmd.Run() if err != nil { + // Check if timeout was exceeded + if ctx.Err() == context.DeadlineExceeded { + return stderr.String(), context.DeadlineExceeded + } return stderr.String(), err } return out.String(), nil diff --git a/e2e/fuzz_test.go b/e2e/fuzz_test.go index c425279..384c18f 100644 --- a/e2e/fuzz_test.go +++ b/e2e/fuzz_test.go @@ -42,7 +42,7 @@ func FuzzPublishTopicName(f *testing.F) { // Valid topics might succeed (if subscribed) or fail (if not subscribed) // The key is that the CLI should handle the input without panicking if err != nil { - // Verify error is handled gracefully (not a panic) + // Any error should be handled gracefully (not a panic or crash) if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { t.Fatalf("CLI panicked or crashed on topic %q: %v\nOutput: %s", topic, err, out) } @@ -87,7 +87,7 @@ func FuzzPublishMessage(f *testing.F) { // Valid messages might succeed (if topic is subscribed) or fail (if not subscribed) // The key is that the CLI should handle the input without panicking if err != nil { - // Verify error is handled gracefully (not a panic) + // Any error should be handled gracefully (not a panic or crash) if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { t.Fatalf("CLI panicked or crashed on message %q: %v\nOutput: %s", message, err, out) } @@ -132,7 +132,7 @@ func FuzzServiceURL(f *testing.F) { // Valid URLs might succeed (if proxy is reachable) or fail (if not) // The key is that the CLI should handle the input without panicking if err != nil { - // Verify error is handled gracefully (not a panic) + // Any error should be handled gracefully (not a panic or crash) if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { t.Fatalf("CLI panicked or crashed on URL %q: %v\nOutput: %s", url, err, out) } @@ -176,7 +176,7 @@ func FuzzFilePath(f *testing.F) { // Valid file paths might succeed (if file exists and topic is subscribed) or fail (if not) // The key is that the CLI should handle the input without panicking if err != nil { - // Verify error is handled gracefully (not a panic) + // Any error should be handled gracefully (not a panic or crash) if strings.Contains(out, "panic") || strings.Contains(out, "fatal") { t.Fatalf("CLI panicked or crashed on file path %q: %v\nOutput: %s", filepath, err, out) }