diff --git a/README.md b/README.md index 23d9288..92fd82a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# RepoFoundry +# Repo Foundry RepoFoundry (`src/create-project.sh`) sets up a new project in one run: @@ -87,6 +87,41 @@ chmod 600 .env # Linux and macOS Every URL must start with `https://` and must not contain a user name, password, query string or fragment. A credential key in this file is rejected. +### `config.env` (project details, optional) + +Any of the details the script asks for can be set in `config.env` instead. +A detail that is set is used and not asked; the summary marks it with +`(from config.env)`. + +| Key | Detail | Accepted value | +| --- | --- | --- | +| `PROJECT_NAME` | repository name | letters, digits, `.`, `_`, `-`; at most 100; not ending in `.git` | +| `PROJECT_DESCRIPTION` | description | at most 350 characters; may be empty | +| `PROJECT_VISIBILITY` | visibility | `private` or `public` | +| `GITEA_OWNER` | Gitea user or organization | letters, digits, `.`, `_`, `-`; at most 39 | +| `USE_GITHUB` | also create a GitHub repository | `yes` or `no` | +| `GITHUB_OWNER` | GitHub user or organization | letters, digits, `-`; used only when GitHub is used | +| `PROJECT_DIRECTORY` | local directory | not empty, not starting with `-` | +| `ENABLE_PLAN_GATE` | enable the plan gate | `yes` or `no` | + +- A key that is present counts as set, even when its value is empty. Only + `PROJECT_DESCRIPTION` may be empty (no description); an empty value for any + other key stops the run. Remove the line to be asked instead. +- An invalid value stops the run before any request to a host and names the + key. The script never falls back to asking for it. +- `USE_GITHUB=no` skips the GitHub owner and every GitHub step; a + `GITHUB_OWNER` set at the same time is ignored, with a warning. +- Only these eight details can be set. The confirmations stay questions that + default to no: create now, reusing an existing repository, an existing + directory, `core.hooksPath` and replacing a template file. +- These keys are accepted in `config.env` only, never in `.env`. + +With all eight set, a run asks only the confirmations: + +```bash +src/create-project.sh --apply # asks only "Create these now (y/n) [n]" +``` + ### `.env` (credentials) | Key | Meaning | diff --git a/config.env.example b/config.env.example index 2f8c03b..63151be 100644 --- a/config.env.example +++ b/config.env.example @@ -30,6 +30,21 @@ GITEA_API_URL=https:///api/v1 # The server may enforce a minimum. Default: 10m0s. #MIRROR_INTERVAL=10m0s +# Optional. Project details that are set here are not asked. A key that is +# present counts as set, even when empty; only PROJECT_DESCRIPTION may be +# empty. An invalid value stops the run and names the key. Remove or comment +# out a line to be asked for it. The confirmations ("Create these now" and +# the questions about existing repositories, directories and files) are +# always asked. +#PROJECT_NAME=my-project +#PROJECT_DESCRIPTION=What the project is for +#PROJECT_VISIBILITY=private # private or public +#GITEA_OWNER=my-organization +#USE_GITHUB=yes # yes or no +#GITHUB_OWNER=my-organization # used only when USE_GITHUB is yes +#PROJECT_DIRECTORY=./my-project +#ENABLE_PLAN_GATE=no # yes or no + # Optional. OWNER/NAME of the SQA-QC-Framework repository on the Gitea server; # it is added to the new project as a submodule over SSH. # Default: TirSystem/SQA-QC-Framework. diff --git a/src/lib/config.sh b/src/lib/config.sh index f454da7..6f691f3 100644 --- a/src/lib/config.sh +++ b/src/lib/config.sh @@ -4,7 +4,7 @@ # # Part of create-project.sh: sourced by it, never run on its own. # -# Provides: unquote_value, parse_env_file, parse_env_entry, validate_config, validate_credentials, require_github_credentials, warn_if_env_unsafe, load_configuration +# Provides: unquote_value, parse_env_file, parse_env_entry, validate_config, check_preset, check_preset_choice, validate_project_presets, validate_credentials, require_github_credentials, warn_if_env_unsafe, load_configuration # unquote_value RAW: strip matching quotes (or a trailing " # comment" on an # unquoted value) and return the value in REPLY. Fails on unbalanced quotes. @@ -103,6 +103,45 @@ validate_config() { CONFIG[FRAMEWORK_REPO]="${CONFIG[FRAMEWORK_REPO]:-$DEFAULT_FRAMEWORK_REPO}" is_valid_framework_repo "${CONFIG[FRAMEWORK_REPO]}" || die "FRAMEWORK_REPO in $CONFIG_FILE must look like OWNER/NAME" + validate_project_presets +} + +# check_preset KEY VALIDATOR HINT: when KEY is set in config.env its value must +# pass VALIDATOR. A key that is present counts as set; only the description +# may be empty. The message names the key, never the value. +check_preset() { + local key="$1" validator="$2" hint="$3" + [[ -n ${CONFIG[$key]+set} ]] || return 0 + if [[ -z ${CONFIG[$key]} && $key != PROJECT_DESCRIPTION ]]; then + die "$key in $CONFIG_FILE is empty; remove the line to be asked, or give a value ($hint)" + fi + "$validator" "${CONFIG[$key]}" || + die "$key in $CONFIG_FILE is not valid: $hint" +} + +# check_preset_choice KEY CHOICE...: like check_preset for a fixed list of +# words; the value is stored in lower case. +check_preset_choice() { + local key="$1" + shift + [[ -n ${CONFIG[$key]+set} ]] || return 0 + [[ -n ${CONFIG[$key]} ]] || + die "$key in $CONFIG_FILE is empty; remove the line to be asked, or give one of: $*" + CONFIG[$key]="${CONFIG[$key],,}" + in_list "${CONFIG[$key]}" "$@" || + die "$key in $CONFIG_FILE must be one of: $*" +} + +# The optional project details that may be preset in config.env. +validate_project_presets() { + check_preset PROJECT_NAME is_valid_repo_name "$HINT_REPO_NAME" + check_preset PROJECT_DESCRIPTION is_valid_description "$HINT_DESCRIPTION" + check_preset_choice PROJECT_VISIBILITY private public + check_preset GITEA_OWNER is_valid_gitea_owner "$HINT_GITEA_OWNER" + check_preset_choice USE_GITHUB yes no + check_preset GITHUB_OWNER is_valid_github_owner "$HINT_GITHUB_OWNER" + check_preset PROJECT_DIRECTORY is_valid_directory "$HINT_DIRECTORY" + check_preset_choice ENABLE_PLAN_GATE yes no } validate_credentials() { diff --git a/src/lib/constants.sh b/src/lib/constants.sh index decf709..5e0aa16 100644 --- a/src/lib/constants.sh +++ b/src/lib/constants.sh @@ -19,11 +19,20 @@ readonly DEFAULT_SSH_PORT=10022 readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework" readonly AGPL_LICENSE_KEY="AGPL-3.0" readonly DEFAULT_BRANCH="main" +# What the prompts and the preset keys in config.env both tell the Maintainer +# when a value is refused. +readonly HINT_REPO_NAME="use letters, digits, '.', '_' or '-' (at most 100), not ending in .git" +readonly HINT_DESCRIPTION="at most $MAX_DESCRIPTION_LENGTH characters and no control characters" +readonly HINT_GITEA_OWNER="use letters, digits, '.', '_' or '-' (at most 39)" +readonly HINT_GITHUB_OWNER="use letters, digits or '-' (at most 39)" +readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters" readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror" "Local project" "Framework" "Skills and hooks" "Templates") # shellcheck disable=SC2034 # read through namerefs (parse_env_file) readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL - GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO) + GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO + PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB + GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE) readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN) CONFIG_FILE="$PROJECT_ROOT/config.env" @@ -41,6 +50,8 @@ TEMP_FILES=() declare -A CONFIG=() declare -A CREDENTIALS=() declare -A PROJECT=() +# Project details that came from config.env instead of a prompt (PRESET[name]=1). +declare -A PRESET=() # Facts found by the preflight checks (logins, owner kinds, repository state). declare -A STATE=() # Outcome of each step in PLAN_STEPS, for the final report. diff --git a/src/lib/project.sh b/src/lib/project.sh index f5b9028..6fce350 100644 --- a/src/lib/project.sh +++ b/src/lib/project.sh @@ -4,34 +4,59 @@ # # Part of create-project.sh: sourced by it, never run on its own. # -# Provides: collect_project_details, yes_no, credential_state, print_summary +# Provides: preset_detail, collect_project_details, collect_github_details, source_note, yes_no, credential_state, print_summary +# preset_detail KEY NAME: a detail set in config.env is used and not asked; +# the value is returned in REPLY and marked in PRESET[NAME]. +preset_detail() { + [[ -n ${CONFIG[$1]+set} ]] || return 1 + REPLY="${CONFIG[$1]}" + PRESET[$2]=1 +} + +# Ask for each project detail, except those set in config.env. collect_project_details() { - prompt_value "Repository name" "" is_valid_repo_name \ - "use letters, digits, '.', '_' or '-' (at most 100), not ending in .git" + preset_detail PROJECT_NAME name || + prompt_value "Repository name" "" is_valid_repo_name "$HINT_REPO_NAME" PROJECT[name]="$REPLY" - prompt_value "Description (optional)" "" is_valid_description \ - "at most $MAX_DESCRIPTION_LENGTH characters and no control characters" + preset_detail PROJECT_DESCRIPTION description || + prompt_value "Description (optional)" "" is_valid_description "$HINT_DESCRIPTION" PROJECT[description]="$REPLY" - prompt_choice "Visibility" private private public + preset_detail PROJECT_VISIBILITY visibility || + prompt_choice "Visibility" private private public PROJECT[visibility]="$REPLY" - prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner \ - "use letters, digits, '.', '_' or '-' (at most 39)" + preset_detail GITEA_OWNER gitea_owner || + prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner "$HINT_GITEA_OWNER" PROJECT[gitea_owner]="$REPLY" - prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y + collect_github_details + preset_detail PROJECT_DIRECTORY directory || + prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory "$HINT_DIRECTORY" + PROJECT[directory]="$REPLY" + if preset_detail ENABLE_PLAN_GATE is_plan_gate_enabled; then + [[ $REPLY == yes ]] && REPLY=1 || REPLY=0 + else + prompt_yes_no "Enable the plan gate" n + fi + PROJECT[is_plan_gate_enabled]="$REPLY" +} + +# Whether GitHub is used, and its owner. A GITHUB_OWNER set while GitHub is +# not used is ignored, with a warning. +collect_github_details() { + if preset_detail USE_GITHUB has_github; then + [[ $REPLY == yes ]] && REPLY=1 || REPLY=0 + else + prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y + fi PROJECT[has_github]="$REPLY" PROJECT[github_owner]="" if ((PROJECT[has_github])); then - prompt_value "GitHub owner (user or organization)" \ - "${CREDENTIALS[GITHUB_USER]:-}" is_valid_github_owner \ - "use letters, digits or '-' (at most 39)" + preset_detail GITHUB_OWNER github_owner || + prompt_value "GitHub owner (user or organization)" "${CREDENTIALS[GITHUB_USER]:-}" is_valid_github_owner "$HINT_GITHUB_OWNER" PROJECT[github_owner]="$REPLY" + elif [[ -n ${CONFIG[GITHUB_OWNER]+set} ]]; then + warn "GITHUB_OWNER in $CONFIG_FILE is ignored because GitHub is not used" fi - prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory \ - "must not be empty, start with '-' or contain control characters" - PROJECT[directory]="$REPLY" - prompt_yes_no "Enable the plan gate" n - PROJECT[is_plan_gate_enabled]="$REPLY" } yes_no() { @@ -50,20 +75,27 @@ credential_state() { fi } +# source_note NAME: the marker shown after a value that came from config.env. +source_note() { + if [[ -n ${PRESET[$1]:-} ]]; then + printf ' (from config.env)' + fi +} + print_summary() { say "" say "$PROJECT_NAME $VERSION" say "Collected details:" - say " Repository : ${PROJECT[name]} (${PROJECT[visibility]})" - say " Description : ${PROJECT[description]:-(none)}" - say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}" + say " Repository : ${PROJECT[name]}$(source_note name) (${PROJECT[visibility]}$(source_note visibility))" + say " Description : ${PROJECT[description]:-(none)}$(source_note description)" + say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}$(source_note gitea_owner)" if ((PROJECT[has_github])); then - say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)" + say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)$(source_note github_owner)" else - say " GitHub : not used" + say " GitHub : not used$(source_note has_github)" fi - say " Directory : ${PROJECT[directory]}" - say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")" + say " Directory : ${PROJECT[directory]}$(source_note directory)" + say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")$(source_note is_plan_gate_enabled)" say "Credentials : GITEA_TOKEN $(credential_state GITEA_TOKEN)," \ "GITHUB_PAT $(credential_state GITHUB_PAT)" } diff --git a/tests/test-presets.sh b/tests/test-presets.sh new file mode 100644 index 0000000..3023491 --- /dev/null +++ b/tests/test-presets.sh @@ -0,0 +1,264 @@ +#!/usr/bin/env bash +# test-presets.sh - tests for the project details preset in config.env +# (MIL-004): a detail that is set there is not asked, an invalid one stops +# the run, and the confirmations stay interactive. Sourced by run-tests.sh. + +# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose + +# The eight keys with a value that differs from the answer given when asked. +readonly PRESET_ALL='PROJECT_NAME=preset-app +PROJECT_DESCRIPTION=From the config +PROJECT_VISIBILITY=public +GITEA_OWNER=PresetOrg +USE_GITHUB=yes +GITHUB_OWNER=preset-gh +PROJECT_DIRECTORY=./preset-dir +ENABLE_PLAN_GATE=yes' + +# The same details as ANSWERS_GITHUB, so a run with all of them preset is the +# same run with no answers. +readonly PRESET_LIKE_ANSWERS='PROJECT_NAME=my-app +PROJECT_DESCRIPTION=A test app +PROJECT_VISIBILITY=private +GITEA_OWNER=TirSystem +USE_GITHUB=yes +GITHUB_OWNER=acme-org +PROJECT_DIRECTORY=./my-app +ENABLE_PLAN_GATE=no' + +# collect_with PRESET_LINES INPUT: parse a config holding the preset lines, +# collect the details and print them one per line. +collect_with() { + printf '%s\n' "$1" >"$WORK/preset.env" + run_lib "$2" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG +validate_project_presets +collect_project_details +for k in name description visibility gitea_owner has_github github_owner directory is_plan_gate_enabled; do + printf "%s=%s\n" "$k" "${PROJECT[$k]}" +done' +} + +# Each key, the answers a run gives for the other seven details, and the +# prompt that must not be shown for the key. +test_each_key_is_used_and_not_asked() { + local key value field label line input + local -A answer=([PROJECT_NAME]=asked-app [PROJECT_DESCRIPTION]="Asked description" + [PROJECT_VISIBILITY]=private [GITEA_OWNER]=AskedOrg [USE_GITHUB]=y + [GITHUB_OWNER]=asked-gh [PROJECT_DIRECTORY]=./asked-dir [ENABLE_PLAN_GATE]=n) + local order=(PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER + USE_GITHUB GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE) + while IFS='|' read -r key value field label; do + input="" + for line in "${order[@]}"; do + if [[ $line != "$key" ]]; then + input+="${answer[$line]}"$'\n' + fi + done + collect_with "$key=$value" "$input" + assert_status "$key preset" 0 "$STATUS" + assert_contains "$key value used" "$OUT" "$field" + assert_not_contains "$key not asked" "$ERR" "$label" + assert_contains "other details still asked" "$OUT" "asked" + done <<'EOF' +PROJECT_NAME|preset-app|name=preset-app|Repository name +PROJECT_DESCRIPTION|From the config|description=From the config|Description +PROJECT_VISIBILITY|public|visibility=public|Visibility +GITEA_OWNER|PresetOrg|gitea_owner=PresetOrg|Gitea owner +USE_GITHUB|yes|has_github=1|Also create a GitHub +GITHUB_OWNER|preset-gh|github_owner=preset-gh|GitHub owner +PROJECT_DIRECTORY|./preset-dir|directory=./preset-dir|Local directory +ENABLE_PLAN_GATE|yes|is_plan_gate_enabled=1|Enable the plan gate +EOF +} + +test_all_keys_set_asks_nothing() { + collect_with "$PRESET_ALL" "" + assert_status "no input needed" 0 "$STATUS" + assert_eq "every value from the config" $'name=preset-app\ndescription=From the config\nvisibility=public\ngitea_owner=PresetOrg\nhas_github=1\ngithub_owner=preset-gh\ndirectory=./preset-dir\nis_plan_gate_enabled=1' "$OUT" + assert_eq "no prompt text at all" "" "$ERR" +} + +test_absent_keys_are_asked_as_before() { + collect_with "PROJECT_NAME=preset-app" $'\n\nTirSystem\nn\n\nn\n' + assert_status "mixed" 0 "$STATUS" + assert_contains "preset name" "$OUT" "name=preset-app" + assert_contains "default directory from the preset name" "$OUT" "directory=./preset-app" + assert_contains "other details asked" "$ERR" "Gitea owner" +} + +test_values_are_taken_in_any_case() { + collect_with $'PROJECT_VISIBILITY=PUBLIC\nUSE_GITHUB=No\nENABLE_PLAN_GATE=YES' $'x-app\n\nTirSystem\n\n' + assert_status "case ignored" 0 "$STATUS" + assert_contains "visibility" "$OUT" "visibility=public" + assert_contains "no GitHub" "$OUT" "has_github=0" + assert_contains "plan gate" "$OUT" "is_plan_gate_enabled=1" +} + +# ------------------------------------------------------- empty and invalid + +test_empty_value_counts_as_set_only_for_the_description() { + collect_with "PROJECT_DESCRIPTION=" $'my-app\npublic\nTirSystem\nn\n\nn\n' + assert_status "empty description accepted" 0 "$STATUS" + assert_contains "description empty" "$OUT" "description=" + assert_not_contains "description not asked" "$ERR" "Description" + local key + for key in PROJECT_NAME PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB GITHUB_OWNER \ + PROJECT_DIRECTORY ENABLE_PLAN_GATE; do + printf '%s=\n' "$key" >"$WORK/preset.env" + run_lib "" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG +validate_project_presets' + assert_status "$key empty" 1 "$STATUS" + assert_contains "$key named" "$ERR" "$key in" + assert_contains "$key says empty" "$ERR" "is empty" + done +} + +test_invalid_values_are_refused_naming_the_key() { + local key value + while IFS='|' read -r key value; do + printf '%s=%s\n' "$key" "$value" >"$WORK/preset.env" + run_lib "" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG +validate_project_presets' + assert_status "$key=$value" 1 "$STATUS" + assert_contains "$key=$value named" "$ERR" "$key in" + done <<'EOF' +PROJECT_NAME|bad name +PROJECT_NAME|x.git +PROJECT_VISIBILITY|internal +GITEA_OWNER|-lead +USE_GITHUB|maybe +USE_GITHUB|1 +GITHUB_OWNER|octo_user +PROJECT_DIRECTORY|-rf +ENABLE_PLAN_GATE|true +EOF + # A description over the limit (350 characters) is refused too. + printf 'PROJECT_DESCRIPTION=%s\n' "$(printf 'a%.0s' $(seq 1 351))" >"$WORK/preset.env" + run_lib "" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG +validate_project_presets' + assert_status "long description" 1 "$STATUS" + assert_contains "named" "$ERR" "PROJECT_DESCRIPTION in" +} + +test_invalid_value_stops_before_any_request_and_never_asks() { + setup_hosts + printf 'PROJECT_NAME=bad name\n' >>"$WORK/config.env" + run_apply "" + assert_status "stopped" 1 "$STATUS" + assert_contains "key named" "$ERR" "PROJECT_NAME in" + assert_not_contains "no value asked instead" "$ERR" "Repository name:" + assert_eq "no request made" "" "$(calls)" +} + +test_new_keys_are_rejected_in_env_and_credentials_in_config() { + setup_hosts + printf 'PROJECT_NAME=my-app\n' >>"$WORK/.env" + run_dry "" + assert_status ".env with a project key" 1 "$STATUS" + assert_contains "unknown in .env" "$ERR" "unknown key 'PROJECT_NAME'" + setup_hosts + printf 'GITEA_TOKEN=abcdefgh12345\n' >>"$WORK/config.env" + run_dry "" + assert_status "config.env with a credential" 1 "$STATUS" + assert_contains "credential refused" "$ERR" "is a credential" +} + +# ------------------------------------------------------------- GitHub + +test_use_github_no_skips_the_owner_and_warns_about_a_stray_one() { + collect_with $'USE_GITHUB=no\nGITHUB_OWNER=acme-org' $'my-app\n\n\nTirSystem\n\nn\n' + assert_status "GitHub off" 0 "$STATUS" + assert_contains "no GitHub" "$OUT" "has_github=0" + assert_contains "no owner" "$OUT" "github_owner=" + assert_not_contains "owner not asked" "$ERR" "GitHub owner (" + assert_contains "ignored with a warning" "$ERR" "GITHUB_OWNER in" + assert_contains "says why" "$ERR" "ignored because GitHub is not used" + collect_with "USE_GITHUB=no" $'my-app\n\n\nTirSystem\n\nn\n' + assert_not_contains "no warning without the key" "$ERR" "ignored" +} + +test_github_owner_preset_is_used_when_github_is_asked_for() { + collect_with "GITHUB_OWNER=preset-gh" $'my-app\n\n\nTirSystem\ny\n\nn\n' + assert_contains "owner from the config" "$OUT" "github_owner=preset-gh" + assert_not_contains "owner not asked" "$ERR" "GitHub owner (" + collect_with "GITHUB_OWNER=preset-gh" $'my-app\n\n\nTirSystem\nn\n\nn\n' + assert_contains "ignored when answered no" "$ERR" "ignored because GitHub is not used" +} + +test_use_github_no_makes_no_github_call() { + setup_hosts + printf '%s\n' "$PRESET_LIKE_ANSWERS" | sed 's/^USE_GITHUB=.*/USE_GITHUB=no/' >>"$WORK/config.env" + run_dry "" + assert_status "dry run" 0 "$STATUS" + assert_contains "GitHub not used" "$OUT" "GitHub repository : not used" + assert_not_contains "no GitHub call" "$(calls)" "api.github.com" + assert_not_contains "no GitHub owner asked" "$ERR" "GitHub owner (" +} + +# ------------------------------------------------------------ the summary + +test_summary_marks_the_values_from_config_env() { + setup_hosts + printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env" + run_dry "" + assert_status "all preset" 0 "$STATUS" + assert_contains "name" "$OUT" "Repository : my-app (from config.env) (private (from config.env))" + assert_contains "description" "$OUT" "Description : A test app (from config.env)" + assert_contains "Gitea" "$OUT" "/TirSystem/my-app (from config.env)" + assert_contains "GitHub" "$OUT" "(AGPL license applied) (from config.env)" + assert_contains "directory" "$OUT" "Directory : ./my-app (from config.env)" + assert_contains "plan gate" "$OUT" "Plan gate : no (from config.env)" +} + +test_summary_marks_nothing_when_everything_is_asked() { + setup_hosts + run_dry "$ANSWERS_GITHUB" + assert_status "all asked" 0 "$STATUS" + assert_not_contains "no marker" "$OUT" "(from config.env)" +} + +# ----------------------------------------------------- the confirmations + +test_with_every_detail_set_only_the_confirmations_are_asked() { + setup_hosts + run_apply "$ANSWERS_GITHUB"y$'\n' + local asked_calls + asked_calls="$(calls)" + remove_workdir + new_workdir + setup_hosts + printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env" + run_apply $'y\n' + assert_status "run with presets" 0 "$STATUS" + assert_eq "same requests as the run that was asked" "$asked_calls" "$(calls)" + assert_contains "created" "$OUT" "This is what exists now" +} + +test_with_every_detail_set_create_now_is_still_asked_and_defaults_to_no() { + setup_hosts + printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env" + run_apply $'n\n' + assert_status "declined" 0 "$STATUS" + assert_contains "says so" "$OUT" "Nothing was created." + assert_not_contains "nothing created" "$(calls)" "POST" + setup_hosts + printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env" + run_apply $'\n' + assert_contains "empty answer means no" "$OUT" "Nothing was created." + assert_not_contains "no POST on the default" "$(calls)" "POST" + setup_hosts + printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env" + run_apply "" + assert_contains "no answer means no" "$OUT" "Nothing was created." + assert_not_contains "nothing created without an answer" "$(calls)" "POST" +} + +test_with_every_detail_set_an_existing_directory_is_not_replaced() { + setup_hosts + printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env" + mkdir -p "$WORK/my-app" + printf 'keep\n' >"$WORK/my-app/mine.txt" + run_apply $'y\n\n' + assert_file_exists "existing file kept" "$WORK/my-app/mine.txt" + assert_eq "content kept" "keep" "$(cat "$WORK/my-app/mine.txt")" +}