agentbbs/internal/mailclients/mailclients_test.go
Anthony Ettinger b51fa5c0e6
Add himalaya + meli mail clients and Shedding Snake arcade game (#88)
Mail clients (internal/mailclients): launch himalaya and meli as
alternatives to the built-in AgentMail reader, pointed at the member's
mailbox with the same master IMAP / loopback-SMTP creds. They run
host-side (secrets never enter a pod), from a throwaway per-session
config that is deleted on exit; operators can override the config
template and argv via env. Wired into the hub ("Mail · Himalaya/Meli",
locked when the binary is absent) and the mail@ route
(ssh -t mail@host himalaya|meli).

Shedding Snake (plugins/arcade): a molting twist on Snake inspired by
cha.rlie.co/shedding-snake. The snake barely grows — each apple sheds
its whole body as a permanent field of scales you must not bite. Walls
wrap, speed ramps up, scales age fresh-teal → dusty-gray, and molt
counts feed a global "shedsnake" leaderboard. Grace period lets you
slither off a fresh molt.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 02:30:12 -07:00

126 lines
3.6 KiB
Go

package mailclients
import (
"os"
"strings"
"testing"
)
func testBackend() Backend {
return Backend{
Name: "alice",
Address: "alice@bbs.profullstack.com",
DisplayName: "alice",
IMAPHost: "127.0.0.1",
IMAPPort: 143,
IMAPPlaintext: true,
SMTPHost: "127.0.0.1",
SMTPPort: 25,
SMTPPlaintext: true,
Login: "alice@bbs.profullstack.com*gateway",
Password: "s3cr3t",
}
}
func TestHimalayaTemplateRenders(t *testing.T) {
out := render(Himalaya.template, testBackend().placeholders())
for _, want := range []string{
`email = "alice@bbs.profullstack.com"`,
`backend.host = "127.0.0.1"`,
`backend.port = 143`,
`backend.encryption.type = "none"`, // plaintext IMAP
`backend.login = "alice@bbs.profullstack.com*gateway"`,
`backend.auth.raw = "s3cr3t"`,
`message.send.backend.port = 25`,
`message.send.backend.encryption.type = "none"`, // loopback relay
} {
if !strings.Contains(out, want) {
t.Errorf("himalaya config missing %q\n---\n%s", want, out)
}
}
// No placeholder should survive rendering.
if strings.Contains(out, "{{") {
t.Errorf("unrendered placeholder in himalaya config:\n%s", out)
}
}
func TestMeliTemplateRenders(t *testing.T) {
out := render(Meli.template, testBackend().placeholders())
for _, want := range []string{
`identity = "alice@bbs.profullstack.com"`,
`server_hostname = "127.0.0.1"`,
`server_username = "alice@bbs.profullstack.com*gateway"`,
`server_password = "s3cr3t"`,
`server_port = 143`,
`port = 25`,
`type = "none"`,
} {
if !strings.Contains(out, want) {
t.Errorf("meli config missing %q\n---\n%s", want, out)
}
}
if strings.Contains(out, "{{") {
t.Errorf("unrendered placeholder in meli config:\n%s", out)
}
}
func TestEncryptionSwitchesWithTLS(t *testing.T) {
be := testBackend()
be.IMAPPlaintext = false
be.SMTPPlaintext = false
out := render(Himalaya.template, be.placeholders())
if !strings.Contains(out, `backend.encryption.type = "tls"`) {
t.Errorf("expected tls IMAP encryption, got:\n%s", out)
}
if !strings.Contains(out, `message.send.backend.encryption.type = "start-tls"`) {
t.Errorf("expected start-tls SMTP encryption, got:\n%s", out)
}
}
func TestConfigTemplateOverride(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "tmpl-*.toml")
if err != nil {
t.Fatal(err)
}
if _, err := f.WriteString("custom {{address}}\n"); err != nil {
t.Fatal(err)
}
_ = f.Close()
t.Setenv(Himalaya.tmplEnv, f.Name())
tmpl, err := Himalaya.configTemplate()
if err != nil {
t.Fatal(err)
}
out := render(tmpl, testBackend().placeholders())
if out != "custom alice@bbs.profullstack.com\n" {
t.Errorf("override not used, got %q", out)
}
}
func TestArgvOverride(t *testing.T) {
// Default argv places -c <config>.
got := Meli.argv("/usr/bin/meli", "/cfg/x.toml")
want := []string{"/usr/bin/meli", "-c", "/cfg/x.toml"}
if strings.Join(got, " ") != strings.Join(want, " ") {
t.Errorf("default argv = %v, want %v", got, want)
}
// Override with a {{config}} placeholder.
t.Setenv(Meli.argsEnv, "--config {{config}} envelope list")
got = Meli.argv("/usr/bin/meli", "/cfg/x.toml")
want = []string{"/usr/bin/meli", "--config", "/cfg/x.toml", "envelope", "list"}
if strings.Join(got, " ") != strings.Join(want, " ") {
t.Errorf("override argv = %v, want %v", got, want)
}
}
func TestBinaryEnvOverrideMissing(t *testing.T) {
t.Setenv(Himalaya.binEnv, "/nonexistent/himalaya-binary")
if _, err := Himalaya.binary(); err == nil {
t.Error("expected error for missing binary override")
}
if Himalaya.Available() {
t.Error("Available() should be false when the override path is missing")
}
}