/ tests / orchestrator-claude-bin.test.sh
orchestrator-claude-bin.test.sh
  1  #!/bin/sh
  2  # Test: resolve_claude_bin skips wrapper scripts and picks real binaries.
  3  # This validates the fix for the CLAUDE_BIN stale-version bug where
  4  # the orchestrator resolved once at startup and never re-resolved when
  5  # Claude auto-updates deleted the old version.
  6  #
  7  # Run: sh tests/orchestrator-claude-bin.test.sh
  8  
  9  set -e
 10  
 11  PASS=0
 12  FAIL=0
 13  
 14  assert_eq() {
 15    local label="$1" expected="$2" actual="$3"
 16    if [ "$expected" = "$actual" ]; then
 17      echo "  PASS: $label"
 18      PASS=$((PASS + 1))
 19    else
 20      echo "  FAIL: $label (expected '$expected', got '$actual')"
 21      FAIL=$((FAIL + 1))
 22    fi
 23  }
 24  
 25  # Create temp directory simulating ~/.local/share/claude/versions/
 26  TMPDIR=$(mktemp -d)
 27  trap "rm -rf $TMPDIR" EXIT
 28  
 29  VERSIONS_DIR="$TMPDIR/versions"
 30  mkdir -p "$VERSIONS_DIR"
 31  
 32  # Create a fake binary (>1MB)
 33  dd if=/dev/zero of="$VERSIONS_DIR/2.1.78" bs=1M count=2 2>/dev/null
 34  chmod +x "$VERSIONS_DIR/2.1.78"
 35  
 36  # Create a wrapper script (small, like the real 2.1.79)
 37  cat > "$VERSIONS_DIR/2.1.79" << 'WRAPPER'
 38  #!/bin/sh
 39  echo "I am a wrapper"
 40  WRAPPER
 41  chmod +x "$VERSIONS_DIR/2.1.79"
 42  
 43  echo "Test 1: resolve_claude_bin picks binary over wrapper"
 44  # Source the resolve function with our test directory
 45  HOME_SAVE="$HOME"
 46  export HOME="$TMPDIR"
 47  mkdir -p "$TMPDIR/.local/share/claude"
 48  ln -s "$VERSIONS_DIR" "$TMPDIR/.local/share/claude/versions"
 49  
 50  # Extract and run resolve_claude_bin
 51  resolve_claude_bin() {
 52    _claude_bin=""
 53    if [ -d "$HOME/.local/share/claude/versions" ]; then
 54      for _v in $(ls -1 "$HOME/.local/share/claude/versions" 2>/dev/null | sort -V -r); do
 55        _candidate="$HOME/.local/share/claude/versions/$_v"
 56        _sz=$(stat -c%s "$_candidate" 2>/dev/null || echo 0)
 57        if [ -x "$_candidate" ] && [ "$_sz" -gt 1000000 ]; then
 58          _claude_bin="$_candidate"
 59          break
 60        fi
 61      done
 62    fi
 63    export CLAUDE_BIN="$_claude_bin"
 64  }
 65  
 66  resolve_claude_bin
 67  # CLAUDE_BIN may resolve through symlink — check basename matches
 68  _resolved_base=$(basename "$CLAUDE_BIN")
 69  assert_eq "should pick 2.1.78 (binary)" "2.1.78" "$_resolved_base"
 70  
 71  echo ""
 72  echo "Test 2: resolve_claude_bin handles only wrapper scripts"
 73  rm "$VERSIONS_DIR/2.1.78"
 74  resolve_claude_bin
 75  assert_eq "should be empty when only wrapper exists" "" "$CLAUDE_BIN"
 76  
 77  echo ""
 78  echo "Test 3: resolve_claude_bin handles empty directory"
 79  rm "$VERSIONS_DIR/2.1.79"
 80  resolve_claude_bin
 81  assert_eq "should be empty when dir is empty" "" "$CLAUDE_BIN"
 82  
 83  echo ""
 84  echo "Test 4: wrapper script resolves to binary (not itself)"
 85  # Recreate both
 86  dd if=/dev/zero of="$VERSIONS_DIR/2.1.78" bs=1M count=2 2>/dev/null
 87  chmod +x "$VERSIONS_DIR/2.1.78"
 88  
 89  cat > "$VERSIONS_DIR/2.1.79" << WRAPPER
 90  #!/bin/sh
 91  _dir="$VERSIONS_DIR"
 92  _latest=\$(ls -1 "\$_dir" 2>/dev/null | grep -v '\.tmp\$' | sort -V | while read -r v; do
 93    _f="\$_dir/\$v"
 94    _sz=\$(stat -c%s "\$_f" 2>/dev/null || echo 0)
 95    [ -x "\$_f" ] && [ "\$_sz" -gt 1000000 ] && echo "\$v"
 96  done | tail -1)
 97  if [ -n "\$_latest" ]; then
 98    echo "\$_dir/\$_latest"
 99  fi
100  WRAPPER
101  chmod +x "$VERSIONS_DIR/2.1.79"
102  
103  RESOLVED=$("$VERSIONS_DIR/2.1.79")
104  assert_eq "wrapper should resolve to 2.1.78" "$VERSIONS_DIR/2.1.78" "$RESOLVED"
105  
106  export HOME="$HOME_SAVE"
107  
108  echo ""
109  echo "Results: $PASS passed, $FAIL failed"
110  [ "$FAIL" -eq 0 ] || exit 1