diff --git a/apps/openant-cli/cmd/parse.go b/apps/openant-cli/cmd/parse.go index 988801f..563ca5a 100644 --- a/apps/openant-cli/cmd/parse.go +++ b/apps/openant-cli/cmd/parse.go @@ -34,12 +34,33 @@ var ( func init() { parseCmd.Flags().StringVarP(&parseOutput, "output", "o", "", "Output directory (default: project scan dir)") parseCmd.Flags().StringVarP(&parseLanguage, "language", "l", "", "Language: python, javascript, go, c, ruby, php, auto") - parseCmd.Flags().StringVar(&parseLevel, "level", "all", "Processing level: all, reachable, codeql, exploitable") + parseCmd.Flags().StringVar(&parseLevel, "level", "reachable", "Processing level: all, reachable, codeql, exploitable") parseCmd.Flags().StringVar(&parseDiffBase, "diff-base", "", "Incremental mode: tag units overlapping diff vs this ref") parseCmd.Flags().IntVar(&parsePR, "pr", 0, "Incremental mode against a GitHub PR number (mutex with --diff-base)") parseCmd.Flags().StringVar(&parseDiffScope, "diff-scope", "changed_functions", "Diff scope: changed_files, changed_functions, callers") } +// buildParsePyArgs assembles the argv passed to the Python `openant parse` +// subprocess. Defaults that match the Python CLI (language=auto, +// level=reachable) are omitted so the Python side stays in charge of the +// canonical default value. +func buildParsePyArgs(repoPath, output, datasetName, language, level, manifestPath string) []string { + pyArgs := []string{"parse", repoPath, "--output", output} + if datasetName != "" { + pyArgs = append(pyArgs, "--name", datasetName) + } + if language != "auto" { + pyArgs = append(pyArgs, "--language", language) + } + if level != "reachable" { + pyArgs = append(pyArgs, "--level", level) + } + if manifestPath != "" { + pyArgs = append(pyArgs, "--diff-manifest", manifestPath) + } + return pyArgs +} + func runParse(cmd *cobra.Command, args []string) { repoPath, ctx, err := resolveRepoArg(args) if err != nil { @@ -92,19 +113,7 @@ func runParse(cmd *cobra.Command, args []string) { } } - pyArgs := []string{"parse", repoPath, "--output", parseOutput} - if datasetName != "" { - pyArgs = append(pyArgs, "--name", datasetName) - } - if parseLanguage != "auto" { - pyArgs = append(pyArgs, "--language", parseLanguage) - } - if parseLevel != "all" { - pyArgs = append(pyArgs, "--level", parseLevel) - } - if manifestPath != "" { - pyArgs = append(pyArgs, "--diff-manifest", manifestPath) - } + pyArgs := buildParsePyArgs(repoPath, parseOutput, datasetName, parseLanguage, parseLevel, manifestPath) result, err := python.Invoke(rt.Path, pyArgs, "", quiet, resolvedAPIKey()) if err != nil { diff --git a/apps/openant-cli/cmd/parse_test.go b/apps/openant-cli/cmd/parse_test.go new file mode 100644 index 0000000..e080df2 --- /dev/null +++ b/apps/openant-cli/cmd/parse_test.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "strings" + "testing" +) + +func TestParseLevelFlagDefaultIsReachable(t *testing.T) { + flag := parseCmd.Flag("level") + if flag == nil { + t.Fatal("parseCmd has no --level flag") + } + if got, want := flag.DefValue, "reachable"; got != want { + t.Errorf("--level default = %q, want %q", got, want) + } +} + +func TestParseLevelFlagUsageMentionsChoices(t *testing.T) { + flag := parseCmd.Flag("level") + if flag == nil { + t.Fatal("parseCmd has no --level flag") + } + for _, choice := range []string{"all", "reachable", "codeql", "exploitable"} { + if !strings.Contains(flag.Usage, choice) { + t.Errorf("--level usage missing %q: %q", choice, flag.Usage) + } + } +} + +func TestBuildParsePyArgsLevelForwarding(t *testing.T) { + tests := []struct { + name string + level string + wantLevel bool // true if --level should appear in argv + }{ + {"default reachable is omitted", "reachable", false}, + {"all is forwarded", "all", true}, + {"codeql is forwarded", "codeql", true}, + {"exploitable is forwarded", "exploitable", true}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + args := buildParsePyArgs("/repo", "/out", "", "auto", tc.level, "") + gotLevel, gotValue := findFlag(args, "--level") + if gotLevel != tc.wantLevel { + t.Errorf("--level present = %v, want %v (argv=%v)", gotLevel, tc.wantLevel, args) + } + if tc.wantLevel && gotValue != tc.level { + t.Errorf("--level value = %q, want %q (argv=%v)", gotValue, tc.level, args) + } + }) + } +} + +func TestBuildParsePyArgsBaseline(t *testing.T) { + args := buildParsePyArgs("/repo", "/out", "org-repo-abc1234", "python", "exploitable", "/tmp/manifest.json") + want := []string{ + "parse", "/repo", + "--output", "/out", + "--name", "org-repo-abc1234", + "--language", "python", + "--level", "exploitable", + "--diff-manifest", "/tmp/manifest.json", + } + if len(args) != len(want) { + t.Fatalf("argv = %v, want %v", args, want) + } + for i := range want { + if args[i] != want[i] { + t.Errorf("argv[%d] = %q, want %q (full=%v)", i, args[i], want[i], args) + } + } +} + +// findFlag returns whether name is present in argv, and its following value +// (or "" if it has no value). +func findFlag(argv []string, name string) (bool, string) { + for i, a := range argv { + if a == name { + if i+1 < len(argv) { + return true, argv[i+1] + } + return true, "" + } + } + return false, "" +}