#!/usr/bin/env fish # Copyright (C) 2026 Rootiest # SPDX-License-Identifier: AGPL-3.0-or-later # # Hermetic unit tests for network-dependent fish functions with full mock coverage # and failure edge-case testing: # - gi (gitignore generation, API fetch, deduplication, error handling) # - gip, gip4, gip6 (public IP resolution, timeouts, IPv4/IPv6 fallback, failure notices) # - qr (terminal QR code generator, qrencode local bypass, curl fallback & network drop) # - bd-pull (Gitea issues sync, missing tokens, API failures, malformed JSON, linking) # - _auto_pull_sync (background safe fast-forward, dirty trees, network drop on fetch) # - gitup (remote fetch & status, network drop handling) # - git-clean (remote fetch/prune, orphaned branch detection & deletion) # - config-update (upstream fetch, up-to-date check, dry-run, force stash & restore) # - repo-open (origin URL parsing, offline local tracking, ls-remote fallback & drop) # - fzf-update (git pull/clone network failure handling) # # MODE: isolated source (realpath (dirname (status filename)))/lib.fish set -p fish_function_path $repo_root/functions set -gx TERM xterm-256color set -g TMPDIRS # Ensure git operations inside tests are hermetic and do not prompt set -gx GIT_AUTHOR_NAME t set -gx GIT_AUTHOR_EMAIL t@t set -gx GIT_COMMITTER_NAME t set -gx GIT_COMMITTER_EMAIL t@t set -gx GIT_CONFIG_COUNT 2 set -gx GIT_CONFIG_KEY_0 commit.gpgsign set -gx GIT_CONFIG_VALUE_0 false set -gx GIT_CONFIG_KEY_1 init.defaultBranch set -gx GIT_CONFIG_VALUE_1 main set -gx GIT_TERMINAL_PROMPT 0 # Helper to create temporary git repos function new_repo set -l d (mktemp -d) set -ga TMPDIRS $d git -C $d init -q git -C $d config user.email t@t git -C $d config user.name t git -C $d config commit.gpgsign false git -C $d config core.hooksPath /dev/null printf '%s\n' $d end # Build a hermetic mock bin directory set -g MOCK_DIR (mktemp -d) set -ga TMPDIRS $MOCK_DIR set -l real_git (command -s git) # 1. Mock curl printf '%s\n' \ '#!/bin/sh' \ 'if [ -n "$MOCK_CURL_LOG" ]; then' \ ' printf "%s\n" "$*" >> "$MOCK_CURL_LOG"' \ fi \ 'if [ -n "$MOCK_CURL_HANDLER" ] && [ -x "$MOCK_CURL_HANDLER" ]; then' \ ' exec "$MOCK_CURL_HANDLER" "$@"' \ fi \ 'if [ -n "$MOCK_CURL_DELAY" ]; then' \ ' sleep "$MOCK_CURL_DELAY"' \ fi \ 'if [ -n "$MOCK_CURL_STDERR" ]; then' \ ' printf "%s\n" "$MOCK_CURL_STDERR" >&2' \ fi \ 'if [ -f "$MOCK_CURL_BODY_FILE" ]; then' \ ' cat "$MOCK_CURL_BODY_FILE"' \ 'elif [ -n "$MOCK_CURL_BODY" ]; then' \ ' printf "%s\n" "$MOCK_CURL_BODY"' \ fi \ 'exit "${MOCK_CURL_STATUS:-0}"' >$MOCK_DIR/curl chmod +x $MOCK_DIR/curl # 2. Mock git shim printf '%s\n' \ '#!/bin/sh' \ 'if [ -n "$MOCK_GIT_LOG" ]; then' \ ' printf "%s\n" "$*" >> "$MOCK_GIT_LOG"' \ fi \ 'if [ -n "$MOCK_GIT_HANDLER" ] && [ -x "$MOCK_GIT_HANDLER" ]; then' \ ' exec "$MOCK_GIT_HANDLER" "$@"' \ fi \ 'for a in "$@"; do' \ ' if [ "$a" = "fetch" ] && [ -n "$MOCK_GIT_FAIL_FETCH" ]; then' \ ' echo "fatal: unable to access: Could not resolve host" >&2' \ ' exit "${MOCK_GIT_FETCH_STATUS:-128}"' \ ' fi' \ ' if [ "$a" = "pull" ] && [ -n "$MOCK_GIT_FAIL_PULL" ]; then' \ ' echo "fatal: unable to access: Could not resolve host" >&2' \ ' exit "${MOCK_GIT_PULL_STATUS:-128}"' \ ' fi' \ ' if [ "$a" = "clone" ] && [ -n "$MOCK_GIT_FAIL_CLONE" ]; then' \ ' echo "fatal: unable to access: Could not resolve host" >&2' \ ' exit "${MOCK_GIT_CLONE_STATUS:-128}"' \ ' fi' \ ' if [ "$a" = "ls-remote" ] && [ -n "$MOCK_GIT_FAIL_LS_REMOTE" ]; then' \ ' echo "fatal: unable to access: Could not resolve host" >&2' \ ' exit "${MOCK_GIT_LS_REMOTE_STATUS:-128}"' \ ' fi' \ done \ "exec $real_git \"\$@\"" >$MOCK_DIR/git chmod +x $MOCK_DIR/git # 3. Mock bd CLI printf '%s\n' \ '#!/bin/sh' \ 'if [ "$1" = "create" ]; then' \ ' echo "Created issue bd-99"' \ ' echo "{\"id\":\"bd-99\"}" >> .beads/issues.jsonl' \ ' exit 0' \ 'elif [ "$1" = "sync" ]; then' \ ' exit 0' \ fi \ 'exit 0' >$MOCK_DIR/bd chmod +x $MOCK_DIR/bd # 4. Mock qrencode printf '%s\n' \ '#!/bin/sh' \ 'if [ -n "$MOCK_QRENCODE_LOG" ]; then' \ ' printf "%s\n" "$*" >> "$MOCK_QRENCODE_LOG"' \ fi \ 'echo "LOCAL_QRENCODE: $*"' \ 'exit 0' >$MOCK_DIR/qrencode chmod +x $MOCK_DIR/qrencode # Prepend MOCK_DIR to PATH so mocks take precedence set -gx PATH $MOCK_DIR $PATH function reset_mocks set -e MOCK_CURL_STATUS set -e MOCK_CURL_BODY set -e MOCK_CURL_BODY_FILE set -e MOCK_CURL_STDERR set -e MOCK_CURL_DELAY set -e MOCK_CURL_LOG set -e MOCK_CURL_HANDLER set -e MOCK_GIT_FAIL_FETCH set -e MOCK_GIT_FETCH_STATUS set -e MOCK_GIT_FAIL_PULL set -e MOCK_GIT_PULL_STATUS set -e MOCK_GIT_FAIL_CLONE set -e MOCK_GIT_CLONE_STATUS set -e MOCK_GIT_FAIL_LS_REMOTE set -e MOCK_GIT_LS_REMOTE_STATUS set -e MOCK_GIT_LOG set -e MOCK_GIT_HANDLER set -e MOCK_QRENCODE_LOG end function cleanup for d in $TMPDIRS test -n "$d"; and command rm -rf $d end end # ───────────────────────────────────────────────────────────────────────────── # 1. gi (gitignore generator) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: gi" reset_mocks set -gx MOCK_CURL_BODY "c,cpp,python,rust" set -l list_out (gi -l) check "gi -l: returns API target list" "c,cpp,python,rust" "$list_out" # Complete network drop on list set -gx MOCK_CURL_STATUS 7 set -gx MOCK_CURL_BODY "" gi -l >/dev/null 2>&1 check "gi -l: returns 0 per contract" 0 $status # stdout mode with valid content reset_mocks set -gx MOCK_CURL_BODY "# Python gitignore\n*.pyc\n__pycache__/" set -l stdout_out (gi -s python) check "gi -s: prints fetched patterns to stdout" "# Python gitignore\n*.pyc\n__pycache__/" "$stdout_out" # stdout mode with network failure / 404 (curl exit 22) reset_mocks set -gx MOCK_CURL_STATUS 22 set -gx MOCK_CURL_BODY "" gi -s invalid_target >/dev/null 2>&1 check "gi -s: API failure returns 1" 1 $status # Append mode outside git repository reset_mocks set -l non_git_dir (mktemp -d) set -ga TMPDIRS $non_git_dir begin set -l prev_pwd $PWD builtin cd $non_git_dir gi python >/dev/null 2>&1 set -l rc $status builtin cd $prev_pwd check "gi : outside git repo returns 1" 1 $rc end # Append mode inside git repo + deduplication reset_mocks set -l repo (new_repo) set -gx MOCK_CURL_BODY "# Python gitignore\n*.pyc" begin set -l prev_pwd $PWD builtin cd $repo gi python >/dev/null 2>&1 check "gi python: first run returns 0" 0 $status check "gi python: creates .gitignore" true (test -f "$repo/.gitignore"; and echo true; or echo false) set -l first_lines (count (command cat "$repo/.gitignore")) # Second run with identical pattern triggers deduplication gi python >/dev/null 2>&1 check "gi python: second run returns 0" 0 $status set -l second_lines (count (command cat "$repo/.gitignore")) check "gi python: deduplication prevents duplicate append" $first_lines $second_lines # Network failure during append skips modifying file set -gx MOCK_CURL_STATUS 7 gi ruby >/dev/null 2>&1 set -l third_lines (count (command cat "$repo/.gitignore")) check "gi: network drop during append preserves file" $second_lines $third_lines builtin cd $prev_pwd end # ───────────────────────────────────────────────────────────────────────────── # 2. gip, gip4, gip6 (IP resolution) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: gip, gip4, gip6" # Create a specialized curl handler for gip IPv4/IPv6 dispatch set -l gip_handler $MOCK_DIR/gip_curl.sh printf '%s\n' \ '#!/bin/sh' \ 'for a in "$@"; do' \ ' if [ "$a" = "-4" ]; then' \ ' if [ -n "$MOCK_FAIL_IPV4" ]; then exit "${MOCK_IPV4_STATUS:-28}"; fi' \ ' echo "198.51.100.1"' \ ' exit 0' \ ' fi' \ ' if [ "$a" = "-6" ]; then' \ ' if [ -n "$MOCK_FAIL_IPV6" ]; then exit "${MOCK_IPV6_STATUS:-28}"; fi' \ ' echo "2001:db8::1"' \ ' exit 0' \ ' fi' \ done \ 'exit 0' >$gip_handler chmod +x $gip_handler reset_mocks set -gx MOCK_CURL_HANDLER $gip_handler # gip: both IPv4 and IPv6 succeed set -l out_both (gip) check "gip: both succeed outputs IPv4" true (string match -q '*IPv4: 198.51.100.1*' -- $out_both; and echo true; or echo false) check "gip: both succeed outputs IPv6" true (string match -q '*IPv6: 2001:db8::1*' -- $out_both; and echo true; or echo false) # gip: IPv4 timeout / failure, IPv6 success set -gx MOCK_FAIL_IPV4 1 set -l out_v4_fail (gip) check "gip: IPv4 failure reports 'Not detected'" true (string match -q '*IPv4: Not detected*' -- $out_v4_fail; and echo true; or echo false) check "gip: IPv4 failure still reports IPv6" true (string match -q '*IPv6: 2001:db8::1*' -- $out_v4_fail; and echo true; or echo false) # gip: IPv4 success, IPv6 failure set -e MOCK_FAIL_IPV4 set -gx MOCK_FAIL_IPV6 1 set -l out_v6_fail (gip) check "gip: IPv6 failure reports IPv4" true (string match -q '*IPv4: 198.51.100.1*' -- $out_v6_fail; and echo true; or echo false) check "gip: IPv6 failure reports 'Not detected'" true (string match -q '*IPv6: Not detected*' -- $out_v6_fail; and echo true; or echo false) # gip: complete network drop (both fail) set -gx MOCK_FAIL_IPV4 1 set -gx MOCK_FAIL_IPV6 1 set -l out_all_fail (gip) check "gip: complete drop reports IPv4 Not detected" true (string match -q '*IPv4: Not detected*' -- $out_all_fail; and echo true; or echo false) check "gip: complete drop reports IPv6 Not detected" true (string match -q '*IPv6: Not detected*' -- $out_all_fail; and echo true; or echo false) # gip4: success set -e MOCK_FAIL_IPV4 set -l out_gip4 (gip4) check "gip4: success prints IP" "198.51.100.1" "$out_gip4" check "gip4: success exits 0" 0 $status # gip4: failure set -gx MOCK_FAIL_IPV4 1 set -gx MOCK_IPV4_STATUS 7 gip4 >/dev/null 2>&1 check "gip4: failure returns curl status" 7 $status # gip6: success set -e MOCK_FAIL_IPV6 set -l out_gip6 (gip6) check "gip6: success prints IPv6" "2001:db8::1" "$out_gip6" check "gip6: success exits 0" 0 $status # gip6: failure / unsupported network set -gx MOCK_FAIL_IPV6 1 set -l out_gip6_err (gip6 2>&1) check "gip6: failure exits 1" 1 $status check "gip6: failure prints notice" true (string match -q '*IPv6 is currently unavailable*' -- $out_gip6_err; and echo true; or echo false) # ───────────────────────────────────────────────────────────────────────────── # 3. qr (QR code generator) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: qr" reset_mocks # Case A: Local qrencode tool is present set -gx MOCK_QRENCODE_LOG (mktemp) set -ga TMPDIRS $MOCK_QRENCODE_LOG set -l qr_local (qr "hello-world") check "qr: local qrencode is preferred offline" true (string match -q '*LOCAL_QRENCODE*' -- $qr_local; and echo true; or echo false) check "qr: curl is never called when qrencode exists" 0 (test -f $MOCK_DIR/curl_log; and count (cat $MOCK_DIR/curl_log); or echo 0) # Case B: Local qrencode is missing, falling back to curl function type if test "$argv[1]" = -q -a "$argv[2]" = qrencode return 1 end builtin type $argv end set -gx MOCK_CURL_BODY UTF8_QR_BODY set -l qr_curl (qr "hello-curl") check "qr: fallback to curl when qrencode is missing" UTF8_QR_BODY "$qr_curl" # Network drop during curl fallback set -gx MOCK_CURL_STATUS 7 set -gx MOCK_CURL_BODY "" qr fail >/dev/null 2>&1 check "qr: curl network drop returns non-zero" 7 $status # Argument-based curl fallback set -gx MOCK_CURL_STATUS 0 set -gx MOCK_CURL_BODY TEXT_QR set -l qr_arg (qr "arg-text") check "qr: argument works via curl fallback" TEXT_QR "$qr_arg" functions -e type # ───────────────────────────────────────────────────────────────────────────── # 4. bd-pull (Gitea issues sync) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: bd-pull" reset_mocks # Missing required arguments & env vars bd-pull >/dev/null 2>&1 check "bd-pull: missing repo argument exits 1" 1 $status begin set -e GITEA_TOKEN set -e GITEA_URL bd-pull rootiest/test >/dev/null 2>&1 check "bd-pull: missing GITEA_TOKEN exits 1" 1 $status set -gx GITEA_TOKEN test_token bd-pull rootiest/test >/dev/null 2>&1 check "bd-pull: missing GITEA_URL exits 1" 1 $status end # Complete network drop / empty response set -gx GITEA_TOKEN dummy_token set -gx GITEA_URL "https://git.test" set -gx MOCK_CURL_STATUS 7 set -gx MOCK_CURL_BODY "" set -l out_drop (bd-pull rootiest/test) check "bd-pull: network drop reports no unlinked issues" true (string match -q '*No unlinked issues found*' -- $out_drop; and echo true; or echo false) # Empty JSON issue list set -gx MOCK_CURL_STATUS 0 set -gx MOCK_CURL_BODY "[]" set -l out_empty (bd-pull rootiest/test) check "bd-pull: empty issue list handled cleanly" true (string match -q '*No unlinked issues found*' -- $out_empty; and echo true; or echo false) # Issues already linked with [ID] prefix set -gx MOCK_CURL_BODY '[{"number": 1, "title": "[bd-1] Already linked issue"}]' set -l out_already (bd-pull rootiest/test) check "bd-pull: already-linked issues skipped" true (string match -q '*No unlinked issues found*' -- $out_already; and echo true; or echo false) # Unlinked issue found: creates bead, patches title, commits & pushes set -l bd_remote (new_repo) git -C $bd_remote config --bool core.bare true set -l bd_repo (new_repo) begin set -l prev_pwd $PWD builtin cd $bd_repo git remote add origin $bd_remote mkdir -p .beads touch .beads/issues.jsonl git add .beads/issues.jsonl git commit -q -m "init beads" git push -q -u origin main set -gx MOCK_CURL_BODY '[{"number": 2, "title": "Web original issue without ID"}]' set -l out_unlinked (bd-pull rootiest/test 2>&1) check "bd-pull: unlinked issue is linked" true (string match -q '*Linked 1 issues*' -- $out_unlinked; and echo true; or echo false) # Verify git commit was made set -l last_msg (git log -1 --pretty=%s) check "bd-pull: commits synced IDs to git" "chore: sync local IDs for web issues" "$last_msg" builtin cd $prev_pwd end # ───────────────────────────────────────────────────────────────────────────── # 5. _auto_pull_sync (background fast-forward) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: _auto_pull_sync" reset_mocks # Non-git directory _auto_pull_sync /tmp >/dev/null 2>&1 check "_auto_pull_sync: non-git directory returns 1" 1 $status # Git repo without upstream branch set -l sync_repo (new_repo) echo test >$sync_repo/file.txt git -C $sync_repo add file.txt git -C $sync_repo commit -q -m initial _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: missing upstream returns 1" 1 $status # Setup upstream branch for sync_repo set -l sync_upstream (new_repo) git -C $sync_upstream config --bool core.bare true git -C $sync_repo remote add origin $sync_upstream git -C $sync_repo push -q -u origin main >/dev/null 2>&1 # Dirty working tree (unstaged modifications) echo dirty >>$sync_repo/file.txt _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: dirty worktree returns 1" 1 $status git -C $sync_repo checkout -q -- file.txt # Dirty index (staged modifications) echo staged >>$sync_repo/staged.txt git -C $sync_repo add staged.txt _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: dirty index returns 1" 1 $status git -C $sync_repo reset -q HEAD staged.txt command rm -f $sync_repo/staged.txt # Clean repo, network failure on git fetch set -gx MOCK_GIT_FAIL_FETCH 1 _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: fetch failure returns 1" 1 $status # Clean repo, successful fast-forward reset_mocks # Add commit to upstream set -l peer_clone (new_repo) git -C $peer_clone clone -q $sync_upstream $peer_clone/work echo "new commit" >$peer_clone/work/new.txt git -C $peer_clone/work add new.txt git -C $peer_clone/work commit -q -m "upstream work" git -C $peer_clone/work push -q origin main _auto_pull_sync $sync_repo >/dev/null 2>&1 check "_auto_pull_sync: clean fast-forward returns 0" 0 $status check "_auto_pull_sync: changes merged into working tree" true (test -f $sync_repo/new.txt; and echo true; or echo false) # ───────────────────────────────────────────────────────────────────────────── # 6. gitup (fetch and status) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: gitup" reset_mocks begin set -l prev_pwd $PWD builtin cd /tmp gitup >/dev/null 2>&1 check "gitup: outside git repo exits 1" 1 $status set -l r (new_repo) builtin cd $r echo a >a && git add a && git commit -q -m a # Network failure on git fetch set -gx MOCK_GIT_FAIL_FETCH 1 gitup >/dev/null 2>&1 check "gitup: network drop during fetch exits non-zero" true (test $status -ne 0; and echo true; or echo false) # Successful fetch reset_mocks set -l up_out (gitup 2>&1) check "gitup: success exits 0" 0 $status check "gitup: displays git status output" true (string match -q '*On branch main*' -- $up_out; and echo true; or echo false) builtin cd $prev_pwd end # ───────────────────────────────────────────────────────────────────────────── # 7. git-clean (fetch --prune and delete orphaned branches) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: git-clean" reset_mocks git-clean --help >/dev/null 2>&1 check "git-clean: --help exits 0" 0 $status begin set -l prev_pwd $PWD set -l r (new_repo) builtin cd $r echo a >a && git add a && git commit -q -m a # Network drop during git fetch --prune set -gx MOCK_GIT_FAIL_FETCH 1 # git-clean continues to local cleanup even if fetch fails set -l clean_out (git-clean 2>&1) check "git-clean: network drop handled, reports tidy" true (string match -q '*No orphaned branches found*' -- $clean_out; and echo true; or echo false) # Orphaned branch detection and cleanup reset_mocks # Simulate an orphaned branch with [gone] tracking git branch orphaned-feat # Create git-clean handler that simulates git branch -vv showing ': gone]' set -l clean_git_handler $MOCK_DIR/git_clean_shim.sh printf '%s\n' \ '#!/bin/sh' \ 'for a in "$@"; do' \ ' if [ "$a" = "-vv" ]; then' \ ' echo " main 1234567 [origin/main] initial"' \ ' echo " orphaned-feat abcdef0 [origin/orphaned-feat: gone] feature"' \ ' exit 0' \ ' fi' \ done \ "exec $real_git \"\$@\"" >$clean_git_handler chmod +x $clean_git_handler set -gx MOCK_GIT_HANDLER $clean_git_handler set -l clean_del_out (git-clean 2>&1) check "git-clean: detects and deletes orphaned branch" true (string match -q '*Deleting orphaned local branches*' -- $clean_del_out; and echo true; or echo false) check "git-clean: orphaned branch was deleted" false (git rev-parse --verify --quiet orphaned-feat >/dev/null 2>&1; and echo true; or echo false) # Regression: git branch -vv marks a branch checked out in ANOTHER # linked worktree with '+', not '*'. Only '*' used to be stripped, so # a gone branch shown with '+' left a bogus "+" entry that then failed # to delete ("branch '+' not found"). reset_mocks git branch orphaned-worktree-feat set -l clean_git_handler2 $MOCK_DIR/git_clean_shim2.sh printf '%s\n' \ '#!/bin/sh' \ 'for a in "$@"; do' \ ' if [ "$a" = "-vv" ]; then' \ ' echo " main 1234567 [origin/main] initial"' \ ' echo "+ orphaned-worktree-feat abcdef0 [origin/orphaned-worktree-feat: gone] feature"' \ ' exit 0' \ ' fi' \ done \ "exec $real_git \"\$@\"" >$clean_git_handler2 chmod +x $clean_git_handler2 set -gx MOCK_GIT_HANDLER $clean_git_handler2 set -l plus_out (git-clean 2>&1) check "git-clean: '+' worktree marker does not leak into a branch name" false (string match -q "*branch '+' not found*" -- $plus_out; and echo true; or echo false) check "git-clean: '+'-marked gone branch is still deleted" false (git rev-parse --verify --quiet orphaned-worktree-feat >/dev/null 2>&1; and echo true; or echo false) reset_mocks builtin cd $prev_pwd end # ───────────────────────────────────────────────────────────────────────────── # 8. config-update (configuration repository sync) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: config-update" reset_mocks config-update --help >/dev/null 2>&1 check "config-update: --help exits 0" 0 $status # Non-git CONFIG_DIR (using mock handler) set -l cfg_update_handler $MOCK_DIR/cfg_update_shim.sh printf '%s\n' \ '#!/bin/sh' \ 'for a in "$@"; do' \ ' if [ "$a" = "fetch" ] && [ -n "$MOCK_CFG_FAIL_FETCH" ]; then' \ ' exit 1' \ ' fi' \ done \ "exec $real_git \"\$@\"" >$cfg_update_handler chmod +x $cfg_update_handler set -gx MOCK_GIT_HANDLER $cfg_update_handler # Network failure on upstream fetch set -l fake_home (mktemp -d) set -ga TMPDIRS $fake_home set -l fake_fish_cfg "$fake_home/.config/fish" mkdir -p (dirname $fake_fish_cfg) set -l cfg_repo (new_repo) command cp -r $cfg_repo $fake_fish_cfg # Test config-update against fake_fish_cfg by setting HOME begin set -lx HOME $fake_home set -gx MOCK_CFG_FAIL_FETCH 1 config-update >/dev/null 2>&1 check "config-update: network fetch failure returns 1" 1 $status end # ───────────────────────────────────────────────────────────────────────────── # 9. repo-open (origin URL normalization and browser deep-linking) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: repo-open" reset_mocks repo-open --help >/dev/null 2>&1 check "repo-open: --help exits 0" 0 $status # Outside git repo begin set -l prev_pwd $PWD builtin cd /tmp repo-open -p >/dev/null 2>&1 check "repo-open: outside git repo returns 1" 1 $status builtin cd $prev_pwd end # In git repo without origin set -l r_no_origin (new_repo) begin set -l prev_pwd $PWD builtin cd $r_no_origin repo-open -p >/dev/null 2>&1 check "repo-open: without origin remote returns 1" 1 $status builtin cd $prev_pwd end # In git repo with GitHub origin remote set -l r_gh (new_repo) git -C $r_gh remote add origin "https://github.com/rootiest/fish-config.git" echo init >$r_gh/file && git -C $r_gh add file && git -C $r_gh commit -q -m init begin set -l prev_pwd $PWD builtin cd $r_gh # Remote ls-remote network failure falls back to default branch set -gx MOCK_GIT_FAIL_LS_REMOTE 1 set -l url_fallback (repo-open -p) check "repo-open: ls-remote network drop falls back to default branch" "https://github.com/rootiest/fish-config" "$url_fallback" # With ls-remote success for current branch reset_mocks set -l gh_ls_handler $MOCK_DIR/gh_ls_shim.sh printf '%s\n' \ '#!/bin/sh' \ 'for a in "$@"; do' \ ' if [ "$a" = "ls-remote" ]; then' \ ' echo "abcdef01 refs/heads/main"' \ ' exit 0' \ ' fi' \ done \ "exec $real_git \"\$@\"" >$gh_ls_handler chmod +x $gh_ls_handler set -gx MOCK_GIT_HANDLER $gh_ls_handler # Test gitlab provider normalization git remote set-url origin "git@gitlab.com:rootiest/fish-config.git" git checkout -q -b feat-test set -l url_gl (repo-open -p) check "repo-open: gitlab ssh url normalized with branch tree" "https://gitlab.com/rootiest/fish-config/-/tree/feat-test" "$url_gl" # Test gitea provider normalization git remote set-url origin "https://git.rootiest.dev/rootiest/fish-config.git" git config browse.provider gitea set -l url_gitea (repo-open -p) check "repo-open: gitea url normalized with src/branch" "https://git.rootiest.dev/rootiest/fish-config/src/branch/feat-test" "$url_gitea" builtin cd $prev_pwd end # ───────────────────────────────────────────────────────────────────────────── # 10. fzf-update (fzf install / git pull) # ───────────────────────────────────────────────────────────────────────────── section "network isolation: fzf-update" reset_mocks # When ~/.fzf exists, git pull fails due to network drop set -l fake_home_fzf (mktemp -d) set -ga TMPDIRS $fake_home_fzf mkdir -p $fake_home_fzf/.fzf begin set -lx HOME $fake_home_fzf set -gx MOCK_GIT_FAIL_PULL 1 fzf-update >/dev/null 2>&1 check "fzf-update: git pull network drop returns non-zero" true (test $status -ne 0; and echo true; or echo false) end # When ~/.fzf does not exist, git clone fails due to network drop set -l fake_home_no_fzf (mktemp -d) set -ga TMPDIRS $fake_home_no_fzf begin set -lx HOME $fake_home_no_fzf set -gx MOCK_GIT_FAIL_CLONE 1 fzf-update >/dev/null 2>&1 check "fzf-update: git clone network drop returns non-zero" true (test $status -ne 0; and echo true; or echo false) end # ───────────────────────────────────────────────────────────────────────────── # Teardown and Final Report # ───────────────────────────────────────────────────────────────────────────── cleanup report