2140d9e173
account list now routes to the agent role; an agent (EMCLI_KEY only) gets a JSON envelope of name/from/can_send, while the admin keeps the full text table. account add/edit/remove stay admin-only. Also emit the agent path's missing-key/open failure as a JSON Failure envelope (per spec), and update the stale run_test case that asserted the old admin-only behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
2.8 KiB
Go
86 lines
2.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// With only EMCLI_KEY set, `account list` emits the reduced JSON envelope:
|
|
// name/from/can_send, and never the IMAP host or login username.
|
|
func TestAccountListAgentJSONView(t *testing.T) {
|
|
adminEnv(t) // both keys + initialized temp DB
|
|
run(t, "account", "add", "--name", "work", "--mode", "RW",
|
|
"--imap-host", "imap.example.com", "--smtp-host", "smtp.example.com",
|
|
"--username", "login@example.com", "--from", "me@example.com")
|
|
run(t, "account", "add", "--name", "alerts", "--mode", "RO",
|
|
"--imap-host", "imap.example.com", "--username", "alerts@example.com")
|
|
|
|
// Drop the admin key → caller is an agent.
|
|
t.Setenv("EMCLI_ADMIN_KEY", "")
|
|
code, out, errOut := run(t, "account", "list")
|
|
if code != 0 {
|
|
t.Fatalf("agent account list should succeed: code=%d err=%q", code, errOut)
|
|
}
|
|
|
|
var env struct {
|
|
Error bool `json:"error"`
|
|
Data struct {
|
|
Accounts []struct {
|
|
Name string `json:"name"`
|
|
From string `json:"from"`
|
|
CanSend bool `json:"can_send"`
|
|
} `json:"accounts"`
|
|
} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal([]byte(out), &env); err != nil {
|
|
t.Fatalf("output is not the agent envelope: %v\n%s", err, out)
|
|
}
|
|
if env.Error || len(env.Data.Accounts) != 2 {
|
|
t.Fatalf("want 2 accounts and no error, got %+v", env)
|
|
}
|
|
// The reduced view must not leak the IMAP host or the login username.
|
|
if strings.Contains(out, "imap.example.com") || strings.Contains(out, "login@example.com") {
|
|
t.Fatalf("agent view leaked host/username:\n%s", out)
|
|
}
|
|
|
|
got := map[string]struct {
|
|
from string
|
|
canSend bool
|
|
}{}
|
|
for _, a := range env.Data.Accounts {
|
|
got[a.Name] = struct {
|
|
from string
|
|
canSend bool
|
|
}{a.From, a.CanSend}
|
|
}
|
|
if g := got["work"]; g.from != "me@example.com" || !g.canSend {
|
|
t.Errorf("work: want from=me@example.com can_send=true, got %+v", g)
|
|
}
|
|
// alerts has no --from → SendFrom() falls back to the username.
|
|
if g := got["alerts"]; g.from != "alerts@example.com" || g.canSend {
|
|
t.Errorf("alerts: want from=alerts@example.com can_send=false, got %+v", g)
|
|
}
|
|
}
|
|
|
|
// With the admin key present, `account list` stays the full human-readable table.
|
|
func TestAccountListAdminTextView(t *testing.T) {
|
|
adminEnv(t)
|
|
run(t, "account", "add", "--name", "work", "--mode", "RW",
|
|
"--imap-host", "imap.example.com", "--smtp-host", "smtp.example.com",
|
|
"--username", "login@example.com", "--from", "me@example.com")
|
|
|
|
code, out, _ := run(t, "account", "list")
|
|
if code != 0 {
|
|
t.Fatalf("admin account list failed: code=%d", code)
|
|
}
|
|
for _, want := range []string{"NAME", "MODE", "IMAP", "USER", "imap.example.com:993", "login@example.com"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Fatalf("admin view missing %q:\n%s", want, out)
|
|
}
|
|
}
|
|
if strings.Contains(out, `"accounts"`) {
|
|
t.Fatalf("admin view should be text, not JSON:\n%s", out)
|
|
}
|
|
}
|