|
1 | 1 | import { describe, test, expect } from "bun:test"; |
2 | 2 | import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises"; |
3 | 3 | import { tmpdir } from "node:os"; |
4 | | -import { join } from "node:path"; |
| 4 | +import { join, resolve } from "node:path"; |
5 | 5 |
|
6 | 6 | import { |
7 | 7 | buildBifrostSource, |
@@ -73,6 +73,18 @@ async function emptyCwd(): Promise<string> { |
73 | 73 | return mkdtemp(join(tmpdir(), "ic-config-")); |
74 | 74 | } |
75 | 75 |
|
| 76 | +async function expectCliHelp(argv: readonly string[]): Promise<void> { |
| 77 | + try { |
| 78 | + await loadConfig([...argv], { globalSettingsPath: NO_SETTINGS }); |
| 79 | + expect.unreachable("expected CliHelpError"); |
| 80 | + } catch (err) { |
| 81 | + expect(err).toBeInstanceOf(CliHelpError); |
| 82 | + const help = err as CliHelpError; |
| 83 | + expect(help.exitCode).toBe(0); |
| 84 | + expect(help.message).toBe(CLI_HELP_TEXT); |
| 85 | + } |
| 86 | +} |
| 87 | + |
76 | 88 | describe("loadConfig", () => { |
77 | 89 | test("resolves provider from the global settings file", async () => { |
78 | 90 | const cwd = await emptyCwd(); |
@@ -726,21 +738,90 @@ describe("loadConfig", () => { |
726 | 738 | }); |
727 | 739 |
|
728 | 740 | test("--help throws CliHelpError with exitCode 0 and full help text", async () => { |
729 | | - await expect( |
730 | | - loadConfig(["--help"], { globalSettingsPath: NO_SETTINGS }), |
731 | | - ).rejects.toBeInstanceOf(CliHelpError); |
732 | | - try { |
733 | | - await loadConfig(["-h"], { globalSettingsPath: NO_SETTINGS }); |
734 | | - expect.unreachable("expected CliHelpError"); |
735 | | - } catch (err) { |
736 | | - expect(err).toBeInstanceOf(CliHelpError); |
737 | | - const help = err as CliHelpError; |
738 | | - expect(help.exitCode).toBe(0); |
739 | | - expect(help.message).toBe(CLI_HELP_TEXT); |
740 | | - expect(help.message).toContain("resume"); |
| 741 | + await expectCliHelp(["--help"]); |
| 742 | + await expectCliHelp(["-h"]); |
| 743 | + }); |
| 744 | + |
| 745 | + test("--help after flags throws CliHelpError", async () => { |
| 746 | + await expectCliHelp(["--force", "--help"]); |
| 747 | + await expectCliHelp(["--force", "-h"]); |
| 748 | + }); |
| 749 | + |
| 750 | + test("--help after a positional throws CliHelpError", async () => { |
| 751 | + await expectCliHelp(["ship it", "--help"]); |
| 752 | + await expectCliHelp(["ship", "it", "--help"]); |
| 753 | + }); |
| 754 | + |
| 755 | + test("--help after a bound flag value throws CliHelpError", async () => { |
| 756 | + await expectCliHelp(["--cwd", ".", "--help"]); |
| 757 | + await expectCliHelp(["--provider", "fireworks", "--help"]); |
| 758 | + }); |
| 759 | + |
| 760 | + test("exec --help throws CliHelpError", async () => { |
| 761 | + await expectCliHelp(["exec", "--help"]); |
| 762 | + await expectCliHelp(["exec", "--director", "--help"]); |
| 763 | + }); |
| 764 | + |
| 765 | + test("resume --pick --help throws CliHelpError", async () => { |
| 766 | + await expectCliHelp(["resume", "--pick", "--help"]); |
| 767 | + }); |
| 768 | + |
| 769 | + test("resume -h / --help throws CliHelpError instead of treating it as a session id", async () => { |
| 770 | + await expectCliHelp(["resume", "-h"]); |
| 771 | + await expectCliHelp(["resume", "--help"]); |
| 772 | + await expectCliHelp(["continue", "-h"]); |
| 773 | + }); |
| 774 | + |
| 775 | + test("value flags do not swallow --help / -h as their value", async () => { |
| 776 | + for (const flag of ["--provider", "--model", "--cwd", "--config", "--profile"] as const) { |
| 777 | + await expectCliHelp([flag, "--help"]); |
| 778 | + await expectCliHelp([flag, "-h"]); |
741 | 779 | } |
742 | 780 | }); |
743 | 781 |
|
| 782 | + test("value flags reject other flag-shaped tokens as values", async () => { |
| 783 | + await expect( |
| 784 | + loadConfig(["--provider", "--force"], { globalSettingsPath: NO_SETTINGS }), |
| 785 | + ).rejects.toThrow("--provider requires a value"); |
| 786 | + await expect( |
| 787 | + loadConfig(["--model", "--cwd"], { globalSettingsPath: NO_SETTINGS }), |
| 788 | + ).rejects.toThrow("--model requires a value"); |
| 789 | + await expect( |
| 790 | + loadConfig(["--cwd", "--tmp"], { globalSettingsPath: NO_SETTINGS }), |
| 791 | + ).rejects.toThrow("--cwd requires a value"); |
| 792 | + await expect( |
| 793 | + loadConfig(["exec", "--director", "--force", "ship it"], { |
| 794 | + globalSettingsPath: NO_SETTINGS, |
| 795 | + }), |
| 796 | + ).rejects.toThrow("--director requires a value"); |
| 797 | + }); |
| 798 | + |
| 799 | + test("value flags still error clearly when the value is omitted", async () => { |
| 800 | + await expect(loadConfig(["--provider"], { globalSettingsPath: NO_SETTINGS })).rejects.toThrow( |
| 801 | + "--provider requires a value", |
| 802 | + ); |
| 803 | + await expect(loadConfig(["--model"], { globalSettingsPath: NO_SETTINGS })).rejects.toThrow( |
| 804 | + "--model requires a value", |
| 805 | + ); |
| 806 | + await expect(loadConfig(["--cwd"], { globalSettingsPath: NO_SETTINGS })).rejects.toThrow( |
| 807 | + "--cwd requires a value", |
| 808 | + ); |
| 809 | + await expect(loadConfig(["--config"], { globalSettingsPath: NO_SETTINGS })).rejects.toThrow( |
| 810 | + "--config requires a value", |
| 811 | + ); |
| 812 | + await expect(loadConfig(["--profile"], { globalSettingsPath: NO_SETTINGS })).rejects.toThrow( |
| 813 | + "--profile requires a value", |
| 814 | + ); |
| 815 | + }); |
| 816 | + |
| 817 | + test("value flags accept a POSIX path that starts with a single dash", async () => { |
| 818 | + const config = await loadConfig(["--cwd", "-my-dir", "do something"], { |
| 819 | + allowUnconfigured: true, |
| 820 | + globalSettingsPath: NO_SETTINGS, |
| 821 | + }); |
| 822 | + expect(config.cwd).toBe(resolve("-my-dir")); |
| 823 | + }); |
| 824 | + |
744 | 825 | test("rejects unknown flags", async () => { |
745 | 826 | await expect(loadConfig(["--unknown"], { globalSettingsPath: NO_SETTINGS })).rejects.toThrow( |
746 | 827 | /unrecognized flag/, |
|
0 commit comments