@@ -16,6 +16,27 @@ function findCommand(program: ReturnType<typeof createProgram>, ...names: string
1616 return current ;
1717}
1818
19+ // Parses argv expecting the unknown-command error path: captures stderr,
20+ // asserts process.exit(1), and returns the stripped stderr output.
21+ async function runExpectingError ( argv : string [ ] ) : Promise < string > {
22+ const prog = createProgram ( ) ;
23+ prog . exitOverride ( ) ;
24+ const errSpy = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
25+ const exitSpy = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ) => {
26+ throw new Error ( "process.exit" ) ;
27+ } ) ;
28+ try {
29+ await prog . parseAsync ( [ "node" , "geonic" , ...argv ] ) ;
30+ } catch {
31+ // expected
32+ }
33+ const output = stripAnsi ( errSpy . mock . calls . map ( ( c ) => c [ 0 ] ) . join ( "\n" ) ) ;
34+ expect ( exitSpy ) . toHaveBeenCalledWith ( 1 ) ;
35+ errSpy . mockRestore ( ) ;
36+ exitSpy . mockRestore ( ) ;
37+ return output ;
38+ }
39+
1940describe ( "help" , ( ) => {
2041 const program = createProgram ( ) ;
2142
@@ -609,21 +630,8 @@ describe("help", () => {
609630 } ) ;
610631
611632 it ( "shows error for invalid command name in help" , async ( ) => {
612- const prog = createProgram ( ) ;
613- prog . exitOverride ( ) ;
614- const errSpy = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
615- const exitSpy = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ) => {
616- throw new Error ( "process.exit" ) ;
617- } ) ;
618- try {
619- await prog . parseAsync ( [ "node" , "geonic" , "help" , "nonexistent" ] ) ;
620- } catch {
621- // expected
622- }
623- const output = errSpy . mock . calls . map ( ( c ) => c [ 0 ] ) . join ( "\n" ) ;
624- expect ( stripAnsi ( output ) ) . toContain ( "'nonexistent' is not a geonic command" ) ;
625- errSpy . mockRestore ( ) ;
626- exitSpy . mockRestore ( ) ;
633+ const output = await runExpectingError ( [ "help" , "nonexistent" ] ) ;
634+ expect ( output ) . toContain ( "'nonexistent' is not a geonic command" ) ;
627635 } ) ;
628636
629637 it ( "shows top-level help when help is called with no args" , async ( ) => {
@@ -684,22 +692,22 @@ describe("help", () => {
684692 } ) ;
685693
686694 describe ( "unknown subcommand with --help" , ( ) => {
687- async function runExpectingError ( argv : string [ ] ) : Promise < string > {
695+ // Parses argv expecting help to be shown: captures stdout and returns it,
696+ // asserting no error was printed.
697+ async function runExpectingHelp ( argv : string [ ] ) : Promise < string > {
688698 const prog = createProgram ( ) ;
689699 prog . exitOverride ( ) ;
700+ const writeSpy = vi . spyOn ( process . stdout , "write" ) . mockImplementation ( ( ) => true ) ;
690701 const errSpy = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
691- const exitSpy = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ) => {
692- throw new Error ( "process.exit" ) ;
693- } ) ;
694702 try {
695703 await prog . parseAsync ( [ "node" , "geonic" , ...argv ] ) ;
696704 } catch {
697- // expected
705+ // exitOverride throws on help
698706 }
699- const output = stripAnsi ( errSpy . mock . calls . map ( ( c ) => c [ 0 ] ) . join ( "\n" ) ) ;
700- expect ( exitSpy ) . toHaveBeenCalledWith ( 1 ) ;
707+ const output = stripAnsi ( writeSpy . mock . calls . map ( ( c ) => c [ 0 ] ) . join ( "" ) ) ;
708+ expect ( errSpy ) . not . toHaveBeenCalled ( ) ;
709+ writeSpy . mockRestore ( ) ;
701710 errSpy . mockRestore ( ) ;
702- exitSpy . mockRestore ( ) ;
703711 return output ;
704712 }
705713
@@ -720,20 +728,25 @@ describe("help", () => {
720728 } ) ;
721729
722730 it ( "still shows help for a leaf command with an argument and --help" , async ( ) => {
723- const prog = createProgram ( ) ;
724- prog . exitOverride ( ) ;
725- const writeSpy = vi . spyOn ( process . stdout , "write" ) . mockImplementation ( ( ) => true ) ;
726- const errSpy = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
727- try {
728- await prog . parseAsync ( [ "node" , "geonic" , "entities" , "get" , "urn:x" , "--help" ] ) ;
729- } catch {
730- // exitOverride throws on help
731- }
732- const output = stripAnsi ( writeSpy . mock . calls . map ( ( c ) => c [ 0 ] ) . join ( "" ) ) ;
731+ const output = await runExpectingHelp ( [ "entities" , "get" , "urn:x" , "--help" ] ) ;
733732 expect ( output ) . toContain ( "geonic entities get" ) ;
734- expect ( errSpy ) . not . toHaveBeenCalled ( ) ;
735- writeSpy . mockRestore ( ) ;
736- errSpy . mockRestore ( ) ;
733+ } ) ;
734+
735+ it ( "echoes the alias the user typed, not the canonical command name" , async ( ) => {
736+ const output = await runExpectingError ( [ "models" , "badsub" , "--help" ] ) ;
737+ expect ( output ) . toContain ( "'models badsub' is not a geonic command" ) ;
738+ expect ( output ) . not . toContain ( "custom-data-models" ) ;
739+ } ) ;
740+
741+ it ( "does not mistake an unknown option's value for a subcommand" , async ( ) => {
742+ // "--bogus json" must not be reported as `geonic entities json`
743+ const output = await runExpectingHelp ( [ "entities" , "--bogus" , "json" , "--help" ] ) ;
744+ expect ( output ) . toContain ( "geonic entities" ) ;
745+ } ) ;
746+
747+ it ( "shows top-level help when --help precedes a non-command token" , async ( ) => {
748+ const output = await runExpectingHelp ( [ "--help" , "hello" ] ) ;
749+ expect ( output ) . toContain ( "AVAILABLE COMMANDS" ) ;
737750 } ) ;
738751 } ) ;
739752
0 commit comments