mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
The shell translator rewrites test bodies and loses Bats reporting. The unit lane needs complete TAP results and JUnit reports without changing how live-cluster suites run. Sourced production helpers still need a dash pass, and unit test bodies need nounset without exporting shell options into subprocesses. Assisted-by: LLM Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
670 lines
24 KiB
Bash
670 lines
24 KiB
Bash
#!/usr/bin/env bats
|
|
# Unit test: the md-no-hardwrap agent hook.
|
|
#
|
|
# The hook is a PreToolUse guard for AI coding agents. It refuses two classes
|
|
# of write:
|
|
#
|
|
# 1. Write/Edit/MultiEdit whose resulting markdown contains a prose
|
|
# paragraph, list item or blockquote spanning more than one line.
|
|
# 2. Bash invocations of `gh` that publish such a body to GitHub.
|
|
#
|
|
# Markdown renderers collapse a soft line break into a space, so a paragraph
|
|
# hardwrapped at ~80 columns gains nothing and loses on narrow viewports and
|
|
# in diffs, where a one-word edit reflows the whole block. Everything that is
|
|
# not prose -- code blocks, tables, headings, front matter, link reference
|
|
# definitions, explicit hard breaks -- keeps its line breaks, because there a
|
|
# break carries meaning.
|
|
#
|
|
# Two properties matter more than coverage of exotic markdown, and most of
|
|
# the cases below defend them:
|
|
#
|
|
# * A false positive is worse than a miss. A hook that blocks legitimate
|
|
# work gets switched off, and then it guards nothing.
|
|
# * The single most common wrap -- a bullet the agent broke across two
|
|
# lines -- must be caught. It is the whole reason the hook exists.
|
|
#
|
|
# The hook speaks the agent hook protocol: a JSON payload on stdin, exit 0 to
|
|
# allow, exit 2 to block with an explanation on stderr.
|
|
|
|
load test_helper
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")/.." && pwd)"
|
|
HOOK="$REPO_ROOT/.claude/hooks/md-no-hardwrap.py"
|
|
|
|
# The hook is written in python3, and so is the payload builder below. Other
|
|
# hack/*.bats deliberately avoid a python3 dependency; a test for a python3
|
|
# hook cannot. Fail loudly when the interpreter is missing rather than passing
|
|
# vacuously -- a green suite that ran nothing is worse than a red one.
|
|
have_python3() {
|
|
command -v python3 >/dev/null 2>&1 && return 0
|
|
echo "python3 is required to exercise .claude/hooks/md-no-hardwrap.py" >&2
|
|
return 1
|
|
}
|
|
|
|
# Feed a JSON payload to the hook and echo its exit code. Never fails the
|
|
# calling test on a non-zero exit -- the exit code IS the assertion subject.
|
|
hook_rc() {
|
|
local rc=0
|
|
printf '%s' "$1" | python3 "$HOOK" >/dev/null 2>&1 || rc=$?
|
|
printf '%s' "$rc"
|
|
}
|
|
|
|
# Payload builders. python3 does the JSON encoding so that newlines, quotes
|
|
# and backslashes survive the trip intact.
|
|
write_payload() {
|
|
python3 -c 'import json,sys; print(json.dumps({"tool_name":"Write","tool_input":{"file_path":sys.argv[1],"content":sys.argv[2]}}))' "$1" "$2"
|
|
}
|
|
|
|
edit_payload() {
|
|
python3 -c 'import json,sys; print(json.dumps({"tool_name":"Edit","tool_input":{"file_path":sys.argv[1],"old_string":sys.argv[2],"new_string":sys.argv[3]}}))' "$1" "$2" "$3"
|
|
}
|
|
|
|
bash_payload() {
|
|
python3 -c 'import json,sys; print(json.dumps({"tool_name":"Bash","tool_input":{"command":sys.argv[1]}}))' "$1"
|
|
}
|
|
|
|
@test "hook is executable" {
|
|
[ -x "$HOOK" ]
|
|
}
|
|
|
|
###############################################################################
|
|
# Markdown: what must be blocked #
|
|
###############################################################################
|
|
|
|
@test "markdown: multi-line prose paragraph is blocked" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md 'A paragraph that the agent
|
|
hardwrapped at eighty columns because of a reflex.')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "markdown: bullet wrapped onto a second line is blocked" {
|
|
have_python3
|
|
# The commonest wrap of all, and the one the hook exists for. A lazy
|
|
# continuation line belongs to the bullet above it, not to a new paragraph.
|
|
payload="$(write_payload /tmp/doc.md '- a bullet the agent wrapped
|
|
at eighty columns
|
|
- a bullet that stays on one line')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "markdown: ordered list item wrapped onto a second line is blocked" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md '1. an ordered item the agent wrapped
|
|
onto a second line')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "markdown: wrapped blockquote is blocked" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md '> a quoted paragraph the agent
|
|
> wrapped across two lines')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
###############################################################################
|
|
# Markdown: what must NOT be blocked #
|
|
###############################################################################
|
|
|
|
@test "markdown: single-line paragraphs pass" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md '# Title
|
|
|
|
One continuous line per paragraph, however long it happens to run.
|
|
|
|
Another paragraph, also a single line.')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: fenced code block keeps its line breaks" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md 'Intro line.
|
|
|
|
```bash
|
|
echo one
|
|
echo two
|
|
```')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: indented code block keeps its line breaks" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md 'Intro line.
|
|
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: demo')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: explicit hard break with two trailing spaces is preserved" {
|
|
have_python3
|
|
# Two trailing spaces render as <br>. Reflowing that onto one line would
|
|
# change the rendered output, so it is a deliberate break, not a wrap.
|
|
payload="$(printf '%s' '{"tool_name":"Write","tool_input":{"file_path":"/tmp/doc.md","content":"Line one, ending in a hard break. \nLine two, its own rendered line.\n"}}')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: explicit hard break with a trailing backslash is preserved" {
|
|
have_python3
|
|
payload="$(printf '%s' '{"tool_name":"Write","tool_input":{"file_path":"/tmp/doc.md","content":"Line one, ending in a backslash break.\\\\\nLine two, its own rendered line.\n"}}')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: adjacent link reference definitions keep one per line" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md '[upstream]: https://example.com/upstream
|
|
[downstream]: https://example.com/downstream')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: footnote definitions keep one per line" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md '[^one]: The first footnote.
|
|
[^two]: The second footnote.')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: lists and tables keep their line breaks" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md '- first bullet
|
|
- second bullet
|
|
|
|
| a | b |
|
|
| --- | --- |
|
|
| 1 | 2 |')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: pipe-less table keeps its line breaks" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md 'a | b
|
|
--- | ---
|
|
1 | 2')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: badge stack keeps one shield per line" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/README.md '[](https://example.com/build)
|
|
[](https://example.com/license)')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: the repo's own README passes the hook" {
|
|
have_python3
|
|
# README.md uses a two-space hard break. A hook that cannot write the repo's
|
|
# front page is a hook that gets switched off.
|
|
payload="$(python3 -c 'import json,sys; p=sys.argv[1]; print(json.dumps({"tool_name":"Write","tool_input":{"file_path":p,"content":open(p,encoding="utf-8").read()}}))' "$REPO_ROOT/README.md")"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: AGENTS.md passes the hook it documents" {
|
|
have_python3
|
|
payload="$(python3 -c 'import json,sys; p=sys.argv[1]; print(json.dumps({"tool_name":"Write","tool_input":{"file_path":p,"content":open(p,encoding="utf-8").read()}}))' "$REPO_ROOT/AGENTS.md")"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
###############################################################################
|
|
# Markdown: Edit hunks are judged against the file, not in isolation #
|
|
###############################################################################
|
|
|
|
@test "markdown: Edit hunk introducing a wrap is blocked" {
|
|
have_python3
|
|
tmp="$(mktemp -d)"
|
|
printf 'Existing single line.\n\nold\n' > "$tmp/doc.md"
|
|
payload="$(edit_payload "$tmp/doc.md" 'old' 'A fresh paragraph that got
|
|
wrapped across two lines.')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "2" ]
|
|
}
|
|
|
|
@test "markdown: Edit hunk landing inside a code fence is allowed" {
|
|
have_python3
|
|
# The hunk alone looks like a wrapped paragraph. Only the surrounding file
|
|
# shows it is YAML inside a fence -- so the file is what must be judged.
|
|
tmp="$(mktemp -d)"
|
|
printf 'Intro line.\n\n```yaml\nPLACEHOLDER\n```\n' > "$tmp/doc.md"
|
|
payload="$(edit_payload "$tmp/doc.md" 'PLACEHOLDER' 'apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: demo')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "0" ]
|
|
}
|
|
|
|
@test "markdown: fixing a typo inside a legacy wrapped paragraph is allowed" {
|
|
have_python3
|
|
# The paragraph stays as wrapped as it was -- the edit adds no wrap. Keying
|
|
# violations by their text would see the changed wording as a brand-new
|
|
# violation and refuse a one-word fix.
|
|
tmp="$(mktemp -d)"
|
|
printf 'A legacy paragraph with teh typo\nwrapped long ago.\n' > "$tmp/doc.md"
|
|
payload="$(edit_payload "$tmp/doc.md" 'teh' 'the')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "0" ]
|
|
}
|
|
|
|
@test "markdown: adding a wrap to an already-wrapped file is still blocked" {
|
|
have_python3
|
|
tmp="$(mktemp -d)"
|
|
printf 'A legacy paragraph that was\nwrapped long ago.\n\nTARGET\n' > "$tmp/doc.md"
|
|
payload="$(edit_payload "$tmp/doc.md" 'TARGET' 'A brand new paragraph that the
|
|
agent wrapped as well.')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "2" ]
|
|
}
|
|
|
|
@test "markdown: Edit preserving a pre-existing wrap elsewhere is allowed" {
|
|
have_python3
|
|
# Legacy files are full of wrapped paragraphs. Editing one word in such a
|
|
# file must not require reflowing the rest of it.
|
|
tmp="$(mktemp -d)"
|
|
printf 'A legacy paragraph that was\nwrapped long ago.\n\nTARGET\n' > "$tmp/doc.md"
|
|
payload="$(edit_payload "$tmp/doc.md" 'TARGET' 'A replacement on one line.')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "0" ]
|
|
}
|
|
|
|
###############################################################################
|
|
# Markdown: paths the repo does not own #
|
|
###############################################################################
|
|
|
|
@test "markdown: vendored upstream chart docs are out of scope" {
|
|
have_python3
|
|
# AGENTS.md forbids editing vendored charts; `make update` regenerates them
|
|
# verbatim from upstream. Flagging their wraps would demand a forbidden fix.
|
|
payload="$(write_payload "$REPO_ROOT/packages/system/cert-manager/charts/cert-manager/README.md" 'A vendored paragraph
|
|
wrapped by its upstream author.')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: published changelogs are out of scope" {
|
|
have_python3
|
|
payload="$(write_payload "$REPO_ROOT/docs/changelogs/v1.5.0.md" 'A released changelog entry
|
|
wrapped when it was published.')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "non-markdown files are none of the hook's business" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/main.go 'package main
|
|
|
|
// A Go comment wrapped at eighty columns, which is correct style
|
|
// for Go and must not be blocked by a markdown rule.
|
|
func main() {}')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
###############################################################################
|
|
# gh: bodies that must be blocked #
|
|
###############################################################################
|
|
|
|
@test "gh: PR body with a wrapped paragraph is blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create --title "fix(x): y" --body "This body was
|
|
hardwrapped by the agent."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: --body= equals form is blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh issue comment 42 --body="A body that was
|
|
wrapped across two lines."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: global flag before the subcommand does not hide the body" {
|
|
have_python3
|
|
# `gh -R owner/repo pr comment` puts the repo between `gh` and the
|
|
# subcommand. Reading the subcommand as "the first non-flag argument" picks
|
|
# up the repo instead, and the body sails through unchecked.
|
|
payload="$(bash_payload 'gh -R cozystack/cozystack pr comment 123 --body "First line of the body
|
|
and its wrapped continuation."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: environment prefix does not hide the body" {
|
|
have_python3
|
|
payload="$(bash_payload 'GH_TOKEN=secret gh issue comment 1 --body "First line of the body
|
|
and its wrapped continuation."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: subshell does not hide the body" {
|
|
have_python3
|
|
payload="$(bash_payload '(gh issue comment 1 --body "First line of the body
|
|
and its wrapped continuation.")')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: chained command after && is still inspected" {
|
|
have_python3
|
|
payload="$(bash_payload 'git push && gh pr create --title "t" --body "First line of the body
|
|
and its wrapped continuation."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: chained command after a semicolon with no space is still inspected" {
|
|
have_python3
|
|
# `push;` is one shlex token unless the lexer treats `;` as punctuation, and
|
|
# then the gh segment that follows is never recognised as one.
|
|
payload="$(bash_payload 'git push; gh pr create --title "t" --body "First line of the body
|
|
and its wrapped continuation."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: body survives a redirect on the same command" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create --title "t" --body "First line of the body
|
|
and its wrapped continuation." > /dev/null')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: heredoc body with a wrapped paragraph is blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create --body-file - <<EOF
|
|
A heredoc body that the agent
|
|
wrapped across two lines.
|
|
EOF')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: --body-file pointing at a hardwrapped file is blocked" {
|
|
have_python3
|
|
tmp="$(mktemp -d)"
|
|
printf 'A body file paragraph split\nacross two lines.\n' > "$tmp/body.md"
|
|
payload="$(bash_payload "gh pr create --body-file $tmp/body.md")"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "2" ]
|
|
}
|
|
|
|
@test "gh: api field body with a wrapped paragraph is blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh api --method POST /repos/o/r/issues/1/comments -f body="A review reply that was
|
|
wrapped across two lines."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: api raw field body with a wrapped paragraph is blocked" {
|
|
have_python3
|
|
# For `gh api`, -F means --raw-field, not --body-file. Reading it as a file
|
|
# name drops the body on the floor and the wrap ships.
|
|
payload="$(bash_payload 'gh api --method POST /repos/o/r/issues/1/comments -F body="A review reply that was
|
|
wrapped across two lines."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
###############################################################################
|
|
# gh: bodies and commands that must NOT be blocked #
|
|
###############################################################################
|
|
|
|
@test "gh: single-line PR body passes" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create --title "fix(x): y" --body "One continuous line, as the renderer expects."')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "gh: read-only subcommands are never blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr view 42 --json body')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "gh: release notes from a published changelog are not blocked" {
|
|
have_python3
|
|
# hack/upload-releasenotes.sh publishes docs/changelogs/<version>.md, which
|
|
# is historical and wrapped. Blocking it would break the release process.
|
|
payload="$(bash_payload "gh release edit v1.5.0 --notes-file $REPO_ROOT/docs/changelogs/v1.5.0.md")"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "gh: api JSON heredoc is not read as markdown" {
|
|
have_python3
|
|
# `gh api --input -` takes JSON, not markdown. Its braces and quoted keys
|
|
# are not a hardwrapped paragraph.
|
|
#
|
|
# The JSON is indented so that no line of it starts with `}`: cozytest.sh
|
|
# converts each @test into a shell function with awk, and a bare `}` in the
|
|
# first column ends that function early, silently truncating the test.
|
|
payload="$(bash_payload 'gh api --method POST /repos/o/r/pulls/1/reviews --input - <<EOF
|
|
{
|
|
"event": "COMMENT",
|
|
"body": "One continuous line."
|
|
}
|
|
EOF')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "gh: api JSON heredoc with a wrapped body field is blocked" {
|
|
have_python3
|
|
# The wrap lives inside the JSON string as a \n escape. Only the decoded
|
|
# body is prose, and that is what must be checked.
|
|
payload="$(bash_payload 'gh api --method POST /repos/o/r/pulls/1/reviews --input - <<EOF
|
|
{
|
|
"event": "COMMENT",
|
|
"body": "A review body that was\nwrapped across two lines."
|
|
}
|
|
EOF')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: a heredoc belonging to another command is not adopted" {
|
|
have_python3
|
|
# A commit message body is wrapped at ~72 columns on purpose -- git and
|
|
# AGENTS.md both require it. The presence of a `gh` call further down the
|
|
# line must not drag that heredoc into the markdown rule.
|
|
payload="$(bash_payload 'git commit --file - <<EOF
|
|
fix(hook): correct the thing
|
|
|
|
The body of a commit message is wrapped at seventy-two columns,
|
|
exactly as git and AGENTS.md require.
|
|
EOF
|
|
gh pr edit 1 --add-label area/ai')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "gh: a YAML heredoc for kubectl is not adopted" {
|
|
have_python3
|
|
payload="$(bash_payload 'kubectl apply -f - <<EOF
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: demo
|
|
EOF
|
|
gh issue comment 1 --body "One continuous line."')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "non-gh shell commands are never blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'printf "line one\nline two\n" > /tmp/notes.txt')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
###############################################################################
|
|
# Robustness: the hook must never hang, crash, or block what it cannot read #
|
|
###############################################################################
|
|
|
|
@test "unparseable payload fails open" {
|
|
have_python3
|
|
rc=0
|
|
printf 'not json at all' | python3 "$HOOK" >/dev/null 2>&1 || rc=$?
|
|
[ "$rc" = "0" ]
|
|
}
|
|
|
|
@test "gh: --body-file on a fifo does not hang" {
|
|
have_python3
|
|
# An unopened fifo blocks forever on read. The hook must refuse to read
|
|
# anything that is not a regular file rather than stall the agent.
|
|
tmp="$(mktemp -d)"
|
|
mkfifo "$tmp/fifo"
|
|
payload="$(bash_payload "gh pr create --body-file $tmp/fifo")"
|
|
rc=0
|
|
printf '%s' "$payload" | python3 "$HOOK" >/dev/null 2>&1 || rc=$?
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "0" ]
|
|
}
|
|
|
|
@test "gh: unbalanced quotes fail open" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create --body "unterminated')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "block message names the rule and the offending lines" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md 'A paragraph that the agent
|
|
hardwrapped at eighty columns.')"
|
|
msg="$(printf '%s' "$payload" | python3 "$HOOK" 2>&1 >/dev/null || true)"
|
|
printf '%s' "$msg" | grep -q 'one continuous line per paragraph'
|
|
printf '%s' "$msg" | grep -q '/tmp/doc.md'
|
|
}
|
|
|
|
###############################################################################
|
|
# The question is always "did this edit ADD the line break", never "is there #
|
|
# a wrap somewhere in the file". These cases pin that distinction down. #
|
|
###############################################################################
|
|
|
|
@test "markdown: rewriting a file as entirely wrapped prose is blocked" {
|
|
have_python3
|
|
# Ties the violation count of the legacy file, so any count-based test waves
|
|
# it through -- while every wrap in it is brand new.
|
|
tmp="$(mktemp -d)"
|
|
printf 'A legacy paragraph that was\nwrapped long ago.\n' > "$tmp/doc.md"
|
|
payload="$(write_payload "$tmp/doc.md" 'A completely new paragraph that the
|
|
agent has just hardwrapped.')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "2" ]
|
|
}
|
|
|
|
@test "markdown: reflowing one paragraph while wrapping another is blocked" {
|
|
have_python3
|
|
tmp="$(mktemp -d)"
|
|
printf 'One wrapped paragraph\nfrom long ago.\n\nSECOND\n' > "$tmp/doc.md"
|
|
payload="$(edit_payload "$tmp/doc.md" 'SECOND' 'A second paragraph the agent
|
|
has now wrapped as well.')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "2" ]
|
|
}
|
|
|
|
@test "markdown: rewriting a legacy file verbatim is allowed" {
|
|
have_python3
|
|
tmp="$(mktemp -d)"
|
|
printf 'A legacy paragraph that was\nwrapped long ago.\n' > "$tmp/doc.md"
|
|
payload="$(write_payload "$tmp/doc.md" 'A legacy paragraph that was
|
|
wrapped long ago.')"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "0" ]
|
|
}
|
|
|
|
@test "gh: a publishing command on the second line of a script is inspected" {
|
|
have_python3
|
|
# A newline is whitespace to shlex, so without explicit handling the gh call
|
|
# folds into the previous command's segment and is never seen.
|
|
payload="$(bash_payload 'git status
|
|
gh pr create --title "t" --body "A body paragraph the agent
|
|
hardwrapped across two lines."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: backslash-continued command with a heredoc body is inspected" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create \
|
|
--title "t" \
|
|
--body-file - <<EOF
|
|
A body paragraph the agent
|
|
hardwrapped across two lines.
|
|
EOF')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: a well-formed cat-heredoc body is allowed" {
|
|
have_python3
|
|
# The argument is shell syntax, not markdown. Reading `$(cat <<EOF` as a
|
|
# wrapped paragraph refuses a body the agent cannot possibly reflow.
|
|
payload="$(bash_payload 'gh pr create --title "t" --body "$(cat <<EOF
|
|
One continuous line per paragraph, exactly as the rule requires.
|
|
EOF
|
|
)"')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "gh: a hardwrapped cat-heredoc body is blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create --title "t" --body "$(cat <<EOF
|
|
A body paragraph the agent
|
|
hardwrapped across two lines.
|
|
EOF
|
|
)"')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: an absolute path to gh is still inspected" {
|
|
have_python3
|
|
payload="$(bash_payload '/usr/bin/gh pr create --title "t" --body "A body paragraph the agent
|
|
hardwrapped across two lines."')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "markdown: a details block keeps its line breaks" {
|
|
have_python3
|
|
payload="$(write_payload /tmp/doc.md '<details>
|
|
<summary>Test output</summary>
|
|
<pre>all green</pre>
|
|
</details>')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "gh: a details block in a PR body is not blocked" {
|
|
have_python3
|
|
payload="$(bash_payload 'gh pr create --title "t" --body "<details>
|
|
<summary>Test output</summary>
|
|
<pre>all green</pre>
|
|
</details>"')"
|
|
[ "$(hook_rc "$payload")" = "0" ]
|
|
}
|
|
|
|
@test "markdown: a leading horizontal rule does not swallow the document" {
|
|
have_python3
|
|
# `---` on line 1 opens front matter only if a closing delimiter follows.
|
|
payload="$(write_payload /tmp/doc.md '---
|
|
|
|
A paragraph the agent
|
|
hardwrapped after a horizontal rule.')"
|
|
[ "$(hook_rc "$payload")" = "2" ]
|
|
}
|
|
|
|
@test "gh: body redirected onto stdin from a file is inspected" {
|
|
have_python3
|
|
# `--body-file -` says the body arrives on stdin, and `< body.md` says where
|
|
# stdin comes from. Recording the first without following the second lets the
|
|
# body publish unread.
|
|
tmp="$(mktemp -d)"
|
|
printf 'A body paragraph the agent\nhardwrapped across two lines.\n' > "$tmp/body.md"
|
|
payload="$(bash_payload "gh pr create --title t --body-file - < $tmp/body.md")"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "2" ]
|
|
}
|
|
|
|
@test "gh: api input redirected onto stdin from a file is inspected" {
|
|
have_python3
|
|
tmp="$(mktemp -d)"
|
|
printf '{"body": "A review body that was\\nwrapped across two lines."}\n' > "$tmp/review.json"
|
|
payload="$(bash_payload "gh api --method POST /repos/o/r/pulls/1/reviews --input - < $tmp/review.json")"
|
|
rc="$(hook_rc "$payload")"
|
|
rm -rf "$tmp"
|
|
[ "$rc" = "2" ]
|
|
}
|