fix(cli): non-zero exit when an agent command emits an error envelope
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+14
-1
@@ -1,6 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,6 +10,10 @@ import (
|
|||||||
"git.dcglab.co.uk/steve/emcli/internal/store"
|
"git.dcglab.co.uk/steve/emcli/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// errCommandFailed signals that a command emitted an error envelope, so the
|
||||||
|
// process should exit non-zero. The JSON envelope is the authoritative detail.
|
||||||
|
var errCommandFailed = errors.New("command reported error")
|
||||||
|
|
||||||
// Mailer is the subset of the IMAP client the agent commands use.
|
// Mailer is the subset of the IMAP client the agent commands use.
|
||||||
type Mailer interface {
|
type Mailer interface {
|
||||||
SelectFolder(folder string) (uint32, uint32, error)
|
SelectFolder(folder string) (uint32, uint32, error)
|
||||||
@@ -26,7 +31,15 @@ type Deps struct {
|
|||||||
Out io.Writer
|
Out io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Deps) emit(e Envelope) error { return e.Write(d.Out) }
|
func (d Deps) emit(e Envelope) error {
|
||||||
|
if err := e.Write(d.Out); err != nil {
|
||||||
|
return err // write failure (rare) — propagate as-is
|
||||||
|
}
|
||||||
|
if e.Error {
|
||||||
|
return errCommandFailed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d Deps) audit(account, action, target, result, reason string) {
|
func (d Deps) audit(account, action, target, result, reason string) {
|
||||||
_ = d.Store.Audit(d.Now(), store.AuditEntry{
|
_ = d.Store.Audit(d.Now(), store.AuditEntry{
|
||||||
|
|||||||
@@ -122,9 +122,7 @@ func TestGetFilteredReturnsNotFound(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
d, buf := newDeps(t, fm)
|
d, buf := newDeps(t, fm)
|
||||||
if err := GetCmd(d, "work", "INBOX", 102); err != nil {
|
_ = GetCmd(d, "work", "INBOX", 102)
|
||||||
t.Fatalf("GetCmd: %v", err)
|
|
||||||
}
|
|
||||||
res := decode(t, buf.Bytes())
|
res := decode(t, buf.Bytes())
|
||||||
if res["error"] != true {
|
if res["error"] != true {
|
||||||
t.Fatal("filtered get must return error envelope")
|
t.Fatal("filtered get must return error envelope")
|
||||||
@@ -135,6 +133,25 @@ func TestGetFilteredReturnsNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetFilteredReturnsErrorForExit(t *testing.T) {
|
||||||
|
fm := &fakeMailer{
|
||||||
|
uidValidity: 1, maxUID: 100,
|
||||||
|
headers: []mail.Header{{UID: 102, From: "eve@evil.com", Subject: "spam"}},
|
||||||
|
full: map[uint32]mail.Message{
|
||||||
|
102: {Header: mail.Header{UID: 102, From: "eve@evil.com", Subject: "spam"}, BodyText: "secret"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
d, buf := newDeps(t, fm)
|
||||||
|
err := GetCmd(d, "work", "INBOX", 102)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("GetCmd on filtered uid must return non-nil error so caller can exit non-zero")
|
||||||
|
}
|
||||||
|
res := decode(t, buf.Bytes())
|
||||||
|
if res["error"] != true {
|
||||||
|
t.Fatalf("envelope must report error=true, got %v", res["error"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAckAdvancesStateAndFiltered(t *testing.T) {
|
func TestAckAdvancesStateAndFiltered(t *testing.T) {
|
||||||
fm := &fakeMailer{
|
fm := &fakeMailer{
|
||||||
uidValidity: 1, maxUID: 100,
|
uidValidity: 1, maxUID: 100,
|
||||||
@@ -145,9 +162,7 @@ func TestAckAdvancesStateAndFiltered(t *testing.T) {
|
|||||||
}
|
}
|
||||||
d, buf := newDeps(t, fm)
|
d, buf := newDeps(t, fm)
|
||||||
// Acking a filtered uid (102) must be rejected as not-found.
|
// Acking a filtered uid (102) must be rejected as not-found.
|
||||||
if err := AckCmd(d, "work", "INBOX", []uint32{102}); err != nil {
|
_ = AckCmd(d, "work", "INBOX", []uint32{102})
|
||||||
t.Fatalf("AckCmd: %v", err)
|
|
||||||
}
|
|
||||||
if decode(t, buf.Bytes())["error"] != true {
|
if decode(t, buf.Bytes())["error"] != true {
|
||||||
t.Fatal("acking filtered uid must fail")
|
t.Fatal("acking filtered uid must fail")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user