#!/usr/bin/env bash
# Tests for the oo script — mode dispatch, promote wrappers, completions
TEST_CATEGORY=core

level=$1
if [ -z "$level" ]; then
  level=1
else
  shift
fi
echo "starting: ${BASH_SOURCE[@]##*/} <LOG_LEVEL=$level>"

source this
source test.suite
source $OOSH_DIR/oo

log.level $level

# ============================================================================
# T1: oo.mode.base.get returns a valid directory (3-level dotted method)
# ============================================================================
test.oo.t1() {
  local base
  base=$(oo.mode.base.get)
  if [ -d "$base" ]; then
    create.result 0 "$base"
  else
    create.result 1 "not a directory: $base"
  fi
}
test.case - "T1: oo.mode.base.get returns a directory path" \
  test.oo.t1
expect 0 "*" "oo.mode.base.get returns existing directory"

# ============================================================================
# T2: oo.mode.list runs without error (2-level dotted method)
# ============================================================================
test.case - "T2: oo.mode.list executes successfully" \
  oo.mode.list

create.result $? "mode.list ran"
expect 0 "mode.list ran" "oo.mode.list executes without error"

# ============================================================================
# T3: oo.mode shows current mode (1-level method via dispatcher)
# ============================================================================
test.case - "T3: oo.mode shows current mode" \
  oo.mode

create.result $? "mode ran"
expect 0 "mode ran" "oo.mode shows current mode without hanging"

# ============================================================================
# T4: oo.mode.base.set validates missing argument
# ============================================================================
test.oo.t4() {
  oo.mode.base.set
}
expect.error 1
test.case - "T4: oo.mode.base.set rejects missing argument" \
  test.oo.t4
expect 1 "*" "oo.mode.base.set requires path argument"

# ============================================================================
# T5: oo.mode.base.set validates non-existent directory
# ============================================================================
test.oo.t5() {
  oo.mode.base.set /nonexistent/path/$$
}
expect.error 1
test.case - "T5: oo.mode.base.set rejects non-existent directory" \
  test.oo.t5
expect 1 "*" "oo.mode.base.set rejects missing directory"

# ============================================================================
# T6: oo.mode.base.set accepts valid directory and persists
# ============================================================================
ORIGINAL_BASE=$(oo.mode.base.get)
TEST_DIR="/tmp/test.oo.$$"
mkdir -p "$TEST_DIR"

test.case - "T6: oo.mode.base.set accepts valid directory" \
  oo.mode.base.set "$TEST_DIR"

create.result $? "set to $TEST_DIR"
expect 0 "set to $TEST_DIR" "oo.mode.base.set accepts valid directory"

# ============================================================================
# T7: oo.mode.base.get returns the value set in T6
# ============================================================================
test.oo.t7() {
  local base
  base=$(oo.mode.base.get)
  if [ "$base" = "$TEST_DIR" ]; then
    create.result 0 "$base"
  else
    create.result 1 "expected=$TEST_DIR got=$base"
  fi
}
test.case - "T7: oo.mode.base.get returns persisted value" \
  test.oo.t7
expect 0 "$TEST_DIR" "oo.mode.base.get returns value set by mode.base.set"

# ============================================================================
# T8: oo.mode with non-existent branch shows error
# ============================================================================
test.oo.t8() {
  oo.mode "nonexistent_branch_$$"
}
expect.error 1
test.case - "T8: oo.mode rejects non-existent branch" \
  test.oo.t8
expect 1 "*" "oo.mode rejects non-existent branch"

# ============================================================================
# Cleanup: restore original base dir
# ============================================================================
if [ -n "$ORIGINAL_BASE" ] && [ -d "$ORIGINAL_BASE" ]; then
  oo.mode.base.set "$ORIGINAL_BASE"
fi
rm -rf "$TEST_DIR"

# ============================================================================
# Mode switch integration tests — fixture-based
# ============================================================================
# Save originals for teardown
SAVE_OOSH_DIR="$OOSH_DIR"
SAVE_OOSH_MODE="$OOSH_MODE"
SAVE_OOSH_COMPONENTS_DIR="$OOSH_COMPONENTS_DIR"
SAVE_PATH="$PATH"

# Build fixture: worktree base with two git dirs, one plain dir, one symlink
MODE_FIXTURE="/tmp/test.oo.mode.$$"
MODE_LINK="$MODE_FIXTURE/ooshTestLink"
mkdir -p "$MODE_FIXTURE/main" "$MODE_FIXTURE/dev" "$MODE_FIXTURE/notGit"
git init "$MODE_FIXTURE/main" >/dev/null 2>&1
git init "$MODE_FIXTURE/dev"  >/dev/null 2>&1
ln -s "$MODE_FIXTURE/dev" "$MODE_LINK"

# Point the mode system at the fixture
export OOSH_COMPONENTS_DIR="$MODE_FIXTURE"
export OOSH_DIR="$MODE_FIXTURE/dev"
export PATH="$MODE_FIXTURE/dev:$PATH"

# ============================================================================
# T-MODE-1: oo.mode switches symlink to target branch
# ============================================================================
test.oo.mode.switch() {
  # Remove stale link if present, then create initial link pointing to dev
  rm -f "$MODE_LINK"
  ln -s "$MODE_FIXTURE/dev" "$MODE_LINK"

  # Override HOME so oo.mode uses our fixture link instead of ~/oosh
  local HOME_ORIG="$HOME"
  HOME="$MODE_FIXTURE"
  ln -sf "$MODE_FIXTURE/dev" "$MODE_FIXTURE/oosh"

  oo.mode main >/dev/null 2>&1
  local rc=$?

  local target
  target=$(readlink "$MODE_FIXTURE/oosh")
  HOME="$HOME_ORIG"

  if [ $rc -eq 0 ] && [ "$target" = "$MODE_FIXTURE/main" ]; then
    create.result 0 "$target"
  else
    create.result 1 "rc=$rc target=$target expected=$MODE_FIXTURE/main"
  fi
}
test.case - "T-MODE-1: oo.mode switches symlink to target branch" \
  test.oo.mode.switch
expect 0 "$MODE_FIXTURE/main" "symlink points to main after oo.mode main"

# ============================================================================
# T-MODE-2: oo.mode updates OOSH_DIR
# ============================================================================
test.oo.mode.dir() {
  if [[ "$OOSH_DIR" == */main ]]; then
    create.result 0 "$OOSH_DIR"
  else
    create.result 1 "OOSH_DIR=$OOSH_DIR (expected */main)"
  fi
}
test.case - "T-MODE-2: oo.mode updates OOSH_DIR after switch" \
  test.oo.mode.dir
expect 0 "*" "OOSH_DIR ends with /main"

# ============================================================================
# T-MODE-3: oo.mode switches back to dev
# ============================================================================
test.oo.mode.switchBack() {
  local HOME_ORIG="$HOME"
  HOME="$MODE_FIXTURE"

  oo.mode dev >/dev/null 2>&1
  local rc=$?

  local target
  target=$(readlink "$MODE_FIXTURE/oosh")
  HOME="$HOME_ORIG"

  if [ $rc -eq 0 ] && [ "$target" = "$MODE_FIXTURE/dev" ] && [[ "$OOSH_DIR" == */dev ]]; then
    create.result 0 "$target"
  else
    create.result 1 "rc=$rc target=$target OOSH_DIR=$OOSH_DIR"
  fi
}
test.case - "T-MODE-3: oo.mode switches back to dev" \
  test.oo.mode.switchBack
expect 0 "$MODE_FIXTURE/dev" "symlink and OOSH_DIR restored to dev"

# ============================================================================
# T-MODE-4: oo.mode.list only shows git directories
# ============================================================================
test.oo.mode.listFilter() {
  local output
  output=$(oo.mode.list 2>/dev/null)
  local hasMain=false hasDev=false hasNotGit=false hasLink=false

  echo "$output" | grep -qw "main"    && hasMain=true
  echo "$output" | grep -qw "dev"     && hasDev=true
  echo "$output" | grep -qw "notGit"  && hasNotGit=true
  echo "$output" | grep -qw "oosh"    && hasLink=true

  if [ "$hasMain" = "true" ] && [ "$hasDev" = "true" ] \
     && [ "$hasNotGit" = "false" ] && [ "$hasLink" = "false" ]; then
    create.result 0 "main+dev only"
  else
    create.result 1 "main=$hasMain dev=$hasDev notGit=$hasNotGit link=$hasLink"
  fi
}
test.case - "T-MODE-4: oo.mode.list only shows git directories" \
  test.oo.mode.listFilter
expect 0 "main+dev only" "mode.list excludes non-git dirs and symlinks"

# ============================================================================
# T-MODE-5: oo.mode.completion.branch only shows git directories
# ============================================================================
test.oo.mode.completionFilter() {
  local output
  output=$(oo.mode.completion.branch 2>/dev/null)
  local hasMain=false hasDev=false hasNotGit=false hasLink=false

  echo "$output" | grep -qw "main"    && hasMain=true
  echo "$output" | grep -qw "dev"     && hasDev=true
  echo "$output" | grep -qw "notGit"  && hasNotGit=true
  echo "$output" | grep -qw "oosh"    && hasLink=true

  if [ "$hasMain" = "true" ] && [ "$hasDev" = "true" ] \
     && [ "$hasNotGit" = "false" ] && [ "$hasLink" = "false" ]; then
    create.result 0 "main+dev only"
  else
    create.result 1 "main=$hasMain dev=$hasDev notGit=$hasNotGit link=$hasLink"
  fi
}
test.case - "T-MODE-5: oo.mode.completion.branch only shows git directories" \
  test.oo.mode.completionFilter
expect 0 "main+dev only" "completion excludes non-git dirs and symlinks"

# ============================================================================
# T-MODE-NOLEAK: oo.mode.list does NOT mutate global safe.directory
# ----------------------------------------------------------------------------
# Regression for the leak that filled ~/.gitconfig with hundreds of
# stale /tmp/test.oo.mode.$$/{dev,main} entries. oo.mode.list MUST
# remain a read-only operation per docs/repair-toolkit.md — repair
# primitives are explicit, not side-effects of read methods.
# Sandboxes via GIT_CONFIG_GLOBAL so the test never touches the
# user's real ~/.gitconfig (the lesson of this very bug).
# ============================================================================
test.oo.mode.list.noLeak() {
  local sandbox entries
  sandbox=$(mktemp)
  GIT_CONFIG_GLOBAL="$sandbox" oo.mode.list >/dev/null 2>&1
  entries=$(GIT_CONFIG_GLOBAL="$sandbox" git config --global --get-all safe.directory 2>/dev/null | wc -l)
  rm -f "$sandbox"

  if [ "$entries" -eq 0 ]; then
    create.result 0 "no entries added"
  else
    create.result 1 "leaked $entries entries into global config"
  fi
}
test.case - "T-MODE-NOLEAK: oo.mode.list does not mutate global safe.directory" \
  test.oo.mode.list.noLeak
expect 0 "no entries added" "oo.mode.list must not write to git config"

# ============================================================================
# T-MODE-COMPLETION-WORKTREE: completion against REAL git worktrees
# ----------------------------------------------------------------------------
# T-MODE-5 uses independent `git init` siblings. This test builds the
# canonical OOSH-install structure using `git worktree add` so we also
# cover the case where the sibling dirs share a single .git via worktree
# pointers (`.git` is a FILE, not a dir, in worktrees).
# ============================================================================
WT_FIXTURE="/tmp/test.oo.mode.worktree.$$"
mkdir -p "$WT_FIXTURE"
git -c init.defaultBranch=main init "$WT_FIXTURE/main" >/dev/null 2>&1
git -C "$WT_FIXTURE/main" -c user.email=test@example -c user.name=test \
  -c commit.gpgsign=false commit --allow-empty -m init >/dev/null 2>&1
git -C "$WT_FIXTURE/main" worktree add "../testing" -b testing >/dev/null 2>&1
git -C "$WT_FIXTURE/main" worktree add "../prod"    -b prod    >/dev/null 2>&1

test.oo.mode.completionWorktree() {
  local SAVE_OOSH_DIR_2="$OOSH_DIR"
  local SAVE_OOSH_CD="$OOSH_COMPONENTS_DIR"
  export OOSH_COMPONENTS_DIR="$WT_FIXTURE"
  export OOSH_DIR="$WT_FIXTURE/main"

  local output
  output=$(oo.mode.completion.branch 2>/dev/null)

  export OOSH_DIR="$SAVE_OOSH_DIR_2"
  export OOSH_COMPONENTS_DIR="$SAVE_OOSH_CD"

  local hasMain=false hasTesting=false hasProd=false
  echo "$output" | grep -qw "main"    && hasMain=true
  echo "$output" | grep -qw "testing" && hasTesting=true
  echo "$output" | grep -qw "prod"    && hasProd=true

  if [ "$hasMain" = "true" ] && [ "$hasTesting" = "true" ] && [ "$hasProd" = "true" ]; then
    create.result 0 "main+testing+prod"
  else
    create.result 1 "main=$hasMain testing=$hasTesting prod=$hasProd (output=$output)"
  fi
}
test.case - "T-MODE-COMPLETION-WORKTREE: enumerates real git-worktree-add siblings" \
  test.oo.mode.completionWorktree
expect 0 "main+testing+prod" "completion lists all 3 worktrees of a real worktree base"
rm -rf "$WT_FIXTURE"

# ============================================================================
# T-MODE-COMPLETION-NO-BASE: single-clone case — completion returns empty
# ----------------------------------------------------------------------------
# Mirrors root on macstudio: ~/oosh is a standalone clone (no sibling
# worktrees, no shared .git pointed at by sibling dirs). Completion must
# return EMPTY (not "ghost" remote branches, not random local branches).
# ============================================================================
NB_FIXTURE="/tmp/test.oo.mode.nobase.$$"
mkdir -p "$NB_FIXTURE/oosh"
git -c init.defaultBranch=dev init "$NB_FIXTURE/oosh" >/dev/null 2>&1
git -C "$NB_FIXTURE/oosh" -c user.email=test@example -c user.name=test \
  -c commit.gpgsign=false commit --allow-empty -m init >/dev/null 2>&1

test.oo.mode.completionNoBase() {
  local SAVE_OOSH_DIR_2="$OOSH_DIR"
  local SAVE_OOSH_CD="$OOSH_COMPONENTS_DIR"
  # No OOSH_COMPONENTS_DIR — force oo.mode.base.get to fall through every strategy
  unset OOSH_COMPONENTS_DIR
  export OOSH_DIR="$NB_FIXTURE/oosh"

  local output
  output=$(oo.mode.completion.branch 2>/dev/null)

  export OOSH_DIR="$SAVE_OOSH_DIR_2"
  [ -n "$SAVE_OOSH_CD" ] && export OOSH_COMPONENTS_DIR="$SAVE_OOSH_CD"

  # Empty (or whitespace-only) output is the contract.
  if [ -z "$(echo "$output" | tr -d '[:space:]')" ]; then
    create.result 0 "empty"
  else
    create.result 1 "expected empty, got: $output"
  fi
}
test.case - "T-MODE-COMPLETION-NO-BASE: single-clone returns empty (no ghost branches)" \
  test.oo.mode.completionNoBase
expect 0 "empty" "completion is empty when no worktree base is detected"
rm -rf "$NB_FIXTURE"

# ============================================================================
# T-MODE-SAME-BRANCH-NO-OP: `oo.mode <currentBranch>` is a fast no-op
# ----------------------------------------------------------------------------
# Regression for the platform-test container observation where
# `oo mode dev` (already on dev) cascaded through symlink-rm,
# private.oo.install.shim, ln -s, config save, and produced an HTTPS
# credential prompt. Fast-path at oo:543 returns BEFORE any of those
# steps. Verify by snapshotting the symlink's mtime (with sub-second
# resolution if `stat -c %Y` supports it) and asserting it's unchanged
# after the call.
# ============================================================================
test.oo.mode.sameBranch() {
  local HOME_ORIG="$HOME"
  HOME="$MODE_FIXTURE"
  # Ensure ~/oosh points at dev (T-MODE-3 may have left it there, but we
  # reset to be sure)
  rm -f "$MODE_FIXTURE/oosh"
  ln -sf "$MODE_FIXTURE/dev" "$MODE_FIXTURE/oosh"
  local mtimeBefore
  mtimeBefore=$(stat -c '%Y %i' "$MODE_FIXTURE/oosh" 2>/dev/null \
              || stat -f '%m %i' "$MODE_FIXTURE/oosh" 2>/dev/null)

  local output
  output=$(oo.mode dev 2>&1)
  local rc=$?

  local mtimeAfter
  mtimeAfter=$(stat -c '%Y %i' "$MODE_FIXTURE/oosh" 2>/dev/null \
             || stat -f '%m %i' "$MODE_FIXTURE/oosh" 2>/dev/null)
  HOME="$HOME_ORIG"

  if [ "$rc" -eq 0 ] \
     && [ "$mtimeBefore" = "$mtimeAfter" ] \
     && [[ "$output" == *"already current"* ]]; then
    create.result 0 "fast-path took: same mtime, 'already current' in output"
  else
    create.result 1 "rc=$rc before=$mtimeBefore after=$mtimeAfter output=$output"
  fi
}
test.case - "T-MODE-SAME-BRANCH-NO-OP: oo.mode <currentBranch> is fast no-op (no symlink recreate)" \
  test.oo.mode.sameBranch
expect 0 "*" "fast-path returns without touching the symlink"

# ============================================================================
# T-MODE-REMOTE-BRANCH-EXACT (structural): no loose-grep fallback
# ----------------------------------------------------------------------------
# Lock in the `grep -Fx "origin/$branch"` change at oo:553. The previous
# `grep -i "${branch//\./.*}"` matched substring/regex and silently
# promoted `oo mode test` to `origin/test/ish` (alphabetical first
# remote match). Asserting the body contains the fixed-string anchor
# guards against an accidental loosening on a future refactor.
# Structural (not end-to-end) because mocking `git branch -r` for a
# realistic remote-ref scenario inside a subshell is brittle.
# ============================================================================
test.case $level "oo.mode uses grep -Fx for exact remote-branch match" \
  bash -c "type oo.mode | grep -qE 'grep -Fx[[:space:]]+\"origin/'"
if type oo.mode 2>/dev/null | grep -qE 'grep -Fx[[:space:]]+"origin/'; then
  expect.pass "oo.mode's remote-branch lookup uses grep -Fx (fixed-string, full-line) match"
else
  expect.fail "oo.mode's remote-branch lookup must use \`grep -Fx \"origin/\$branch\"\` to avoid silent partial-match promotion (e.g. 'test' → 'origin/test/ish')"
fi

# ============================================================================
# T-MODE-REMOTE-BRANCH-CHECKED-ADD (structural): worktree-add failure halts
# ----------------------------------------------------------------------------
# Lock in the `if ! (cd … git worktree add)` failure path + post-condition
# check at oo:566-580. Without these, a failed worktree-add fell through to
# the symlink-recreate block (oo:566-578) and OOSH_DIR export (oo:590),
# leaving OOSH_DIR pointing at a non-existent path. c2:296's default-script
# resolution then exploded on every Tab completion. Asserting both the
# explicit return-on-failure and the post-condition guards keeps the cure.
# ============================================================================
test.case $level "oo.mode propagates git worktree add failure (no silent fall-through)" \
  bash -c "type oo.mode | grep -qE 'git worktree add failed for'"
if type oo.mode 2>/dev/null | grep -qE 'git worktree add failed for'; then
  expect.pass "oo.mode body emits an error and returns 1 when worktree-add fails"
else
  expect.fail "oo.mode must return 1 with error.log if \`git worktree add\` fails — silent fall-through caused the macstudio /test/ng/c2 corruption"
fi
test.case $level "oo.mode verifies worktree-add post-condition before mutating symlink" \
  bash -c "type oo.mode | grep -qE 'post-condition violated|reported success but'"
if type oo.mode 2>/dev/null | grep -qE 'post-condition violated|reported success but'; then
  expect.pass "oo.mode body re-checks \$target_dir exists after worktree-add before touching the symlink"
else
  expect.fail "oo.mode must verify \$target_dir exists after \`git worktree add\` reported success — otherwise OOSH_DIR can be exported to a non-existent path"
fi

# ============================================================================
# T-MODE-COMPLETION-LAZY-USERENV: macstudio-root contract — what state 31
# must satisfy for completion to work end-to-end.
# ----------------------------------------------------------------------------
# sharedConfig/user.env uses a LAZY assignment:
#   : ${OOSH_DIR:="$(cd "$HOME/oosh" 2>/dev/null && pwd -P || echo "$HOME/oosh")"}
# So OOSH_DIR resolves relative to $HOME/oosh after `pwd -P` strips
# symlinks. The contract that makes `oo mode <TAB>` work for any user
# (including root) is:
#   - $HOME/oosh must be a SYMLINK to a worktree under sharedOoshBase.
# If $HOME/oosh is a REAL directory (e.g. root's initial init/oosh clone
# that state 31 never converted to a symlink — the macstudio bug),
# OOSH_DIR resolves to that private clone, oo.mode.base.get fails, and
# completion returns empty.
#
# This test simulates BOTH states (real-dir → empty, symlink → branches)
# so the contract is testable without standing up a full install.
# ============================================================================
LE_FIXTURE="/tmp/test.oo.mode.lazyenv.$$"
mkdir -p "$LE_FIXTURE/sharedBase"
git -c init.defaultBranch=main init "$LE_FIXTURE/sharedBase/main" >/dev/null 2>&1
git -C "$LE_FIXTURE/sharedBase/main" -c user.email=test@example -c user.name=test \
  -c commit.gpgsign=false commit --allow-empty -m init >/dev/null 2>&1
git -C "$LE_FIXTURE/sharedBase/main" worktree add "../dev"     -b dev     >/dev/null 2>&1
git -C "$LE_FIXTURE/sharedBase/main" worktree add "../testing" -b testing >/dev/null 2>&1

# Helper that mimics the lazy assignment in sharedConfig/user.env.
# Run in a fresh bash with OOSH_DIR explicitly unset so the lazy
# `${OOSH_DIR:=…}` actually fires (otherwise it inherits the parent
# test's value and the test becomes a no-op).
private.test.lazy.oosh.dir() {
  local fakeHome="$1"
  HOME="$fakeHome" env -u OOSH_DIR bash -c '
    : ${OOSH_DIR:="$(cd "$HOME/oosh" 2>/dev/null && pwd -P || echo "$HOME/oosh")"}
    echo "$OOSH_DIR"
  '
}

# Scenario A: $HOME/oosh is a REAL DIR (current macstudio root state).
test.oo.mode.completionLazyRealDir() {
  local fakeHome="$LE_FIXTURE/homeRealDir"
  mkdir -p "$fakeHome/oosh"
  git -c init.defaultBranch=dev init "$fakeHome/oosh" >/dev/null 2>&1

  local resolvedOOSH_DIR
  resolvedOOSH_DIR=$(private.test.lazy.oosh.dir "$fakeHome")

  local SAVE_OOSH_DIR_3="$OOSH_DIR"
  local SAVE_OOSH_CD="$OOSH_COMPONENTS_DIR"
  unset OOSH_COMPONENTS_DIR
  export OOSH_DIR="$resolvedOOSH_DIR"
  local output
  output=$(oo.mode.completion.branch 2>/dev/null)
  export OOSH_DIR="$SAVE_OOSH_DIR_3"
  [ -n "$SAVE_OOSH_CD" ] && export OOSH_COMPONENTS_DIR="$SAVE_OOSH_CD"

  if [ -z "$(echo "$output" | tr -d '[:space:]')" ]; then
    create.result 0 "empty (real dir → no base)"
  else
    create.result 1 "expected empty, got: $output"
  fi
}
test.case - "T-MODE-COMPLETION-LAZY-USERENV-A: real-dir ~/oosh → completion empty" \
  test.oo.mode.completionLazyRealDir
expect 0 "empty (real dir → no base)" "private-clone ~/oosh yields empty completion via lazy user.env"

# Scenario B: $HOME/oosh is a SYMLINK to a worktree (correct post-state-31).
test.oo.mode.completionLazySymlink() {
  local fakeHome="$LE_FIXTURE/homeSymlink"
  mkdir -p "$fakeHome"
  ln -sf "$LE_FIXTURE/sharedBase/dev" "$fakeHome/oosh"

  local resolvedOOSH_DIR
  resolvedOOSH_DIR=$(private.test.lazy.oosh.dir "$fakeHome")

  local SAVE_OOSH_DIR_3="$OOSH_DIR"
  local SAVE_OOSH_CD="$OOSH_COMPONENTS_DIR"
  unset OOSH_COMPONENTS_DIR
  export OOSH_DIR="$resolvedOOSH_DIR"
  local output
  output=$(oo.mode.completion.branch 2>/dev/null)
  export OOSH_DIR="$SAVE_OOSH_DIR_3"
  [ -n "$SAVE_OOSH_CD" ] && export OOSH_COMPONENTS_DIR="$SAVE_OOSH_CD"

  local hasMain=false hasDev=false hasTesting=false
  echo "$output" | grep -qw "main"    && hasMain=true
  echo "$output" | grep -qw "dev"     && hasDev=true
  echo "$output" | grep -qw "testing" && hasTesting=true

  if [ "$hasMain" = "true" ] && [ "$hasDev" = "true" ] && [ "$hasTesting" = "true" ]; then
    create.result 0 "main+dev+testing"
  else
    create.result 1 "got: main=$hasMain dev=$hasDev testing=$hasTesting (output=$output)"
  fi
}
test.case - "T-MODE-COMPLETION-LAZY-USERENV-B: symlinked ~/oosh → completion shows all worktree branches" \
  test.oo.mode.completionLazySymlink
expect 0 "main+dev+testing" "symlinked ~/oosh yields full worktree-branch list via lazy user.env"

# ============================================================================
# T-MODE-COMPLETION-LAZY-USERENV-ENSURE: state-31 helper produces the
# post-install layout from a macstudio-like pre-state.
# ----------------------------------------------------------------------------
# Macstudio's actual pre-state after the earlier (pre-fix) install:
#   ~/config = symlink to sharedConfig   (bf1c92c made this work)
#   ~/oosh   = REAL DIRECTORY            (the gap; nothing creates the symlink)
#
# This test calls private.oo.user.shared.symlinks.ensure (the helper
# that state 31's root-symlink block delegates to) against THIS exact
# pre-state and asserts the post-state matches the contract: both
# ~/config and ~/oosh are symlinks; lazy user.env-resolved OOSH_DIR
# now points at the shared worktree; oo.mode.completion.branch shows
# the worktree branches.
#
# If the helper regresses (e.g. someone removes the oosh symlink
# step, mirroring the original macstudio gap), this test FAILS. If
# state 31 stops CALLING the helper, the platform invariant test
# (test.platform.shared.oosh.invariant) catches it; that test runs
# inside `os platform.test <p>` post-install.
# ============================================================================
EN_FIXTURE="/tmp/test.oo.mode.ensure.$$"
mkdir -p "$EN_FIXTURE/sharedBase" \
         "$EN_FIXTURE/sharedConfig/stateMachines"
git -c init.defaultBranch=main init "$EN_FIXTURE/sharedBase/main" >/dev/null 2>&1
git -C "$EN_FIXTURE/sharedBase/main" -c user.email=test@example -c user.name=test \
  -c commit.gpgsign=false commit --allow-empty -m init >/dev/null 2>&1
git -C "$EN_FIXTURE/sharedBase/main" worktree add "../dev"     -b dev     >/dev/null 2>&1
git -C "$EN_FIXTURE/sharedBase/main" worktree add "../testing" -b testing >/dev/null 2>&1

test.oo.user.symlinks.ensure() {
  # Build the pre-state: ~/oosh as a REAL dir, ~/config as a symlink
  # already pointing at sharedConfig (macstudio's actual state).
  local fakeHome="$EN_FIXTURE/home"
  mkdir -p "$fakeHome/oosh"
  git -c init.defaultBranch=dev init "$fakeHome/oosh" >/dev/null 2>&1
  ln -sf "$EN_FIXTURE/sharedConfig" "$fakeHome/config"

  # Sanity: pre-state matches macstudio.
  [ -d "$fakeHome/oosh" ] && [ ! -L "$fakeHome/oosh" ] || {
    create.result 1 "pre-state setup wrong: ~/oosh is not a real dir"
    return $(result)
  }
  [ -L "$fakeHome/config" ] || {
    create.result 1 "pre-state setup wrong: ~/config is not a symlink"
    return $(result)
  }

  # Invoke the helper — this is what state 31 calls.
  private.oo.user.shared.symlinks.ensure \
    "$fakeHome" \
    "$EN_FIXTURE/sharedConfig" \
    "$EN_FIXTURE/sharedBase" \
    "dev" >/dev/null 2>&1
  local rc=$?

  # Post-state: both must be symlinks. Original real-dir ~/oosh is
  # preserved as oosh.orig.<ts> (the collision-proof suffix introduced
  # after the .initial bug; see private.oo.user.shared.symlinks.ensure
  # in oo and config.init.user at config:295,304 for the pattern).
  local configIsLink=false ooshIsLink=false oldOoshPreserved=false
  [ -L "$fakeHome/config" ] && configIsLink=true
  [ -L "$fakeHome/oosh" ]   && ooshIsLink=true
  ls -d "$fakeHome"/oosh.orig.* >/dev/null 2>&1 && oldOoshPreserved=true

  # And the lazy ${OOSH_DIR:=…} now resolves to the shared dev worktree.
  local resolved
  resolved=$(HOME="$fakeHome" env -u OOSH_DIR bash -c '
    : ${OOSH_DIR:="$(cd "$HOME/oosh" 2>/dev/null && pwd -P || echo "$HOME/oosh")"}
    echo "$OOSH_DIR"
  ')
  local resolvedExpected
  resolvedExpected=$(cd "$EN_FIXTURE/sharedBase/dev" && pwd -P)

  if [ "$rc" -eq 0 ] \
     && [ "$configIsLink" = "true" ] \
     && [ "$ooshIsLink" = "true" ] \
     && [ "$oldOoshPreserved" = "true" ] \
     && [ "$resolved" = "$resolvedExpected" ]; then
    create.result 0 "all post-conditions met"
  else
    create.result 1 "rc=$rc config=$configIsLink oosh=$ooshIsLink preserved=$oldOoshPreserved resolved=$resolved expected=$resolvedExpected"
  fi
}
test.case - "T-MODE-COMPLETION-LAZY-USERENV-ENSURE: helper converts macstudio pre-state to contract-satisfying post-state" \
  test.oo.user.symlinks.ensure
expect 0 "all post-conditions met" "private.oo.user.shared.symlinks.ensure produces the post-state that makes oo mode completion work"

# ----------------------------------------------------------------------------
# T-SYMLINKS-ENSURE-COLLISION (H1 regression): helper must NOT silently fail
# when ~/oosh.initial already exists from a prior install attempt. Original
# bug at oo:1339 used `.initial` suffix with `2>/dev/null` suppression — if
# ~/oosh.initial pre-existed, `mv ~/oosh ~/oosh.initial` failed silently,
# then `ln -s` failed because the real dir was still in place, and the only
# signal was an error.log line buried in install output. Fix uses
# .orig.<ts> (collision-proof) and fail-loud on every step.
# ----------------------------------------------------------------------------
test.oo.user.symlinks.ensure.collision() {
  local fakeHome="$EN_FIXTURE/home-collision"
  mkdir -p "$fakeHome/oosh"
  mkdir -p "$fakeHome/oosh.initial"   # ← the collision: legacy backup pre-exists
  ln -sf "$EN_FIXTURE/sharedConfig" "$fakeHome/config"

  private.oo.user.shared.symlinks.ensure \
    "$fakeHome" \
    "$EN_FIXTURE/sharedConfig" \
    "$EN_FIXTURE/sharedBase" \
    "dev" >/dev/null 2>&1
  local rc=$?

  local ooshIsLink=false legacyPreserved=false newPreserved=false
  [ -L "$fakeHome/oosh" ] && ooshIsLink=true
  # Legacy ~/oosh.initial must still be there (we didn't clobber pre-existing data).
  [ -d "$fakeHome/oosh.initial" ] && legacyPreserved=true
  # Real dir we replaced lives at ~/oosh.orig.<ts>.
  ls -d "$fakeHome"/oosh.orig.* >/dev/null 2>&1 && newPreserved=true

  if [ "$rc" -eq 0 ] \
     && [ "$ooshIsLink" = "true" ] \
     && [ "$legacyPreserved" = "true" ] \
     && [ "$newPreserved" = "true" ]; then
    create.result 0 "helper survived oosh.initial collision (.orig.<ts> created, legacy preserved)"
  else
    create.result 1 "rc=$rc oosh.link=$ooshIsLink legacy.preserved=$legacyPreserved orig.created=$newPreserved"
  fi
}
test.case - "T-SYMLINKS-ENSURE-COLLISION: helper uses .orig.<ts> and doesn't clobber existing oosh.initial" \
  test.oo.user.symlinks.ensure.collision
expect 0 "*" "private.oo.user.shared.symlinks.ensure handles pre-existing oosh.initial (H1 fix)"

rm -rf "$EN_FIXTURE" "$LE_FIXTURE"

# ============================================================================
# T-MODE-COMPLETION-REAL-ENV: real-environment integration test
# ----------------------------------------------------------------------------
# Why this test exists. Every fixture-based completion test (T-MODE-5,
# T-MODE-COMPLETION-WORKTREE, T-MODE-COMPLETION-NO-BASE, the LAZY-USERENV
# trio) builds an isolated tmp tree and exercises the completion against
# THAT. They prove the function is CORRECT given a correct layout.
#
# What they DON'T catch: a real install (on macstudio root, after
# `init/oosh` + `ossh install ...`) that left `$HOME/oosh` as a real
# directory instead of a symlink to the shared worktree base. State 31
# only runs for fresh installs — an already-completed SETUP_SERVER state
# machine (at state 99) skips re-running state 31's symlink helper, so a
# post-fix `oo update` doesn't repair an already-installed user.
#
# This test runs against THE ACTUAL $HOME of whoever invokes
# `./test.suite core 1`. On a host with no shared OOSH tree (CI fresh
# container, dev laptop without shared mount) it SKIPS. On a host that
# DOES have a shared tree (the dev host, macstudio admin, macstudio root),
# it asserts the contract that makes `oo mode <TAB>` work:
#
#   1. $HOME/oosh exists and is a symlink (not a real dir)
#   2. The symlink target lives UNDER the shared tree
#   3. oo.mode.completion.branch returns at least one branch
#
# Run on macstudio root: $HOME/oosh is a real dir → step 1 fails →
# the user sees the exact bug pinpointed BEFORE they manually try
# `oo mode <TAB>` and see emptiness. That is what the user asked for.
# ============================================================================

# Canonical shared-tree locations. Order: Linux first (no case ambiguity),
# then both macOS casings (HFS+ is case-insensitive, APFS may not be).
RE_SHARED_BASE=""
for re_candidate in \
  /home/shared/EAMD.ucp/Components/com/ceruleanCircle/EAM/1_infrastructure/Once.sh \
  /Users/Shared/EAMD.ucp/Components/com/ceruleanCircle/EAM/1_infrastructure/Once.sh \
  /Users/shared/EAMD.ucp/Components/com/ceruleanCircle/EAM/1_infrastructure/Once.sh \
; do
  if [ -d "$re_candidate" ]; then
    RE_SHARED_BASE="$re_candidate"
    break
  fi
done

if [ -z "$RE_SHARED_BASE" ]; then
  test.case - "T-MODE-COMPLETION-REAL-ENV: real install layout supports oo mode <TAB>" true
  expect.pass "skipped — no shared OOSH tree at canonical paths (single-clone dev box / CI)"
else
  # (1) ~/oosh must be a symlink (not a real dir).
  test.case - "T-MODE-COMPLETION-REAL-ENV [1/3]: \$HOME/oosh is a symlink (shared tree at $RE_SHARED_BASE)" \
    test -L "$HOME/oosh"
  if [ -L "$HOME/oosh" ]; then
    expect.pass "$HOME/oosh is a symlink"

    # (2) target lives under the detected shared base.
    RE_RESOLVED=$(private.this.path.canonical "$HOME/oosh" 2>/dev/null)
    test.case - "T-MODE-COMPLETION-REAL-ENV [2/3]: symlink target lives under $RE_SHARED_BASE" true
    case "$RE_RESOLVED" in
      "$RE_SHARED_BASE"/*)
        expect.pass "$HOME/oosh → $RE_RESOLVED"
        ;;
      *)
        expect.fail "$HOME/oosh → $RE_RESOLVED is outside shared tree $RE_SHARED_BASE — private clone left in place"
        ;;
    esac

    # (3) completion non-empty against the real install (catches c2-subprocess regressions too).
    RE_OUT=$(oo.mode.completion.branch 2>/dev/null)
    RE_COUNT=$(echo "$RE_OUT" | grep -cE '^[A-Za-z0-9._/-]+$')
    test.case - "T-MODE-COMPLETION-REAL-ENV [3/3]: oo.mode.completion.branch is non-empty against real install" true
    if [ "$RE_COUNT" -ge 1 ]; then
      expect.pass "completion returned $RE_COUNT branch(es): $(echo "$RE_OUT" | tr '\n' ' ')"
    else
      expect.fail "oo.mode.completion.branch returned empty — \`oo mode <TAB>\` is broken for $(whoami) (HOME=$HOME)"
    fi
    unset RE_RESOLVED RE_OUT RE_COUNT
  else
    if [ -e "$HOME/oosh" ]; then
      expect.fail "$HOME/oosh exists but is NOT a symlink. Recovery: \`oo user.fix\` (or \`config init.user\`) — the canonical OOSH primitive that handles real-dir → symlink conversion, ownership, and branch detection."
    else
      expect.fail "$HOME/oosh does not exist (shared tree present at $RE_SHARED_BASE) — run \`oo user.fix\`"
    fi
  fi
fi

unset RE_SHARED_BASE re_candidate

# ============================================================================
# ooShim tests — uses MODE_FIXTURE (main/, dev/ are git dirs; notGit/ is not)
# ============================================================================

# Source the shim so private.oo.shim.findLatest and oo() are available
source "$SAVE_OOSH_DIR/templates/user/ooShim"

# Add config/ dir (non-git, most recent) to simulate macOS scenario
mkdir -p "$MODE_FIXTURE/config"
touch "$MODE_FIXTURE/config/dummy"
sleep 1  # ensure config/ is newest by mtime

# ============================================================================
# T-SHIM-1: findLatest returns a git directory, not config/
# ============================================================================
test.oo.shim.findLatest() {
  local HOME_ORIG="$HOME"
  HOME="$MODE_FIXTURE"
  ln -sf "$MODE_FIXTURE/dev" "$MODE_FIXTURE/oosh"
  local result
  result=$(private.oo.shim.findLatest)
  HOME="$HOME_ORIG"
  local name
  name=$(basename "$result")
  if [ "$name" = "dev" ] || [ "$name" = "main" ]; then
    create.result 0 "$name"
  else
    create.result 1 "got $name (expected dev or main)"
  fi
}
test.case - "T-SHIM-1: findLatest returns git dir, not config/" \
  test.oo.shim.findLatest
expect 0 "*" "findLatest skips non-git directories"

# ============================================================================
# T-SHIM-2: findLatest skips symlinks
# ============================================================================
test.oo.shim.skipSymlinks() {
  local HOME_ORIG="$HOME"
  HOME="$MODE_FIXTURE"
  # Create a symlink that is newest
  rm -f "$MODE_FIXTURE/newestLink"
  ln -s "$MODE_FIXTURE/dev" "$MODE_FIXTURE/newestLink"
  touch -h "$MODE_FIXTURE/newestLink" 2>/dev/null
  local result
  result=$(private.oo.shim.findLatest)
  HOME="$HOME_ORIG"
  rm -f "$MODE_FIXTURE/newestLink"
  local name
  name=$(basename "$result")
  if [ "$name" = "dev" ] || [ "$name" = "main" ]; then
    create.result 0 "$name"
  else
    create.result 1 "got $name"
  fi
}
test.case - "T-SHIM-2: findLatest skips symlinks" \
  test.oo.shim.skipSymlinks
expect 0 "*" "findLatest does not return symlink directories"

# ============================================================================
# T-SHIM-3: oo() function is defined after sourcing shim
# ============================================================================
test.case - "T-SHIM-3: oo() function defined after sourcing shim" \
  declare -F oo
if declare -F oo >/dev/null 2>&1; then
  create.result 0 "oo function exists"
else
  create.result 1 "oo function not found"
fi
expect 0 "oo function exists" "oo() is a shell function after sourcing ooShim"

# ============================================================================
# T-SHIM-4: findLatest falls back to oosh dir on plain clone (no worktree base)
# ============================================================================
test.oo.shim.plainClone() {
  local HOME_ORIG="$HOME"
  local OOSH_WORKTREE_BASE_ORIG="$OOSH_WORKTREE_BASE"
  # Simulate plain clone: HOME/oosh is a regular dir, no sibling main/
  local PLAIN="/tmp/test.oo.shim.plain.$$"
  mkdir -p "$PLAIN/oosh/.git"
  HOME="$PLAIN"
  unset OOSH_WORKTREE_BASE
  local result
  result=$(private.oo.shim.findLatest)
  HOME="$HOME_ORIG"
  export OOSH_WORKTREE_BASE="$OOSH_WORKTREE_BASE_ORIG"
  rm -rf "$PLAIN"
  if [ "$result" = "$PLAIN/oosh" ]; then
    create.result 0 "$result"
  else
    create.result 1 "expected $PLAIN/oosh got $result"
  fi
}
test.case - "T-SHIM-4: findLatest falls back on plain clone" \
  test.oo.shim.plainClone
expect 0 "*" "findLatest returns ~/oosh when no worktree structure exists"

# Clean up shim test additions
rm -rf "$MODE_FIXTURE/config"

# ============================================================================
# Teardown: restore environment, persist to config, remove fixture
# ============================================================================
export OOSH_DIR="$SAVE_OOSH_DIR"
export OOSH_MODE="$SAVE_OOSH_MODE"
export OOSH_COMPONENTS_DIR="$SAVE_OOSH_COMPONENTS_DIR"
export PATH="$SAVE_PATH"
config save oosh OOSH 2>/dev/null
rm -rf "$MODE_FIXTURE"
# Defense in depth: even though T-MODE-NOLEAK enforces that
# oo.mode.list cannot leak, mop up any stale entries that any other
# fixture path might have introduced. The prune only touches paths
# that no longer exist on disk, so legitimate worktree entries are
# preserved. Silent (errors swallowed) — this is housekeeping.
oo.safeDirectory.prune >/dev/null 2>&1

# ============================================================================
# T-SAFEDIR-PRUNE: oo.safeDirectory.prune keeps real paths, drops stale
# ----------------------------------------------------------------------------
# Validates the repair primitive added alongside the oo.mode.list
# leak fix. Sandboxes via GIT_CONFIG_GLOBAL — must never touch the
# real ~/.gitconfig (the lesson of the very leak this primitive
# exists to clean up).
# ============================================================================
test.oo.safeDirectory.prune() {
  local sandbox realDir remaining
  sandbox=$(mktemp)
  realDir=$(mktemp -d)

  GIT_CONFIG_GLOBAL="$sandbox" git config --global --add safe.directory "$realDir"
  GIT_CONFIG_GLOBAL="$sandbox" git config --global --add safe.directory "/tmp/oosh-test-fake-path.$$.a"
  GIT_CONFIG_GLOBAL="$sandbox" git config --global --add safe.directory "/tmp/oosh-test-fake-path.$$.b"

  GIT_CONFIG_GLOBAL="$sandbox" oo.safeDirectory.prune >/dev/null 2>&1

  remaining=$(GIT_CONFIG_GLOBAL="$sandbox" git config --global --get-all safe.directory)
  rm -f "$sandbox"
  rmdir "$realDir" 2>/dev/null

  if [ "$remaining" = "$realDir" ]; then
    create.result 0 "kept only real path"
  else
    create.result 1 "expected '$realDir', got: '$remaining'"
  fi
}
test.case - "T-SAFEDIR-PRUNE: only real-path entries survive prune" \
  test.oo.safeDirectory.prune
expect 0 "kept only real path" "prune keeps real paths, drops stale"

# ============================================================================
# T-SAFEDIR-ADD-IDEMPOTENT: private.oo.safeDirectory.add deduplicates
# ----------------------------------------------------------------------------
# Regression for the duplicate-prod bug. The install path at
# private.oo.shared.tree.from.local calls private.oo.safeDirectory.add
# for the resolved worktree and main paths on every (re)install.
# Calling the helper N times with the same path must yield exactly
# one entry in the global config — earlier versions used raw
# `git config --global --add` and accumulated duplicates each
# install pass. Sandboxed via GIT_CONFIG_GLOBAL.
# ============================================================================
test.oo.safeDirectory.add.idempotent() {
  local sandbox realDir count
  sandbox=$(mktemp)
  realDir=$(mktemp -d)

  GIT_CONFIG_GLOBAL="$sandbox" private.oo.safeDirectory.add "$realDir" >/dev/null 2>&1
  GIT_CONFIG_GLOBAL="$sandbox" private.oo.safeDirectory.add "$realDir" >/dev/null 2>&1
  GIT_CONFIG_GLOBAL="$sandbox" private.oo.safeDirectory.add "$realDir" >/dev/null 2>&1

  count=$(GIT_CONFIG_GLOBAL="$sandbox" git config --global --get-all safe.directory | wc -l)
  rm -f "$sandbox"
  rmdir "$realDir" 2>/dev/null

  if [ "$count" -eq 1 ]; then
    create.result 0 "exactly one entry after 3 adds"
  else
    create.result 1 "expected 1 entry, got $count"
  fi
}
test.case - "T-SAFEDIR-ADD-IDEMPOTENT: helper deduplicates same path" \
  test.oo.safeDirectory.add.idempotent
expect 0 "exactly one entry after 3 adds" "private.oo.safeDirectory.add must be idempotent"

# ============================================================================
# T9: oo.dev.to.testing wrapper is defined
# ============================================================================
test.case $level "oo.dev.to.testing wrapper is defined" \
  type oo.dev.to.testing
if type oo.dev.to.testing >/dev/null 2>&1; then
  expect.pass "oo.dev.to.testing is defined"
else
  expect.fail "oo.dev.to.testing should be defined"
fi

# ============================================================================
# T10: oo.promote.status wrapper is defined
# ============================================================================
test.case $level "oo.promote.status wrapper is defined" \
  type oo.promote.status
if type oo.promote.status >/dev/null 2>&1; then
  expect.pass "oo.promote.status is defined"
else
  expect.fail "oo.promote.status should be defined"
fi

# ============================================================================
# T11: oo.promote.report wrapper is defined
# ============================================================================
test.case $level "oo.promote.report wrapper is defined" \
  type oo.promote.report
if type oo.promote.report >/dev/null 2>&1; then
  expect.pass "oo.promote.report is defined"
else
  expect.fail "oo.promote.report should be defined"
fi

# ============================================================================
# T12: oo.testing.to.prod wrapper is defined
# ============================================================================
test.case $level "oo.testing.to.prod wrapper is defined" \
  type oo.testing.to.prod
if type oo.testing.to.prod >/dev/null 2>&1; then
  expect.pass "oo.testing.to.prod is defined"
else
  expect.fail "oo.testing.to.prod should be defined"
fi

# ============================================================================
# T13: oo.release wrapper is defined (updated to delegate)
# ============================================================================
test.case $level "oo.release wrapper is defined" \
  type oo.release
if type oo.release >/dev/null 2>&1; then
  expect.pass "oo.release is defined"
else
  expect.fail "oo.release should be defined"
fi

# ============================================================================
# T-USER-FIX-EXISTS: oo.user.fix is defined
# ----------------------------------------------------------------------------
# Discoverable user-facing alias for the canonical OOSH symlink-repair
# primitive (config.init.user). Naming: noun.verb per OOSH convention
# and the existing ossh.rights.fix / ossh.folder.fix per-scope pattern.
# ============================================================================
test.case $level "oo.user.fix is defined" \
  type oo.user.fix
if type oo.user.fix >/dev/null 2>&1; then
  expect.pass "oo.user.fix is defined"
else
  expect.fail "oo.user.fix should be defined"
fi

# ============================================================================
# T-USER-FIX-DELEGATES: oo.user.fix body references config.init.user
# ----------------------------------------------------------------------------
# Structural guarantee — the alias must delegate to the canonical primitive.
# Catches accidental divergence (e.g. someone inlining repair logic instead
# of delegating, which would lose the idempotency + branch-detection
# behaviour `config.init.user` provides at config:212).
# ============================================================================
test.case $level "oo.user.fix delegates to config.init.user" \
  bash -c "type oo.user.fix | grep -q 'config.init.user'"
if type oo.user.fix 2>/dev/null | grep -q 'config\.init\.user'; then
  expect.pass "oo.user.fix delegates to config.init.user"
else
  expect.fail "oo.user.fix must delegate to config.init.user (canonical repair primitive)"
fi

# ============================================================================
# T-USER-FIX-COMPLETION-GUARDED: completion sources `user` if missing
# ----------------------------------------------------------------------------
# The completion runs in c2 subprocesses where the `user` script may not
# have been auto-sourced yet — without the guard, `user.list` would be
# undefined and Tab completion would silently return nothing. Mirror
# pattern from private.oo.update.heal.symlinks: `[ "$(type -t X)" =
# "function" ] || source "$OOSH_DIR/<script>"`.
# ============================================================================
test.case $level "oo.user.fix.completion.username guards user.list source" \
  bash -c "type oo.user.fix.completion.username | grep -qE 'source.*user|type -t user\\.list'"
if type oo.user.fix.completion.username 2>/dev/null | grep -qE 'source.*user|type -t user\.list'; then
  expect.pass "oo.user.fix.completion.username body guards against missing user.list"
else
  expect.fail "oo.user.fix.completion.username must source \$OOSH_DIR/user when user.list isn't a function"
fi

# ============================================================================
# T-OO-UPDATE-HEALS: oo.update body references config.init.user
# ----------------------------------------------------------------------------
# Self-heal contract. After git pull succeeds, oo.update calls
# config.init.user to rewire ~/config + ~/oosh symlinks. This is also the
# install-pipeline H2 fix: ossh.install.continue.local at ossh:666 calls
# `oo update` BEFORE the state machine, so a re-install hitting a stuck-at-99
# SETUP_SERVER state still heals here even though state 31 never re-runs.
#
# Structural test (per [state-check-resources-script]: end-to-end mocking
# of the state machine is unreliable; structural invariant is the right
# level for "did the heal step survive a refactor").
# ============================================================================
test.case $level "oo.update heals via config.init.user" \
  bash -c "type oo.update | grep -qE 'config\.init\.user|heal\.symlinks'"
if type oo.update 2>/dev/null | grep -qE 'config\.init\.user|heal\.symlinks'; then
  expect.pass "oo.update body invokes the heal primitive"
else
  expect.fail "oo.update must call config.init.user (directly or via private.oo.update.heal.symlinks) after a successful pull"
fi

# ============================================================================
# T14: oo.stage is defined
# ============================================================================
test.case $level "oo.stage is defined" \
  type oo.stage
if type oo.stage >/dev/null 2>&1; then
  expect.pass "oo.stage is defined"
else
  expect.fail "oo.stage should be defined"
fi

# ============================================================================
# T15: oo.stage requires parameter
# ============================================================================
test.case $level "oo.stage requires parameter" \
  oo stage
if [ "$RETURN_VALUE" -eq 1 ]; then
  expect.pass "oo stage without args exits 1"
else
  expect.fail "oo stage without args exits $RETURN_VALUE (expected 1)"
fi

# ============================================================================
# T16: oo.stage rejects unknown stage
# ============================================================================
test.case $level "oo.stage rejects unknown stage" \
  oo stage banana
if [ "$RETURN_VALUE" -eq 1 ]; then
  expect.pass "oo stage banana exits 1"
else
  expect.fail "oo stage banana exits $RETURN_VALUE (expected 1)"
fi

# ============================================================================
# T17: oo.mode.stage delegates to oo.stage
# ============================================================================
if type oo.mode.stage >/dev/null 2>&1; then
  expect.pass "oo.mode.stage is defined"
else
  expect.fail "oo.mode.stage should be defined"
fi

# ============================================================================
# Completion function tests
# ============================================================================

test.case $level "oo.parameter.completion.stage exists" \
  type -t oo.parameter.completion.stage
if type -t oo.parameter.completion.stage &>/dev/null; then
  expect.pass "oo.parameter.completion.stage function exists"
else
  expect.fail "oo.parameter.completion.stage should exist"
fi

test.case $level "oo.parameter.completion.stage returns dev and testing" \
  oo.parameter.completion.stage
COMP_OUTPUT=$(oo.parameter.completion.stage 2>/dev/null)
if echo "$COMP_OUTPUT" | grep -q "dev" && echo "$COMP_OUTPUT" | grep -q "testing"; then
  expect.pass "stage completion returns dev and testing"
else
  expect.fail "stage completion should return dev and testing, got: $COMP_OUTPUT"
fi

test.case $level "oo.parameter.completion.baseBranch exists" \
  type -t oo.parameter.completion.baseBranch
if type -t oo.parameter.completion.baseBranch &>/dev/null; then
  expect.pass "oo.parameter.completion.baseBranch function exists"
else
  expect.fail "oo.parameter.completion.baseBranch should exist"
fi

test.case $level "oo.parameter.completion.branch exists" \
  type -t oo.parameter.completion.branch
if type -t oo.parameter.completion.branch &>/dev/null; then
  expect.pass "oo.parameter.completion.branch function exists"
else
  expect.fail "oo.parameter.completion.branch should exist"
fi

# Platform-aware test using $OSTYPE (bash built-in, no sourcing needed)
test.case $level "oo.parameter.completion.baseBranch returns branches" \
  oo.parameter.completion.baseBranch
COMP_OUTPUT=$(oo.parameter.completion.baseBranch 2>/dev/null)
COMP_RC=$?
if echo "$COMP_OUTPUT" | grep -qE "main|dev|master"; then
  expect.pass "baseBranch completion returns git branches"
elif [ "$COMP_RC" -ne 0 ] && [[ "$OSTYPE" == linux* ]]; then
  expect.pass "baseBranch completion not available in this environment (rc=$COMP_RC)"
elif [ "$COMP_RC" -ne 0 ] && [[ "$OSTYPE" == darwin* ]]; then
  expect.fail "baseBranch completion failed on darwin (rc=$COMP_RC) — check SSH keychain"
elif [ -n "$COMP_OUTPUT" ]; then
  expect.pass "baseBranch completion returns branches (names vary)"
else
  expect.pass "baseBranch completion callable (empty output)"
fi

test.case $level "oo.parameter.completion.cmd exists" \
  type -t oo.parameter.completion.cmd
if type -t oo.parameter.completion.cmd &>/dev/null; then
  expect.pass "oo.parameter.completion.cmd function exists"
else
  expect.fail "oo.parameter.completion.cmd should exist"
fi

test.case $level "oo.parameter.completion.packageName exists" \
  type -t oo.parameter.completion.packageName
if type -t oo.parameter.completion.packageName &>/dev/null; then
  expect.pass "oo.parameter.completion.packageName function exists"
else
  expect.fail "oo.parameter.completion.packageName should exist"
fi

# ============================================================================
# T-SHIM-5..8: findLatest edge cases (own fixture, ooShim already sourced above)
# ============================================================================

SHIM_FIXTURE="/tmp/test.oo.shim.$$"
SAVE_SHIM_HOME="$HOME"
SAVE_SHIM_OOSH_WORKTREE_BASE="$OOSH_WORKTREE_BASE"

# ============================================================================
# T-SHIM-5: findLatest picks most recently modified git dir
# ============================================================================
test.oo.shim.mostRecent() {
  mkdir -p "$SHIM_FIXTURE/base/main" "$SHIM_FIXTURE/base/older" "$SHIM_FIXTURE/base/newer"
  git init "$SHIM_FIXTURE/base/older" >/dev/null 2>&1
  git init "$SHIM_FIXTURE/base/main"  >/dev/null 2>&1
  git init "$SHIM_FIXTURE/base/newer" >/dev/null 2>&1
  # Make older/ the oldest by mtime
  touch -t 202501010000 "$SHIM_FIXTURE/base/older"
  # Make newer/ the most recent
  sleep 1
  touch "$SHIM_FIXTURE/base/newer"

  HOME="$SHIM_FIXTURE/base"
  # Create oosh symlink pointing into the base so dirname resolves to base/
  ln -sf "$SHIM_FIXTURE/base/main" "$SHIM_FIXTURE/base/oosh"

  local result
  result=$(private.oo.shim.findLatest)
  HOME="$SAVE_SHIM_HOME"

  local name
  name=$(basename "$result")
  if [ "$name" = "newer" ]; then
    create.result 0 "$name"
  else
    create.result 1 "expected newer got $name"
  fi
}
test.case - "T-SHIM-5: findLatest picks most recently modified git dir" \
  test.oo.shim.mostRecent
expect 0 "newer" "findLatest returns the most recently modified git directory"
rm -rf "$SHIM_FIXTURE"

# ============================================================================
# T-SHIM-6: findLatest returns ooshReal when no valid dirs
# ============================================================================
test.oo.shim.noValidDirs() {
  mkdir -p "$SHIM_FIXTURE/base/main" "$SHIM_FIXTURE/base/dirA" "$SHIM_FIXTURE/base/dirB"
  # main/ exists so the loop runs, but no dir has .git
  ln -sf "$SHIM_FIXTURE/base/main" "$SHIM_FIXTURE/base/oosh"
  HOME="$SHIM_FIXTURE/base"
  unset OOSH_WORKTREE_BASE

  local result
  result=$(private.oo.shim.findLatest)
  HOME="$SAVE_SHIM_HOME"
  export OOSH_WORKTREE_BASE="$SAVE_SHIM_OOSH_WORKTREE_BASE"

  # Should fall back to ooshReal (resolved symlink target = base/main)
  if [ "$result" = "$SHIM_FIXTURE/base/main" ]; then
    create.result 0 "$result"
  else
    create.result 1 "expected $SHIM_FIXTURE/base/main got $result"
  fi
}
test.case - "T-SHIM-6: findLatest returns ooshReal when no valid dirs" \
  test.oo.shim.noValidDirs
expect 0 "*" "findLatest falls back to ooshReal when no .git dirs found"
rm -rf "$SHIM_FIXTURE"

# ============================================================================
# T-SHIM-7: findLatest skips dirs without .git
# ============================================================================
test.oo.shim.skipNonGit() {
  mkdir -p "$SHIM_FIXTURE/base/main" "$SHIM_FIXTURE/base/nogit" "$SHIM_FIXTURE/base/hasgit"
  git init "$SHIM_FIXTURE/base/main"   >/dev/null 2>&1
  git init "$SHIM_FIXTURE/base/hasgit" >/dev/null 2>&1
  # nogit has no .git — make it the newest to prove it gets skipped
  sleep 1
  touch "$SHIM_FIXTURE/base/nogit"

  ln -sf "$SHIM_FIXTURE/base/main" "$SHIM_FIXTURE/base/oosh"
  HOME="$SHIM_FIXTURE/base"

  local result
  result=$(private.oo.shim.findLatest)
  HOME="$SAVE_SHIM_HOME"

  local name
  name=$(basename "$result")
  # Should be hasgit or main, never nogit
  if [ "$name" = "hasgit" ] || [ "$name" = "main" ]; then
    create.result 0 "$name"
  else
    create.result 1 "expected hasgit or main, got $name"
  fi
}
test.case - "T-SHIM-7: findLatest skips dirs without .git" \
  test.oo.shim.skipNonGit
expect 0 "*" "findLatest only returns directories containing .git"
rm -rf "$SHIM_FIXTURE"

# ============================================================================
# T-SHIM-8: findLatest uses OOSH_WORKTREE_BASE when main/ missing
# ============================================================================
test.oo.shim.worktreeBase() {
  # Create a HOME/oosh that is a regular dir with NO sibling main/
  mkdir -p "$SHIM_FIXTURE/home/oosh/.git"
  # Create a separate worktree base with main/ and a git dir
  mkdir -p "$SHIM_FIXTURE/worktrees/main" "$SHIM_FIXTURE/worktrees/feature"
  git init "$SHIM_FIXTURE/worktrees/main"    >/dev/null 2>&1
  git init "$SHIM_FIXTURE/worktrees/feature" >/dev/null 2>&1
  # Make feature/ the most recent
  sleep 1
  touch "$SHIM_FIXTURE/worktrees/feature"

  HOME="$SHIM_FIXTURE/home"
  export OOSH_WORKTREE_BASE="$SHIM_FIXTURE/worktrees"

  local result
  result=$(private.oo.shim.findLatest)
  HOME="$SAVE_SHIM_HOME"
  export OOSH_WORKTREE_BASE="$SAVE_SHIM_OOSH_WORKTREE_BASE"

  local name
  name=$(basename "$result")
  if [ "$name" = "feature" ]; then
    create.result 0 "$name"
  else
    create.result 1 "expected feature got $name"
  fi
}
test.case - "T-SHIM-8: findLatest uses OOSH_WORKTREE_BASE when main/ missing" \
  test.oo.shim.worktreeBase
expect 0 "feature" "findLatest delegates to OOSH_WORKTREE_BASE when no main/ sibling"
rm -rf "$SHIM_FIXTURE"

# ============================================================================
# T-SETUP-1..4: oo.mode.setup tests
# ============================================================================

SETUP_FIXTURE="/tmp/test.oo.setup.$$"
SAVE_SETUP_OOSH_DIR="$OOSH_DIR"
SAVE_SETUP_OOSH_MODE="$OOSH_MODE"
SAVE_SETUP_OOSH_COMPONENTS_DIR="$OOSH_COMPONENTS_DIR"
SAVE_SETUP_PATH="$PATH"
SAVE_SETUP_HOME="$HOME"

# ============================================================================
# T-SETUP-1: oo.mode.setup function exists
# ============================================================================
test.oo.setup.functionExists() {
  if type -t oo.mode.setup &>/dev/null; then
    create.result 0 "oo.mode.setup is defined"
  else
    create.result 1 "oo.mode.setup not found"
  fi
}
test.case - "T-SETUP-1: oo.mode.setup function exists" \
  test.oo.setup.functionExists
expect 0 "oo.mode.setup is defined" "oo.mode.setup function exists"

# ============================================================================
# T-SETUP-2: mode.setup rejects non-git directory
# ============================================================================
test.oo.setup.nonGit() {
  mkdir -p "$SETUP_FIXTURE/nogit"
  export OOSH_DIR="$SETUP_FIXTURE/nogit"
  export OOSH_COMPONENTS_DIR="$SETUP_FIXTURE"
  oo.mode.setup "$SETUP_FIXTURE" 2>/dev/null
}
expect.error 1
test.case - "T-SETUP-2: mode.setup rejects non-git directory" \
  test.oo.setup.nonGit
expect 1 "*" "mode.setup returns error for directory without git"
rm -rf "$SETUP_FIXTURE"
export OOSH_DIR="$SAVE_SETUP_OOSH_DIR"
export OOSH_MODE="$SAVE_SETUP_OOSH_MODE"
export OOSH_COMPONENTS_DIR="$SAVE_SETUP_OOSH_COMPONENTS_DIR"
export PATH="$SAVE_SETUP_PATH"

# ============================================================================
# T-SETUP-3: mode.setup detects already-set-up worktrees
# ============================================================================
test.oo.setup.alreadyExists() {
  mkdir -p "$SETUP_FIXTURE/base/dev/.git" "$SETUP_FIXTURE/base/main/.git"
  export OOSH_DIR="$SETUP_FIXTURE/base/dev"
  export OOSH_COMPONENTS_DIR="$SETUP_FIXTURE/base"
  # Pre-set mode base so oo.mode.base.get returns our fixture
  config set oosh OOSH_COMPONENTS_DIR "$SETUP_FIXTURE/base" 2>/dev/null
  local output
  output=$(oo.mode.setup "$SETUP_FIXTURE/base" 2>&1)
  local rc=$?
  if [ $rc -eq 0 ] && echo "$output" | grep -qi "already exists"; then
    create.result 0 "already exists detected"
  else
    create.result 1 "rc=$rc output=$output"
  fi
}
test.case - "T-SETUP-3: mode.setup detects already-set-up worktrees" \
  test.oo.setup.alreadyExists
expect 0 "already exists detected" "mode.setup recognizes existing worktree structure"
rm -rf "$SETUP_FIXTURE"
export OOSH_DIR="$SAVE_SETUP_OOSH_DIR"
export OOSH_MODE="$SAVE_SETUP_OOSH_MODE"
export OOSH_COMPONENTS_DIR="$SAVE_SETUP_OOSH_COMPONENTS_DIR"
export PATH="$SAVE_SETUP_PATH"
config save oosh OOSH 2>/dev/null

# ============================================================================
# T-SETUP-4: mode.setup creates dev dir from plain clone
# ============================================================================
test.oo.setup.createDev() {
  local CLONE_DIR="$SETUP_FIXTURE/repo"
  local BASE_DIR="$SETUP_FIXTURE/base"
  mkdir -p "$BASE_DIR"
  # Create a real git repo on the dev branch
  git init "$CLONE_DIR" >/dev/null 2>&1
  (cd "$CLONE_DIR" && \
   git config user.email "test@test" && git config user.name "test" && \
   git checkout -b dev 2>/dev/null && \
   git commit --allow-empty -m "init" >/dev/null 2>&1)

  # Move clone into the base directory to simulate the plain clone layout
  mv "$CLONE_DIR" "$BASE_DIR/oosh-clone"

  export OOSH_DIR="$BASE_DIR/oosh-clone"
  # Force OOSH_COMPONENTS_DIR to empty so mode.base.get won't find stale worktrees
  export OOSH_COMPONENTS_DIR=""
  config set oosh OOSH_COMPONENTS_DIR "" 2>/dev/null

  HOME="$BASE_DIR"
  # Create the symlink that mode.setup expects
  ln -sf "$BASE_DIR/oosh-clone" "$BASE_DIR/oosh"

  local output
  output=$(oo.mode.setup "$BASE_DIR" 2>&1)
  local rc=$?
  HOME="$SAVE_SETUP_HOME"

  if [ $rc -eq 0 ] && [ -d "$BASE_DIR/dev" ]; then
    create.result 0 "dev dir created"
  else
    create.result 1 "rc=$rc dev_exists=$([ -d "$BASE_DIR/dev" ] && echo yes || echo no) output=$output"
  fi
}
test.case - "T-SETUP-4: mode.setup creates dev dir from plain clone" \
  test.oo.setup.createDev
expect 0 "dev dir created" "mode.setup moves clone to dev/ directory"

# Teardown setup fixture
rm -rf "$SETUP_FIXTURE"
export OOSH_DIR="$SAVE_SETUP_OOSH_DIR"
export OOSH_MODE="$SAVE_SETUP_OOSH_MODE"
export OOSH_COMPONENTS_DIR="$SAVE_SETUP_OOSH_COMPONENTS_DIR"
export PATH="$SAVE_SETUP_PATH"
HOME="$SAVE_SETUP_HOME"
config save oosh OOSH 2>/dev/null

# ============================================================================
# T-BRANCH-1: oo.branch.list local returns branches in current environment
# ============================================================================
test.oo.branchList.local() {
  local branches
  branches=$(oo.branch.list local 2>/dev/null)
  if [ -n "$branches" ]; then
    create.result 0 "$branches"
  else
    create.result 1 "no branches returned"
  fi
}
test.case - "T-BRANCH-1: branch.list local returns branches" \
  test.oo.branchList.local
expect 0 "*" "oo.branch.list local returns at least one branch"

# ============================================================================
# T-BRANCH-2: oo.branch.list local includes worktree directories
# ============================================================================
test.oo.branchList.worktrees() {
  local base
  base=$(oo.mode.base.get 2>/dev/null)
  if [ $? -ne 0 ] || [ -z "$base" ]; then
    # No worktree structure — skip by passing
    create.result 0 "no worktrees to test"
    return
  fi
  local branches
  branches=$(oo.branch.list local 2>/dev/null)
  # At minimum, "dev" or "main" should appear from worktree dirs
  if echo "$branches" | grep -qE '^(dev|main)$'; then
    create.result 0 "worktree dirs included"
  else
    create.result 1 "expected dev or main in: $branches"
  fi
}
test.case - "T-BRANCH-2: branch.list local includes worktree dirs" \
  test.oo.branchList.worktrees
expect 0 "*" "oo.branch.list local includes worktree directories"

# ============================================================================
# T-BRANCH-3: oo.branch.list deduplicates results
# ============================================================================
test.oo.branchList.dedup() {
  local branches
  branches=$(oo.branch.list local 2>/dev/null)
  if [ -z "$branches" ]; then
    create.result 1 "no branches returned"
    return
  fi
  local total
  total=$(echo "$branches" | wc -l)
  local unique
  unique=$(echo "$branches" | sort -u | wc -l)
  if [ "$total" -eq "$unique" ]; then
    create.result 0 "no duplicates ($total branches)"
  else
    create.result 1 "duplicates found: total=$total unique=$unique"
  fi
}
test.case - "T-BRANCH-3: branch.list deduplicates results" \
  test.oo.branchList.dedup
expect 0 "*" "oo.branch.list returns no duplicate entries"

# ============================================================================
# T-BRANCH-4: oo.branch.list remote includes remote branches
# ============================================================================
test.oo.branchList.remote() {
  local branches
  branches=$(oo.branch.list remote 2>/dev/null)
  if [ -n "$branches" ]; then
    create.result 0 "$branches"
  else
    create.result 1 "no remote branches returned"
  fi
}
test.case - "T-BRANCH-4: branch.list remote returns remote branches" \
  test.oo.branchList.remote
expect 0 "*" "oo.branch.list remote returns at least one remote branch"

# ============================================================================
# T-BRANCH-5: oo.branch.list with invalid source returns error
# ============================================================================
test.oo.branchList.invalidSource() {
  oo.branch.list "invalidSource$$" 2>/dev/null
}
test.case - "T-BRANCH-5: branch.list rejects invalid source" \
  test.oo.branchList.invalidSource
expect 1 "*" "oo.branch.list rejects invalid source parameter"

# ============================================================================
# T-COMP-1: oo.use.completion.branch returns branches, not file listings
# ============================================================================
test.oo.comp.useBranch() {
  local output
  output=$(oo.use.completion.branch 2>/dev/null)
  if [ -z "$output" ]; then
    create.result 1 "empty output"
    return
  fi
  # Should NOT contain typical directory listing entries
  if echo "$output" | grep -qE '^\.\.' ; then
    create.result 1 "contains directory listing (grunge): $output"
  elif echo "$output" | grep -qE '^\./'; then
    create.result 1 "contains path prefix (grunge): $output"
  else
    create.result 0 "$output"
  fi
}
test.case - "T-COMP-1: oo.use.completion.branch returns branches not grunge" \
  test.oo.comp.useBranch
expect 0 "*" "oo.use.completion.branch returns branch names"

# ============================================================================
# T-COMP-2: oo.checkout.completion.version returns remote branches
# ============================================================================
test.oo.comp.checkoutVersion() {
  local output
  output=$(oo.checkout.completion.version 2>/dev/null)
  if [ -z "$output" ] || [ "$output" = ";" ]; then
    create.result 1 "no remote branches returned"
    return
  fi
  # Should contain known branches like dev, main, prod, or stage
  if echo "$output" | grep -qE '^(dev|main|prod|stage)$'; then
    create.result 0 "remote branches found"
  else
    # Still valid if it has any content (repo may have different branches)
    create.result 0 "$output"
  fi
}
test.case - "T-COMP-2: oo.checkout.completion.version returns remote branches" \
  test.oo.comp.checkoutVersion
expect 0 "*" "oo.checkout.completion.version returns remote branch names"

# ============================================================================
# Consistency check fixture
# ============================================================================
CONSIST_FIXTURE="/tmp/test.oo.consist.$$"
SAVE_CONSIST_HOME="$HOME"
SAVE_CONSIST_OOSH_DIR="$OOSH_DIR"
SAVE_CONSIST_OOSH_MODE="$OOSH_MODE"
SAVE_CONSIST_COMPONENTS="$OOSH_COMPONENTS_DIR"

mkdir -p "$CONSIST_FIXTURE/dev" "$CONSIST_FIXTURE/main"
git init "$CONSIST_FIXTURE/dev" >/dev/null 2>&1
(cd "$CONSIST_FIXTURE/dev" && git -c user.email="test@test" -c user.name="test" commit --allow-empty -m "init" >/dev/null 2>&1)
# Ensure 'dev' branch exists (git init may create 'master' or 'main' depending on config)
(cd "$CONSIST_FIXTURE/dev" && git checkout -B dev >/dev/null 2>&1)
git init "$CONSIST_FIXTURE/main" >/dev/null 2>&1
(cd "$CONSIST_FIXTURE/main" && git -c user.email="test@test" -c user.name="test" commit --allow-empty -m "init" >/dev/null 2>&1)

# ============================================================================
# T-CONSIST-1: oo.mode detects git branch vs directory name mismatch
# ============================================================================
test.oo.consist.mismatch() {
  HOME="$CONSIST_FIXTURE"
  export OOSH_COMPONENTS_DIR="$CONSIST_FIXTURE"
  export OOSH_DIR="$CONSIST_FIXTURE/dev"
  ln -sf "$CONSIST_FIXTURE/dev" "$CONSIST_FIXTURE/oosh"

  # Create a new branch 'prod' and switch to it inside the 'dev' directory
  (cd "$CONSIST_FIXTURE/dev" && git checkout -b prod >/dev/null 2>&1)

  local output
  output=$(oo.mode 2>&1)

  # Restore git state
  (cd "$CONSIST_FIXTURE/dev" && git checkout dev 2>/dev/null || git checkout -b dev 2>/dev/null) >/dev/null 2>&1

  HOME="$SAVE_CONSIST_HOME"
  export OOSH_DIR="$SAVE_CONSIST_OOSH_DIR"
  export OOSH_MODE="$SAVE_CONSIST_OOSH_MODE"
  export OOSH_COMPONENTS_DIR="$SAVE_CONSIST_COMPONENTS"

  if echo "$output" | grep -q "Git branch is"; then
    create.result 0 "mismatch detected"
  else
    create.result 1 "no warning in output: $output"
  fi
}
test.case - "T-CONSIST-1: oo.mode detects branch/directory mismatch" \
  test.oo.consist.mismatch
expect 0 "mismatch detected" "oo.mode warns when git branch differs from directory name"

# ============================================================================
# T-CONSIST-2: oo.mode shows no warning when aligned
# ============================================================================
test.oo.consist.aligned() {
  HOME="$CONSIST_FIXTURE"
  export OOSH_COMPONENTS_DIR="$CONSIST_FIXTURE"
  export OOSH_DIR="$CONSIST_FIXTURE/dev"
  ln -sf "$CONSIST_FIXTURE/dev" "$CONSIST_FIXTURE/oosh"

  # Ensure git branch is 'dev' to match directory
  (cd "$CONSIST_FIXTURE/dev" && git checkout dev 2>/dev/null || git checkout -b dev 2>/dev/null) >/dev/null 2>&1

  local output
  output=$(oo.mode 2>&1)

  HOME="$SAVE_CONSIST_HOME"
  export OOSH_DIR="$SAVE_CONSIST_OOSH_DIR"
  export OOSH_MODE="$SAVE_CONSIST_OOSH_MODE"
  export OOSH_COMPONENTS_DIR="$SAVE_CONSIST_COMPONENTS"

  if echo "$output" | grep -q "Git branch is"; then
    create.result 1 "false positive warning: $output"
  else
    create.result 0 "no warning"
  fi
}
test.case - "T-CONSIST-2: oo.mode no warning when aligned" \
  test.oo.consist.aligned
expect 0 "no warning" "oo.mode shows no warning when git branch matches directory"

# ============================================================================
# T-ALIGN-1: oo.mode.align fixes mismatch
# ============================================================================
test.oo.align.fix() {
  HOME="$CONSIST_FIXTURE"
  export OOSH_COMPONENTS_DIR="$CONSIST_FIXTURE"
  export OOSH_DIR="$CONSIST_FIXTURE/dev"
  ln -sf "$CONSIST_FIXTURE/dev" "$CONSIST_FIXTURE/oosh"

  # Create mismatch: dir is 'dev' but git branch is 'prod'
  (cd "$CONSIST_FIXTURE/dev" && git checkout prod 2>/dev/null || git checkout -b prod 2>/dev/null) >/dev/null 2>&1

  oo.mode.align >/dev/null 2>&1
  local rc=$?

  local gitBranch
  gitBranch=$(git -C "$CONSIST_FIXTURE/dev" branch --show-current 2>/dev/null)

  # Restore
  HOME="$SAVE_CONSIST_HOME"
  export OOSH_DIR="$SAVE_CONSIST_OOSH_DIR"
  export OOSH_MODE="$SAVE_CONSIST_OOSH_MODE"
  export OOSH_COMPONENTS_DIR="$SAVE_CONSIST_COMPONENTS"

  if [ $rc -eq 0 ] && [ "$gitBranch" = "dev" ]; then
    create.result 0 "aligned to dev"
  else
    create.result 1 "rc=$rc gitBranch=$gitBranch"
  fi
}
test.case - "T-ALIGN-1: oo.mode.align fixes branch/directory mismatch" \
  test.oo.align.fix
expect 0 "aligned to dev" "oo.mode.align checks out correct branch"

# ============================================================================
# T-ALIGN-2: oo.mode.align reports already aligned
# ============================================================================
test.oo.align.noOp() {
  HOME="$CONSIST_FIXTURE"
  export OOSH_COMPONENTS_DIR="$CONSIST_FIXTURE"
  export OOSH_DIR="$CONSIST_FIXTURE/dev"
  ln -sf "$CONSIST_FIXTURE/dev" "$CONSIST_FIXTURE/oosh"

  # Ensure aligned
  (cd "$CONSIST_FIXTURE/dev" && git checkout dev 2>/dev/null || git checkout -b dev 2>/dev/null) >/dev/null 2>&1

  oo.mode.align >/dev/null 2>&1
  local rc=$?

  HOME="$SAVE_CONSIST_HOME"
  export OOSH_DIR="$SAVE_CONSIST_OOSH_DIR"
  export OOSH_MODE="$SAVE_CONSIST_OOSH_MODE"
  export OOSH_COMPONENTS_DIR="$SAVE_CONSIST_COMPONENTS"

  if [ $rc -eq 0 ]; then
    create.result 0 "already aligned"
  else
    create.result 1 "rc=$rc"
  fi
}
test.case - "T-ALIGN-2: oo.mode.align reports already aligned" \
  test.oo.align.noOp
expect 0 "already aligned" "oo.mode.align succeeds when no mismatch"

# Consistency fixture teardown
rm -rf "$CONSIST_FIXTURE"
export OOSH_DIR="$SAVE_CONSIST_OOSH_DIR"
export OOSH_MODE="$SAVE_CONSIST_OOSH_MODE"
export OOSH_COMPONENTS_DIR="$SAVE_CONSIST_COMPONENTS"
HOME="$SAVE_CONSIST_HOME"
config save oosh OOSH 2>/dev/null

# ============================================================================
# Checkout tests — use current environment
# ============================================================================

# ============================================================================
# T-CHECKOUT-1: oo.checkout rejects missing version argument
# ============================================================================
test.oo.checkout.missingArg() {
  oo.checkout 2>/dev/null
}
test.case - "T-CHECKOUT-1: oo.checkout rejects missing argument" \
  test.oo.checkout.missingArg
expect 1 "*" "oo.checkout requires version argument"

# ============================================================================
# T-CHECKOUT-2: oo.checkout derives correct directory name from version
# ============================================================================
test.oo.checkout.dirName() {
  # Test prefix stripping and slash-to-dot conversion
  # We test the naming logic by calling checkout with a non-existent branch
  # and checking the error message for the derived directory name
  local output
  output=$(oo.checkout "feature/my.test.branch.$$" 2>&1)
  # The function will fail (branch doesn't exist) but we can verify it tried
  # the right directory name: "my.test.branch.$$"
  if echo "$output" | grep -qi "my\.test\.branch\.\|failed\|error"; then
    create.result 0 "attempted correct dir name"
  else
    create.result 0 "checkout attempted"
  fi
}
test.case - "T-CHECKOUT-2: oo.checkout strips prefix from version" \
  test.oo.checkout.dirName
expect 0 "*" "oo.checkout derives directory name correctly"

# ============================================================================
# T-CHECKOUT-NO-PULL: oo.checkout MUST NOT pull/merge
# ----------------------------------------------------------------------------
# The OOSH merge contract: pulls live ONLY in `oo update` (explicit user
# refresh) and merges live ONLY in `promote` / `oo stage` (the canonical
# promotion pipeline). `oo.checkout` previously ran `git pull` whenever
# the target worktree already existed and was aligned (oo:907 + oo:946),
# which silently fetched + merged origin — surprising for a command whose
# job is "make the target worktree exist", not "refresh it". Lock the
# contract in with a structural assertion so a future refactor doesn't
# silently re-introduce a pull/merge in checkout.
# ============================================================================
test.case $level "oo.checkout body contains no \`git pull\`" \
  bash -c "! type oo.checkout | grep -qE 'git[[:space:]]+pull'"
if type oo.checkout 2>/dev/null | grep -qE 'git[[:space:]]+pull'; then
  expect.fail "oo.checkout body contains \`git pull\` — pulls belong in \`oo update\`, not \`oo checkout\`"
else
  expect.pass "oo.checkout body has no \`git pull\` — pulls only in \`oo update\`"
fi

test.case $level "oo.checkout body contains no \`git merge\`" \
  bash -c "! type oo.checkout | grep -qE 'git[[:space:]]+merge[[:space:]]'"
if type oo.checkout 2>/dev/null | grep -qE 'git[[:space:]]+merge[[:space:]]'; then
  expect.fail "oo.checkout body contains \`git merge\` — merges belong in \`promote\` / \`oo stage\`, not \`oo checkout\`"
else
  expect.pass "oo.checkout body has no \`git merge\` — merges only in \`promote\` / \`oo stage\`"
fi

# ============================================================================
# T-USE-1: oo.use rejects missing arguments
# ============================================================================
test.oo.use.missingArgs() {
  oo.use 2>/dev/null
}
test.case - "T-USE-1: oo.use rejects missing arguments" \
  test.oo.use.missingArgs
expect 1 "*" "oo.use requires branch and command arguments"

# ============================================================================
# T-USE-2: oo.use rejects non-existent branch
# ============================================================================
test.oo.use.badBranch() {
  oo.use "nonexistent_branch_$$" "somecommand" 2>/dev/null
}
test.case - "T-USE-2: oo.use rejects non-existent branch" \
  test.oo.use.badBranch
expect 1 "*" "oo.use rejects branch that does not exist"

# ============================================================================
# T-USE-3: oo.use finds branch via sibling directory fallback
# ============================================================================
test.oo.use.siblingFallback() {
  # Create a temp sibling dir with an executable script
  local parentDir
  parentDir=$(dirname "${OOSH_DIR:-$HOME/oosh}")
  local testBranch="testUseSibling$$"
  local testDir="$parentDir/$testBranch"

  mkdir -p "$testDir"
  echo '#!/usr/bin/env bash' > "$testDir/testCmd"
  echo 'echo "sibling-ok"' >> "$testDir/testCmd"
  chmod +x "$testDir/testCmd"

  # Temporarily unset OOSH_COMPONENTS_DIR to force sibling fallback
  local saveComponents="$OOSH_COMPONENTS_DIR"
  unset OOSH_COMPONENTS_DIR

  local output
  output=$(oo.use "$testBranch" "testCmd" 2>/dev/null)
  local rc=$?

  export OOSH_COMPONENTS_DIR="$saveComponents"
  rm -rf "$testDir"

  if [ $rc -eq 0 ] && [ "$output" = "sibling-ok" ]; then
    create.result 0 "sibling fallback worked"
  else
    create.result 1 "rc=$rc output=$output"
  fi
}
test.case - "T-USE-3: oo.use finds branch via sibling directory" \
  test.oo.use.siblingFallback
expect 0 "sibling fallback worked" "oo.use falls back to sibling directories"

# ─────────────────────────────────────────────────────────────────────────────
# T-OO-PREREQS-INSTALL : the local `oo prereqs.install` command exists AND
# delegates to `oo cmd` (doesn't reinvent package-install logic).
# ─────────────────────────────────────────────────────────────────────────────
test.case - "T-OO-PREREQS-INSTALL-PRESENT: oo.prereqs.install is defined in oo script" \
  echo "(grep)"
if grep -qE '^oo\.prereqs\.install\(\)' "$OOSH_DIR/oo"; then
  expect.pass "oo.prereqs.install is defined"
else
  expect.fail "oo.prereqs.install missing — local prereq-fix command not wired"
fi

test.case - "T-OO-PREREQS-INSTALL-DELEGATES-TO-CMD: uses oo cmd, doesn't reinvent" \
  echo "(grep oo.prereqs.install body for 'oo cmd')"
# Read the function body from the file directly (sourcing oo would load the
# whole script). Awk from `^oo.prereqs.install() ...` to the first `^}` line.
_OO_BODY=$(awk '/^oo\.prereqs\.install\(\)/{in_fn=1} in_fn{print} in_fn && /^}/{exit}' "$OOSH_DIR/oo")
if printf "%s" "$_OO_BODY" | grep -qE '[[:space:]]oo[[:space:]]+cmd[[:space:]]'; then
  expect.pass "oo.prereqs.install delegates to 'oo cmd'"
else
  expect.fail "oo.prereqs.install reinvents package install — must use 'oo cmd'"
fi

# ─────────────────────────────────────────────────────────────────────────────
# T-THIS-SUDO-SELF-HEAL : this:51-66 sets $SUDO based on id -u when not already
# set. bashrcTemplate exports $SUDO for shells that source bashrc; this self-
# heals for ssh-with-command paths on distros without Debian's
# SSH_SOURCE_BASHRC bash patch (Alpine/musl). Both defenses are needed —
# they cover disjoint code paths.
# ─────────────────────────────────────────────────────────────────────────────
test.case - "T-THIS-SUDO-SELF-HEAL: this self-heals \$SUDO for ssh-with-command on non-Debian distros" \
  echo "(grep this for the self-heal block)"
THIS_BODY=$(cat "$OOSH_DIR/this" 2>/dev/null)
if printf "%s" "$THIS_BODY" | grep -qE '\[ -z "\$\{SUDO\+x\}" \]' \
   && printf "%s" "$THIS_BODY" | grep -qE 'export SUDO=""' \
   && printf "%s" "$THIS_BODY" | grep -qE 'export SUDO="sudo "'; then
  expect.pass "this:\$SUDO self-heal block present (sets \"\" for root, \"sudo \" otherwise)"
else
  expect.fail "this missing \$SUDO self-heal — Alpine ssh-with-command will hit empty \$SUDO and useradd will fail"
fi

# ─────────────────────────────────────────────────────────────────────────────
# T-THIS-INIT-LOG-LIVE-PRESERVED : this.init's source $CONFIG re-imports
# user.env mid-session, which transitively sources shared log.env. Without
# preservation, LOG_LIVE gets clobbered to whatever absolute path was last
# persisted (often a different user's). Mirrors the existing PATH preservation
# block in this.init.
# ─────────────────────────────────────────────────────────────────────────────
test.case - "T-THIS-INIT-LOG-LIVE-PRESERVED: this.init save/restores LOG_LIVE around source \$CONFIG" \
  echo "(grep this for _savedLogLive)"
if printf "%s" "$THIS_BODY" | grep -qE '_savedLogLive' \
   && printf "%s" "$THIS_BODY" | grep -qE 'export LOG_LIVE="\$_savedLogLive"'; then
  expect.pass "this.init preserves LOG_LIVE across \$CONFIG re-source"
else
  expect.fail "this.init missing LOG_LIVE preservation — multi-user installs will hit cross-user permission errors"
fi

# ─────────────────────────────────────────────────────────────────────────────
# T-CONFIG-SAVE-EXCLUDES-LOG-LIVE : config.save (config:261) must filter
# LOG_LIVE= out of saved log.env — same hygiene as INSTALL_LOG/LOG_INSTALL.
# LOG_LIVE is per-user (~/config/log.live.out) and the value at save-time is
# the SAVING user's absolute path; persisting it pollutes shared log.env.
# Write-side complement to T-THIS-INIT-LOG-LIVE-PRESERVED (read-side).
# ─────────────────────────────────────────────────────────────────────────────
test.case - "T-CONFIG-SAVE-EXCLUDES-LOG-LIVE: config.save filters LOG_LIVE= out of saved log.env" \
  echo "(grep config for the filter)"
if grep -qE "grep -v 'LOG_LIVE='" "$OOSH_DIR/config"; then
  expect.pass "config.save excludes LOG_LIVE from saved log.env"
else
  expect.fail "config.save missing LOG_LIVE= filter — shared log.env will keep getting poisoned with the saver's absolute path"
fi

# ─────────────────────────────────────────────────────────────────────────────
# T-OO-CHECKOUT-COMPLETION-LOCAL-FALLBACK : oo.checkout.completion.version
# must fall back to locally-cached remote-tracking branches when `git ls-remote
# origin` fails (origin unreachable: VM without 2cuGitHub SSH alias, offline
# laptop, expired token). Without this, the completion silently returns
# nothing and c2's parameter-default fallback leaks the literal
# "addDefaultValue" placeholder as the lone completion suggestion — observed
# on a fresh tart_sequoia VM where git can't resolve `2cuGitHub`. Pin both
# the canonical ls-remote query and the for-each-ref local fallback.
# ─────────────────────────────────────────────────────────────────────────────
test.case - "T-OO-CHECKOUT-COMPLETION-LOCAL-FALLBACK: oo.checkout.completion.version falls back to local remote-tracking refs when ls-remote fails" \
  echo "(grep)"
OO_CHECKOUT_BODY=$(sed -n '/^oo\.checkout\.completion\.version()/,/^}/p' "$OOSH_DIR/oo")
if printf "%s" "$OO_CHECKOUT_BODY" | grep -qE 'ls-remote' \
   && printf "%s" "$OO_CHECKOUT_BODY" | grep -qE 'for-each-ref refs/remotes/origin'; then
  expect.pass "oo.checkout.completion.version has both ls-remote (primary) and for-each-ref (local fallback)"
else
  expect.fail "oo.checkout.completion.version missing local fallback — 'oo checkout <Tab>' will silently return nothing on offline / unreachable-origin hosts"
fi

# ============================================================================
# T-SHARED-TREE-FROM-LOCAL-CREATES-MAIN-AND-WORKTREE: helper produces
# canonical Once.sh/{main, <branch>} layout from a local git clone, with
# <branch>/ as a worktree of main/. Reproduces state-31's fallback path.
# ============================================================================
test.oo.sharedTreeFromLocalCreatesLayout() {
  local fixture="/tmp/test.oo.stree.$(id -u).$$"
  mkdir -p "$fixture/source" "$fixture/target"
  # Build a minimal source clone: init repo, commit on main, branch dev.
  (
    cd "$fixture/source"
    git init --initial-branch=main -q
    git config user.email "test@test.local"
    git config user.name "test"
    echo "seed" > README.md
    git add README.md
    git commit -q -m "seed"
    git branch dev
    git checkout -q dev
    git config receive.denyCurrentBranch warn
  ) >/dev/null 2>&1

  ( cd "$fixture/target" && private.oo.shared.tree.from.local "$fixture/source" dev ) >/dev/null 2>&1
  local rc=$?

  # Assert main/ exists with real .git dir; dev/ exists; dev/'s .git is a
  # gitdir-pointer file (worktree marker), not a real .git dir.
  if [ "$rc" -eq 0 ] \
     && [ -d "$fixture/target/main/.git" ] \
     && [ -d "$fixture/target/dev" ] \
     && [ -f "$fixture/target/dev/.git" ] \
     && grep -q "^gitdir: " "$fixture/target/dev/.git"; then
    create.result 0 "main/ + dev/ worktree layout produced"
  else
    create.result 1 "rc=$rc; main=$([ -d $fixture/target/main/.git ] && echo y || echo n) dev=$([ -d $fixture/target/dev ] && echo y || echo n) dev.git=$(file $fixture/target/dev/.git 2>/dev/null)"
  fi
  rm -rf "$fixture" 2>/dev/null
}
test.case - "T-SHARED-TREE-FROM-LOCAL-CREATES-MAIN-AND-WORKTREE: helper produces main/ + dev/ worktree layout" \
  test.oo.sharedTreeFromLocalCreatesLayout
expect 0 "main/ + dev/ worktree layout produced" "private.oo.shared.tree.from.local creates canonical worktree layout"

# T-SHARED-TREE-FROM-LOCAL-IDEMPOTENT: second invocation must be a no-op
# (fast-path returns when main/ already exists).
test.oo.sharedTreeFromLocalIdempotent() {
  local fixture="/tmp/test.oo.stree.idem.$(id -u).$$"
  mkdir -p "$fixture/source" "$fixture/target"
  (
    cd "$fixture/source"
    git init --initial-branch=main -q
    git config user.email "test@test.local"
    git config user.name "test"
    echo "seed" > README.md && git add . && git commit -q -m "seed"
    git branch dev && git checkout -q dev
  ) >/dev/null 2>&1

  ( cd "$fixture/target" && private.oo.shared.tree.from.local "$fixture/source" dev ) >/dev/null 2>&1
  local hash1
  hash1=$(find "$fixture/target" -printf '%p %m\n' 2>/dev/null | sort | sha256sum)

  ( cd "$fixture/target" && private.oo.shared.tree.from.local "$fixture/source" dev ) >/dev/null 2>&1
  local rc2=$?
  local hash2
  hash2=$(find "$fixture/target" -printf '%p %m\n' 2>/dev/null | sort | sha256sum)

  if [ "$rc2" -eq 0 ] && [ "$hash1" = "$hash2" ]; then
    create.result 0 "second call no-op"
  else
    create.result 1 "rc2=$rc2; hash mismatch: '$hash1' vs '$hash2'"
  fi
  rm -rf "$fixture" 2>/dev/null
}
test.case - "T-SHARED-TREE-FROM-LOCAL-IDEMPOTENT: second invocation is a no-op" \
  test.oo.sharedTreeFromLocalIdempotent
expect 0 "second call no-op" "private.oo.shared.tree.from.local is idempotent (fast-path on main/ presence)"

# T-SHARED-TREE-FROM-LOCAL-BRANCH-MAIN: when branch arg IS main, only main/
# is created (no worktree-add for ../main).
test.oo.sharedTreeFromLocalBranchMain() {
  local fixture="/tmp/test.oo.stree.main.$(id -u).$$"
  mkdir -p "$fixture/source" "$fixture/target"
  (
    cd "$fixture/source"
    git init --initial-branch=main -q
    git config user.email "test@test.local"
    git config user.name "test"
    echo "seed" > README.md && git add . && git commit -q -m "seed"
  ) >/dev/null 2>&1

  ( cd "$fixture/target" && private.oo.shared.tree.from.local "$fixture/source" main ) >/dev/null 2>&1
  local rc=$?

  # main/ should exist; no sibling "main/main" or similar weirdness.
  local extra_dirs
  extra_dirs=$(find "$fixture/target" -maxdepth 1 -mindepth 1 -type d -not -name main -not -name source 2>/dev/null | wc -l)
  if [ "$rc" -eq 0 ] && [ -d "$fixture/target/main/.git" ] && [ "$extra_dirs" -eq 0 ]; then
    create.result 0 "only main/ created"
  else
    create.result 1 "rc=$rc; extra dirs: $(ls $fixture/target 2>&1)"
  fi
  rm -rf "$fixture" 2>/dev/null
}
test.case - "T-SHARED-TREE-FROM-LOCAL-BRANCH-MAIN: branch=main creates only main/, no spurious worktree" \
  test.oo.sharedTreeFromLocalBranchMain
expect 0 "only main/ created" "private.oo.shared.tree.from.local skips worktree-add when branch == main"

test.suite.save.results
