diff --git a/cli.go b/cli.go index 8d11614..acccb07 100644 --- a/cli.go +++ b/cli.go @@ -8,8 +8,30 @@ import ( "sync" ) +// NewApp builds the proxycheck CLI application. It is shared by the cmd binary +// and the tests so both exercise the same flag wiring. +func NewApp() *cli.App { + return &cli.App{ + Name: "proxycheck", + Description: "Proxy checker tool", + Version: "0.0.5", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "judge", Usage: "Set judge", Value: "proxyjudge.us"}, + &cli.IntFlag{Name: "threads", Usage: "Count of threads", Value: 10}, + }, + Action: Action, + } +} + func Action(c *cli.Context) error { + // Resolve the judge from the --judge flag before doing any work; an unknown + // name aborts here, so no proxy is checked. + judge, err := ResolveJudge(c.String("judge")) + if err != nil { + return err + } + // If pass args, use it or read stdin as input file with proxies var feed Feed @@ -28,7 +50,7 @@ func Action(c *cli.Context) error { go func(proxyAddrs chan string) { defer wg.Done() for proxyAddr := range proxyAddrs { - if res := Check(proxyAddr, &AZEnvPhpJudge{}); res.Online { + if res := Check(proxyAddr, judge); res.Online { fmt.Printf("%s\t%s\t%s\n", proxyAddr, strings.Join(res.Protocols, ","), res.Speed.String()) } else { fmt.Fprintf(os.Stderr, "invalid proxy %s: %v\n", proxyAddr, res.Err) @@ -47,6 +69,10 @@ func Action(c *cli.Context) error { proxyAddrs <- proxyAddr } + // Closing the channel lets the workers' range loops finish so wg.Wait + // returns once the feed is exhausted. + close(proxyAddrs) + wg.Wait() return nil } diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..b08391e --- /dev/null +++ b/cli_test.go @@ -0,0 +1,18 @@ +package proxycheck + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestApp_UnknownJudge is the regression test for the --judge flag: an unknown +// judge name must abort with an error before any proxy is checked. A deliberately +// unparsable address keeps the run off the network if the guard is missing. +func TestApp_UnknownJudge(t *testing.T) { + err := NewApp().Run([]string{"proxycheck", "--judge", "foo", "not-an-addr"}) + assert.Error(t, err, "unknown judge must cause a non-nil error") + if err != nil { + assert.Contains(t, err.Error(), "unknown judge: foo") + } +} diff --git a/cmd/main.go b/cmd/main.go index bdc6ef7..d1a3b33 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,20 +5,10 @@ import ( "os" "github.com/memclutter/proxycheck" - cli "github.com/urfave/cli/v2" ) func main() { - if err := (&cli.App{ - Name: "proxycheck", - Description: "Proxy checker tool", - Version: "0.0.5", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "judge", Usage: "Set judge", Value: "proxyjudge.us"}, - &cli.IntFlag{Name: "threads", Usage: "Count of threads", Value: 10}, - }, - Action: proxycheck.Action, - }).Run(os.Args); err != nil { + if err := proxycheck.NewApp().Run(os.Args); err != nil { log.Fatalf("app run failed: %s", err) } } diff --git a/judges.go b/judges.go index e02fc34..cad0904 100644 --- a/judges.go +++ b/judges.go @@ -1,6 +1,11 @@ package proxycheck -import "time" +import ( + "fmt" + "sort" + "strings" + "time" +) type AZEnvPhpJudge struct{} @@ -16,3 +21,24 @@ var Judges = map[string]Judge{ "azenv.php": AZEnvPhpJudge{}, "proxyjudge.us": ProxyjudgeUsJudge{}, } + +// ResolveJudge returns the Judge registered under name. An unknown name yields +// an error that lists the available judge names. +func ResolveJudge(name string) (Judge, error) { + judge, ok := Judges[name] + if !ok { + return nil, fmt.Errorf("unknown judge: %s (available: %s)", name, judgeNames()) + } + return judge, nil +} + +// judgeNames returns the registered judge names, sorted and comma-joined, so +// error messages are deterministic. +func judgeNames() string { + names := make([]string, 0, len(Judges)) + for name := range Judges { + names = append(names, name) + } + sort.Strings(names) + return strings.Join(names, ", ") +} diff --git a/judges_test.go b/judges_test.go index 2e50129..0ea11ba 100644 --- a/judges_test.go +++ b/judges_test.go @@ -1,11 +1,37 @@ package proxycheck import ( - "github.com/stretchr/testify/assert" "net/url" "testing" + + "github.com/stretchr/testify/assert" ) +func TestResolveJudge(t *testing.T) { + t.Run("azenv.php resolves", func(t *testing.T) { + judge, err := ResolveJudge("azenv.php") + assert.NoError(t, err) + assert.IsType(t, AZEnvPhpJudge{}, judge) + }) + + t.Run("proxyjudge.us resolves", func(t *testing.T) { + judge, err := ResolveJudge("proxyjudge.us") + assert.NoError(t, err) + assert.IsType(t, ProxyjudgeUsJudge{}, judge) + }) + + t.Run("unknown name errors", func(t *testing.T) { + judge, err := ResolveJudge("foo") + assert.Nil(t, judge) + assert.Error(t, err) + if err != nil { + assert.Contains(t, err.Error(), "unknown judge: foo") + assert.Contains(t, err.Error(), "azenv.php") + assert.Contains(t, err.Error(), "proxyjudge.us") + } + }) +} + func TestJudge_TargetURL(t *testing.T) { testingTable := []struct { title string