Add tests for the step that excludes .claude, .agents and AGENTS.md from git in the new project: the three paths are ignored and absent from git status after a run, while framework, .gitmodules and docs/artifact-registry.md stay visible; no .gitignore is written and nothing is committed; a second run writes each entry and the comment once and keeps the existing lines, also when the file has no final newline; nothing is written for a path git already ignores; a tracked path stays tracked and is named; the step is skipped without the framework; the dry-run plan lists it. The existing .env exclusion test also checks its comment line. Refs #68 Task: MIL-009#4
551 lines
25 KiB
Bash
551 lines
25 KiB
Bash
#!/usr/bin/env bash
|
|
# test-local.sh - tests for the local project (MIL-003): the directory, the
|
|
# git repository and its remotes, the framework submodule, skills, hooks and
|
|
# plan gate, and the templates. Real git runs here, against local bare
|
|
# repositories that stand in for Gitea and the framework (git rewrites the
|
|
# remote addresses, see write_gitconfig); only the two hosts' APIs are stubs.
|
|
# Sourced by run-tests.sh.
|
|
|
|
# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose
|
|
|
|
# local_answers DIR GITHUB GATE: the prompt answers; the result is in
|
|
# LOCAL_ANSWERS (kept in a variable because $(...) would drop the last newline).
|
|
local_answers() {
|
|
if [[ $2 == y ]]; then
|
|
printf -v LOCAL_ANSWERS 'my-app\nA test app\npublic\nTirSystem\ny\nacme-org\n%s\n%s\n' "$1" "$3"
|
|
else
|
|
printf -v LOCAL_ANSWERS 'my-app\n\n\nTirSystem\nn\n%s\n%s\n' "$1" "$3"
|
|
fi
|
|
}
|
|
|
|
# project_git DIR ARGS...: git in the project with the tests' own config.
|
|
project_git() {
|
|
local dir="$1"
|
|
shift
|
|
GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir" "$@"
|
|
}
|
|
|
|
# files_with_secret DIR: any file below DIR (the .git folder included) that
|
|
# holds one of the fake tokens.
|
|
files_with_secret() {
|
|
grep -rlF -e "$FAKE_GITEA_TOKEN" -e "$FAKE_GITHUB_PAT" "$1" 2>/dev/null || true
|
|
}
|
|
|
|
readonly SSH_FRAMEWORK_URL="ssh://git@git.example.test:10022/TirSystem/SQA-QC-Framework.git"
|
|
|
|
# ----------------------------------------------------- directory and remotes
|
|
|
|
test_local_project_gets_the_origin_remote_only_and_the_license_history() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_file_exists "directory created" "$dir/.git"
|
|
assert_eq "branch is main" "main" "$(project_git "$dir" symbolic-ref --short HEAD)"
|
|
assert_eq "origin over SSH, no credential" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)"
|
|
assert_eq "origin is the only remote, also with GitHub" "origin" "$(project_git "$dir" remote)"
|
|
assert_eq "no github remote" "" "$(project_git "$dir" config --get remote.github.url || true)"
|
|
assert_not_contains "no GitHub address in the git config" "$(cat "$dir/.git/config")" "github.com"
|
|
assert_eq "the license commit is the whole history" "1" "$(project_git "$dir" rev-list --count HEAD)"
|
|
assert_eq "it is the Gitea commit" "Initial commit" "$(project_git "$dir" log -1 --format=%s)"
|
|
assert_file_exists "LICENSE from Gitea" "$dir/LICENSE"
|
|
assert_eq "branch follows origin" "origin" "$(project_git "$dir" config --get branch.main.remote)"
|
|
assert_eq "no token in any file of the project" "" "$(files_with_secret "$dir")"
|
|
assert_contains "reported" "$OUT" "Local project : created $dir (origin over SSH)"
|
|
}
|
|
|
|
test_gitea_only_project_has_the_origin_remote_only_and_no_commit() {
|
|
setup_hosts
|
|
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" n n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_eq "origin" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)"
|
|
assert_eq "origin is the only remote" "origin" "$(project_git "$dir" remote)"
|
|
assert_file_missing "no license file" "$dir/LICENSE"
|
|
assert_eq "no commit was made" "0" "$(project_git "$dir" rev-list --all --count)"
|
|
assert_eq "no token in any file" "" "$(files_with_secret "$dir")"
|
|
}
|
|
|
|
test_existing_directory_is_used_only_after_a_yes() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir"
|
|
printf 'mine\n' >"$dir/keep.txt"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\nn\n'
|
|
assert_status "answered no" 1 "$STATUS"
|
|
assert_contains "asked" "$ERR" "The directory $dir already exists and already has files. Use it"
|
|
assert_contains "stopped" "$ERR" "stopped: choose another directory or remove this one"
|
|
assert_file_missing "nothing added to the directory" "$dir/.git"
|
|
assert_not_contains "no repository created before the answer" "$(calls)" "POST"
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
|
|
assert_status "answered yes" 0 "$STATUS"
|
|
assert_eq "the existing file is untouched" "mine" "$(cat "$dir/keep.txt")"
|
|
assert_file_exists "project created beside it" "$dir/.git"
|
|
}
|
|
|
|
test_an_empty_existing_directory_is_also_confirmed() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
|
|
assert_status "empty directory, yes" 0 "$STATUS"
|
|
assert_contains "asked" "$ERR" "already exists and is empty. Use it"
|
|
}
|
|
|
|
test_a_file_that_would_be_overwritten_by_the_license_history_is_kept() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir"
|
|
printf 'my own license\n' >"$dir/LICENSE"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
|
|
assert_status "git refuses to overwrite" 1 "$STATUS"
|
|
assert_contains "says why" "$ERR" "git would overwrite files in $dir"
|
|
assert_eq "the file is intact" "my own license" "$(cat "$dir/LICENSE")"
|
|
assert_contains "step failed" "$OUT" "Local project : FAILED"
|
|
assert_contains "later steps not attempted" "$OUT" "Framework : not attempted"
|
|
}
|
|
|
|
test_a_github_remote_from_an_earlier_version_is_left_alone() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir"
|
|
project_git "$dir" init -q
|
|
project_git "$dir" remote add github https://github.com/acme-org/my-app.git
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_eq "the github remote is kept as it was" "https://github.com/acme-org/my-app.git" "$(project_git "$dir" config --get remote.github.url)"
|
|
assert_eq "origin was added beside it" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)"
|
|
assert_eq "no other remote" $'github\norigin' "$(project_git "$dir" remote | sort)"
|
|
}
|
|
|
|
test_a_remote_with_another_address_is_never_replaced() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir"
|
|
project_git "$dir" init -q
|
|
project_git "$dir" remote add origin https://elsewhere.example.test/x/y.git
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
|
|
assert_status "different origin" 1 "$STATUS"
|
|
assert_contains "says so" "$ERR" "the remote 'origin' in $dir already points to https://elsewhere.example.test/x/y.git"
|
|
assert_eq "origin unchanged" "https://elsewhere.example.test/x/y.git" "$(project_git "$dir" config --get remote.origin.url)"
|
|
}
|
|
|
|
# ---------------------------------------------------------------- framework
|
|
|
|
test_framework_is_a_submodule_and_installed_in_order() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_eq "submodule address" "$SSH_FRAMEWORK_URL" "$(project_git "$dir" config -f .gitmodules --get submodule.framework.url)"
|
|
assert_file_exists "submodule content" "$dir/framework/scripts/install-skills.sh"
|
|
assert_file_exists "skills installed" "$dir/.claude/skills/coding-conventions/SKILL.md"
|
|
assert_file_exists "skills for the other harness" "$dir/.agents/skills/.framework-skills"
|
|
assert_eq "hooks path" "framework/githooks" "$(project_git "$dir" config --local --get core.hooksPath)"
|
|
assert_eq "plan gate off" "" "$(project_git "$dir" config --local --get planGate.enabled || true)"
|
|
assert_contains "reported" "$OUT" "Framework : created $SSH_FRAMEWORK_URL"
|
|
assert_contains "reported once" "$OUT" "Skills and hooks : created (skills installed; hooks installed; plan gate off)"
|
|
}
|
|
|
|
test_skills_and_hooks_are_installed_once() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "first run" 0 "$STATUS"
|
|
run_lib "" "PROJECT[directory]='$dir'
|
|
PROJECT[is_plan_gate_enabled]=0
|
|
install_framework
|
|
echo \"\${STATE[skills_note]}|\${STATE[hooks_note]}\""
|
|
assert_status "second pass" 0 "$STATUS"
|
|
assert_eq "nothing installed twice" "skills already installed|hooks already installed" "$OUT"
|
|
}
|
|
|
|
test_the_plan_gate_is_optional_and_refuses_unplanned_commits() {
|
|
setup_hosts
|
|
local dir="$WORK/project" out
|
|
local_answers "$dir" y y
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply with the plan gate" 0 "$STATUS"
|
|
assert_eq "plan gate on" "true" "$(project_git "$dir" config --local --get planGate.enabled)"
|
|
assert_contains "reported" "$OUT" "plan gate on)"
|
|
# The hooks refuse a commit on main, so work on a branch.
|
|
project_git "$dir" checkout -q -b work
|
|
mkdir -p "$dir/src"
|
|
printf 'x\n' >"$dir/src/x.txt"
|
|
project_git "$dir" add src/x.txt
|
|
out="$(project_git "$dir" commit -q -m "unplanned change" 2>&1 || true)"
|
|
assert_contains "refused without a task trailer" "$out" "plan-first gate: no 'Task: MIL-NNN#N' trailer"
|
|
assert_eq "no commit was made" "1" "$(project_git "$dir" rev-list --count HEAD)"
|
|
}
|
|
|
|
test_without_the_plan_gate_the_same_commit_is_allowed() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
project_git "$dir" checkout -q -b work
|
|
mkdir -p "$dir/src"
|
|
printf 'x\n' >"$dir/src/x.txt"
|
|
project_git "$dir" add src/x.txt
|
|
project_git "$dir" commit -q -m "change"
|
|
assert_eq "commit made" "2" "$(project_git "$dir" rev-list --count HEAD)"
|
|
}
|
|
|
|
test_the_hooks_refuse_a_commit_on_main() {
|
|
setup_hosts
|
|
local dir="$WORK/project" out
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
printf 'x\n' >"$dir/x.txt"
|
|
project_git "$dir" add x.txt
|
|
out="$(project_git "$dir" commit -q -m "on main" 2>&1 || true)"
|
|
assert_contains "refused on main" "$out" "refusing to commit directly on 'main'"
|
|
}
|
|
|
|
test_an_existing_hooks_path_is_not_replaced_without_a_yes() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir"
|
|
project_git "$dir" init -q
|
|
project_git "$dir" config core.hooksPath .githooks
|
|
local_answers "$dir" y y
|
|
# create now, use the directory, keep the hooks path
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\nn\n'
|
|
assert_status "run continues" 0 "$STATUS"
|
|
assert_contains "asked" "$ERR" "core.hooksPath is already '.githooks'. Replace it with framework/githooks"
|
|
assert_eq "hooks path kept" ".githooks" "$(project_git "$dir" config --local --get core.hooksPath)"
|
|
assert_contains "reported" "$OUT" "kept the existing hooks path '.githooks'"
|
|
assert_contains "the plan gate is not on without the hooks" "$ERR" "the plan gate is not enabled because the framework hooks were not installed"
|
|
# Answering yes replaces it.
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\ny\n'
|
|
assert_eq "hooks path replaced after a yes" "framework/githooks" "$(project_git "$dir" config --local --get core.hooksPath)"
|
|
}
|
|
|
|
test_a_global_hooks_path_is_reported_not_changed() {
|
|
setup_hosts
|
|
git config --file "$WORK/gitconfig" core.hooksPath global-hooks-dir
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_contains "warns" "$ERR" "your global core.hooksPath is 'global-hooks-dir'"
|
|
assert_eq "the global setting is untouched" "global-hooks-dir" "$(git config --file "$WORK/gitconfig" --get core.hooksPath)"
|
|
assert_eq "the project has its own" "framework/githooks" "$(project_git "$dir" config --local --get core.hooksPath)"
|
|
}
|
|
|
|
test_a_project_root_in_the_environment_cannot_redirect_the_framework_scripts() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
PROJECT_ROOT="$WORK/elsewhere" run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_file_missing "nothing installed where the environment pointed" "$WORK/elsewhere"
|
|
assert_file_exists "installed in the project" "$dir/.claude/skills/.framework-skills"
|
|
}
|
|
|
|
# ---------------------------------------------------------------- templates
|
|
|
|
test_templates_are_copied() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_eq "AGENTS.md is the template" "$(cat "$dir/framework/templates/AGENTS-template.md")" "$(cat "$dir/AGENTS.md")"
|
|
assert_eq "registry is the template" "$(cat "$dir/framework/templates/artifact-registry-template.md")" "$(cat "$dir/docs/artifact-registry.md")"
|
|
assert_contains "reported" "$OUT" "Templates : created (copied AGENTS.md; copied docs/artifact-registry.md)"
|
|
}
|
|
|
|
test_existing_template_targets_are_replaced_only_after_a_yes() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir/docs"
|
|
printf 'my agents file\n' >"$dir/AGENTS.md"
|
|
printf 'my registry\n' >"$dir/docs/artifact-registry.md"
|
|
local_answers "$dir" y n
|
|
# create now, use the directory, keep AGENTS.md, replace the registry
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\nn\ny\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_contains "asked about AGENTS.md" "$ERR" "AGENTS.md already exists. Replace it with the framework template"
|
|
assert_eq "AGENTS.md kept" "my agents file" "$(cat "$dir/AGENTS.md")"
|
|
assert_eq "registry replaced after a yes" "$(cat "$dir/framework/templates/artifact-registry-template.md")" "$(cat "$dir/docs/artifact-registry.md")"
|
|
assert_contains "reported" "$OUT" "kept existing AGENTS.md; copied docs/artifact-registry.md"
|
|
}
|
|
|
|
# ------------------------------------------------------------- git excludes
|
|
|
|
# exclude_count FILE LINE: how many lines of FILE are exactly LINE.
|
|
exclude_count() {
|
|
grep -cxF -e "$2" "$1" || true
|
|
}
|
|
|
|
test_the_framework_files_are_ignored_by_git_and_nothing_else_changes() {
|
|
setup_hosts
|
|
local dir="$WORK/project" exclude path listing
|
|
exclude="$dir/.git/info/exclude"
|
|
local_answers "$dir" y n
|
|
# create now, and create the project's .env as well
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_contains "reported" "$OUT" "Git excludes : created (/.claude /.agents /AGENTS.md added to .git/info/exclude)"
|
|
for path in /.claude /.agents /AGENTS.md; do
|
|
assert_eq "one entry for $path" "1" "$(exclude_count "$exclude" "$path")"
|
|
done
|
|
for path in .claude .agents AGENTS.md .claude/skills/coding-conventions/SKILL.md .agents/skills/.framework-skills; do
|
|
check
|
|
if ! project_git "$dir" check-ignore -q -- "$path"; then
|
|
fail "$path is not ignored by git"
|
|
fi
|
|
done
|
|
for path in framework .gitmodules docs/artifact-registry.md; do
|
|
check
|
|
if project_git "$dir" check-ignore -q --no-index -- "$path"; then
|
|
fail "$path is ignored by git but is part of the project"
|
|
fi
|
|
done
|
|
listing="$(project_git "$dir" status --porcelain)"
|
|
assert_not_contains ".claude is not listed" "$listing" ".claude"
|
|
assert_not_contains ".agents is not listed" "$listing" ".agents"
|
|
assert_not_contains "AGENTS.md is not listed" "$listing" "AGENTS.md"
|
|
assert_contains "the submodule is listed" "$listing" "framework"
|
|
assert_contains "the registry is listed" "$listing" "docs/"
|
|
assert_file_missing "no .gitignore is written" "$dir/.gitignore"
|
|
assert_eq "nothing was committed" "1" "$(project_git "$dir" rev-list --count HEAD)"
|
|
assert_eq "the .env keeps its own entry, once" "1" "$(exclude_count "$exclude" ".env")"
|
|
assert_contains "the .env comment is unchanged" "$(cat "$exclude")" "# RepoFoundry: the credentials file of this project"
|
|
}
|
|
|
|
test_the_git_excludes_are_written_once_and_keep_the_existing_lines() {
|
|
local dir="$WORK/p" exclude="$WORK/p/.git/info/exclude" path
|
|
mkdir -p "$dir"
|
|
project_git "$dir" init -q
|
|
printf 'build/' >"$exclude" # no trailing newline
|
|
run_lib "" 'PROJECT[directory]="'"$dir"'"
|
|
exclude_framework_files
|
|
echo "${STEP_STATUS["Git excludes"]}"
|
|
exclude_framework_files
|
|
echo "${STEP_STATUS["Git excludes"]} ${STEP_DETAIL["Git excludes"]}"'
|
|
assert_status "run" 0 "$STATUS"
|
|
assert_eq "created, then reused" $'created\nreused (/.claude /.agents /AGENTS.md already excluded)' "$OUT"
|
|
assert_eq "the old line is kept on its own line" "1" "$(exclude_count "$exclude" "build/")"
|
|
for path in /.claude /.agents /AGENTS.md; do
|
|
assert_eq "entry for $path written once" "1" "$(exclude_count "$exclude" "$path")"
|
|
done
|
|
assert_eq "one comment line" "1" "$(exclude_count "$exclude" "# RepoFoundry: the files installed from the framework")"
|
|
}
|
|
|
|
test_nothing_is_written_for_paths_git_already_ignores() {
|
|
local dir="$WORK/p" exclude="$WORK/p/.git/info/exclude"
|
|
mkdir -p "$dir"
|
|
project_git "$dir" init -q
|
|
printf '.claude\n.agents\nAGENTS.md\n' >"$dir/.gitignore"
|
|
run_lib "" 'PROJECT[directory]="'"$dir"'"
|
|
exclude_framework_files
|
|
echo "${STEP_STATUS["Git excludes"]}"'
|
|
assert_status "run" 0 "$STATUS"
|
|
assert_eq "reused" "reused" "$OUT"
|
|
assert_eq "no entry written" "0" "$(grep -c '^/' "$exclude" || true)"
|
|
assert_eq "no comment written" "0" "$(exclude_count "$exclude" "# RepoFoundry: the files installed from the framework")"
|
|
}
|
|
|
|
test_a_tracked_framework_file_stays_tracked_and_is_named() {
|
|
local dir="$WORK/p"
|
|
mkdir -p "$dir/.agents" "$dir/.claude"
|
|
project_git "$dir" init -q
|
|
printf 'mine\n' >"$dir/AGENTS.md"
|
|
printf 'x\n' >"$dir/.agents/keep"
|
|
printf 'y\n' >"$dir/.claude/new" # not tracked
|
|
project_git "$dir" add AGENTS.md .agents/keep
|
|
project_git "$dir" commit -q -m seed
|
|
run_lib "" 'PROJECT[directory]="'"$dir"'"
|
|
exclude_framework_files
|
|
echo "${STEP_STATUS["Git excludes"]} ${STEP_DETAIL["Git excludes"]}"'
|
|
assert_status "run" 0 "$STATUS"
|
|
assert_contains "names the tracked paths" "$OUT" "git tracks .agents AGENTS.md, so it is not ignored"
|
|
assert_eq "both are still tracked" $'.agents/keep\nAGENTS.md' "$(project_git "$dir" ls-files)"
|
|
assert_eq "the content is unchanged" "mine" "$(cat "$dir/AGENTS.md")"
|
|
assert_eq "nothing is staged or modified" "" "$(project_git "$dir" status --porcelain)"
|
|
check
|
|
if ! project_git "$dir" check-ignore -q -- .claude; then
|
|
fail ".claude, which git does not track, is not ignored"
|
|
fi
|
|
}
|
|
|
|
# ----------------------------------------------------------- SSH and errors
|
|
|
|
test_without_ssh_the_run_stops_unless_the_framework_is_skipped() {
|
|
setup_hosts
|
|
write_ssh_stub 255
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\nn\n'
|
|
assert_status "answered no" 1 "$STATUS"
|
|
assert_contains "explains" "$ERR" "SSH to Gitea (git.example.test port 10022) did not work, so the framework cannot be added"
|
|
assert_contains "says what to do" "$ERR" "stopped: set up SSH access to Gitea (see the README) and run again"
|
|
assert_not_contains "nothing created" "$(calls)" "POST"
|
|
assert_file_missing "no directory" "$dir"
|
|
}
|
|
|
|
test_without_ssh_the_framework_steps_are_skipped_after_a_yes() {
|
|
setup_hosts
|
|
write_ssh_stub 255
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
|
|
assert_status "continue without the framework" 0 "$STATUS"
|
|
assert_eq "origin over HTTPS, no credential" "https://git.example.test/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)"
|
|
assert_eq "the license history arrived over HTTPS" "1" "$(project_git "$dir" rev-list --count HEAD)"
|
|
assert_eq "no token in any file" "" "$(files_with_secret "$dir")"
|
|
assert_eq "no temporary files left" "" "$(find "$WORK/tmp" -mindepth 1)"
|
|
assert_contains "framework skipped" "$OUT" "Framework : skipped (no SSH access to Gitea)"
|
|
assert_contains "skills and hooks skipped" "$OUT" "Skills and hooks : skipped (no SSH access to Gitea)"
|
|
assert_contains "templates skipped" "$OUT" "Templates : skipped (no SSH access to Gitea)"
|
|
assert_contains "git excludes skipped" "$OUT" "Git excludes : skipped (no SSH access to Gitea)"
|
|
assert_not_contains "nothing excluded: .claude" "$(cat "$dir/.git/info/exclude")" "/.claude"
|
|
assert_not_contains "nothing excluded: AGENTS.md" "$(cat "$dir/.git/info/exclude")" "/AGENTS.md"
|
|
assert_file_missing "no submodule" "$dir/.gitmodules"
|
|
assert_file_missing "no AGENTS.md" "$dir/AGENTS.md"
|
|
}
|
|
|
|
test_a_failed_submodule_gives_an_actionable_message() {
|
|
setup_hosts
|
|
find "$WORK/remote/TirSystem/SQA-QC-Framework.git" \( -type f -o -type l \) -delete
|
|
find "$WORK/remote/TirSystem/SQA-QC-Framework.git" -depth -type d -exec rmdir {} +
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "no framework repository" 1 "$STATUS"
|
|
assert_contains "names the address" "$ERR" "git could not add the framework from $SSH_FRAMEWORK_URL"
|
|
assert_contains "says how to test the access" "$ERR" "ssh -p 10022 -T git@git.example.test"
|
|
assert_contains "step failed" "$OUT" "Framework : FAILED"
|
|
assert_contains "the rest not attempted" "$OUT" "Skills and hooks : not attempted"
|
|
assert_contains "the project itself exists" "$OUT" "Local project : created"
|
|
}
|
|
|
|
test_the_framework_repository_is_configurable() {
|
|
setup_hosts
|
|
mkdir -p "$WORK/remote/Other"
|
|
cp -R "$WORK/remote/TirSystem/SQA-QC-Framework.git" "$WORK/remote/Other/Framework.git"
|
|
printf 'FRAMEWORK_REPO=Other/Framework\n' >>"$WORK/config.env"
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y n
|
|
run_apply "$LOCAL_ANSWERS"$'y\n'
|
|
assert_status "apply" 0 "$STATUS"
|
|
assert_eq "submodule address" "ssh://git@git.example.test:10022/Other/Framework.git" "$(project_git "$dir" config -f .gitmodules --get submodule.framework.url)"
|
|
}
|
|
|
|
test_framework_repo_must_look_like_owner_and_name() {
|
|
local value
|
|
for value in "nope" "a/b/c" "../x" "a/.." "a b/c" "/x"; do
|
|
printf 'GITEA_URL=https://git.example.test\nFRAMEWORK_REPO=%s\n' "$value" >"$WORK/c.env"
|
|
run_lib "" "parse_env_file \"$WORK/c.env\" CONFIG_KEYS CONFIG
|
|
validate_config"
|
|
assert_status "FRAMEWORK_REPO=$value" 1 "$STATUS"
|
|
assert_contains "message" "$ERR" "FRAMEWORK_REPO"
|
|
done
|
|
}
|
|
|
|
# --------------------------------------------------------------- the plan
|
|
|
|
test_the_dry_run_plan_describes_the_local_steps_and_creates_nothing() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
local_answers "$dir" y y
|
|
run_dry "$LOCAL_ANSWERS"
|
|
assert_status "dry run" 0 "$STATUS"
|
|
assert_contains "project" "$OUT" "Local project : create $dir (new directory), git on main, no commit"
|
|
assert_contains "framework" "$OUT" "Framework : add $SSH_FRAMEWORK_URL as a submodule"
|
|
assert_contains "skills and hooks" "$OUT" "Skills and hooks : install once; plan gate yes"
|
|
assert_contains "templates" "$OUT" "Templates : AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced)"
|
|
assert_contains "git excludes" "$OUT" "Git excludes : /.claude /.agents /AGENTS.md go into .git/info/exclude (no tracked file changes)"
|
|
assert_file_missing "nothing created" "$dir"
|
|
}
|
|
|
|
test_the_plan_marks_an_existing_directory_and_missing_ssh() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
mkdir -p "$dir"
|
|
printf 'x\n' >"$dir/a.txt"
|
|
write_ssh_stub 255
|
|
local_answers "$dir" y n
|
|
run_dry "$LOCAL_ANSWERS"
|
|
assert_contains "existing directory" "$OUT" "use the existing directory $dir, which has files (you will be asked)"
|
|
assert_contains "no SSH" "$OUT" "NOT possible without SSH to Gitea; you will be asked whether to go on without it"
|
|
assert_not_contains "no git excludes without the framework" "$OUT" "Git excludes"
|
|
assert_contains "HTTPS origin" "$OUT" "will use HTTPS (SSH test: failed"
|
|
}
|
|
|
|
test_a_project_path_that_is_a_file_is_refused_in_the_preflight() {
|
|
setup_hosts
|
|
local dir="$WORK/project"
|
|
printf 'x\n' >"$dir"
|
|
local_answers "$dir" y n
|
|
run_dry "$LOCAL_ANSWERS"
|
|
assert_status "path is a file" 1 "$STATUS"
|
|
assert_contains "message" "$ERR" "already exists and is not a directory"
|
|
}
|
|
|
|
# ------------------------------------------------------------------ helpers
|
|
|
|
test_remote_addresses_are_built_from_the_configuration() {
|
|
run_lib "" 'CONFIG[GITEA_URL]=https://git.example.test/sub
|
|
CONFIG[GITEA_SSH_PORT]=2222 CONFIG[FRAMEWORK_REPO]=Org/Fw
|
|
PROJECT[gitea_owner]=TirSystem PROJECT[name]=my-app
|
|
STATE[is_ssh_ok]=1
|
|
origin_url; echo
|
|
STATE[is_ssh_ok]=0
|
|
origin_url; echo
|
|
framework_url; echo
|
|
gitea_host; echo'
|
|
assert_eq "addresses" $'ssh://git@git.example.test:2222/TirSystem/my-app.git\nhttps://git.example.test/sub/TirSystem/my-app.git\nssh://git@git.example.test:2222/Org/Fw.git\ngit.example.test' "$OUT"
|
|
}
|
|
|
|
test_the_https_fetch_hands_the_token_over_through_the_environment_only() {
|
|
# A stub git plays the part of the server asking for credentials: it runs
|
|
# the GIT_ASKPASS helper the way git does and records the answers.
|
|
local real_git
|
|
real_git="$(command -v git)"
|
|
write_stub git "
|
|
if [[ \$* == *'config --get remote.origin.url'* ]]; then echo https://git.example.test/TirSystem/my-app.git; exit 0; fi
|
|
if [[ \$* == *fetch* ]]; then
|
|
printf '%s\n' \"\$@\" >>\"\$STUB_DIR/git.args\"
|
|
\"\$GIT_ASKPASS\" 'Username for https://git.example.test: ' >>\"\$STUB_DIR/askpass.out\"
|
|
\"\$GIT_ASKPASS\" 'Password for https://git.example.test: ' >>\"\$STUB_DIR/askpass.out\"
|
|
exit 0
|
|
fi
|
|
exec '$real_git' \"\$@\""
|
|
run_lib "" "setup_temp_dir
|
|
STATE[gitea_login]=gitea-user
|
|
CREDENTIALS[GITEA_TOKEN]='$FAKE_GITEA_TOKEN'
|
|
fetch_origin '$WORK'
|
|
cleanup"
|
|
assert_status "fetch" 0 "$STATUS"
|
|
assert_eq "user name and token reach git" $'gitea-user\n'"$FAKE_GITEA_TOKEN" "$(cat "$WORK/askpass.out")"
|
|
assert_not_contains "token not on the git command line" "$(cat "$WORK/git.args")" "$FAKE_GITEA_TOKEN"
|
|
assert_eq "the helper is removed" "" "$(find "$WORK/tmp" -mindepth 1)"
|
|
}
|
|
|
|
test_a_failed_https_fetch_is_reported_to_the_caller() {
|
|
write_stub git "
|
|
if [[ \$* == *'config --get remote.origin.url'* ]]; then echo https://git.example.test/TirSystem/my-app.git; exit 0; fi
|
|
if [[ \$* == *fetch* ]]; then exit 128; fi
|
|
exit 0"
|
|
run_lib "" "setup_temp_dir
|
|
STATE[gitea_login]=gitea-user
|
|
CREDENTIALS[GITEA_TOKEN]='$FAKE_GITEA_TOKEN'
|
|
if fetch_origin '$WORK'; then echo ok; else echo failed; fi
|
|
cleanup"
|
|
assert_eq "failure passed on" "failed" "$OUT"
|
|
assert_eq "the helper is removed" "" "$(find "$WORK/tmp" -mindepth 1)"
|
|
}
|