From 09e78bf1510df0e6c921320fc77f341242cf9978 Mon Sep 17 00:00:00 2001 From: Markus Graf Date: Thu, 26 Mar 2026 14:39:05 +0100 Subject: [PATCH] test(04-02): add failing tests for --filter, --read flags, mutual exclusion, deriveOutputPath - TestFlagMutualExclusion: --read and -i together returns mutually exclusive error - TestMissingSource: no source returns interface required error mentioning --read - TestDeriveOutputPath: pcap->mp3 filename derivation - TestFilterFlagRegistered: --filter flag present - TestReadFlagRegistered: --read flag present - TestInvalidBPFFilter: invalid BPF filter errors before capture - TestHelpOutputNewFlags: --help shows --filter and --read --- cmd/netsynth/main_test.go | 194 +++++++++++++++++++++++++++++++++++++- 1 file changed, 190 insertions(+), 4 deletions(-) diff --git a/cmd/netsynth/main_test.go b/cmd/netsynth/main_test.go index 9631409..6fde72a 100644 --- a/cmd/netsynth/main_test.go +++ b/cmd/netsynth/main_test.go @@ -13,21 +13,31 @@ func newTestCmd() *cobra.Command { var testIface string var testListIfaces bool var testVerbose bool + var testFilter string + var testRead string + var testOutput string rootCmd := &cobra.Command{ - Use: "netsynth", - Short: "Sonify live network traffic into ambient audio", - RunE: run, + Use: "netsynth", + Short: "Sonify live network traffic into ambient audio", + RunE: run, + SilenceUsage: true, } rootCmd.Flags().StringVarP(&testIface, "interface", "i", "", "Network interface to capture on") rootCmd.Flags().BoolVar(&testListIfaces, "list-interfaces", false, "List available network interfaces and exit") rootCmd.Flags().BoolVar(&testVerbose, "verbose", false, "Print per-window protocol activity to stderr") + rootCmd.Flags().StringVarP(&testOutput, "output", "o", "", "Output MP3 file path") + rootCmd.Flags().StringVar(&testFilter, "filter", "", "BPF filter expression") + rootCmd.Flags().StringVar(&testRead, "read", "", "Read packets from pcap file instead of live capture") // Wire test variables to package-level vars used by run() rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { ifaceName = testIface listIfaces = testListIfaces verbose = testVerbose + outputPath = testOutput + bpfFilter = testFilter + readPath = testRead return nil } @@ -40,6 +50,9 @@ func TestListInterfacesFlag(t *testing.T) { ifaceName = "" listIfaces = false verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" rootCmd := &cobra.Command{ Use: "netsynth", @@ -48,6 +61,8 @@ func TestListInterfacesFlag(t *testing.T) { rootCmd.Flags().StringVarP(&ifaceName, "interface", "i", "", "Network interface") rootCmd.Flags().BoolVar(&listIfaces, "list-interfaces", false, "List interfaces") rootCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output") + rootCmd.Flags().StringVar(&bpfFilter, "filter", "", "BPF filter expression") + rootCmd.Flags().StringVar(&readPath, "read", "", "Read from pcap file") var buf bytes.Buffer rootCmd.SetErr(&buf) @@ -61,11 +76,15 @@ func TestListInterfacesFlag(t *testing.T) { } // TestMissingInterfaceFlag verifies that running without -i returns an error containing "interface required". +// Per plan: error message should also mention "--read" (updated behavior). func TestMissingInterfaceFlag(t *testing.T) { // Reset global state ifaceName = "" listIfaces = false verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" rootCmd := &cobra.Command{ Use: "netsynth", @@ -75,6 +94,8 @@ func TestMissingInterfaceFlag(t *testing.T) { rootCmd.Flags().StringVarP(&ifaceName, "interface", "i", "", "Network interface") rootCmd.Flags().BoolVar(&listIfaces, "list-interfaces", false, "List interfaces") rootCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output") + rootCmd.Flags().StringVar(&bpfFilter, "filter", "", "BPF filter expression") + rootCmd.Flags().StringVar(&readPath, "read", "", "Read from pcap file") var outBuf, errBuf bytes.Buffer rootCmd.SetOut(&outBuf) @@ -87,14 +108,21 @@ func TestMissingInterfaceFlag(t *testing.T) { if !strings.Contains(err.Error(), "interface required") { t.Errorf("expected error to contain 'interface required', got: %q", err.Error()) } + // Updated behavior: error should also mention --read + if !strings.Contains(err.Error(), "--read") { + t.Errorf("expected error to mention '--read', got: %q", err.Error()) + } } -// TestHelpOutput verifies that --help output contains all three flags. +// TestHelpOutput verifies that --help output contains all flags including new ones. func TestHelpOutput(t *testing.T) { // Reset global state ifaceName = "" listIfaces = false verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" rootCmd := &cobra.Command{ Use: "netsynth", @@ -104,6 +132,8 @@ func TestHelpOutput(t *testing.T) { rootCmd.Flags().StringVarP(&ifaceName, "interface", "i", "", "Network interface") rootCmd.Flags().BoolVar(&listIfaces, "list-interfaces", false, "List interfaces") rootCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output") + rootCmd.Flags().StringVar(&bpfFilter, "filter", "", "BPF filter expression") + rootCmd.Flags().StringVar(&readPath, "read", "", "Read from pcap file") var buf bytes.Buffer rootCmd.SetOut(&buf) @@ -134,3 +164,159 @@ func TestHelpOutput(t *testing.T) { } } } + +// TestFlagMutualExclusion verifies that --read and -i together return a "mutually exclusive" error. +func TestFlagMutualExclusion(t *testing.T) { + // Reset global state + ifaceName = "" + listIfaces = false + verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" + + rootCmd := newTestCmd() + rootCmd.SetArgs([]string{"-i", "eth0", "--read", "capture.pcap"}) + + var outBuf, errBuf bytes.Buffer + rootCmd.SetOut(&outBuf) + rootCmd.SetErr(&errBuf) + + err := rootCmd.Execute() + if err == nil { + t.Fatal("expected error when both --read and -i are given, got nil") + } + if !strings.Contains(err.Error(), "mutually exclusive") { + t.Errorf("expected error to contain 'mutually exclusive', got: %q", err.Error()) + } +} + +// TestMissingSource verifies that running without --read and without -i returns an appropriate error. +func TestMissingSource(t *testing.T) { + // Reset global state + ifaceName = "" + listIfaces = false + verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" + + rootCmd := newTestCmd() + + var outBuf, errBuf bytes.Buffer + rootCmd.SetOut(&outBuf) + rootCmd.SetErr(&errBuf) + + err := rootCmd.Execute() + if err == nil { + t.Fatal("expected error when neither --read nor -i provided, got nil") + } + if !strings.Contains(err.Error(), "interface required") { + t.Errorf("expected error to contain 'interface required', got: %q", err.Error()) + } + if !strings.Contains(err.Error(), "--read") { + t.Errorf("expected error to mention '--read', got: %q", err.Error()) + } +} + +// TestDeriveOutputPath verifies output filename derivation from pcap input path. +func TestDeriveOutputPath(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"capture.pcap", "capture.mp3"}, + {"/tmp/net.pcap", "/tmp/net.mp3"}, + {"noext", "noext.mp3"}, + {"traffic.pcap.gz", "traffic.pcap.mp3"}, // only last extension replaced + {"./local.pcap", "./local.mp3"}, + } + for _, tc := range tests { + got := deriveOutputPath(tc.input) + if got != tc.expected { + t.Errorf("deriveOutputPath(%q) = %q, want %q", tc.input, got, tc.expected) + } + } +} + +// TestFilterFlagRegistered verifies --filter flag is registered on the root command. +func TestFilterFlagRegistered(t *testing.T) { + // Reset global state + ifaceName = "" + listIfaces = false + verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" + + rootCmd := newTestCmd() + f := rootCmd.Flags().Lookup("filter") + if f == nil { + t.Fatal("expected --filter flag to be registered, got nil") + } +} + +// TestReadFlagRegistered verifies --read flag is registered on the root command. +func TestReadFlagRegistered(t *testing.T) { + // Reset global state + ifaceName = "" + listIfaces = false + verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" + + rootCmd := newTestCmd() + f := rootCmd.Flags().Lookup("read") + if f == nil { + t.Fatal("expected --read flag to be registered, got nil") + } +} + +// TestInvalidBPFFilter verifies that an invalid BPF filter returns a clear error before capture. +func TestInvalidBPFFilter(t *testing.T) { + // Reset global state + ifaceName = "" + listIfaces = false + verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" + + rootCmd := newTestCmd() + rootCmd.SetArgs([]string{"-i", "lo", "--filter", "invalid garbage xyz"}) + + var outBuf, errBuf bytes.Buffer + rootCmd.SetOut(&outBuf) + rootCmd.SetErr(&errBuf) + + err := rootCmd.Execute() + if err == nil { + t.Fatal("expected error for invalid BPF filter, got nil") + } + if !strings.Contains(strings.ToLower(err.Error()), "invalid") { + t.Errorf("expected error to contain 'invalid', got: %q", err.Error()) + } +} + +// TestHelpOutputNewFlags verifies --help output contains --filter and --read flags. +func TestHelpOutputNewFlags(t *testing.T) { + // Reset global state + ifaceName = "" + listIfaces = false + verbose = false + bpfFilter = "" + readPath = "" + outputPath = "" + + rootCmd := newTestCmd() + + usageStr := rootCmd.UsageString() + + if !strings.Contains(usageStr, "--filter") { + t.Errorf("expected help/usage output to contain '--filter', usage: %s", usageStr) + } + if !strings.Contains(usageStr, "--read") { + t.Errorf("expected help/usage output to contain '--read', usage: %s", usageStr) + } +}