Back to Repositories

Testing CLI Command Execution and Hook Management in Kamal

This test suite implements unit testing for the CLI functionality of Kamal, focusing on command execution, environment variable handling, and hook management. It provides comprehensive test coverage for the command-line interface components with proper setup and teardown procedures.

Test Coverage Overview

The test suite provides extensive coverage of CLI operations including environment variable management, hook execution, and directory setup. Key functionality tested includes:

  • Environment variable handling for VERSION, RAILS_MASTER_KEY, and MYSQL_ROOT_PASSWORD
  • Hook execution and failure scenarios
  • Directory structure creation and management
  • Docker buildx inspection verification

Implementation Analysis

The testing approach utilizes ActiveSupport::TestCase as the base framework, implementing setup and teardown methods for consistent test environments. The implementation employs stubbing patterns through SSHKit backend abstractions and includes helper methods for hook validation and command execution verification.

Technical Details

Testing tools and components include:

  • ActiveSupport::TestCase framework
  • SSHKit for command execution simulation
  • Custom helper methods for hook testing and command verification
  • Environment variable manipulation utilities
  • Docker buildx integration testing capabilities

Best Practices Demonstrated

The test suite exemplifies several testing best practices including proper test isolation through setup/teardown methods, comprehensive stub implementation for external dependencies, and clear helper method organization. Notable practices include:

  • Consistent environment cleanup
  • Modular helper methods for common testing scenarios
  • Robust command execution verification
  • Clear separation of test setup and assertions

basecamp/kamal

test/cli/cli_test_case.rb

            
require "test_helper"

class CliTestCase < ActiveSupport::TestCase
  setup do
    ENV["VERSION"]             = "999"
    ENV["RAILS_MASTER_KEY"]    = "123"
    ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
    Object.send(:remove_const, :KAMAL)
    Object.const_set(:KAMAL, Kamal::Commander.new)
  end

  teardown do
    ENV.delete("RAILS_MASTER_KEY")
    ENV.delete("MYSQL_ROOT_PASSWORD")
    ENV.delete("VERSION")
  end

  private
    def fail_hook(hook)
      @executions = []
      Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)

      SSHKit::Backend::Abstract.any_instance.stubs(:execute)
        .with { |*args| @executions << args; args != [ ".kamal/hooks/#{hook}" ] }
      SSHKit::Backend::Abstract.any_instance.stubs(:execute)
        .with { |*args| args.first == ".kamal/hooks/#{hook}" }
        .raises(SSHKit::Command::Failed.new("failed"))
    end

    def stub_setup
      SSHKit::Backend::Abstract.any_instance.stubs(:execute)
        .with { |*args| args == [ :mkdir, "-p", ".kamal/apps/app" ] }
      SSHKit::Backend::Abstract.any_instance.stubs(:execute)
        .with { |arg1, arg2, arg3| arg1 == :mkdir && arg2 == "-p" && arg3 == ".kamal/lock-app" }
      SSHKit::Backend::Abstract.any_instance.stubs(:execute)
        .with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/lock-app" }
      SSHKit::Backend::Abstract.any_instance.stubs(:execute)
        .with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/lock-app/details" }
      SSHKit::Backend::Abstract.any_instance.stubs(:execute)
        .with(:docker, :buildx, :inspect, "kamal-local-docker-container")
    end

    def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false, secrets: false)
      assert_match %r{usr/bin/env\s\.kamal/hooks/#{hook}}, output
    end

    def with_argv(*argv)
      old_argv = ARGV
      ARGV.replace(*argv)
      yield
    ensure
      ARGV.replace(old_argv)
    end
end