diff --git a/tests/cli/_lib/run.sh b/tests/cli/_lib/run.sh new file mode 100755 index 0000000..9678796 --- /dev/null +++ b/tests/cli/_lib/run.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +run_capture_stdout() { + local expected_status="$1" + local output_file="$2" + shift 2 + + set +e + "$@" >"$output_file" + local status=$? + set -e + + if [[ "$status" -ne "$expected_status" ]]; then + printf 'expected exit %s, got %s\n' "$expected_status" "$status" >&2 + if [[ -s "$output_file" ]]; then + printf 'stdout:\n' >&2 + cat "$output_file" >&2 + fi + return 1 + fi +} + +run_capture_all() { + local expected_status="$1" + local output_file="$2" + shift 2 + + set +e + "$@" >"$output_file" 2>&1 + local status=$? + set -e + + if [[ "$status" -ne "$expected_status" ]]; then + printf 'expected exit %s, got %s\n' "$expected_status" "$status" >&2 + if [[ -s "$output_file" ]]; then + printf 'output:\n' >&2 + cat "$output_file" >&2 + fi + return 1 + fi +} + +assert_jq() { + local expression="$1" + local input_file="$2" + jq -e "$expression" "$input_file" >/dev/null +} + +assert_contains() { + local needle="$1" + local input_file="$2" + grep -Fq "$needle" "$input_file" +} diff --git a/tests/cli/lint/catalog/list/Taskfile.yaml b/tests/cli/lint/catalog/list/Taskfile.yaml new file mode 100644 index 0000000..89464fb --- /dev/null +++ b/tests/cli/lint/catalog/list/Taskfile.yaml @@ -0,0 +1,18 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core-lint ../../../../../cmd/core-lint + + lang="$(cat fixtures/lang.txt)" + output="$(mktemp)" + run_capture_all 0 "$output" ./bin/core-lint lint catalog list --lang "$lang" + grep -Fq "go-sec-001" "$output" + grep -Fq "rule(s)" "$output" + EOF diff --git a/tests/cli/lint/catalog/list/fixtures/lang.txt b/tests/cli/lint/catalog/list/fixtures/lang.txt new file mode 100644 index 0000000..4023f20 --- /dev/null +++ b/tests/cli/lint/catalog/list/fixtures/lang.txt @@ -0,0 +1 @@ +go diff --git a/tests/cli/lint/catalog/show/Taskfile.yaml b/tests/cli/lint/catalog/show/Taskfile.yaml new file mode 100644 index 0000000..72e27f1 --- /dev/null +++ b/tests/cli/lint/catalog/show/Taskfile.yaml @@ -0,0 +1,18 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core-lint ../../../../../cmd/core-lint + + rule_id="$(cat fixtures/rule-id.txt)" + output="$(mktemp)" + run_capture_stdout 0 "$output" ./bin/core-lint lint catalog show "$rule_id" + jq -e '.id == "go-sec-001" and .severity == "high" and (.languages | index("go") != null)' "$output" >/dev/null + jq -e '.title == "SQL wildcard injection in LIKE clauses"' "$output" >/dev/null + EOF diff --git a/tests/cli/lint/catalog/show/fixtures/rule-id.txt b/tests/cli/lint/catalog/show/fixtures/rule-id.txt new file mode 100644 index 0000000..0dea602 --- /dev/null +++ b/tests/cli/lint/catalog/show/fixtures/rule-id.txt @@ -0,0 +1 @@ +go-sec-001 diff --git a/tests/cli/lint/check/Taskfile.yaml b/tests/cli/lint/check/Taskfile.yaml new file mode 100644 index 0000000..19e1ffb --- /dev/null +++ b/tests/cli/lint/check/Taskfile.yaml @@ -0,0 +1,17 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core-lint ../../../../cmd/core-lint + + output="$(mktemp)" + run_capture_stdout 0 "$output" ./bin/core-lint lint check --format=json fixtures + jq -e 'length == 1 and .[0].rule_id == "go-cor-003" and .[0].file == "input.go"' "$output" >/dev/null + jq -e '.[0].severity == "medium" and .[0].fix != ""' "$output" >/dev/null + EOF diff --git a/tests/cli/lint/check/fixtures/input.go b/tests/cli/lint/check/fixtures/input.go new file mode 100644 index 0000000..9c9883d --- /dev/null +++ b/tests/cli/lint/check/fixtures/input.go @@ -0,0 +1,12 @@ +//go:build ignore + +package sample + +type service struct{} + +func (service) Process(string) error { return nil } + +func Run() { + svc := service{} + _ = svc.Process("data") +} diff --git a/tests/cli/qa/_harness/main.go b/tests/cli/qa/_harness/main.go new file mode 100644 index 0000000..e19eb9f --- /dev/null +++ b/tests/cli/qa/_harness/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "forge.lthn.ai/core/cli/pkg/cli" + _ "forge.lthn.ai/core/lint/cmd/qa" +) + +func main() { + cli.WithAppName("core") + cli.Main() +} diff --git a/tests/cli/qa/audit/Taskfile.yaml b/tests/cli/qa/audit/Taskfile.yaml new file mode 100644 index 0000000..bd4c8f6 --- /dev/null +++ b/tests/cli/qa/audit/Taskfile.yaml @@ -0,0 +1,20 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core ../_harness + + cd fixtures/project + output="$(mktemp)" + export PATH="$(pwd)/../bin:$PATH" + run_capture_stdout 1 "$output" ../../bin/core qa audit --json + jq -e '.results[0].tool == "composer" and .results[0].vulnerabilities == 1' "$output" >/dev/null + jq -e '.has_vulnerabilities == true and .vulnerabilities == 1' "$output" >/dev/null + jq -e '.results[0].advisories[0].Package == "vendor/package-a"' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/audit/fixtures/bin/composer b/tests/cli/qa/audit/fixtures/bin/composer new file mode 100755 index 0000000..1e8ffb9 --- /dev/null +++ b/tests/cli/qa/audit/fixtures/bin/composer @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +cat <<'JSON' +{ + "advisories": { + "vendor/package-a": [ + { + "title": "Remote Code Execution", + "link": "https://example.com/advisory/1", + "cve": "CVE-2026-0001", + "affectedVersions": ">=1.0,<1.5" + } + ] + } +} +JSON +exit 1 diff --git a/tests/cli/qa/audit/fixtures/project/composer.json b/tests/cli/qa/audit/fixtures/project/composer.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/audit/fixtures/project/composer.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/audit/fixtures/project/src/Bad.php b/tests/cli/qa/audit/fixtures/project/src/Bad.php new file mode 100644 index 0000000..15b5f13 --- /dev/null +++ b/tests/cli/qa/audit/fixtures/project/src/Bad.php @@ -0,0 +1,5 @@ +/dev/null + jq -e '(.missing | length == 1) and (.missing[0].name == "Beta")' "$output" >/dev/null + jq -e '(.warnings | length == 1) and (.warnings[0].path == "fixtures/src")' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/docblock/fixtures/src/a.go b/tests/cli/qa/docblock/fixtures/src/a.go new file mode 100644 index 0000000..af5e783 --- /dev/null +++ b/tests/cli/qa/docblock/fixtures/src/a.go @@ -0,0 +1,6 @@ +//go:build ignore + +package sample + +// Alpha demonstrates a documented exported function. +func Alpha() {} diff --git a/tests/cli/qa/docblock/fixtures/src/b.go b/tests/cli/qa/docblock/fixtures/src/b.go new file mode 100644 index 0000000..d8e7178 --- /dev/null +++ b/tests/cli/qa/docblock/fixtures/src/b.go @@ -0,0 +1,5 @@ +//go:build ignore + +package sample + +func Beta() {} diff --git a/tests/cli/qa/docblock/fixtures/src/broken.go b/tests/cli/qa/docblock/fixtures/src/broken.go new file mode 100644 index 0000000..d0d991b --- /dev/null +++ b/tests/cli/qa/docblock/fixtures/src/broken.go @@ -0,0 +1,5 @@ +//go:build ignore + +package sample + +func Broken( diff --git a/tests/cli/qa/fmt/Taskfile.yaml b/tests/cli/qa/fmt/Taskfile.yaml new file mode 100644 index 0000000..9e1c7f7 --- /dev/null +++ b/tests/cli/qa/fmt/Taskfile.yaml @@ -0,0 +1,18 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core ../_harness + + cd fixtures/project + output="$(mktemp)" + export PATH="../bin:$PATH" + run_capture_stdout 0 "$output" ../../bin/core qa fmt --json + jq -e '.tool == "pint" and .changed == true and .files[0].path == "src/Bad.php"' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/fmt/fixtures/project/composer.json b/tests/cli/qa/fmt/fixtures/project/composer.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/fmt/fixtures/project/composer.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/fmt/fixtures/project/src/Bad.php b/tests/cli/qa/fmt/fixtures/project/src/Bad.php new file mode 100644 index 0000000..15b5f13 --- /dev/null +++ b/tests/cli/qa/fmt/fixtures/project/src/Bad.php @@ -0,0 +1,5 @@ +/dev/null + jq -e '.summary.passing == 1 and .summary.errors == 1' "$output" >/dev/null + jq -e '.repos[0].status == "error" and .repos[0].name == "beta"' "$output" >/dev/null + jq -e '.repos[1].status == "passing" and .repos[1].name == "alpha"' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/health/fixtures/bin/gh b/tests/cli/qa/health/fixtures/bin/gh new file mode 100755 index 0000000..9c46786 --- /dev/null +++ b/tests/cli/qa/health/fixtures/bin/gh @@ -0,0 +1,26 @@ +#!/usr/bin/env sh + +case "$*" in + *"--repo forge/alpha"*) + cat <<'JSON' +[ + { + "status": "completed", + "conclusion": "success", + "name": "CI", + "headSha": "abc123", + "updatedAt": "2026-03-30T00:00:00Z", + "url": "https://example.com/alpha/run/1" + } +] +JSON + ;; + *"--repo forge/beta"*) + printf '%s\n' 'simulated workflow lookup failure' >&2 + exit 1 + ;; + *) + printf '%s\n' "unexpected gh invocation: $*" >&2 + exit 1 + ;; +esac diff --git a/tests/cli/qa/health/fixtures/repos.yaml b/tests/cli/qa/health/fixtures/repos.yaml new file mode 100644 index 0000000..d783c64 --- /dev/null +++ b/tests/cli/qa/health/fixtures/repos.yaml @@ -0,0 +1,8 @@ +version: 1 +org: forge +base_path: . +repos: + alpha: + type: module + beta: + type: module diff --git a/tests/cli/qa/infection/Taskfile.yaml b/tests/cli/qa/infection/Taskfile.yaml new file mode 100644 index 0000000..1d53c25 --- /dev/null +++ b/tests/cli/qa/infection/Taskfile.yaml @@ -0,0 +1,22 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core ../_harness + + cd fixtures/project + output="$(mktemp)" + run_capture_all 1 "$output" ../../bin/core qa infection --min-msi 80 --min-covered-msi 90 --threads 8 --filter src --only-covered + grep -Fq "Mutation Testing" "$output" + grep -Fq -- "--min-msi=80" "$output" + grep -Fq -- "--min-covered-msi=90" "$output" + grep -Fq -- "--threads=8" "$output" + grep -Fq -- "--filter=src" "$output" + grep -Fq -- "--only-covered" "$output" + EOF diff --git a/tests/cli/qa/infection/fixtures/project/composer.json b/tests/cli/qa/infection/fixtures/project/composer.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/infection/fixtures/project/composer.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/infection/fixtures/project/infection.json b/tests/cli/qa/infection/fixtures/project/infection.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/infection/fixtures/project/infection.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/infection/fixtures/project/src/Bad.php b/tests/cli/qa/infection/fixtures/project/src/Bad.php new file mode 100644 index 0000000..15b5f13 --- /dev/null +++ b/tests/cli/qa/infection/fixtures/project/src/Bad.php @@ -0,0 +1,5 @@ +/dev/null + jq -e '.categories[0].category == "needs_response" and .categories[0].issues[0].repo_name == "alpha"' "$output" >/dev/null + jq -e '.categories[0].issues[0].action_hint != ""' "$output" >/dev/null + jq -e '.fetch_errors[0].repo == "beta"' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/issues/fixtures/bin/gh b/tests/cli/qa/issues/fixtures/bin/gh new file mode 100755 index 0000000..a8d3ba3 --- /dev/null +++ b/tests/cli/qa/issues/fixtures/bin/gh @@ -0,0 +1,42 @@ +#!/usr/bin/env sh + +case "$*" in + *"api user"*) + printf '%s\n' 'alice' + ;; + *"issue list --repo forge/alpha"*) + cat <<'JSON' +[ + { + "number": 7, + "title": "Clarify agent output", + "state": "OPEN", + "body": "Explain behaviour", + "createdAt": "2026-03-30T00:00:00Z", + "updatedAt": "2026-03-30T11:00:00Z", + "author": {"login": "bob"}, + "assignees": {"nodes": [{"login": "alice"}]}, + "labels": {"nodes": [{"name": "agent:ready"}]}, + "comments": { + "totalCount": 1, + "nodes": [ + { + "author": {"login": "carol"}, + "createdAt": "2026-03-30T10:30:00Z" + } + ] + }, + "url": "https://example.com/issues/7" + } +] +JSON + ;; + *"issue list --repo forge/beta"*) + printf '%s\n' 'simulated issue query failure' >&2 + exit 1 + ;; + *) + printf '%s\n' "unexpected gh invocation: $*" >&2 + exit 1 + ;; +esac diff --git a/tests/cli/qa/issues/fixtures/repos.yaml b/tests/cli/qa/issues/fixtures/repos.yaml new file mode 100644 index 0000000..d783c64 --- /dev/null +++ b/tests/cli/qa/issues/fixtures/repos.yaml @@ -0,0 +1,8 @@ +version: 1 +org: forge +base_path: . +repos: + alpha: + type: module + beta: + type: module diff --git a/tests/cli/qa/psalm/Taskfile.yaml b/tests/cli/qa/psalm/Taskfile.yaml new file mode 100644 index 0000000..c7d0592 --- /dev/null +++ b/tests/cli/qa/psalm/Taskfile.yaml @@ -0,0 +1,17 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core ../_harness + + cd fixtures/project + output="$(mktemp)" + run_capture_stdout 1 "$output" ../../bin/core qa psalm --json + jq -e '.tool == "psalm" and .issues[0].file == "src/Bad.php" and .issues[0].line == 3' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/psalm/fixtures/project/composer.json b/tests/cli/qa/psalm/fixtures/project/composer.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/psalm/fixtures/project/composer.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/psalm/fixtures/project/psalm.xml b/tests/cli/qa/psalm/fixtures/project/psalm.xml new file mode 100644 index 0000000..f576a91 --- /dev/null +++ b/tests/cli/qa/psalm/fixtures/project/psalm.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/cli/qa/psalm/fixtures/project/src/Bad.php b/tests/cli/qa/psalm/fixtures/project/src/Bad.php new file mode 100644 index 0000000..33bde41 --- /dev/null +++ b/tests/cli/qa/psalm/fixtures/project/src/Bad.php @@ -0,0 +1,5 @@ +/dev/null + jq -e '.mine | length == 0 and .requested | length == 1' "$output" >/dev/null + jq -e '.requested[0].number == 42 and .requested[0].title == "Refine agent output"' "$output" >/dev/null + jq -e '.fetch_errors[0].repo == "forge/example" and .fetch_errors[0].scope == "mine"' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/review/fixtures/bin/gh b/tests/cli/qa/review/fixtures/bin/gh new file mode 100755 index 0000000..5fd840d --- /dev/null +++ b/tests/cli/qa/review/fixtures/bin/gh @@ -0,0 +1,37 @@ +#!/usr/bin/env sh + +case "$*" in + *"pr list --state open --search author:@me --json"*) + printf '%s\n' 'simulated author query failure' >&2 + exit 1 + ;; + *"pr list --state open --search review-requested:@me --json"*) + cat <<'JSON' +[ + { + "number": 42, + "title": "Refine agent output", + "author": {"login": "alice"}, + "state": "OPEN", + "isDraft": false, + "mergeable": "MERGEABLE", + "reviewDecision": "", + "url": "https://example.com/pull/42", + "headRefName": "feature/agent-output", + "createdAt": "2026-03-30T00:00:00Z", + "updatedAt": "2026-03-30T00:00:00Z", + "additions": 12, + "deletions": 3, + "changedFiles": 2, + "statusCheckRollup": {"contexts": []}, + "reviewRequests": {"nodes": []}, + "reviews": [] + } +] +JSON + ;; + *) + printf '%s\n' "unexpected gh invocation: $*" >&2 + exit 1 + ;; +esac diff --git a/tests/cli/qa/security/Taskfile.yaml b/tests/cli/qa/security/Taskfile.yaml new file mode 100644 index 0000000..8467a6b --- /dev/null +++ b/tests/cli/qa/security/Taskfile.yaml @@ -0,0 +1,21 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core ../_harness + + cd fixtures/project + output="$(mktemp)" + export PATH="$(pwd)/../bin:$PATH" + run_capture_stdout 1 "$output" ../../bin/core qa security --json + jq -e '.summary.total == 4 and .summary.passed == 0' "$output" >/dev/null + jq -e '.summary.critical == 3 and .summary.high == 1' "$output" >/dev/null + jq -e '.checks[0].id == "app_key_set" and .checks[1].id == "composer_audit"' "$output" >/dev/null + jq -e '.checks[] | select(.id == "debug_mode") | .passed == false' "$output" >/dev/null + EOF diff --git a/tests/cli/qa/security/fixtures/bin/composer b/tests/cli/qa/security/fixtures/bin/composer new file mode 100755 index 0000000..1e8ffb9 --- /dev/null +++ b/tests/cli/qa/security/fixtures/bin/composer @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +cat <<'JSON' +{ + "advisories": { + "vendor/package-a": [ + { + "title": "Remote Code Execution", + "link": "https://example.com/advisory/1", + "cve": "CVE-2026-0001", + "affectedVersions": ">=1.0,<1.5" + } + ] + } +} +JSON +exit 1 diff --git a/tests/cli/qa/security/fixtures/project/.env b/tests/cli/qa/security/fixtures/project/.env new file mode 100644 index 0000000..c85b6bc --- /dev/null +++ b/tests/cli/qa/security/fixtures/project/.env @@ -0,0 +1,3 @@ +APP_DEBUG=true +APP_KEY=short +APP_URL=http://example.com diff --git a/tests/cli/qa/security/fixtures/project/composer.json b/tests/cli/qa/security/fixtures/project/composer.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/security/fixtures/project/composer.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/security/fixtures/project/src/Bad.php b/tests/cli/qa/security/fixtures/project/src/Bad.php new file mode 100644 index 0000000..15b5f13 --- /dev/null +++ b/tests/cli/qa/security/fixtures/project/src/Bad.php @@ -0,0 +1,5 @@ +/dev/null + EOF diff --git a/tests/cli/qa/stan/fixtures/project/composer.json b/tests/cli/qa/stan/fixtures/project/composer.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/stan/fixtures/project/composer.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/stan/fixtures/project/phpstan.neon b/tests/cli/qa/stan/fixtures/project/phpstan.neon new file mode 100644 index 0000000..7c47856 --- /dev/null +++ b/tests/cli/qa/stan/fixtures/project/phpstan.neon @@ -0,0 +1,2 @@ +parameters: + level: 5 diff --git a/tests/cli/qa/stan/fixtures/project/src/Bad.php b/tests/cli/qa/stan/fixtures/project/src/Bad.php new file mode 100644 index 0000000..4fab7fe --- /dev/null +++ b/tests/cli/qa/stan/fixtures/project/src/Bad.php @@ -0,0 +1,5 @@ +' "$output" + EOF diff --git a/tests/cli/qa/test/fixtures/project/composer.json b/tests/cli/qa/test/fixtures/project/composer.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/cli/qa/test/fixtures/project/composer.json @@ -0,0 +1 @@ +{} diff --git a/tests/cli/qa/test/fixtures/project/tests/Unit/ExampleTest.php b/tests/cli/qa/test/fixtures/project/tests/Unit/ExampleTest.php new file mode 100644 index 0000000..d3306d7 --- /dev/null +++ b/tests/cli/qa/test/fixtures/project/tests/Unit/ExampleTest.php @@ -0,0 +1,11 @@ +assertTrue(false); + } +} diff --git a/tests/cli/qa/test/fixtures/project/vendor/bin/phpunit b/tests/cli/qa/test/fixtures/project/vendor/bin/phpunit new file mode 100755 index 0000000..6517de9 --- /dev/null +++ b/tests/cli/qa/test/fixtures/project/vendor/bin/phpunit @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +junit="" + +while [ $# -gt 0 ]; do + case "$1" in + --log-junit) + shift + junit="$1" + ;; + esac + shift +done + +printf '%s' '' >"$junit" diff --git a/tests/cli/qa/watch/Taskfile.yaml b/tests/cli/qa/watch/Taskfile.yaml new file mode 100644 index 0000000..073335e --- /dev/null +++ b/tests/cli/qa/watch/Taskfile.yaml @@ -0,0 +1,22 @@ +version: "3" + +tasks: + test: + cmds: + - | + bash <<'EOF' + set -euo pipefail + source ../../_lib/run.sh + + go build -trimpath -ldflags="-s -w" -o bin/core ../_harness + + repo="$(cat fixtures/repo.txt)" + commit="$(cat fixtures/commit.txt)" + output="$(mktemp)" + export PATH="$(pwd)/fixtures/bin:$PATH" + run_capture_all 1 "$output" ./bin/core qa watch --repo "$repo" --commit "$commit" --timeout 1s + grep -Fq "forge/example" "$output" + grep -Fq "01234567" "$output" + grep -Fq "Job: Build" "$output" + grep -Fq "Error: fatal: build failed in src/app.go:17" "$output" + EOF diff --git a/tests/cli/qa/watch/fixtures/bin/gh b/tests/cli/qa/watch/fixtures/bin/gh new file mode 100755 index 0000000..6762a52 --- /dev/null +++ b/tests/cli/qa/watch/fixtures/bin/gh @@ -0,0 +1,46 @@ +#!/usr/bin/env sh + +case "$*" in + *"run list --repo forge/example --commit 0123456789abcdef"*) + cat <<'JSON' +[ + { + "databaseId": 7, + "name": "CI", + "displayTitle": "CI", + "status": "completed", + "conclusion": "failure", + "headSha": "0123456789abcdef", + "url": "https://example.com/workflows/7", + "createdAt": "2026-03-30T00:00:00Z", + "updatedAt": "2026-03-30T00:00:00Z" + } +] +JSON + ;; + *"run view 7 --repo forge/example --json jobs"*) + cat <<'JSON' +{"jobs":[ + { + "databaseId": 11, + "name": "Build", + "status": "completed", + "conclusion": "failure", + "url": "https://example.com/workflows/7/jobs/11", + "steps": [ + {"name": "Compile", "status": "completed", "conclusion": "failure", "number": 3} + ] + } +]} +JSON + ;; + *"run view 7 --repo forge/example --log-failed"*) + cat <<'EOF' +fatal: build failed in src/app.go:17 +EOF + ;; + *) + printf '%s\n' "unexpected gh invocation: $*" >&2 + exit 1 + ;; +esac diff --git a/tests/cli/qa/watch/fixtures/commit.txt b/tests/cli/qa/watch/fixtures/commit.txt new file mode 100644 index 0000000..8d6a8d5 --- /dev/null +++ b/tests/cli/qa/watch/fixtures/commit.txt @@ -0,0 +1 @@ +0123456789abcdef diff --git a/tests/cli/qa/watch/fixtures/repo.txt b/tests/cli/qa/watch/fixtures/repo.txt new file mode 100644 index 0000000..8036d44 --- /dev/null +++ b/tests/cli/qa/watch/fixtures/repo.txt @@ -0,0 +1 @@ +forge/example