//go:build linux || freebsd // +build linux freebsd package cli import ( "bytes" "errors" "os" "strings" "testing" "time" ) // --------------------------------------------------------------------------- // parser.go tests // --------------------------------------------------------------------------- func TestParseArgs(t *testing.T) { tests := []struct { name string args []string want map[string]string wantErr bool }{ { name: "empty args", args: []string{}, want: map[string]string{}, }, { name: "single key-value pair", args: []string{"--url", "https://example.com/file.zip"}, want: map[string]string{"url": "https://example.com/file.zip"}, }, { name: "boolean flag", args: []string{"--verbose"}, want: map[string]string{"verbose": "true"}, }, { name: "multiple boolean flags", args: []string{"--verbose", "--debug", "--resume"}, want: map[string]string{ "verbose": "true", "debug": "true", "resume": "true", }, }, { name: "mixed boolean and value flags", args: []string{"--url", "https://example.com/file.zip", "--verbose", "--output", "file.zip"}, want: map[string]string{ "url": "https://example.com/file.zip", "verbose": "true", "output": "file.zip", }, }, { name: "flag with equals in value", args: []string{"--proxy", "http://user:pass@proxy.example.com:8080"}, want: map[string]string{"proxy": "http://user:pass@proxy.example.com:8080"}, }, { name: "multiple key-value pairs", args: []string{"--key1", "val1", "--key2", "val2", "--key3", "val3"}, want: map[string]string{ "key1": "val1", "key2": "val2", "key3": "val3", }, }, { name: "flag with empty string value", args: []string{"--output", ""}, want: map[string]string{"output": ""}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseArgs(tt.args) if (err != nil) != tt.wantErr { t.Errorf("ParseArgs() error = %v, wantErr %v", err, tt.wantErr) return } if len(got) != len(tt.want) { t.Errorf("ParseArgs() got %v, want %v", got, tt.want) return } for k, v := range tt.want { if got[k] != v { t.Errorf("ParseArgs()[%q] = %q, want %q", k, got[k], v) } } }) } } func TestValidateRequiredFlags(t *testing.T) { tests := []struct { name string flags map[string]string required []string wantErr bool errMsg string }{ { name: "all required present", flags: map[string]string{"url": "http://example.com", "output": "file.zip"}, required: []string{"url", "output"}, wantErr: false, }, { name: "single required missing", flags: map[string]string{"output": "file.zip"}, required: []string{"url"}, wantErr: true, errMsg: "missing required flags: url", }, { name: "multiple required missing", flags: map[string]string{}, required: []string{"url", "output", "verbose"}, wantErr: true, errMsg: "missing required flags: url, output, verbose", }, { name: "no required flags", flags: map[string]string{"url": "http://example.com"}, required: []string{}, wantErr: false, }, { name: "empty flags map with required", flags: map[string]string{}, required: []string{"url"}, wantErr: true, errMsg: "missing required flags: url", }, { name: "nil flags map", flags: nil, required: []string{"url"}, wantErr: true, errMsg: "missing required flags: url", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidateRequiredFlags(tt.flags, tt.required) if (err != nil) != tt.wantErr { t.Errorf("ValidateRequiredFlags() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil && err.Error() != tt.errMsg { t.Errorf("ValidateRequiredFlags() error message = %q, want %q", err.Error(), tt.errMsg) } }) } } func TestParserGetBool(t *testing.T) { p := NewParser("test") p.fs.Bool("verbose", false, "verbose output") p.fs.Bool("debug", false, "debug mode") // Parse with verbose=true if err := p.Parse([]string{"--verbose"}); err != nil { t.Fatalf("Parse() error = %v", err) } if !p.GetBool("verbose") { t.Error("GetBool(verbose) = false, want true") } if p.GetBool("debug") { t.Error("GetBool(debug) = true, want false (default)") } } func TestParserGetString(t *testing.T) { p := NewParser("test") p.fs.String("url", "", "URL to download") p.fs.String("output", "default.txt", "output file") p.fs.String("proxy", "", "proxy URL") // Parse with custom values if err := p.Parse([]string{"--url", "https://example.com", "--output", "file.zip"}); err != nil { t.Fatalf("Parse() error = %v", err) } if got := p.GetString("url"); got != "https://example.com" { t.Errorf("GetString(url) = %q, want %q", got, "https://example.com") } if got := p.GetString("output"); got != "file.zip" { t.Errorf("GetString(output) = %q, want %q", got, "file.zip") } // Unset flag should return default value if got := p.GetString("proxy"); got != "" { t.Errorf("GetString(proxy) = %q, want %q (default)", got, "") } } func TestParserGetBoolDefaultTrue(t *testing.T) { p := NewParser("test") p.fs.Bool("no-color", false, "disable colors") if err := p.Parse([]string{}); err != nil { t.Fatalf("Parse() error = %v", err) } if p.GetBool("no-color") { t.Error("GetBool(no-color) = true, want false (default)") } } func TestParserGetStringDefault(t *testing.T) { p := NewParser("test") p.fs.String("output", "default.txt", "output file") if err := p.Parse([]string{}); err != nil { t.Fatalf("Parse() error = %v", err) } if got := p.GetString("output"); got != "default.txt" { t.Errorf("GetString(output) = %q, want %q (default)", got, "default.txt") } } func TestParserParse(t *testing.T) { p := NewParser("test") p.fs.String("url", "", "URL") p.fs.Bool("verbose", false, "verbose") // Valid parse if err := p.Parse([]string{"--url", "http://example.com", "--verbose"}); err != nil { t.Errorf("Parse() unexpected error: %v", err) } if got := p.GetString("url"); got != "http://example.com" { t.Errorf("GetString(url) = %q, want %q", got, "http://example.com") } if !p.GetBool("verbose") { t.Error("GetBool(verbose) should be true") } } // --------------------------------------------------------------------------- // flags.go tests // --------------------------------------------------------------------------- func TestPrintError(t *testing.T) { var buf bytes.Buffer err := errors.New("connection refused") PrintError(&buf, err) output := buf.String() if !strings.Contains(output, "Error:") { t.Errorf("PrintError output = %q, want it to contain 'Error:'", output) } if !strings.Contains(output, "connection refused") { t.Errorf("PrintError output = %q, want it to contain 'connection refused'", output) } // Should contain the unicode error symbol if !strings.Contains(output, "✗") { t.Errorf("PrintError output = %q, want it to contain '✗'", output) } // No ANSI codes since buffer is not a terminal if strings.Contains(output, "\033[") { t.Errorf("PrintError output = %q, should not contain ANSI codes when writing to buffer", output) } } func TestPrintWarning(t *testing.T) { var buf bytes.Buffer PrintWarning(&buf, "rate limit exceeded") output := buf.String() if !strings.Contains(output, "Warning:") { t.Errorf("PrintWarning output = %q, want it to contain 'Warning:'", output) } if !strings.Contains(output, "rate limit exceeded") { t.Errorf("PrintWarning output = %q, want it to contain 'rate limit exceeded'", output) } if !strings.Contains(output, "⚠") { t.Errorf("PrintWarning output = %q, want it to contain '⚠'", output) } if strings.Contains(output, "\033[") { t.Errorf("PrintWarning output = %q, should not contain ANSI codes when writing to buffer", output) } } func TestPrintSuccess(t *testing.T) { var buf bytes.Buffer PrintSuccess(&buf, "download complete") output := buf.String() if !strings.Contains(output, "✓") { t.Errorf("PrintSuccess output = %q, want it to contain '✓'", output) } if !strings.Contains(output, "download complete") { t.Errorf("PrintSuccess output = %q, want it to contain 'download complete'", output) } // Success message format is "✓ \n" if output != "✓ download complete\n" { t.Errorf("PrintSuccess output = %q, want %q", output, "✓ download complete\n") } if strings.Contains(output, "\033[") { t.Errorf("PrintSuccess output = %q, should not contain ANSI codes when writing to buffer", output) } } func TestPrintInfo(t *testing.T) { var buf bytes.Buffer PrintInfo(&buf, "processing file") output := buf.String() if !strings.Contains(output, "ℹ") { t.Errorf("PrintInfo output = %q, want it to contain 'ℹ'", output) } if !strings.Contains(output, "processing file") { t.Errorf("PrintInfo output = %q, want it to contain 'processing file'", output) } // Info message format is "ℹ \n" if output != "ℹ processing file\n" { t.Errorf("PrintInfo output = %q, want %q", output, "ℹ processing file\n") } if strings.Contains(output, "\033[") { t.Errorf("PrintInfo output = %q, should not contain ANSI codes when writing to buffer", output) } } func TestPrintErrorWithNilError(t *testing.T) { var buf bytes.Buffer PrintError(&buf, nil) output := buf.String() if !strings.Contains(output, "Error:") { t.Errorf("PrintError with nil output = %q, want it to contain 'Error:'", output) } // nil error prints "" if !strings.Contains(output, "") { t.Errorf("PrintError with nil output = %q, want it to contain ''", output) } } func TestFormatError(t *testing.T) { tests := []struct { name string err error context string wantMsg string wantNil bool }{ { name: "with context", err: errors.New("not found"), context: "downloading file", wantMsg: "downloading file: not found", }, { name: "empty context", err: errors.New("something went wrong"), context: "", wantMsg: "something went wrong", }, { name: "nil error with context", err: nil, context: "processing", wantMsg: "processing: %!w()", }, { name: "nil error empty context", err: nil, context: "", wantMsg: "", wantNil: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := FormatError(tt.err, tt.context) if tt.wantNil { if got != nil { t.Errorf("FormatError() = %v, want nil", got) } return } if got == nil { t.Fatal("FormatError() returned nil, want non-nil") } if got.Error() != tt.wantMsg { t.Errorf("FormatError() = %q, want %q", got.Error(), tt.wantMsg) } // Verify error wrapping: if original error is non-nil with non-empty context, // the original should be accessible via errors.Unwrap if tt.err != nil && tt.context != "" { if !errors.Is(got, tt.err) { t.Errorf("FormatError() should wrap the original error, errors.Is(got, tt.err) = false") } } }) } } func TestIndent(t *testing.T) { tests := []struct { name string text string spaces int want string }{ { name: "single line", text: "hello", spaces: 2, want: " hello", }, { name: "multiple lines", text: "line1\nline2\nline3", spaces: 4, want: " line1\n line2\n line3", }, { name: "empty string", text: "", spaces: 4, want: "", }, { name: "zero spaces", text: "hello", spaces: 0, want: "hello", }, { name: "lines with empty lines in between", text: "line1\n\nline3", spaces: 2, want: " line1\n\n line3", }, { name: "trailing newline", text: "hello\n", spaces: 3, want: " hello\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Indent(tt.text, tt.spaces) if got != tt.want { t.Errorf("Indent() = %q, want %q", got, tt.want) } }) } } func TestIsTerminal(t *testing.T) { var buf bytes.Buffer // A buffer is not a terminal if IsTerminal(&buf) { t.Error("IsTerminal(buffer) = true, want false") } } func TestIsTerminalWithFile(t *testing.T) { // /dev/null is a valid *os.File but not a terminal f, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0) if err != nil { t.Skipf("cannot open %s: %v", os.DevNull, err) } defer f.Close() // The result depends on the environment (TERM env var, etc.) // Just verify it doesn't panic and returns a bool result := IsTerminal(f) _ = result // should not panic } func TestPrintUsage(t *testing.T) { var buf bytes.Buffer PrintUsage(&buf) output := buf.String() sections := []string{ "NAME", "SYNOPSIS", "DESCRIPTION", "TARGET", "PROTOCOLS", "EXAMPLES", } for _, section := range sections { if !strings.Contains(output, section) { t.Errorf("PrintUsage output missing section %q", section) } } // Should NOT contain ANSI codes since buffer is not a terminal if strings.Contains(output, "\033[") { t.Errorf("PrintUsage output should not contain ANSI codes when writing to buffer") } } func TestColorize(t *testing.T) { var buf bytes.Buffer // On a buffer (non-terminal), should return plain text result := Colorize(&buf, ColorRed, "error text") if result != "error text" { t.Errorf("Colorize(buffer) = %q, want %q (plain text)", result, "error text") } } // --------------------------------------------------------------------------- // progress.go tests func TestFormatDuration(t *testing.T) { tests := []struct { name string d time.Duration want string }{ { name: "zero duration", d: 0, want: "0s", }, { name: "seconds only", d: 30 * time.Second, want: "30s", }, { name: "minutes and seconds", d: 2*time.Minute + 15*time.Second, want: "2m15s", }, { name: "hours and minutes", d: 3*time.Hour + 45*time.Minute, want: "3h45m", }, { name: "exactly one minute", d: 1 * time.Minute, want: "1m0s", }, { name: "exactly one hour", d: 1 * time.Hour, want: "1h0m", }, { name: "single second", d: 1 * time.Second, want: "1s", }, { name: "seconds rounded down", d: 59*time.Second + 999*time.Millisecond, want: "1m0s", }, { name: "rounds to nearest second", d: 500 * time.Millisecond, want: "1s", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := formatDuration(tt.d) if got != tt.want { t.Errorf("formatDuration(%v) = %q, want %q", tt.d, got, tt.want) } }) } } func TestParseInt(t *testing.T) { tests := []struct { name string s string want int }{ { name: "simple number", s: "42", want: 42, }, { name: "zero", s: "0", want: 0, }, { name: "large number", s: "123456789", want: 123456789, }, { name: "string with trailing non-digits", s: "80columns", want: 80, }, { name: "empty string", s: "", want: 0, }, { name: "no digits", s: "abc", want: 0, }, { name: "mixed with non-digits at start", s: "abc123", want: 0, }, { name: "negative sign not supported by parseInt", s: "-5", want: 0, }, { name: "leading zeros", s: "007", want: 7, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := parseInt(tt.s) if got != tt.want { t.Errorf("parseInt(%q) = %d, want %d", tt.s, got, tt.want) } }) } } func TestMax(t *testing.T) { tests := []struct { name string a, b int want int }{ { name: "a greater", a: 10, b: 5, want: 10, }, { name: "b greater", a: 3, b: 7, want: 7, }, { name: "equal values", a: 42, b: 42, want: 42, }, { name: "negative values", a: -5, b: -10, want: -5, }, { name: "zero and positive", a: 0, b: 100, want: 100, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := max(tt.a, tt.b) if got != tt.want { t.Errorf("max(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want) } }) } } func TestNewProgressBarWithNilConfig(t *testing.T) { pb := NewProgressBar(nil) if pb == nil { t.Fatal("NewProgressBar(nil) returned nil") } if pb.config == nil { t.Fatal("NewProgressBar(nil) config is nil, should use defaults") } if pb.config.Total != -1 { t.Errorf("default Total = %d, want -1", pb.config.Total) } if pb.config.Writer != os.Stderr { t.Errorf("default Writer should be os.Stderr") } if pb.current != 0 { t.Errorf("current = %d, want 0", pb.current) } if pb.barWidth < 20 { t.Errorf("barWidth = %d, want >= 20", pb.barWidth) } } func TestNewProgressBarWithCustomConfig(t *testing.T) { var buf bytes.Buffer onCompleteCalled := false onComplete := func() { onCompleteCalled = true } cfg := &ProgressBarConfig{ Total: 1000, Width: 30, Colors: false, Writer: &buf, OnComplete: onComplete, } pb := NewProgressBar(cfg) if pb == nil { t.Fatal("NewProgressBar(cfg) returned nil") } if pb.config.Total != 1000 { t.Errorf("Total = %d, want 1000", pb.config.Total) } if pb.config.Width != 30 { t.Errorf("Width = %d, want 30", pb.config.Width) } if pb.config.Writer != &buf { t.Errorf("Writer mismatch") } if pb.barWidth != 30 { t.Errorf("barWidth = %d, want 30", pb.barWidth) } // Call Finish which should trigger OnComplete pb.Finish() if !onCompleteCalled { t.Error("OnComplete was not called") } } func TestNewProgressBarWithWidthZero(t *testing.T) { // Width 0 should auto-calculate based on terminal width cfg := &ProgressBarConfig{ Total: 500, Width: 0, Colors: false, Writer: os.Stderr, } pb := NewProgressBar(cfg) if pb.barWidth < 20 { t.Errorf("auto-calculated barWidth = %d, want >= 20", pb.barWidth) } } func TestProgressBarUpdateDoesNotPanic(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 1000, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) if pb == nil { t.Fatal("NewProgressBar returned nil") } // Multiple updates should not panic defer func() { if r := recover(); r != nil { t.Fatalf("ProgressBar.Update panicked: %v", r) } }() pb.Update(0) pb.Update(100) pb.Update(500) pb.Update(1000) if pb.current != 1000 { t.Errorf("current = %d, want 1000", pb.current) } } func TestProgressBarUpdateExceedsTotal(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 100, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) defer func() { if r := recover(); r != nil { t.Fatalf("ProgressBar.Update panicked when exceeding total: %v", r) } }() pb.Update(200) // current can exceed total, should not panic } func TestProgressBarFinishDoesNotPanic(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 1000, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) defer func() { if r := recover(); r != nil { t.Fatalf("ProgressBar.Finish panicked: %v", r) } }() pb.Update(800) pb.Finish() // After finish, current should equal total if pb.current != 1000 { t.Errorf("after Finish, current = %d, want %d (Total)", pb.current, 1000) } // Output should contain completion message output := buf.String() if !strings.Contains(output, "complete") { t.Errorf("Finish output = %q, want it to contain 'complete'", output) } } func TestProgressBarFinishWithUnknownTotal(t *testing.T) { var buf bytes.Buffer // Total = -1 (unknown) cfg := &ProgressBarConfig{ Total: -1, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) defer func() { if r := recover(); r != nil { t.Fatalf("ProgressBar.Finish with unknown total panicked: %v", r) } }() pb.Update(500) pb.Finish() // With unknown total, current should stay at the last value (not overwritten) if pb.current != 500 { t.Errorf("after Finish with Total=-1, current = %d, want %d", pb.current, 500) } } func TestProgressBarAddDoesNotPanic(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 1000, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) defer func() { if r := recover(); r != nil { t.Fatalf("ProgressBar.Add panicked: %v", r) } }() pb.Add(100) pb.Add(200) pb.Add(300) if pb.current != 600 { t.Errorf("after Add calls, current = %d, want 600", pb.current) } } func TestProgressBarCancelDoesNotPanic(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 1000, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) defer func() { if r := recover(); r != nil { t.Fatalf("ProgressBar.Cancel panicked: %v", r) } }() pb.Update(300) pb.Cancel() // Output should contain cancellation message output := buf.String() if !strings.Contains(output, "cancelled") { t.Errorf("Cancel output = %q, want it to contain 'cancelled'", output) } } func TestProgressBarWithColors(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 100, Width: 20, Colors: true, Writer: &buf, } pb := NewProgressBar(cfg) defer func() { if r := recover(); r != nil { t.Fatalf("ProgressBar with colors panicked: %v", r) } }() pb.Update(50) pb.Finish() // Should not panic with colors enabled even on a buffer } func TestProgressBarWithOnComplete(t *testing.T) { var buf bytes.Buffer callCount := 0 cfg := &ProgressBarConfig{ Total: 100, Width: 40, Colors: false, Writer: &buf, OnComplete: func() { callCount++ }, } pb := NewProgressBar(cfg) pb.Finish() if callCount != 1 { t.Errorf("OnComplete called %d times, want 1", callCount) } // Calling Finish again should call OnComplete again pb.Finish() if callCount != 2 { t.Errorf("OnComplete called %d times after second Finish, want 2", callCount) } } func TestProgressBarUpdateAfterFinish(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 100, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) pb.Finish() defer func() { if r := recover(); r != nil { t.Fatalf("Update after Finish panicked: %v", r) } }() // Update after finish should not panic pb.Update(999) } func TestDefaultProgressBarConfig(t *testing.T) { cfg := DefaultProgressBarConfig() if cfg == nil { t.Fatal("DefaultProgressBarConfig() returned nil") } if cfg.Total != -1 { t.Errorf("Total = %d, want -1", cfg.Total) } if cfg.Width != 0 { t.Errorf("Width = %d, want 0", cfg.Width) } if !cfg.Colors { t.Error("Colors = false, want true") } if cfg.Writer != os.Stderr { t.Errorf("Writer should be os.Stderr") } } func TestPrintProgressInfo(t *testing.T) { var buf bytes.Buffer PrintProgressInfo(&buf, "connecting to server") output := buf.String() if !strings.Contains(output, "•") { t.Errorf("PrintProgressInfo output = %q, want it to contain '•'", output) } if !strings.Contains(output, "connecting to server") { t.Errorf("PrintProgressInfo output = %q, want it to contain 'connecting to server'", output) } if strings.Contains(output, "\033[") { t.Errorf("PrintProgressInfo output should not contain ANSI codes when writing to buffer") } } func TestPrintProgressSuccess(t *testing.T) { var buf bytes.Buffer PrintProgressSuccess(&buf, "file saved") output := buf.String() if !strings.Contains(output, "✓") { t.Errorf("PrintProgressSuccess output = %q, want it to contain '✓'", output) } if !strings.Contains(output, "file saved") { t.Errorf("PrintProgressSuccess output = %q, want it to contain 'file saved'", output) } if strings.Contains(output, "\033[") { t.Errorf("PrintProgressSuccess output should not contain ANSI codes when writing to buffer") } } func TestProgressBarRenderRateLimit(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 1000, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) pb.lastUpdate = time.Now().Add(-100 * time.Millisecond) // Ensure update is not rate-limited // This should trigger render since enough time passed pb.Update(500) // Output should contain bar characters and percentage output := buf.String() if !strings.Contains(output, "%") { t.Errorf("render output = %q, want it to contain '%%'", output) } } func TestProgressBarRenderRateLimitSkip(t *testing.T) { var buf bytes.Buffer cfg := &ProgressBarConfig{ Total: 1000, Width: 40, Colors: false, Writer: &buf, } pb := NewProgressBar(cfg) pb.lastUpdate = time.Now() // Set lastUpdate to now so the next update is rate-limited // This should NOT trigger render because of rate limiting pb.Update(100) output := buf.String() if output != "" { t.Errorf("render should have been skipped due to rate limiting, but output = %q", output) } } // --------------------------------------------------------------------------- // Integration: parser + flags // --------------------------------------------------------------------------- func TestParseArgsAndValidate(t *testing.T) { // Integration test: parse args and then validate required flags args := []string{"--url", "https://example.com", "--output", "file.zip", "--verbose"} parsed, err := ParseArgs(args) if err != nil { t.Fatalf("ParseArgs() error = %v", err) } // Validate required fields if err := ValidateRequiredFlags(parsed, []string{"url"}); err != nil { t.Errorf("ValidateRequiredFlags() error = %v, want nil", err) } if err := ValidateRequiredFlags(parsed, []string{"url", "output"}); err != nil { t.Errorf("ValidateRequiredFlags() error = %v, want nil", err) } // Missing required flag should fail if err := ValidateRequiredFlags(parsed, []string{"nonexistent"}); err == nil { t.Error("ValidateRequiredFlags() should have returned error for missing flag") } // Verify parsed values if parsed["url"] != "https://example.com" { t.Errorf("parsed url = %q, want %q", parsed["url"], "https://example.com") } if parsed["output"] != "file.zip" { t.Errorf("parsed output = %q, want %q", parsed["output"], "file.zip") } if parsed["verbose"] != "true" { t.Errorf("parsed verbose = %q, want %q", parsed["verbose"], "true") } } func TestDefaultProgressBarConfigReturnsNewInstance(t *testing.T) { // Ensure multiple calls return independent instances cfg1 := DefaultProgressBarConfig() cfg2 := DefaultProgressBarConfig() if cfg1 == cfg2 { t.Error("DefaultProgressBarConfig() should return new instances each call") } // Modifying cfg1 should not affect cfg2 cfg1.Total = 100 if cfg2.Total != -1 { t.Error("modifying one config should not affect the other") } }