From 9d608c3184aec11066cd771f5e21c03fb64bb119 Mon Sep 17 00:00:00 2001 From: Eshaan Negi <11eshaansinghnegi@gmail.com> Date: Thu, 26 Mar 2026 10:19:40 +0530 Subject: [PATCH 1/5] feat: add support for reading request body from file (-D flag) --- cmd/config.go | 13 +++++++++++++ cmd/configfile.go | 1 + cmd/run.go | 3 +++ 3 files changed, 17 insertions(+) diff --git a/cmd/config.go b/cmd/config.go index 4c194ae..70e785b 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "net/url" + "os" "sort" "strings" "time" @@ -30,6 +31,7 @@ type RunConfig struct { Duration time.Duration Method string Body string + BodyFile string Headers []string Verbose bool } @@ -58,6 +60,17 @@ func (c *RunConfig) Validate() error { } c.ParsedTarget = u + if c.BodyFile != "" { + if c.Body != "" { + return fmt.Errorf("cannot use both -b (body string) and -D (body file)") + } + data, err := os.ReadFile(c.BodyFile) + if err != nil { + return fmt.Errorf("failed to read body file %q: %w", c.BodyFile, err) + } + c.Body = string(data) + } + if c.Concurrency <= 0 { return fmt.Errorf("concurrency must be positive, got %d", c.Concurrency) } diff --git a/cmd/configfile.go b/cmd/configfile.go index af90101..d8e5d1a 100644 --- a/cmd/configfile.go +++ b/cmd/configfile.go @@ -20,6 +20,7 @@ type fileConfig struct { Duration *string `json:"duration" yaml:"duration"` Method *string `json:"method" yaml:"method"` Body *string `json:"body" yaml:"body"` + BodyFile *string `json:"body_file" yaml:"body_file"` Headers []string `json:"headers" yaml:"headers"` Verbose *bool `json:"verbose" yaml:"verbose"` } diff --git a/cmd/run.go b/cmd/run.go index 1447376..3907e44 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -54,6 +54,7 @@ Latency Percentiles: duration, _ := f.GetDuration("duration") method, _ := f.GetString("method") body, _ := f.GetString("body") + bodyFile, _ := f.GetString("bodyfile") headers, _ := f.GetStringArray("header") verbose, _ := f.GetBool("verbose") outputFormat, _ := f.GetString("output") @@ -75,6 +76,7 @@ Latency Percentiles: Duration: duration, Method: strings.ToUpper(method), Body: body, + BodyFile: bodyFile, Headers: headers, Verbose: verbose, } @@ -135,6 +137,7 @@ Latency Percentiles: cmd.Flags().DurationP("duration", "d", 0, "Duration to run the test. Overrides -n when set (e.g., 10s, 1m)") cmd.Flags().StringP("method", "m", "GET", "HTTP method to use") cmd.Flags().StringP("body", "b", "", "Request body content") + cmd.Flags().StringP("bodyfile", "D", "", "Path to file containing the request body") cmd.Flags().StringArrayP("header", "H", []string{}, "HTTP header in 'Key: Value' format (can be repeated)") cmd.Flags().StringP("config", "f", "", "Path to configuration file (JSON/YAML)") cmd.Flags().BoolP("verbose", "v", false, "Enable verbose output") From 60e16627fe1443e202cb68a38687bae3ac0102c8 Mon Sep 17 00:00:00 2001 From: Eshaan Negi <11eshaansinghnegi@gmail.com> Date: Thu, 26 Mar 2026 12:34:48 +0530 Subject: [PATCH 2/5] fix: use kebab-case for body-file flag --- cmd/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/run.go b/cmd/run.go index 3907e44..df5557b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -137,7 +137,7 @@ Latency Percentiles: cmd.Flags().DurationP("duration", "d", 0, "Duration to run the test. Overrides -n when set (e.g., 10s, 1m)") cmd.Flags().StringP("method", "m", "GET", "HTTP method to use") cmd.Flags().StringP("body", "b", "", "Request body content") - cmd.Flags().StringP("bodyfile", "D", "", "Path to file containing the request body") + cmd.Flags().StringP("body-file", "D", "", "Path to file containing the request body") cmd.Flags().StringArrayP("header", "H", []string{}, "HTTP header in 'Key: Value' format (can be repeated)") cmd.Flags().StringP("config", "f", "", "Path to configuration file (JSON/YAML)") cmd.Flags().BoolP("verbose", "v", false, "Enable verbose output") From eb9f9fafc5f096a80b69ccfded37f802af4de1ec Mon Sep 17 00:00:00 2001 From: Eshaan Negi <11eshaansinghnegi@gmail.com> Date: Thu, 26 Mar 2026 12:53:31 +0530 Subject: [PATCH 3/5] fix: properly handle body_file in config merge and CLI flag --- cmd/configfile.go | 4 ++++ cmd/run.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/configfile.go b/cmd/configfile.go index d8e5d1a..d7abe00 100644 --- a/cmd/configfile.go +++ b/cmd/configfile.go @@ -117,6 +117,10 @@ func mergeConfig(file *fileConfig, cli RunConfig, changed map[string]bool) (RunC merged.Body = *file.Body } + if file.BodyFile != nil && !changed["body-file"] { + merged.BodyFile = *file.BodyFile + } + if len(file.Headers) > 0 && !changed["header"] { merged.Headers = make([]string, len(file.Headers)) copy(merged.Headers, file.Headers) diff --git a/cmd/run.go b/cmd/run.go index df5557b..4621ced 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -54,7 +54,7 @@ Latency Percentiles: duration, _ := f.GetDuration("duration") method, _ := f.GetString("method") body, _ := f.GetString("body") - bodyFile, _ := f.GetString("bodyfile") + bodyFile, _ := f.GetString("body-file") headers, _ := f.GetStringArray("header") verbose, _ := f.GetBool("verbose") outputFormat, _ := f.GetString("output") From b4791c0800b2e0d7c09f8d967c8cc10ea9a5cf31 Mon Sep 17 00:00:00 2001 From: Eshaan Negi <11eshaansinghnegi@gmail.com> Date: Thu, 26 Mar 2026 13:02:49 +0530 Subject: [PATCH 4/5] test: validate body and body-file flags --- cmd/config_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/cmd/config_test.go b/cmd/config_test.go index a435321..a8030f4 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -1,6 +1,8 @@ package cmd import ( + "os" + "strings" "testing" "time" @@ -8,6 +10,16 @@ import ( ) func TestRunConfig_Validate(t *testing.T) { + tmpFile, err := os.CreateTemp("", "body*.txt") + if err != nil { + t.Fatalf("failed to create temp file: %v", err) + } + defer os.Remove(tmpFile.Name()) + if _, err := tmpFile.Write([]byte("hello file body")); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + tmpFile.Close() + validConfig := func() RunConfig { return RunConfig{ Target: "https://example.com/api", @@ -25,6 +37,7 @@ func TestRunConfig_Validate(t *testing.T) { mutate func(*RunConfig) wantErr bool errMsg string + check func(*testing.T, *RunConfig) }{ { name: "Valid configuration", @@ -225,6 +238,35 @@ func TestRunConfig_Validate(t *testing.T) { wantErr: true, errMsg: `invalid header format ": some-value", expected 'Key: Value' without spaces in the key`, }, + { + name: "Both body and body file set", + mutate: func(c *RunConfig) { + c.Body = "some body text" + c.BodyFile = "some_file.txt" + }, + wantErr: true, + errMsg: "cannot use both -b (body string) and -D (body file)", + }, + { + name: "Nonexistent body file", + mutate: func(c *RunConfig) { + c.BodyFile = "does_not_exist_12345.txt" + }, + wantErr: true, + errMsg: "failed to read body file \"does_not_exist_12345.txt\"", + }, + { + name: "Valid body file", + mutate: func(c *RunConfig) { + c.BodyFile = tmpFile.Name() + }, + wantErr: false, + check: func(t *testing.T, c *RunConfig) { + if c.Body != "hello file body" { + t.Errorf("expected Body to be %q, got %q", "hello file body", c.Body) + } + }, + }, } for _, tt := range tests { @@ -236,7 +278,7 @@ func TestRunConfig_Validate(t *testing.T) { if tt.wantErr { if err == nil { t.Errorf("expected error but got nil") - } else if tt.errMsg != "" && err.Error() != tt.errMsg { + } else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) { t.Errorf("expected error containing %q, got %q", tt.errMsg, err.Error()) } } else { @@ -244,6 +286,10 @@ func TestRunConfig_Validate(t *testing.T) { t.Errorf("unexpected error: %v", err) } } + + if tt.check != nil { + tt.check(t, &cfg) + } }) } } From 2688b29d0fd25a37afd045b3517cba5994a8940b Mon Sep 17 00:00:00 2001 From: Eshaan Negi <11eshaansinghnegi@gmail.com> Date: Thu, 26 Mar 2026 13:14:19 +0530 Subject: [PATCH 5/5] fix: handle errcheck warnings in config tests --- cmd/config_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/config_test.go b/cmd/config_test.go index a8030f4..aae9b59 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -14,11 +14,15 @@ func TestRunConfig_Validate(t *testing.T) { if err != nil { t.Fatalf("failed to create temp file: %v", err) } - defer os.Remove(tmpFile.Name()) + defer func() { + _ = os.Remove(tmpFile.Name()) + }() if _, err := tmpFile.Write([]byte("hello file body")); err != nil { t.Fatalf("failed to write temp file: %v", err) } - tmpFile.Close() + if err := tmpFile.Close(); err != nil { + t.Fatalf("failed to close temp file: %v", err) + } validConfig := func() RunConfig { return RunConfig{