diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 94742d34..d4c7fc71 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -361,11 +361,13 @@ class CliAppTest < CliTestCase SSHKit::Backend::Abstract.any_instance.expects(:exec) .with("ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v'") - run_command("exec", "-i", "ruby -v").tap do |output| - assert_hook_ran "pre-connect", output - assert_match "docker login -u [REDACTED] -p [REDACTED]", output - assert_match "Get most recent version available as an image...", output - assert_match "Launching interactive command with version latest via SSH from new container on 1.1.1.1...", output + stub_stdin_tty do + run_command("exec", "-i", "ruby -v").tap do |output| + assert_hook_ran "pre-connect", output + assert_match "docker login -u [REDACTED] -p [REDACTED]", output + assert_match "Get most recent version available as an image...", output + assert_match "Launching interactive command with version latest via SSH from new container on 1.1.1.1...", output + end end end @@ -374,11 +376,26 @@ class CliAppTest < CliTestCase SSHKit::Backend::Abstract.any_instance.expects(:exec) .with("ssh -t root@1.1.1.1 -p 22 'docker exec -it app-web-999 ruby -v'") - run_command("exec", "-i", "--reuse", "ruby -v").tap do |output| - assert_hook_ran "pre-connect", output - assert_match "Get current version of running container...", output - assert_match "Running /usr/bin/env sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done on 1.1.1.1", output - assert_match "Launching interactive command with version 999 via SSH from existing container on 1.1.1.1...", output + stub_stdin_tty do + run_command("exec", "-i", "--reuse", "ruby -v").tap do |output| + assert_hook_ran "pre-connect", output + assert_match "Get current version of running container...", output + assert_match "Running /usr/bin/env sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done on 1.1.1.1", output + assert_match "Launching interactive command with version 999 via SSH from existing container on 1.1.1.1...", output + end + end + end + + test "exec interactive with pipe on STDIN" do + Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) + SSHKit::Backend::Abstract.any_instance.expects(:exec) + .with("ssh -t root@1.1.1.1 -p 22 'docker exec -i app-web-999 ruby -v'") + + stub_stdin_file do + run_command("exec", "-i", "--reuse", "ruby -v").tap do |output| + assert_hook_ran "pre-connect", output + assert_match "Launching interactive command with version 999 via SSH from existing container on 1.1.1.1...", output + end end end diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 6ff9902e..19db9dcf 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -118,14 +118,21 @@ class CommandsAccessoryTest < ActiveSupport::TestCase test "execute in new container over ssh" do new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do assert_match %r{docker run -it --rm --network kamal --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env private.registry/mysql:8.0 mysql -u root}, - new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root") + stub_stdin_tty { new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root") } end end test "execute in existing container over ssh" do new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do assert_match %r{docker exec -it app-mysql mysql -u root}, - new_command(:mysql).execute_in_existing_container_over_ssh("mysql", "-u", "root") + stub_stdin_tty { new_command(:mysql).execute_in_existing_container_over_ssh("mysql", "-u", "root") } + end + end + + test "execute in existing container with piped input over ssh" do + new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do + assert_match %r{docker exec -i app-mysql mysql -u root}, + stub_stdin_file { new_command(:mysql).execute_in_existing_container_over_ssh("mysql", "-u", "root") } end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 33418a00..284c8eda 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -288,7 +288,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "execute in new container over ssh" do assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size="10m" dhh/app:999 bin/rails c}, - new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) + stub_stdin_tty { new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) } end test "execute in new container over ssh with tags" do @@ -296,18 +296,23 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } } assert_equal "ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails c'", - new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) + stub_stdin_tty { new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) } end test "execute in new container with custom options over ssh" do @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } } assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c}, - new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) + stub_stdin_tty { new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) } end test "execute in existing container over ssh" do assert_match %r{docker exec -it app-web-999 bin/rails c}, - new_command.execute_in_existing_container_over_ssh("bin/rails", "c", env: {}) + stub_stdin_tty { new_command.execute_in_existing_container_over_ssh("bin/rails", "c", env: {}) } + end + + test "execute in existing container with piped input over ssh" do + assert_match %r{docker exec -i app-web-999 bin/rails c}, + stub_stdin_file { new_command.execute_in_existing_container_over_ssh("bin/rails", "c", env: {}) } end test "run over ssh" do diff --git a/test/test_helper.rb b/test/test_helper.rb index 1704fa29..fd72cc9c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -3,6 +3,7 @@ require "active_support/test_case" require "active_support/testing/autorun" require "active_support/testing/stream" require "rails/test_unit/line_filtering" +require "pty" require "debug" require "mocha/minitest" # using #stubs that can alter returns require "minitest/autorun" # using #stub that take args @@ -48,6 +49,27 @@ class ActiveSupport::TestCase capture(:stderr) { yield }.strip end + def stub_stdin_tty + PTY.open do |master, slave| + stub_stdin(master) { yield } + end + end + + def stub_stdin_file + File.open("/dev/null", "r") do |file| + stub_stdin(file) { yield } + end + end + + def stub_stdin(io) + original_stdin = STDIN.dup + STDIN.reopen(io) + yield + ensure + STDIN.reopen(original_stdin) + original_stdin.close + end + def with_test_secrets(**files) setup_test_secrets(**files) yield