Commit ec6df8d798

ec6df8d7986ce96a3e6bc43e2daa0842139591bd

parent: a55fb297ba

Verified · cmc ci/build: success ci/test: success

cmc <hello@cleberg.net> · 2026-10-14 07:36 UTC

notifications settings show: reply_available in --json

Whether the instance reads replies to its mail, so a client offers the
reply switch only where turning it on can succeed, as the account page
does. Plain output is unchanged.

Closes #334

Layout: unified · split

.gitbay/wiki/Users.org +2 −1
@@ -974,7 +974,8 @@ merge request mail then carries a =Reply-To= address, and replying to
974it posts your reply as a comment on the thread, as you. Off by 974it posts your reply as a comment on the thread, as you. Off by
975default; the account page has the switch when the instance reads 975default; the account page has the switch when the instance reads
976replies, and turning it on is refused where it does not 976replies, and turning it on is refused where it does not
977(=[mail.inbound]= off). A reply is posted only when it comes from one 977(=[mail.inbound]= off). =notifications settings show --json= says which
978as =reply_available=. A reply is posted only when it comes from one
978of your verified addresses, you can still comment on the thread, and 979of your verified addresses, you can still comment on the thread, and
979the mail it answers is less than thirty days old. Quoted text (lines 980the mail it answers is less than thirty days old. Quoted text (lines
980starting with =>=, the "On … wrote:" line and what follows it, the 981starting with =>=, the "On … wrote:" line and what follows it, the
internal/control/notifications.go +4 −1
@@ -268,7 +268,10 @@ func emitNotificationSettings(c *Ctx) int {
268 if err != nil { 268 if err != nil {
269 return c.fail(protocol.ExitFailure, "%v", err) 269 return c.fail(protocol.ExitFailure, "%v", err)
270 } 270 }
271 return c.emitView(map[string]bool{"mail": mail, "watch": watch, "push": push, "reply": reply}, func(w io.Writer) { 271 // reply_available says whether the instance reads replies, so a client
272 // offers the reply switch only where turning it on can succeed.
273 return c.emitView(map[string]bool{"mail": mail, "watch": watch, "push": push, "reply": reply,
274 "reply_available": c.Cfg.Mail.Inbound.Enabled}, func(w io.Writer) {
272 onOff := func(on bool) string { 275 onOff := func(on bool) string {
273 if on { 276 if on {
274 return "on" 277 return "on"
internal/control/stage3notify_test.go +27
@@ -1,9 +1,13 @@
1package control 1package control
2 2
3import ( 3import (
4 "bytes"
5 "encoding/json"
6 "io"
4 "strings" 7 "strings"
5 "testing" 8 "testing"
6 9
10 "gitbay.org/gitbay/internal/protocol"
7 "gitbay.org/gitbay/internal/store" 11 "gitbay.org/gitbay/internal/store"
8) 12)
9 13
@@ -72,3 +76,26 @@ func TestNotifyScreens(t *testing.T) {
72 checkLegend(t, out) 76 checkLegend(t, out)
73 } 77 }
74} 78}
79
80// A client shows the reply switch only where the instance reads replies,
81// as the account page does; turning it on is refused elsewhere.
82func TestNotificationSettingsSayWhetherRepliesAreRead(t *testing.T) {
83 st, _, u := notifyFixture(t)
84 for _, enabled := range []bool{false, true} {
85 var out bytes.Buffer
86 c := &Ctx{User: u, Scope: "full", Store: st, Stdout: &out, Stderr: io.Discard, JSON: true}
87 c.Cfg.Mail.Inbound.Enabled = enabled
88 if code := Dispatch(c, []string{"notifications", "settings", "show"}); code != protocol.ExitOK {
89 t.Fatalf("exit %d", code)
90 }
91 var env struct {
92 Data map[string]bool `json:"data"`
93 }
94 if err := json.Unmarshal(out.Bytes(), &env); err != nil {
95 t.Fatal(err)
96 }
97 if got, ok := env.Data["reply_available"]; !ok || got != enabled {
98 t.Errorf("inbound %v: reply_available %v (present %v)", enabled, got, ok)
99 }
100 }
101}