Add the RepoFoundry foundation: config parsing, HTTP helper, prompts, tests
create-project.sh validates config.env and .env (parsed, never sourced), checks the required tools, asks for the project details and prints a summary. It makes no network call and no change on disk yet. - config.env.example and .env.example hold placeholders only - tokens go through a private curl config file, never the command line - tests run in private directories with a stub curl; shellcheck and shfmt are part of tests/run-tests.sh Task: MIL-001#1 Task: MIL-001#2 Task: MIL-001#3 Task: MIL-001#4 Task: MIL-001#5 Task: MIL-001#6 Closes #3 Closes #4 Closes #5 Closes #6 Closes #7 Closes #8 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-http.sh - tests for the HTTP helper, the tool check and the JSON
|
||||
# helpers (MIL-001 task: tool check and HTTP helper). A stub curl stands in
|
||||
# for the network. Sourced by run-tests.sh.
|
||||
|
||||
# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose
|
||||
test_http_request_keeps_token_off_the_command_line() {
|
||||
write_curl_stub
|
||||
run_lib "" "setup_temp_dir
|
||||
http_request GET https://git.example.test/api/v1/user token '$FAKE_GITEA_TOKEN'
|
||||
echo \"status=\$HTTP_STATUS\"
|
||||
cleanup"
|
||||
assert_status "request succeeds" 0 "$STATUS"
|
||||
assert_contains "status captured" "$OUT" "status=200"
|
||||
assert_file_exists "stub saw the call" "$WORK/curl.args"
|
||||
assert_not_contains "token not in arguments" "$(cat "$WORK/curl.args")" "$FAKE_GITEA_TOKEN"
|
||||
assert_contains "token in private config" "$(cat "$WORK/curl.config")" "Authorization: token $FAKE_GITEA_TOKEN"
|
||||
assert_contains "redirects are not followed" "$(cat "$WORK/curl.args")" "--silent"
|
||||
assert_not_contains "no redirect flag" "$(cat "$WORK/curl.args")" "--location"
|
||||
assert_not_contains "no -L flag" "$(cat "$WORK/curl.args")" $'\n-L\n'
|
||||
assert_not_contains "token not printed" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
|
||||
}
|
||||
|
||||
test_http_request_cleans_up_its_files() {
|
||||
write_curl_stub
|
||||
run_lib "" "setup_temp_dir
|
||||
http_request GET https://git.example.test/api/v1/user token '$FAKE_GITEA_TOKEN'
|
||||
cleanup"
|
||||
assert_eq "no temp files left" "" "$(find "$WORK/tmp" -mindepth 1 2>/dev/null)"
|
||||
}
|
||||
|
||||
test_http_request_bearer_scheme_and_body() {
|
||||
write_curl_stub
|
||||
run_lib "" "setup_temp_dir
|
||||
http_request POST https://api.github.com/user/repos bearer '$FAKE_GITHUB_PAT' '{\"name\":\"x\"}'
|
||||
cleanup"
|
||||
assert_status "POST succeeds" 0 "$STATUS"
|
||||
assert_contains "bearer header" "$(cat "$WORK/curl.config")" "Authorization: Bearer $FAKE_GITHUB_PAT"
|
||||
assert_contains "content type" "$(cat "$WORK/curl.config")" "Content-Type: application/json"
|
||||
assert_contains "body sent from a file" "$(cat "$WORK/curl.args")" "--data-binary"
|
||||
assert_not_contains "token not in arguments" "$(cat "$WORK/curl.args")" "$FAKE_GITHUB_PAT"
|
||||
}
|
||||
|
||||
test_http_status_messages() {
|
||||
local code expected
|
||||
while IFS='|' read -r code expected; do
|
||||
run_lib "" "describe_http_status $code"
|
||||
assert_contains "HTTP $code" "$OUT" "$expected"
|
||||
done <<'EOF'
|
||||
401|authentication failed
|
||||
403|scopes
|
||||
404|not found
|
||||
422|already exist
|
||||
429|rate limited
|
||||
503|server reported an error
|
||||
418|unexpected HTTP status 418
|
||||
EOF
|
||||
}
|
||||
|
||||
test_http_network_failure() {
|
||||
write_curl_stub
|
||||
run_lib "" "setup_temp_dir
|
||||
STUB_CURL_EXIT=7 http_request GET https://git.example.test/api/v1/user token '$FAKE_GITEA_TOKEN' || echo \"failed: \$HTTP_ERROR\"
|
||||
cleanup"
|
||||
assert_contains "network error reported" "$OUT" "failed: could not reach git.example.test: could not connect"
|
||||
assert_not_contains "token not in the error" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
|
||||
}
|
||||
|
||||
test_http_request_refuses_unsafe_input() {
|
||||
local case_name args expected
|
||||
write_curl_stub
|
||||
while IFS='|' read -r case_name args expected; do
|
||||
run_lib "" "setup_temp_dir
|
||||
http_request $args
|
||||
cleanup"
|
||||
assert_status "$case_name" 1 "$STATUS"
|
||||
assert_contains "$case_name message" "$ERR" "$expected"
|
||||
done <<'EOF'
|
||||
http URL|GET http://git.example.test/x token abcdefgh12345|invalid or non-https URL
|
||||
user info URL|GET https://u:p@git.example.test/x token abcdefgh12345|invalid or non-https URL
|
||||
bad method|TRACE https://git.example.test/x token abcdefgh12345|unsupported HTTP method
|
||||
bad token|GET https://git.example.test/x token 'bad token'|malformed token
|
||||
bad scheme|GET https://git.example.test/x basic abcdefgh12345|unknown authentication scheme
|
||||
EOF
|
||||
assert_file_missing "curl never called" "$WORK/curl.args"
|
||||
}
|
||||
|
||||
test_check_tools_stops_before_any_change() {
|
||||
# An empty PATH: no git, curl or mktemp, so the check must fail first and
|
||||
# the script must not create anything, not even a temporary directory.
|
||||
write_fixtures
|
||||
mkdir -p "$WORK/emptybin"
|
||||
STATUS=0
|
||||
PATH="$WORK/emptybin" TMPDIR="$WORK/tmp" "$BASH" "$SCRIPT" \
|
||||
--config "$WORK/config.env" --env "$WORK/.env" </dev/null \
|
||||
>"$WORK/out.txt" 2>"$WORK/err.txt" || STATUS=$?
|
||||
assert_status "missing tools" 1 "$STATUS"
|
||||
assert_contains "names the tools" "$(cat "$WORK/err.txt")" "required tool(s) not found: git curl mktemp"
|
||||
assert_eq "nothing created" "" "$(find "$WORK/tmp" -mindepth 1 2>/dev/null)"
|
||||
}
|
||||
|
||||
test_missing_jq_is_only_a_warning() {
|
||||
run_lib "" "command() { if [[ \$1 == -v && \$2 == jq ]]; then return 1; fi; builtin command \"\$@\"; }
|
||||
check_tools
|
||||
echo \"HAS_JQ=\$HAS_JQ\""
|
||||
assert_status "jq optional" 0 "$STATUS"
|
||||
assert_contains "jq flag cleared" "$OUT" "HAS_JQ=0"
|
||||
assert_contains "warning shown" "$ERR" "jq not found"
|
||||
}
|
||||
|
||||
test_json_escape() {
|
||||
run_lib "" 'json_escape "say \"hi\" \\ back"; echo; json_escape $'"'"'a\nb\tc'"'"'; echo'
|
||||
assert_eq "escaped" $'say \\"hi\\" \\\\ back\na\\nb\\tc' "$OUT"
|
||||
}
|
||||
|
||||
test_json_get_with_and_without_jq() {
|
||||
local mode
|
||||
printf '{"id": 42, "name": "RepoFoundry", "private": false, "other": {"name": "x"}}\n' >"$WORK/r.json"
|
||||
for mode in 0 1; do
|
||||
if ((mode)) && ! command -v jq >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
run_lib "" "HAS_JQ=$mode
|
||||
json_get '$WORK/r.json' id
|
||||
json_get '$WORK/r.json' name
|
||||
json_get '$WORK/r.json' private
|
||||
json_get '$WORK/r.json' missing"
|
||||
assert_eq "json_get with HAS_JQ=$mode" $'42\nRepoFoundry\nfalse' "$OUT"
|
||||
done
|
||||
}
|
||||
Reference in New Issue
Block a user