rickub/clipublic Fork
⑂ 507eaf7
Commits
⬇ Clone ▾
git clone https://git.rickub.com/rickub/cli.git
git clone ssh://git@rickub.com/rickub/cli.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

tests: pin -b - stdin for issue create and pr createUnverified

Closes the last open acceptance criterion of rickub/rickub issue 279
(criterion 3): a test asserts -b - reaches the API as the stdin content
for BOTH issue create and pr create, bite-proven by reverting the
pr-create half.

The shared resolver (cmd/body.go) and its structural guard
(TestEveryBodyFlagUsesTheSharedResolver) landed with issue 94s fix, and
the comment commands have had stdin-to-API tests since; the two CREATE
commands - the exact commands 279 reported, pr create once storing a
literal dash and discarding an 8.9 KB description with a success line -
had no test asserting the stdin bytes land on the wire.

Bite-proven as the issue prescribes: reverting only runPRCreates
resolveBody call fails ONLY TestPRCreateBodyDashReadsStdin (posted the
literal dash) while TestIssueCreateBodyDashReadsStdin stays green, so
each half is independently pinned. Restored, the full cmd suite passes.
Olivier Girardot committed 2026-10-11T19:10:11+02:00 Browse files
507eaf7 parent: a97c085
modified cmd/body_test.go +74 -0
@@ -209,6 +209,80 @@ func TestMRCommentAnchoredBlankBodyIsRefused(t *testing.T) {
209209 }
210210 }
211211
212+// TestIssueCreateBodyDashReadsStdin and TestPRCreateBodyDashReadsStdin close
213+// rickub/rickub#279's acceptance criterion 3: "-b - reaches the API as the stdin
214+// content for BOTH issue create and pr create". The comment commands above
215+// already pin their halves; these pin the two CREATE halves — `pr create -b -`
216+// is the exact command that once stored a literal "-" and discarded an 8.9 KB
217+// description with a success line. Each test fails on a parser that uses the
218+// flag value verbatim (sent == "-"), which is how the pr-create half regressed
219+// before the shared resolver existed.
220+func TestIssueCreateBodyDashReadsStdin(t *testing.T) {
221+ var sent string
222+ defer withCreateBodyServer(t, &sent, "/issues")()
223+
224+ const text = "## Summary\n\nPins the portal gun back on the shelf.\n\n- one\n- two"
225+ if _, _, err := executeStdin(t, text+"\n", "issue", "create", "-R", "rick/portal",
226+ "-t", "the gun slips", "-b", "-"); err != nil {
227+ t.Fatalf("execute: %v", err)
228+ }
229+ if sent == "-" {
230+ t.Fatal(`issue create -b - posted the literal "-" — rickub/rickub#279 has regressed`)
231+ }
232+ if sent != text {
233+ t.Fatalf("posted body = %q, want the stdin text %q", sent, text)
234+ }
235+}
236+
237+func TestPRCreateBodyDashReadsStdin(t *testing.T) {
238+ var sent string
239+ defer withCreateBodyServer(t, &sent, "/merge-requests")()
240+
241+ const text = "## What\n\nA long description piped in from a file, the way MR bodies\nare actually written.\n"
242+ if _, _, err := executeStdin(t, text, "pr", "create", "-R", "rick/portal",
243+ "--base", "main", "--head", "fix/pin-the-gun", "-t", "fix: pin the gun", "-b", "-"); err != nil {
244+ t.Fatalf("execute: %v", err)
245+ }
246+ if sent == "-" {
247+ t.Fatal(`pr create -b - posted the literal "-" — rickub/rickub#279 has regressed`)
248+ }
249+ if sent != strings.TrimRight(text, "\r\n") {
250+ t.Fatalf("posted body = %q, want the stdin text %q", sent, text)
251+ }
252+}
253+
254+// withCreateBodyServer stands up a fake that answers a create endpoint (path
255+// suffix) and records the request JSON's body field, wiring the CLI at it. The
256+// suffix keeps the two tests honest about WHICH create they exercised; any
257+// other path fails the test rather than passing as an empty capture.
258+func withCreateBodyServer(t *testing.T, got *string, suffix string) func() {
259+ t.Helper()
260+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
261+ if r.Method != http.MethodPost || !strings.HasSuffix(r.URL.Path, suffix) {
262+ t.Errorf("create request used %s %s, want POST …%s", r.Method, r.URL.Path, suffix)
263+ }
264+ var body struct {
265+ Body string `json:"body"`
266+ }
267+ b, err := io.ReadAll(r.Body)
268+ if err != nil {
269+ t.Errorf("reading request body: %v", err)
270+ }
271+ if err := json.Unmarshal(b, &body); err != nil {
272+ t.Errorf("request body %q is not the JSON the CLI should send: %v", b, err)
273+ }
274+ *got = body.Body
275+ w.WriteHeader(http.StatusCreated)
276+ _ = json.NewEncoder(w).Encode(map[string]any{
277+ "number": 62, "title": "recorded", "state": "open",
278+ })
279+ }))
280+ t.Setenv("XDG_CONFIG_HOME", t.TempDir())
281+ t.Setenv("RICKUB_HOST", srv.URL)
282+ t.Setenv("RICKUB_TOKEN", "rickub_pat_test")
283+ return srv.Close
284+}
285+
212286 // TestBodyFlagHelpIsIdentical: both commands document the stdin behaviour in the
213287 // same words, because they read the same constant. Issue #94's criterion 4.
214288 func TestBodyFlagHelpIsIdentical(t *testing.T) {
@@ -209,6 +209,80 @@ func TestMRCommentAnchoredBlankBodyIsRefused(t *testing.T) {
209 }209 }
210 }210 }
211 211
212+// TestIssueCreateBodyDashReadsStdin and TestPRCreateBodyDashReadsStdin close
213+// rickub/rickub#279's acceptance criterion 3: "-b - reaches the API as the stdin
214+// content for BOTH issue create and pr create". The comment commands above
215+// already pin their halves; these pin the two CREATE halves — `pr create -b -`
216+// is the exact command that once stored a literal "-" and discarded an 8.9 KB
217+// description with a success line. Each test fails on a parser that uses the
218+// flag value verbatim (sent == "-"), which is how the pr-create half regressed
219+// before the shared resolver existed.
220+func TestIssueCreateBodyDashReadsStdin(t *testing.T) {
221+ var sent string
222+ defer withCreateBodyServer(t, &sent, "/issues")()
223+
224+ const text = "## Summary\n\nPins the portal gun back on the shelf.\n\n- one\n- two"
225+ if _, _, err := executeStdin(t, text+"\n", "issue", "create", "-R", "rick/portal",
226+ "-t", "the gun slips", "-b", "-"); err != nil {
227+ t.Fatalf("execute: %v", err)
228+ }
229+ if sent == "-" {
230+ t.Fatal(`issue create -b - posted the literal "-" — rickub/rickub#279 has regressed`)
231+ }
232+ if sent != text {
233+ t.Fatalf("posted body = %q, want the stdin text %q", sent, text)
234+ }
235+}
236+
237+func TestPRCreateBodyDashReadsStdin(t *testing.T) {
238+ var sent string
239+ defer withCreateBodyServer(t, &sent, "/merge-requests")()
240+
241+ const text = "## What\n\nA long description piped in from a file, the way MR bodies\nare actually written.\n"
242+ if _, _, err := executeStdin(t, text, "pr", "create", "-R", "rick/portal",
243+ "--base", "main", "--head", "fix/pin-the-gun", "-t", "fix: pin the gun", "-b", "-"); err != nil {
244+ t.Fatalf("execute: %v", err)
245+ }
246+ if sent == "-" {
247+ t.Fatal(`pr create -b - posted the literal "-" — rickub/rickub#279 has regressed`)
248+ }
249+ if sent != strings.TrimRight(text, "\r\n") {
250+ t.Fatalf("posted body = %q, want the stdin text %q", sent, text)
251+ }
252+}
253+
254+// withCreateBodyServer stands up a fake that answers a create endpoint (path
255+// suffix) and records the request JSON's body field, wiring the CLI at it. The
256+// suffix keeps the two tests honest about WHICH create they exercised; any
257+// other path fails the test rather than passing as an empty capture.
258+func withCreateBodyServer(t *testing.T, got *string, suffix string) func() {
259+ t.Helper()
260+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
261+ if r.Method != http.MethodPost || !strings.HasSuffix(r.URL.Path, suffix) {
262+ t.Errorf("create request used %s %s, want POST …%s", r.Method, r.URL.Path, suffix)
263+ }
264+ var body struct {
265+ Body string `json:"body"`
266+ }
267+ b, err := io.ReadAll(r.Body)
268+ if err != nil {
269+ t.Errorf("reading request body: %v", err)
270+ }
271+ if err := json.Unmarshal(b, &body); err != nil {
272+ t.Errorf("request body %q is not the JSON the CLI should send: %v", b, err)
273+ }
274+ *got = body.Body
275+ w.WriteHeader(http.StatusCreated)
276+ _ = json.NewEncoder(w).Encode(map[string]any{
277+ "number": 62, "title": "recorded", "state": "open",
278+ })
279+ }))
280+ t.Setenv("XDG_CONFIG_HOME", t.TempDir())
281+ t.Setenv("RICKUB_HOST", srv.URL)
282+ t.Setenv("RICKUB_TOKEN", "rickub_pat_test")
283+ return srv.Close
284+}
285+
212 // TestBodyFlagHelpIsIdentical: both commands document the stdin behaviour in the286 // TestBodyFlagHelpIsIdentical: both commands document the stdin behaviour in the
213 // same words, because they read the same constant. Issue #94's criterion 4.287 // same words, because they read the same constant. Issue #94's criterion 4.
214 func TestBodyFlagHelpIsIdentical(t *testing.T) {288 func TestBodyFlagHelpIsIdentical(t *testing.T) {