From 613a288dead4c19c00dee6fbb60d46bc3edf8889 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Mon, 5 Oct 2026 21:41:47 +0800 Subject: [PATCH] Fix the defects found by the live end-to-end run, add RC-017 - Close stdin for ssh, git, curl and the framework scripts, so a real ssh no longer swallows answers meant for later prompts. - Count LICENSE and LICENSE + README.md (what Gitea creates) as content the script made, so a partly created repository can be reused. - Document the observed token scopes (write:user for user-owned Gitea repositories) and the Gitea README.md in the README. - Correct criterion 2 of MIL-002 (new Proposed version row). - Record the run and the final security review as RC-017. - Tests: stdin regression, initial_only reuse, other files count as content, example config needs its address edited. Task: MIL-003#6 Task: MIL-002#1 Refs #20 Co-Authored-By: Claude Sonnet 5.5 --- README.md | 33 +++--- config.env.example | 4 +- .../mil-002-repositories-and-mirror.md | 5 +- .../sqa/reviews/rc-017-e2e-security-review.md | 106 ++++++++++++++++++ src/lib/framework.sh | 2 +- src/lib/git.sh | 5 +- src/lib/http.sh | 2 +- src/lib/preflight.sh | 16 ++- src/lib/steps.sh | 2 +- tests/lib.sh | 3 + tests/test-config.sh | 13 ++- tests/test-hosts.sh | 39 +++++++ tests/test-local.sh | 6 +- 13 files changed, 203 insertions(+), 33 deletions(-) create mode 100644 docs/sqa/reviews/rc-017-e2e-security-review.md diff --git a/README.md b/README.md index dbb47c4..23d9288 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,12 @@ and owner (a user or an organization, separately on each host), shows a plan, and only creates anything after you pass `--apply` and answer yes. > **Status.** The script is tested with stubbed host APIs and real git against -> local repositories (see [Development](#development)). A first run against -> real GitHub and Gitea repositories is still to be recorded. +> local repositories (see [Development](#development)). A first end-to-end run +> on real GitHub and Gitea repositories, with organization owners on both, has +> passed (2026-10-05, review record RC-017). Creating a repository under your +> own Gitea account needs the `write:user` token scope (see +> [Token permissions](#token-permissions)); that path has not been completed +> yet. ## Contents @@ -63,7 +67,7 @@ are accepted, and anything else stops the run with a message that names the key and the line, never the value. ```bash -cp config.env.example config.env # service addresses, not secret +cp config.env.example config.env # service addresses, not secret: set GITEA_URL (and GITEA_API_URL) cp .env.example .env # credentials: keep private chmod 600 .env # Linux and macOS ``` @@ -195,7 +199,7 @@ repositories for the chosen owner and to push to the new one. | Create a private repository | classic token with the `repo` scope | GitHub REST documentation, "Create a repository" | | Create a public repository only | classic token with `public_repo` is enough | same | | Push from the Gitea mirror | covered by `repo` | | -| Check that you belong to the organization owner | probably `read:org` | **Not confirmed**: the GitHub documentation names no scope for this call. If the script says you do not belong to an organization that you do belong to, add `read:org`. | +| Check that you belong to the organization owner | worked with a classic token that has `repo` and `admin:org` | `read:org` alone was **not tested**: the GitHub documentation names no scope for this call. If the script says you do not belong to an organization that you do belong to, add `read:org`. | - **Organization owners:** you must be an active member who is allowed to create repositories in the organization. Organizations that require SSO or @@ -211,9 +215,9 @@ repositories for the chosen owner and to push to the new one. | Need | Scope | Source | | --- | --- | --- | | Read the account the token belongs to | `read:user` | Gitea documentation | -| Create repositories, manage the push mirror | `write:repository` | Gitea documentation | -| Look up an organization and your permissions in it | `read:organization` | Gitea documentation | -| Create a repository in an organization | probably `write:organization` as well | **Not confirmed**: expected from how the Gitea API groups organization calls; to be confirmed in the first end-to-end run. | +| Create a repository **under your own account** | `write:user` | **Confirmed by a real server**: without it Gitea answers `required=[write:user]` | +| Create a repository in an organization, manage its push mirror | `write:organization` and `write:repository` | worked with a token that has both, plus `read:user`; the minimum was not narrowed down | +| Look up an organization and your permissions in it | covered by the scopes above | worked in the end-to-end run | A missing scope shows up as an HTTP 403 with the server's own message. The script stops before it creates anything when a preflight check is refused. @@ -256,7 +260,7 @@ step, `2` a usage error. | A tool, a config key or a token is missing or invalid | stops before any request | fix it and run again | | A token is refused, an owner is unknown, a name is taken, the license is missing | stops in the preflight; nothing was created | fix the cause | | The host cannot be reached | stops with the host name | try again | -| A repository already exists and is empty (Gitea: or holds only the license) | offers to reuse it (default no) | answer, or choose another name | +| A repository already exists and is empty (Gitea: or holds only the license and the README Gitea adds) | offers to reuse it (default no) | answer, or choose another name | | A repository already has content | stops | choose another name or remove it | | A step fails after another succeeded | stops and prints what exists, what failed and how to continue | fix the cause and run the **same command again with `--apply`**: what was created is offered for reuse | | The mirror is refused (disabled, interval too short) | keeps the repositories and reports it | change `MIRROR_INTERVAL` or ask the Gitea administrator, then run again | @@ -285,17 +289,20 @@ web interface and the project directory by hand. this, and revoke it if the Gitea server is ever in doubt. - **`sync_on_commit` may be ignored.** When a push mirror is created through the API, some Gitea versions ignore `sync_on_commit` (upstream issue - go-gitea/gitea#22990). The script reads the mirror back and warns if the - setting was not applied; the mirror then syncs on its interval + go-gitea/gitea#22990). On Gitea 1.27.3 it was applied: a branch pushed to + Gitea reached GitHub within seconds. The script reads the mirror back and + warns if the setting was not applied; the mirror then syncs on its interval (`MIRROR_INTERVAL`, default 10 minutes). The first sync is requested right after the mirror is created. - **The server decides the shortest interval** and whether push mirrors are allowed at all. A refused mirror stops the run with the server's message; the repositories created so far are kept. - **The license commit.** Gitea adds the license file when the repository is - created with `auto_init`. The script sends no README, so the repository - should hold only `LICENSE`; this is still to be confirmed against a real - server. + created with `auto_init`, and on the real server it also adds a generated + `README.md`. Both are mirrored to GitHub and become the first commit of the + local project. A Gitea repository that holds only these files counts as + content this script created and is offered for reuse; a repository with + anything else counts as having content and is refused. - **No rollback.** See [Error handling and recovery](#error-handling-and-recovery). - **Mirror direction is Gitea to GitHub only.** Push to Gitea; GitHub is a copy. diff --git a/config.env.example b/config.env.example index 2b8f361..2f8c03b 100644 --- a/config.env.example +++ b/config.env.example @@ -17,10 +17,10 @@ GITHUB_API_URL=https://api.github.com GITHUB_WEB_URL=https://github.com # Gitea instance base URL. Repository links are derived from it. -GITEA_URL=https://git.tirsystem.com/ +GITEA_URL=https:/// # Gitea REST API base URL. If you leave this out it is GITEA_URL + /api/v1. -GITEA_API_URL=https://git.tirsystem.com/api/v1 +GITEA_API_URL=https:///api/v1 # Optional. SSH port of the Gitea server, used for the SSH check and later # for the framework submodule. Default: 10022. diff --git a/docs/milestones/mil-002-repositories-and-mirror.md b/docs/milestones/mil-002-repositories-and-mirror.md index c5c1841..93e9fec 100644 --- a/docs/milestones/mil-002-repositories-and-mirror.md +++ b/docs/milestones/mil-002-repositories-and-mirror.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Initial version | [424f14f] | | 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Optional GitHub; choosing GitHub applies the AGPL license to the Gitea repository
Cited US-001.02
Purpose and criterion 1 reworded for optional GitHub
Target date accepted | [02875ae] | +| 2026-10-05 | Proposed | Jens Tirsvad Nielsen | S02 | Criterion 2 corrected after the live run: Gitea also adds a README.md with the license | pending | --- @@ -27,7 +27,7 @@ Decide whether the script creates the Gitea repository and, if GitHub is chosen, | # | Criterion (objectively checkable) | Go | No-Go | | --- | --- | --- | --- | | 1 | Repositories are created under the owner chosen at the prompt, for a user owner and for an organization owner, on each host used | Both verified | Any under the wrong owner | -| 2 | The GitHub repository is created empty. The Gitea repository holds only the AGPL license file when GitHub is chosen, otherwise it is empty. Neither has a generated README or `.gitignore` | Verified | Any other commit present | +| 2 | The GitHub repository is created empty. The Gitea repository holds only the AGPL license file and the `README.md` that Gitea generates for it when GitHub is chosen, otherwise it is empty. Neither has a `.gitignore` | Verified | Any other file present | | 3 | When GitHub is chosen, a commit pushed to Gitea (including the license file) appears on GitHub; nothing flows the other way. When GitHub is not chosen, no GitHub call is made | Verified | Wrong direction, no sync, or a GitHub call without the choice | | 4 | Mirror credentials are never part of a remote URL, log or output | None found | Any found | | 5 | With an invalid token on one host, the script stops before creating anything or reports exactly what was created and how to continue | Verified | Silent or misleading | @@ -76,5 +76,4 @@ Decide whether the script creates the Gitea repository and, if GitHub is chosen, [US-001]: ../user-stories.md [UC-001]: ../uc-001/uc.md [MIL-001]: ./mil-001-foundation.md -[424f14f]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/424f14f4f5577bb47fea41c8f3a655dca953e6d8 [02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a diff --git a/docs/sqa/reviews/rc-017-e2e-security-review.md b/docs/sqa/reviews/rc-017-e2e-security-review.md new file mode 100644 index 0000000..d189434 --- /dev/null +++ b/docs/sqa/reviews/rc-017-e2e-security-review.md @@ -0,0 +1,106 @@ +# SQA Review Record: End-to-end test and final security review (MIL-003) + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-017 | +| CrossReference | [MIL-003], [MIL-002], [RC-016] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-05 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: the whole flow of `src/create-project.sh` (branch `mil-003-scaffold-and-release` plus the fixes below), run against real GitHub and Gitea repositories; this is task 6 (issue #20) of [MIL-003] and the live evidence for [MIL-002]. +- Checklist used: the Go/No-Go criteria of [MIL-003] and the credential, ownership, mirror, submodule and API items named in its task 6. No QC checklist covers an end-to-end run; the code itself was reviewed in [RC-016]. +- Review date: 2026-10-05 +- Hosts: Gitea 1.27.3 at `git.tirsystem.com` (SSH on port 10022) and GitHub; real tokens from `.env` (a classic GitHub token with `repo` and `admin:org`; a Gitea token with `write:repository`, `write:organization`, `read:user` and other scopes, but not `write:user`). + +## End-to-end runs + +| Run | Owners | Result | +| --- | --- | --- | +| Dry run | `Tirsvad` on both hosts | All preflight checks passed; nothing created. | +| First `--apply` | `Tirsvad` on both hosts | Stopped before creating anything: **a defect** (see finding F1). | +| A, after the fix | user `Tirsvad` on both hosts | GitHub repository created. Gitea refused: `required=[write:user]`, which the token lacks. The script stopped, reported what existed (GitHub created, Gitea FAILED, the rest not attempted) and how to continue, and deleted nothing. | +| B | organization `TirSystem-BashScript` on both hosts, plan gate on | Everything created: both repositories, the mirror, the local project, the framework, skills, hooks, plan gate and templates. No warning. | + +After run B the following was checked independently of the script's own report: + +- **Gitea:** private, owner the organization, default branch `main`, contents `LICENSE` and `README.md`; push mirror to `https://github.com/TirSystem-BashScript/repofoundry-e2e-org.git`, interval `10m0s`, `sync_on_commit` true, `last_error` empty. +- **GitHub:** private, owner the organization, contents `LICENSE` and `README.md`, one commit `Initial commit` (arrived through the mirror). +- **Local project:** on `main` with that one commit as its whole history, tracking `origin`; `origin` is `ssh://git@git.tirsystem.com:10022/TirSystem-BashScript/repofoundry-e2e-org.git` and `github` is `https://github.com/TirSystem-BashScript/repofoundry-e2e-org.git`; the submodule `framework` comes from `ssh://git@git.tirsystem.com:10022/TirSystem/SQA-QC-Framework.git`; `core.hooksPath` is `framework/githooks` and `planGate.enabled` is true; skills are installed for both harnesses; `AGENTS.md` equals the framework template; no commit was made by the script. +- **Hooks and gate, for real:** a commit on `main` was refused ("refusing to commit directly on 'main'"); a `src/` change without a `Task:` trailer on a branch was refused ("plan-first gate"); a documentation-only commit on a branch was accepted. +- **Mirror direction, for real:** that commit was pushed to Gitea over SSH and the branch `work` appeared on GitHub within seconds, without a manual sync. + +## Checklist Results (MIL-003 Go/No-Go) + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | `git remote -v` shows `origin` (Gitea) and, when GitHub was chosen, `github`, with no credentials in any URL; with GitHub chosen the local history contains the license commit | Pass | Run B: configured addresses above, credential-free; history is the Gitea license commit. | +| 2 | `framework` is a submodule of `ssh://git@git.tirsystem.com:10022/TirSystem/SQA-QC-Framework.git` and the install scripts have run once, in the documented order | Pass | Run B: `.gitmodules` holds that address; skills, then hooks, then templates; each once. | +| 3 | An existing directory, `AGENTS.md` or `docs/artifact-registry.md` is never overwritten without a yes | Pass | Verified by the automated tests with real git (existing directory, conflicting `LICENSE`, existing templates); not repeated on the real hosts. | +| 4 | With the plan gate enabled, a commit touching `src/` or `tests/` without a `Task: MIL-NNN#N` trailer is refused | Pass | Run B, for real. | +| 5 | An existing `core.hooksPath` is reported and not replaced without consent | Pass | Verified by the automated tests with real git (local and global setting); not repeated on the real hosts. | +| 6 | README covers installation, configuration, usage examples, security decisions, error handling and stakeholders, in clear English | N-A | The sections are written; the review by S02 has not happened yet (action item). | +| 7 | End-to-end run on disposable repositories passes and the final review records no open security finding | Fail | No open security finding, and the organization-owner run passes on both hosts. The user-owner run could not be completed on Gitea (token scope). | +| 8 | All acceptance criteria of US-001.03 in [US-001] are met | Pass | Run B: remotes without credentials, framework, skills, hooks, plan gate and templates in place; the "asks first" criterion by the automated tests. | + +## Final security review + +| Item | Result | Evidence | +| --- | --- | --- | +| Credential handling | No finding | Both tokens were searched for in every file of the new project, including the whole `.git` folder, and in all output of all runs: zero hits. Remote addresses and `.gitmodules` carry no credential. Tokens went to `curl` through a private configuration file and, for an HTTPS fetch, to git through `GIT_ASKPASS` and the environment (covered by tests; the live runs used SSH). | +| Repository ownership | No finding | Created under the owner chosen at the prompt on both hosts (organization in run B; GitHub user in run A). `GITHUB_USER` was only a default. | +| Mirror direction | No finding | Gitea is the source: a branch pushed to Gitea reached GitHub on its own. Nothing was pushed from GitHub; that direction was not tested. | +| Submodule setup | No finding | Added over SSH on port 10022 from the configured framework repository; the SSH test and the host key check passed. | +| API limitations | Findings F2 to F4 | `sync_on_commit` was applied on Gitea 1.27.3 (the upstream bug did not occur). Token scopes and the README Gitea adds are covered below. | +| Residual risks | Accepted, documented | The mirror password (the GitHub token) is stored by the Gitea server. The tokens used here are broad (for example `admin:org` on GitHub); tokens limited to what the script needs would reduce the damage of a leak. | + +## Findings + +| # | Finding | Severity | Status | +| --- | --- | --- | --- | +| F1 | The real `ssh` used for the SSH test reads standard input and swallowed the answers meant for later prompts, so a run that was piped or pasted stopped before creating anything. The test stub did not read stdin, so no test could see it. | Defect (no data lost) | Fixed: stdin is closed for `ssh`, `git`, `curl` and the framework scripts; the test stub now reads stdin like the real tool; a regression test fails without the fix. | +| F2 | Gitea adds a generated `README.md` next to the `LICENSE`. The script, the README and MIL-002 criterion 2 assumed only the license. A repository this script created was therefore counted as "has content" and could not be reused after a partial failure. | Defect | Fixed: `LICENSE` and `LICENSE` + `README.md` count as content created by the script; any other file still counts as content; tests added; README corrected. MIL-002 criterion 2 corrected (new `Proposed` version row, to be accepted). | +| F3 | Creating a repository under one's own Gitea account needs the `write:user` scope, not `write:repository`. | Documentation | Fixed in the README, marked as confirmed by the server. | +| F4 | Documentation had marked two scopes "not confirmed". Result: `write:user` is needed for user-owned Gitea repositories (F3); organization-owned repositories worked with `write:organization`, `write:repository` and `read:user`, and the minimum was not narrowed down; the GitHub membership check worked with `repo` and `admin:org`, `read:org` alone was not tested. | Documentation | README updated with what was observed. | + +## Files created and external prerequisites + +The script creates, in the new project: `.git`, `.gitmodules`, `framework/` (submodule), `.agents/skills/` and `.claude/skills/`, `AGENTS.md`, `docs/artifact-registry.md`, and (through the Gitea history) `LICENSE` and `README.md`. It never deletes anything. + +External prerequisites: bash 4.4 or later, `git`, `curl`, `mktemp`; optional `jq` and `ssh`. An SSH key in the Gitea account and a known host key for `git.tirsystem.com` port 10022 (the script refuses unknown host keys). A GitHub token that can create repositories and push (classic `repo`), only when GitHub is chosen. A Gitea token with `write:repository`, `write:organization` and `read:user`, plus `write:user` for a repository under one's own account. Push mirrors must be enabled on the Gitea server. + +## Disposable resources left in place (nothing was deleted) + +- Gitea: `TirSystem-BashScript/repofoundry-e2e-org` (private; branches `main` and `work`). +- GitHub: `TirSystem-BashScript/repofoundry-e2e-org` (private; branches `main` and `work`). +- GitHub: `Tirsvad/repofoundry-e2e-user` (private, empty; created by run A). +- A local temporary directory with the run output and the new project. + +## Overall Verdict + +Go-with-conditions — No open security finding, the organization-owner flow works end to end on both hosts, and the two defects the live run found are fixed with regression tests. Criterion 6 awaits the README review, and criterion 7 is not complete because the user-owner run could not finish on Gitea without `write:user`. Author and reviewer are the same person for now (S01 and S02 are both held by the Maintainer), so the framework independence rule is not met; re-review when a second person takes S02. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| Create a Gitea token that also has `write:user` and repeat run A (the empty GitHub repository `Tirsvad/repofoundry-e2e-user` is offered for reuse) to complete criterion 7 | S01 | 2026-10-16 | +| Review the README against criterion 6 | S02 | 2026-10-12 | +| Accept the corrected criterion 2 of [MIL-002] (version row `Proposed`) | S02 | 2026-10-12 | +| Delete the disposable repositories listed above in the web interfaces | S01 | 2026-10-12 | +| Run `tests/run-tests.sh` on Linux and macOS (carried over from [RC-016]) | S02 | 2026-10-30 | +| Consider tokens limited to what the script needs, for the Gitea and GitHub accounts used with it | S02 | 2026-10-30 | + +--- + +[MIL-003]: ../../milestones/mil-003-scaffold-and-release.md +[MIL-002]: ../../milestones/mil-002-repositories-and-mirror.md +[RC-016]: ./rc-016-create-project-sh.md +[US-001]: ../../user-stories.md diff --git a/src/lib/framework.sh b/src/lib/framework.sh index ab1098c..6005b4b 100644 --- a/src/lib/framework.sh +++ b/src/lib/framework.sh @@ -47,7 +47,7 @@ run_framework_script() { abs="$(cd "$dir" && pwd)" make_temp_file out="$REPLY" - if ! (cd "$abs" && env PROJECT_ROOT="$abs" bash "framework/scripts/$script" "$@") >"$out" 2>&1; then + if ! (cd "$abs" && env PROJECT_ROOT="$abs" bash "framework/scripts/$script" "$@" "$out" 2>&1; then die "the framework script $script failed: $(tail -n 3 "$out" | tr '\n' ' ')" fi } diff --git a/src/lib/git.sh b/src/lib/git.sh index e158fd9..e952674 100644 --- a/src/lib/git.sh +++ b/src/lib/git.sh @@ -6,13 +6,14 @@ # # Provides: git_project, fetch_origin -# git_project DIR ARGS...: run git in DIR. Prompts are switched off, so a +# git_project DIR ARGS...: run git in DIR. Prompts are switched off and stdin +# is closed (git must not eat the answers meant for later prompts), so a # missing credential or SSH key fails at once instead of waiting for input. git_project() { local dir="$1" shift GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh} -o BatchMode=yes" \ - git -C "$dir" "$@" + git -C "$dir" "$@" /dev/null)" || curl_status=$? + --config "$config_file" "${data_args[@]}" /dev/null)" || curl_status=$? # The configuration file holds the token and the body may hold another one # (the mirror password): remove both now instead of at exit. rm -f -- "$config_file" ${body_file:+"$body_file"} diff --git a/src/lib/preflight.sh b/src/lib/preflight.sh index 5d6e751..7aea73f 100644 --- a/src/lib/preflight.sh +++ b/src/lib/preflight.sh @@ -28,7 +28,8 @@ check_gitea_license() { } # inspect_repository HOST: record in STATE[HOST_repo] whether the repository -# is free (does not exist), empty, license_only or not_empty. +# is free (does not exist), empty, initial_only (just the LICENSE and the +# README.md Gitea adds) or not_empty. inspect_repository() { local host="$1" owner name names owner="$(repo_owner "$host")" @@ -45,10 +46,13 @@ inspect_repository() { return 0 fi expect_status "cannot read the contents of the $(host_label "$host") repository $owner/$name" 200 - names="$(json_values "$HTTP_BODY_FILE" name)" - case "$names" in + # Gitea adds a README.md of its own next to the LICENSE when it creates a + # repository with a license (seen on a real server), so both count as the + # content this script creates. + names="$(json_values "$HTTP_BODY_FILE" name | sort | tr '\n' ' ')" + case "${names% }" in "") STATE[${host}_repo]="empty" ;; - LICENSE) STATE[${host}_repo]="license_only" ;; + "LICENSE" | "LICENSE README.md") STATE[${host}_repo]="initial_only" ;; *) STATE[${host}_repo]="not_empty" ;; esac } @@ -117,7 +121,7 @@ test_gitea_ssh() { return 0 fi output="$(ssh -p "$port" -o BatchMode=yes -o ConnectTimeout=5 \ - -o StrictHostKeyChecking=yes -T "git@$host" 2>&1)" || status=$? + -o StrictHostKeyChecking=yes -T "git@$host" &1)" || status=$? if ((status == 0)) || [[ $output == *"successfully authenticated"* ]]; then STATE[is_ssh_ok]=1 STATE[ssh_note]="passed" @@ -138,7 +142,7 @@ decide_existing_repositories() { case "$state" in free) ;; empty) STATE[reuse_$host]=1 ;; - license_only) + initial_only) if [[ $host == gitea ]] && ((PROJECT[has_github])); then STATE[reuse_$host]=1 else diff --git a/src/lib/steps.sh b/src/lib/steps.sh index 351eadb..1b241d6 100644 --- a/src/lib/steps.sh +++ b/src/lib/steps.sh @@ -45,7 +45,7 @@ report_outcome() { say "The project is in ${PROJECT[directory]}. Nothing was committed there: review it, then work on a branch." else say "To continue: fix the problem named above and run the same command again with --apply." - say "A repository this run created is still empty (or holds only the license), so the next run offers to reuse it." + say "A repository this run created is still empty (or holds only the license and the README Gitea adds), so the next run offers to reuse it." say "A directory, remotes and submodule created so far are used again by the next run; you are asked before an existing directory or file is touched." say "Nothing is deleted automatically. To start over, delete the repositories above in the web interface and the project directory by hand." fi diff --git a/tests/lib.sh b/tests/lib.sh index 05252c4..1d6408d 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -244,6 +244,9 @@ STUB write_ssh_stub() { cat >"$WORK/bin/ssh" </dev/null printf '%s\n' "\$@" >>"\$STUB_DIR/ssh.args" if [[ ${1:-0} == 0 ]]; then echo "Hi there, gitea-user! You've successfully authenticated, but Gitea does not provide shell access." diff --git a/tests/test-config.sh b/tests/test-config.sh index 69ddd34..fded59d 100644 --- a/tests/test-config.sh +++ b/tests/test-config.sh @@ -158,10 +158,21 @@ test_example_files_hold_placeholders_only() { fail ".env.example has a value for ${line%%=*}; it must be empty" fi done < <(grep -E '^[A-Z_]+=' "$REPO_ROOT/.env.example") + # The example holds a placeholder for the Gitea address, so it must be + # edited before use: as it is, it is refused with a clear message... run_lib "" "parse_env_file \"$REPO_ROOT/config.env.example\" CONFIG_KEYS CONFIG validate_config echo parsed" - assert_contains "config.env.example is valid" "$OUT" "parsed" + assert_status "placeholder address is refused" 1 "$STATUS" + assert_contains "names the key" "$ERR" "GITEA_URL" + # ...and with a real address in its place the rest of the file is valid. + sed -e 's|^GITEA_URL=.*|GITEA_URL=https://git.example.test/|' \ + -e 's|^GITEA_API_URL=.*|GITEA_API_URL=https://git.example.test/api/v1|' \ + "$REPO_ROOT/config.env.example" >"$WORK/example.env" + run_lib "" "parse_env_file \"$WORK/example.env\" CONFIG_KEYS CONFIG +validate_config +echo parsed" + assert_contains "config.env.example is valid once the address is set" "$OUT" "parsed" run_lib "" "parse_env_file \"$REPO_ROOT/.env.example\" CREDENTIAL_KEYS CREDENTIALS echo parsed" assert_contains ".env.example parses" "$OUT" "parsed" diff --git a/tests/test-hosts.sh b/tests/test-hosts.sh index 66f0808..c6e709f 100644 --- a/tests/test-hosts.sh +++ b/tests/test-hosts.sh @@ -476,3 +476,42 @@ mirror_field '$WORK/m.json' https://github.com/o/b.git sync_on_commit" assert_eq "the right mirror is chosen with jq" "true" "$OUT" fi } + +# ------------------------------------------------- found by the live e2e run + +test_a_repository_this_script_created_earlier_can_be_reused() { + # Seen on a real Gitea: creating a repository with a license also adds a + # README.md. After a failed mirror step the next run must still reuse it. + setup_hosts + prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}' + prepend_route 'GET|/api/v1/repos/TirSystem/my-app/contents|200|[{"name":"README.md","type":"file"},{"name":"LICENSE","type":"file"}]' + run_apply "$ANSWERS_GITHUB"$'y\ny\n' + assert_status "LICENSE and README.md" 0 "$STATUS" + assert_not_contains "not created again" "$(calls)" "$GITEA_REPO_CALL" + assert_contains "reported" "$OUT" "Gitea repository : reused" +} + +test_other_files_still_count_as_content() { + local listing + for listing in '[{"name":"README.md","type":"file"}]' \ + '[{"name":"LICENSE","type":"file"},{"name":"README.md","type":"file"},{"name":"main.c","type":"file"}]' \ + '[{"name":"LICENSE","type":"file"},{"name":"src","type":"dir"}]'; do + setup_hosts + prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}' + prepend_route "GET|/api/v1/repos/TirSystem/my-app/contents|200|$listing" + run_dry "$ANSWERS_GITHUB" + assert_status "content: $listing" 1 "$STATUS" + assert_contains "refused" "$ERR" "already exists and has content" + done +} + +test_children_never_eat_the_answers_meant_for_later_prompts() { + # The real ssh reads standard input until it ends; the stub does too. The + # script closes stdin for ssh, git, curl and the framework scripts, so the + # answers that follow the SSH test still reach the later prompts. + setup_hosts + run_apply "$ANSWERS_GITHUB"$'y\n' + assert_status "the final question is still answered" 0 "$STATUS" + assert_contains "created" "$OUT" "Done. This is what exists now:" + assert_not_contains "no lost input" "$ERR" "no input available" +} diff --git a/tests/test-local.sh b/tests/test-local.sh index 2f36833..8e7e77f 100644 --- a/tests/test-local.sh +++ b/tests/test-local.sh @@ -217,13 +217,13 @@ test_an_existing_hooks_path_is_not_replaced_without_a_yes() { test_a_global_hooks_path_is_reported_not_changed() { setup_hosts - git config --file "$WORK/gitconfig" core.hooksPath /somewhere/global-hooks + 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 '/somewhere/global-hooks'" - assert_eq "the global setting is untouched" "/somewhere/global-hooks" "$(git config --file "$WORK/gitconfig" --get core.hooksPath)" + 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)" }