+
+ # Last-resort fallback: no rule matches "ls", but it's a recognized $PATH
+ # executable, so it resolves via command passthrough instead of failing.
+ def test_resolves_a_recognized_executable_via_command_passthrough
+ clause = Utterance.build("ls").clauses.first
+ assert_equal :command, clause.tier
+ assert_equal "execute", clause.node[:name]
+ end
+end
+
+class CommandToolTest < Minitest::Test
+ def test_lookup_matches_a_clause_starting_with_a_command_token
+ tokens = Lexer.downcase_words(Lexer.tokenize("ls -la"))
+ node = CommandTool.lookup(tokens)
+ refute_nil node
+ assert_equal "execute", node[:name]
+ assert_equal %w[ls -la], node[:args][:argv].map(&:text)
+ end
+
+ def test_lookup_misses_on_an_ordinary_word
+ tokens = Lexer.downcase_words(Lexer.tokenize("bork the garble"))
+ assert_nil CommandTool.lookup(tokens)
+ end
+
+ def test_lookup_drops_trailing_sentence_punctuation
+ tokens = Lexer.downcase_words(Lexer.tokenize("ls."))
+ node = CommandTool.lookup(tokens)
+ assert_equal ["ls"], node[:args][:argv].map(&:text)
+ end
+
+ # The whole point of argv-form Open3 (no shell string) is that shell
+ # metacharacters in a literal's text can't be interpreted - this is the
+ # property that makes passthrough safe to add at all.
+ def test_execute_does_not_invoke_a_shell
+ argv = [Lexer::Token.new(:command, "echo"), Lexer::Token.new(:literal, "a; echo pwned", :string)]
+ out, = capture_io { CommandTool::TOOL.action.call(argv: argv) }
+ assert_equal "a; echo pwned\n", out
+ end