diff --git a/cmd/spectre/main.go b/cmd/spectre/main.go index da941f8..a399981 100644 --- a/cmd/spectre/main.go +++ b/cmd/spectre/main.go @@ -10,6 +10,15 @@ import ( ) func main() { + pw, err := doMain(os.Args[1:]) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(pw) +} + +func doMain(args []string) (string, error) { fs := flag.NewFlagSet("spectre", flag.ExitOnError) fs.Usage = func() { fmt.Fprintln(fs.Output(), "spectre [FLAGS] [site]") @@ -30,11 +39,11 @@ func main() { return }) - if err := fs.Parse(os.Args[1:]); err != nil { - panic(err) + if err := fs.Parse(args); err != nil { + return "", err } if err := checkEnv(fs); err != nil { - panic(err) + return "", err } if templateFlag == "" { @@ -42,23 +51,45 @@ func main() { } if *usernameFlag == "" || *secretFlag == "" || fs.NArg() < 1 { - panic("username, secret, and site are required") + return "", requiredArgs{ + missingUsername: *usernameFlag == "", + missingSecret: *secretFlag == "", + missingSite: fs.NArg() < 1, + } } s, err := spectre.New(*usernameFlag, *secretFlag, spectre.WithScoper(spectre.SimpleScoper{ Key: *scoperFlag, })) if err != nil { - panic(err) + return "", err } - pw := s.Site(fs.Arg(0), + return s.Site(fs.Arg(0), spectre.WithScope(scopeFlag), spectre.WithTemplate(templateFlag), spectre.WithCounter(*counterFlag), - ) + ), nil +} - fmt.Println(pw) +type requiredArgs struct { + missingUsername bool + missingSecret bool + missingSite bool +} + +func (r requiredArgs) Error() string { + s := "--username, --secret, and are required, missing: " + if r.missingUsername { + s += "\n- username" + } + if r.missingSecret { + s += "\n- secret" + } + if r.missingSite { + s += "\n- site name" + } + return s } func checkEnv(fs *flag.FlagSet) error { diff --git a/cmd/spectre/main_test.go b/cmd/spectre/main_test.go new file mode 100644 index 0000000..2f28f8d --- /dev/null +++ b/cmd/spectre/main_test.go @@ -0,0 +1,76 @@ +package main + +import ( + _ "embed" + "encoding/xml" + "fmt" + "testing" +) + +// These are the exact same tests as spectre_test.go +// These are here just to make sure the CLI is giving the same outputs +func TestCLI(t *testing.T) { + var tests TestCases + if err := xml.Unmarshal(testsXML, &tests); err != nil { + t.Log("could not load test data") + t.FailNow() + } + + dc := tests.Cases[0] + for _, tc := range tests.Cases[1:] { + t.Run(tc.ID, func(t *testing.T) { + user := def(dc.UserName, tc.UserName) + secret := def(dc.UserSecret, tc.UserSecret) + siteName := def(dc.SiteName, tc.SiteName) + template := def(dc.ResultType, tc.ResultType) + counter := def(dc.KeyCounter, tc.KeyCounter) + scope := def(dc.KeyPurpose, tc.KeyPurpose) + + args := []string{ + "--username", user, + "--secret", secret, + "--template", template, + "--counter", counter, + "--scope", scope, + siteName, + } + fmt.Println(args) + + pw, err := doMain(args) + if err != nil { + t.Log(err) + t.FailNow() + } + + if pw != tc.Result { + t.Log("passwords did not match") + t.Fail() + } + }) + } +} + +//go:embed spectre_tests.xml +var testsXML []byte + +type TestCases struct { + Cases []TestCase `xml:"case"` +} + +type TestCase struct { + ID string `xml:"id,attr"` + UserName string `xml:"userName"` + UserSecret string `xml:"userSecret"` + SiteName string `xml:"siteName"` + ResultType string `xml:"resultType"` + KeyCounter string `xml:"keyCounter"` + KeyPurpose string `xml:"keyPurpose"` + Result string `xml:"result"` +} + +func def(def, alt string) string { + if alt != "" { + return alt + } + return def +} diff --git a/spectre_tests.xml b/cmd/spectre/spectre_tests.xml similarity index 100% rename from spectre_tests.xml rename to cmd/spectre/spectre_tests.xml diff --git a/spectre_test.go b/spectre_test.go index 3bfcec8..cf888ad 100644 --- a/spectre_test.go +++ b/spectre_test.go @@ -84,7 +84,7 @@ func Example_second() { // Output: Ig^JIcxD!*)TbefJBi6- } -//go:embed spectre_tests.xml +//go:embed cmd/spectre/spectre_tests.xml var testsXML []byte type TestCases struct {