|
6 | 6 | class CommandsTest < Minitest::Test |
7 | 7 | Commands = ImportmapUpdate::Commands |
8 | 8 |
|
9 | | - # ---- ShellRunner ---- |
10 | | - |
11 | 9 | def test_shell_runner_captures_stdout |
12 | 10 | result = Commands::ShellRunner.new.run("echo", "hello world") |
13 | | - assert_equal "hello world\n", result.stdout |
| 11 | + assert_equal "hello world\n", result.output |
14 | 12 | assert_predicate result, :success? |
15 | 13 | assert_equal 0, result.exit_code |
16 | 14 | end |
17 | 15 |
|
18 | 16 | def test_shell_runner_captures_stderr_and_failure |
19 | | - # `sh -c` lets us write to stderr cleanly without depending on a |
20 | | - # specific binary's behavior. |
21 | 17 | result = Commands::ShellRunner.new.run("sh", "-c", "echo oops 1>&2; exit 3") |
22 | | - assert_equal "oops\n", result.stderr |
| 18 | + assert_equal "oops\n", result.output |
23 | 19 | assert_equal 3, result.exit_code |
24 | 20 | refute_predicate result, :success? |
25 | 21 | end |
26 | | - |
27 | | - def test_shell_runner_bang_raises_on_non_zero_exit |
28 | | - err = assert_raises(Commands::CommandError) do |
29 | | - Commands::ShellRunner.new.run!("sh", "-c", "echo nope 1>&2; exit 1") |
30 | | - end |
31 | | - assert_equal 1, err.result.exit_code |
32 | | - assert_includes err.message, "exited 1" |
33 | | - assert_includes err.message, "nope" |
34 | | - end |
35 | | - |
36 | | - def test_shell_runner_argv_is_safe_from_shell_metacharacters |
37 | | - # argv-style invocation must not interpret `;` as a command separator. |
38 | | - # If it did, this test would attempt to delete a file. |
39 | | - result = Commands::ShellRunner.new.run("echo", "a; rm -rf /b") |
40 | | - assert_equal "a; rm -rf /b\n", result.stdout |
41 | | - end |
42 | | - |
43 | | - # ---- FixtureRunner: literal matching ---- |
44 | | - |
45 | | - def test_fixture_runner_returns_recorded_result_for_exact_argv_match |
46 | | - runner = Commands::FixtureRunner.new |
47 | | - runner.add( |
48 | | - pattern: ["bin/importmap", "outdated"], |
49 | | - stdout: "| Package | Current | Latest |\n" |
50 | | - ) |
51 | | - result = runner.run("bin/importmap", "outdated") |
52 | | - assert_equal "| Package | Current | Latest |\n", result.stdout |
53 | | - assert_predicate result, :success? |
54 | | - end |
55 | | - |
56 | | - def test_fixture_runner_records_calls_in_order |
57 | | - runner = Commands::FixtureRunner.new |
58 | | - runner.add(pattern: ["bin/importmap", "outdated"], stdout: "") |
59 | | - runner.add(pattern: ["bin/importmap", "audit"], stdout: "") |
60 | | - runner.run("bin/importmap", "outdated") |
61 | | - runner.run("bin/importmap", "audit") |
62 | | - assert_equal [ |
63 | | - ["bin/importmap", "outdated"], |
64 | | - ["bin/importmap", "audit"] |
65 | | - ], runner.calls |
66 | | - end |
67 | | - |
68 | | - def test_fixture_runner_first_matching_pattern_wins |
69 | | - # Order matters: if you register a fallback first, it'll swallow more |
70 | | - # specific patterns. This test pins the behavior so callers know to |
71 | | - # register specific patterns before general ones. |
72 | | - runner = Commands::FixtureRunner.new |
73 | | - runner.add(pattern: ["bin/importmap", "pin", "lodash@4.17.21"], stdout: "first\n") |
74 | | - runner.add(pattern: ["bin/importmap", "pin", "lodash@4.17.21"], stdout: "second\n") |
75 | | - assert_equal "first\n", runner.run("bin/importmap", "pin", "lodash@4.17.21").stdout |
76 | | - end |
77 | | - |
78 | | - # ---- FixtureRunner: regex matching ---- |
79 | | - |
80 | | - def test_fixture_runner_supports_regex_elements_in_patterns |
81 | | - # Package versions change; the pattern uses a regex to allow any semver. |
82 | | - runner = Commands::FixtureRunner.new |
83 | | - runner.add( |
84 | | - pattern: ["bin/importmap", "pin", /\Alodash@\d+\.\d+\.\d+\z/], |
85 | | - stdout: "Pinned lodash\n" |
86 | | - ) |
87 | | - assert_equal "Pinned lodash\n", runner.run("bin/importmap", "pin", "lodash@4.17.21").stdout |
88 | | - end |
89 | | - |
90 | | - def test_fixture_runner_regex_must_match_exactly_at_position |
91 | | - runner = Commands::FixtureRunner.new |
92 | | - runner.add(pattern: ["bin/importmap", "pin", /\Alodash@/], stdout: "ok\n") |
93 | | - err = assert_raises(RuntimeError) do |
94 | | - runner.run("bin/importmap", "pin", "axios@1.7.0") |
95 | | - end |
96 | | - assert_match(/No fixture matched/, err.message) |
97 | | - end |
98 | | - |
99 | | - # ---- FixtureRunner: misses and errors ---- |
100 | | - |
101 | | - def test_fixture_runner_raises_clearly_when_no_pattern_matches |
102 | | - runner = Commands::FixtureRunner.new |
103 | | - runner.add(pattern: ["bin/importmap", "outdated"], stdout: "") |
104 | | - err = assert_raises(RuntimeError) { runner.run("bin/importmap", "audit") } |
105 | | - assert_includes err.message, "No fixture matched" |
106 | | - assert_includes err.message, "audit" |
107 | | - end |
108 | | - |
109 | | - def test_fixture_runner_argv_size_mismatch_does_not_match |
110 | | - # A 2-element pattern must not match a 3-element call. |
111 | | - runner = Commands::FixtureRunner.new |
112 | | - runner.add(pattern: ["bin/importmap", "outdated"], stdout: "ok\n") |
113 | | - assert_raises(RuntimeError) { runner.run("bin/importmap", "outdated", "--verbose") } |
114 | | - end |
115 | | - |
116 | | - def test_fixture_runner_bang_raises_command_error_on_recorded_failure |
117 | | - runner = Commands::FixtureRunner.new |
118 | | - runner.add( |
119 | | - pattern: ["bin/importmap", "pin", "lodash@4.17.21"], |
120 | | - stderr: "network error", |
121 | | - exit_code: 1 |
122 | | - ) |
123 | | - err = assert_raises(Commands::CommandError) { runner.run!("bin/importmap", "pin", "lodash@4.17.21") } |
124 | | - assert_equal 1, err.result.exit_code |
125 | | - assert_includes err.message, "network error" |
126 | | - end |
127 | 22 | end |
0 commit comments