Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/04_brief_extraction.bats
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ teardown() {
local brief
brief=$(git config --file "$proj/.repos/.orbit" --get repos.myrepo.brief)
[ "${#brief}" -le 120 ]
[[ "$brief" != *" "* ]] || [[ "$brief" =~ [a-z]$ ]]
refute_contains "$brief" " " || assert_matches "$brief" '[a-z]$'
}

@test "brief: skips HTML block elements and their plain-text contents" {
Expand Down
6 changes: 3 additions & 3 deletions tests/05_workspace_lifecycle.bats
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ teardown() {
cd "$proj" && orbit new "first task" >/dev/null 2>&1
local created
created=$(git config --file "$proj/task-01/.orbit" --get workspace.created)
[[ "$created" =~ ^[0-9]+$ ]]
assert_matches "$created" '^[0-9]+$'
}

@test "new: --name creates custom-named workspace directory" {
Expand Down Expand Up @@ -126,7 +126,7 @@ teardown() {
cd "$proj/dev" && orbit done >/dev/null 2>&1
local done_at
done_at=$(git config --file "$proj/dev/.orbit" --get workspace.done-at)
[[ "$done_at" =~ ^[0-9]+$ ]]
assert_matches "$done_at" '^[0-9]+$'
}

@test "done: writes done-date in ISO format" {
Expand All @@ -139,7 +139,7 @@ teardown() {
cd "$proj/dev" && orbit done >/dev/null 2>&1
local done_date
done_date=$(git config --file "$proj/dev/.orbit" --get workspace.done-date)
[[ "$done_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]
assert_matches "$done_date" '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
}

@test "done: --pr writes PR URL to [pr] section" {
Expand Down
2 changes: 1 addition & 1 deletion tests/06_worktree_integration.bats
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ teardown() {
# scoped branch prints without the ws/<ws>/ prefix; clean repo folds to 'clean'
assert_contains "$output" "myrepo"
assert_contains "$output" "clean"
[[ "$output" != *"ws/dev/main"* ]]
refute_contains "$output" "ws/dev/main"
}

@test "status: raw mode branch gets the conversion steering line (parity with context/done)" {
Expand Down
2 changes: 1 addition & 1 deletion tests/07_metadata.bats
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ teardown() {
cd "$proj/manual-ws" && orbit goal "set from nothing" >/dev/null 2>&1
local created
created=$(git config --file "$proj/manual-ws/.orbit" --get workspace.created)
[[ "$created" =~ ^[0-9]+$ ]]
assert_matches "$created" '^[0-9]+$'
}
24 changes: 12 additions & 12 deletions tests/09_prune.bats
Original file line number Diff line number Diff line change
Expand Up @@ -699,8 +699,8 @@ setup_project_with_done_workspace() {
run bash -c "cd '$proj/dev' && PATH='$stubs':\$PATH ORBIT_ROOT='$proj' bash '$ORBIT_CMD' prune --dry-run 2>&1"
[ "$status" -ne 0 ]
assert_contains "$output" "prune must be run from the project root"
[[ "$output" != *"&&"* ]]
[[ "$output" != *"cd $proj"* ]]
refute_contains "$output" "&&"
refute_contains "$output" "cd $proj"
assert_dir_exists "$proj/dev"
}

Expand Down Expand Up @@ -1235,9 +1235,9 @@ setup_project_with_done_workspace() {
assert_contains "$output" " myrepo:"
assert_contains "$output" " raw-orphan (unmerged)"
assert_contains "$output" 'git -C ".repos/myrepo" branch -D raw-orphan'
[[ "$output" != *"raw-pushed"* ]]
[[ "$output" != *"raw-active"* ]]
[[ "$output" != *"release-1.2"* ]]
refute_contains "$output" "raw-pushed"
refute_contains "$output" "raw-active"
refute_contains "$output" "release-1.2"
}

@test "prune: report does not leak git's native branch-deletion output" {
Expand All @@ -1251,7 +1251,7 @@ setup_project_with_done_workspace() {
run bash -c "cd '$proj' && ORBIT_ROOT='$proj' bash '$ORBIT_CMD' prune 2>&1"
[ "$status" -eq 0 ]
assert_contains "$output" "deleted branch (merged): ws/dev/main"
[[ "$output" != *"Deleted branch"* ]]
refute_contains "$output" "Deleted branch"
}

@test "prune --dry-run: ghost residue reported with would-forms, nothing deleted" {
Expand Down Expand Up @@ -1300,7 +1300,7 @@ setup_project_with_done_workspace() {
assert_contains "$output" "untraceable branches (raw, no remote, no workspace)"
assert_contains "$output" " ws/lonely (merged)"
# raw arm of the three-condition: residue present ⇒ NOT "nothing to prune"
[[ "$output" != *"nothing to prune"* ]]
refute_contains "$output" "nothing to prune"
run git -C "$proj/.repos/myrepo" rev-parse --verify --quiet refs/heads/ws/lonely
[ "$status" -eq 0 ]

Expand Down Expand Up @@ -1331,7 +1331,7 @@ setup_project_with_done_workspace() {
run bash -c "cd '$proj' && ORBIT_ROOT='$proj' bash '$ORBIT_CMD' prune 2>&1"
[ "$status" -eq 0 ]
assert_contains "$output" 'git -C ".repos/myrepo" branch -D evil\;name'
[[ "$output" != *'branch -D evil;name'* ]]
refute_contains "$output" 'branch -D evil;name'
}

@test "prune: raw current branch never enters the branch pipeline — raw report only" {
Expand Down Expand Up @@ -1463,15 +1463,15 @@ setup_project_with_done_workspace() {
run cat "$SANDBOX/e.txt"
assert_contains "$output" "untraceable branches (raw, no remote, no workspace)"
run cat "$SANDBOX/o.txt"
[[ "$output" != *"untraceable branches"* ]]
refute_contains "$output" "untraceable branches"

# dry-run: everything is report → stdout
run bash -c "cd '$proj' && ORBIT_ROOT='$proj' bash '$ORBIT_CMD' prune --dry-run >'$SANDBOX/o2.txt' 2>'$SANDBOX/e2.txt'"
[ "$status" -eq 0 ]
run cat "$SANDBOX/o2.txt"
assert_contains "$output" "untraceable branches (raw, no remote, no workspace)"
run cat "$SANDBOX/e2.txt"
[[ "$output" != *"untraceable branches"* ]]
refute_contains "$output" "untraceable branches"
}

@test "prune: closing block — single caveat, scoped suggestions before raw commands" {
Expand Down Expand Up @@ -1578,7 +1578,7 @@ setup_project_with_done_workspace() {
run bash -c "cd '$proj' && ORBIT_ROOT='$proj' bash '$ORBIT_CMD' prune 2>&1"
[ "$status" -eq 0 ]
assert_contains "$output" " raw-orphan (unknown)"
[[ "$output" != *"raw-orphan (unknown) — review"* ]]
refute_contains "$output" "raw-orphan (unknown) — review"
}

@test "prune: branch checked out in another workspace is NOT deleted — failure surfaces, no false 'deleted'" {
Expand Down Expand Up @@ -1689,7 +1689,7 @@ setup_project_with_done_workspace() {
[ "$status" -eq 0 ]
assert_contains "$output" "pruned: dev (0 worktrees removed, 1 branch deleted)"
# deletion lines carry the recovery handle
[[ "$output" =~ deleted\ branch\ \(merged\):\ ws/dev/main\ \(was\ [0-9a-f]+\) ]]
assert_matches "$output" 'deleted\ branch\ \(merged\):\ ws/dev/main\ \(was\ [0-9a-f]+\)'
[ ! -d "$proj/dev" ]
run git -C "$proj/.repos/myrepo" rev-parse --verify --quiet refs/heads/ws/dev/main
[ "$status" -ne 0 ]
Expand Down
10 changes: 5 additions & 5 deletions tests/12_context.bats
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ teardown() {
run bash -c "cd '$proj/ws1' && ORBIT_ROOT='$proj' bash '$ORBIT_CMD' context"
[ "$status" -eq 0 ]
assert_contains "$output" "state: active"
[[ "$output" != *"This is the memo for context test."* ]]
refute_contains "$output" "This is the memo for context test."
}

@test "context --json: bare form mirrors the cruise block (no memos)" {
Expand Down Expand Up @@ -199,10 +199,10 @@ teardown() {
# authoritative identity hint
assert_contains "$output" "A mock repository for testing. ("
assert_contains "$output" "no memo (write the card via orbit memo <repo>"
[[ "$output" != *"has no memo, using README instead"* ]]
refute_contains "$output" "has no memo, using README instead"
# no stale header from the old format
[[ "$output" != *"=== PRIME"* ]]
[[ "$output" != *"primed"* ]]
refute_contains "$output" "=== PRIME"
refute_contains "$output" "primed"
}

@test "context --startup: reignite shows memos + per-repo status when populated" {
Expand All @@ -218,7 +218,7 @@ teardown() {
assert_contains "$output" "status: 1 jots | memo thin"
assert_contains "$output" "residual finding"
# roster is prime-only
[[ "$output" != *"available in pool"* ]]
refute_contains "$output" "available in pool"
}

@test "context: --startup and --prime are mutually exclusive" {
Expand Down
2 changes: 1 addition & 1 deletion tests/14_config.bats
Original file line number Diff line number Diff line change
Expand Up @@ -243,5 +243,5 @@ teardown() {
# the index entry is untouched
run git config --file "$proj/.repos/.orbit" --get repos.myrepo.url
[ "$status" -eq 0 ]
[[ "$output" != "http://evil" ]]
[ "$output" != "http://evil" ]
}
6 changes: 3 additions & 3 deletions tests/18_sync.bats
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ _push_update_to() {
[ "$status" -eq 0 ]
assert_contains "$output" "myrepo: already up to date"
# no false fast-forward claim when HEAD did not move
[[ "$output" != *"fast-forwarded"* ]]
refute_contains "$output" "fast-forwarded"
}

@test "sync: batch mode distinguishes fast-forwarded vs up-to-date and tallies" {
Expand Down Expand Up @@ -721,8 +721,8 @@ _push_update_to() {
run bash -c "cd '$proj/dev' && PATH='$stubs':\$PATH ORBIT_ROOT='$proj' bash '$ORBIT_CMD' sync myrepo --force 2>&1"
[ "$status" -ne 0 ]
assert_contains "$output" "sync --force must be run from the project root"
[[ "$output" != *"&&"* ]]
[[ "$output" != *"cd $proj"* ]]
refute_contains "$output" "&&"
refute_contains "$output" "cd $proj"
}

@test "sync --force: replays the intended command when the session is clean" {
Expand Down
2 changes: 1 addition & 1 deletion tests/21_version.bats
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ teardown() {
@test "version: prints a semver on stdout and exits 0" {
run bash -c "bash '$ORBIT_CMD' version"
[ "$status" -eq 0 ]
[[ "$output" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
assert_matches "$output" '^[0-9]+\.[0-9]+\.[0-9]+$'
}

@test "version: --version and -v are aliases" {
Expand Down
54 changes: 27 additions & 27 deletions tests/23_install_cli.bats
Original file line number Diff line number Diff line change
Expand Up @@ -61,68 +61,68 @@ EOF
@test "help: --help exits 0 and prints usage" {
run_install --help
[ "$status" -eq 0 ]
[[ "$output" == *"usage:"* ]]
assert_contains "$output" "usage:"
}

@test "parse: unknown flag exits 1" {
run_install --bogus
[ "$status" -eq 1 ]
[[ "$output" == *"unknown option: --bogus"* ]]
assert_contains "$output" "unknown option: --bogus"
}

@test "uninstall: no target is rejected" {
run_install --uninstall
[ "$status" -eq 1 ]
[[ "$output" == *"--uninstall requires at least one target"* ]]
assert_contains "$output" "--uninstall requires at least one target"
}

@test "uninstall: a single plugin target no-ops when its CLI is absent" {
run_install --uninstall --claude
[ "$status" -eq 0 ]
[[ "$output" == *"claude CLI not found"* ]]
assert_contains "$output" "claude CLI not found"
}

@test "uninstall: --all runs every target and exits 0 when nothing is installed" {
run_install --uninstall --all
[ "$status" -eq 0 ]
# --all expands to all plugin targets; missing CLIs are skipped, not fatal.
[[ "$output" == *"codex CLI not found"* ]] || [[ "$output" == *"Removed"* ]]
[[ "$output" == *"Done."* ]]
assert_contains "$output" "codex CLI not found" || assert_contains "$output" "Removed"
assert_contains "$output" "Done."
}

@test "uninstall: --cli reports nothing to remove when runtime is absent" {
run_install --uninstall --cli
[ "$status" -eq 0 ]
[[ "$output" == *"nothing to remove"* ]]
assert_contains "$output" "nothing to remove"
}

# --- network resilience (ORBIT_RETRY / ORBIT_RETRY_DELAY_SECONDS / ORBIT_TIMEOUT_SECONDS) ---

@test "env: non-numeric ORBIT_RETRY is rejected" {
run env HOME="$FAKE_HOME" PATH="/usr/bin:/bin" ORBIT_RETRY=abc bash "$INSTALL"
[ "$status" -eq 1 ]
[[ "$output" == *"ORBIT_RETRY must be a positive integer"* ]]
assert_contains "$output" "ORBIT_RETRY must be a positive integer"
}

@test "env: ORBIT_RETRY=0 is rejected" {
run env HOME="$FAKE_HOME" PATH="/usr/bin:/bin" ORBIT_RETRY=0 bash "$INSTALL"
[ "$status" -eq 1 ]
[[ "$output" == *"ORBIT_RETRY must be >= 1"* ]]
assert_contains "$output" "ORBIT_RETRY must be >= 1"
}

@test "env: non-numeric ORBIT_TIMEOUT_SECONDS is rejected" {
run env HOME="$FAKE_HOME" PATH="/usr/bin:/bin" ORBIT_TIMEOUT_SECONDS=soon bash "$INSTALL"
[ "$status" -eq 1 ]
[[ "$output" == *"ORBIT_TIMEOUT_SECONDS must be a positive integer"* ]]
assert_contains "$output" "ORBIT_TIMEOUT_SECONDS must be a positive integer"
}

@test "retry: a download that fails transiently succeeds within ORBIT_RETRY" {
write_fake_curl # fails the first 2 invocations, succeeds on the 3rd
run_install_mocked CURL_FAILS=2 ORBIT_SOURCE=acme/widgets \
ORBIT_RETRY=3 ORBIT_RETRY_DELAY_SECONDS=0 bash "$INSTALL"
[ "$status" -eq 0 ]
[[ "$output" == *"attempt 1/3 failed"* ]]
[[ "$output" == *"Installed orbit command to:"* ]]
assert_contains "$output" "attempt 1/3 failed"
assert_contains "$output" "Installed orbit command to:"
[ -x "$FAKE_HOME/.local/bin/orbit" ]
}

Expand All @@ -131,10 +131,10 @@ EOF
run_install_mocked CURL_FAILS=99 ORBIT_SOURCE=acme/widgets \
ORBIT_RETRY=2 ORBIT_RETRY_DELAY_SECONDS=0 bash "$INSTALL"
[ "$status" -eq 1 ]
[[ "$output" == *"giving up after 2 attempts"* ]]
[[ "$output" == *"Could not resolve host"* ]] # the real curl error
[[ "$output" == *"failed to fetch orbit.sh"* ]]
[[ "$output" == *"local path source needs no network"* ]] # escape-hatch hint
assert_contains "$output" "giving up after 2 attempts"
assert_contains "$output" "Could not resolve host" # the real curl error
assert_contains "$output" "failed to fetch orbit.sh"
assert_contains "$output" "local path source needs no network" # escape-hatch hint
}

@test "timeout: a hung download is killed at ORBIT_TIMEOUT_SECONDS, not waited out" {
Expand All @@ -143,8 +143,8 @@ EOF
run_install_mocked ORBIT_SOURCE=acme/widgets \
ORBIT_RETRY=2 ORBIT_RETRY_DELAY_SECONDS=0 ORBIT_TIMEOUT_SECONDS=1 bash "$INSTALL"
[ "$status" -eq 1 ]
[[ "$output" == *"timed out (1s)"* ]]
[[ "$output" == *"failed to fetch orbit.sh"* ]]
assert_contains "$output" "timed out (1s)"
assert_contains "$output" "failed to fetch orbit.sh"
}

@test "timeout: a TERM-immune download is KILLed after the grace period" {
Expand All @@ -158,8 +158,8 @@ EOF
run_install_mocked ORBIT_SOURCE=acme/widgets \
ORBIT_RETRY=1 ORBIT_RETRY_DELAY_SECONDS=0 ORBIT_TIMEOUT_SECONDS=1 bash "$INSTALL"
[ "$status" -eq 1 ]
[[ "$output" == *"timed out after 1s"* ]] # giving-up message names the timeout
[[ "$output" == *"failed to fetch orbit.sh"* ]]
assert_contains "$output" "timed out after 1s" # giving-up message names the timeout
assert_contains "$output" "failed to fetch orbit.sh"
}

# --- marketplace error surfacing (fake claude on PATH) ---------------------
Expand Down Expand Up @@ -193,19 +193,19 @@ EOF
run_install_mocked ORBIT_SOURCE="${BATS_TEST_DIRNAME}/.." \
ORBIT_RETRY=2 ORBIT_RETRY_DELAY_SECONDS=0 bash "$INSTALL" --claude
[ "$status" -eq 1 ]
[[ "$output" == *"unable to connect to github.com (fake)"* ]] # the real add error
[[ "$output" == *"marketplace add/update failed"* ]] # the causal warning…
[[ "$output" == *"cause is the add error"* ]] # …naming the root cause
[[ "$output" == *"not found in marketplace"* ]] # the install error
assert_contains "$output" "unable to connect to github.com (fake)" # the real add error
assert_contains "$output" "marketplace add/update failed" # the causal warning…
assert_contains "$output" "cause is the add error" # …naming the root cause
assert_contains "$output" "not found in marketplace" # the install error
}

@test "marketplace: refresh failure warns but does not block an offline install" {
write_fake_claude
run_install_mocked ORBIT_SOURCE="${BATS_TEST_DIRNAME}/.." \
ORBIT_RETRY=2 ORBIT_RETRY_DELAY_SECONDS=0 CLAUDE_INSTALL_OK=1 bash "$INSTALL" --claude
[ "$status" -eq 0 ]
[[ "$output" == *"cause is the add error"* ]]
[[ "$output" == *"Installed Orbit plugin into Claude Code"* ]]
assert_contains "$output" "cause is the add error"
assert_contains "$output" "Installed Orbit plugin into Claude Code"
[ -e "$MOCK_STATE/plugin-installed" ]
}

Expand Down Expand Up @@ -278,7 +278,7 @@ run_install_chained() {
[ "$(sed -n '1p' "$MOCK_STATE/calls")" = "curl https://raw.githubusercontent.com/orbcli/orbit/main/orbit.sh" ]
[ "$(sed -n '2p' "$MOCK_STATE/calls")" = "git-clone https://github.com/orbcli/orbit.git" ]
[ "$(wc -l < "$MOCK_STATE/calls" | tr -d ' ')" -eq 2 ] # SSH never needed
[[ "$output" == *"attempt 1/3 via orbcli/orbit failed"* ]]
assert_contains "$output" "attempt 1/3 via orbcli/orbit failed"
[ -x "$FAKE_HOME/.local/bin/orbit" ]
# The clone saw the no-prompt env (installer must never ask interactively):
[ "$(sed -n '1p' "$MOCK_STATE/gtp")" = "gtp=0" ]
Expand Down
Loading
Loading