package cli import ( "errors" "path/filepath" "strings" "testing" "git.dcglab.co.uk/steve/emcli/internal/store" ) func doctorDeps(t *testing.T, accounts []store.Account, imap, smtp func(store.Account) error) (Deps, *[]byte) { t.Helper() st, err := store.Open(filepath.Join(t.TempDir(), "e.db"), testKey()) if err != nil { t.Fatalf("store: %v", err) } t.Cleanup(func() { st.Close() }) for _, a := range accounts { if _, err := st.AddAccount(a); err != nil { t.Fatalf("AddAccount %s: %v", a.Name, err) } } buf := &[]byte{} d := Deps{Store: st, CheckIMAP: imap, CheckSMTP: smtp, Out: bufWriter{buf}} return d, buf } func roAcc(name string) store.Account { return store.Account{Name: name, Mode: "RO", IMAPHost: "h", IMAPPort: 993, IMAPSecurity: "tls", AuthType: "password", Username: "u@x.com", Password: "p"} } func rwAcc(name string) store.Account { a := roAcc(name) a.Mode = "RW" a.SMTPHost, a.SMTPPort, a.SMTPSecurity = "h", 465, "tls" return a } func TestDoctorAllOK(t *testing.T) { ok := func(store.Account) error { return nil } d, buf := doctorDeps(t, []store.Account{rwAcc("work")}, ok, ok) if err := DoctorCmd(d, ""); err != nil { t.Fatalf("DoctorCmd returned error when all checks pass: %v", err) } out := string(*buf) if !strings.Contains(out, "work") || strings.Contains(strings.ToLower(out), "fail") { t.Fatalf("unexpected report:\n%s", out) } } func TestDoctorReportsSMTPFailure(t *testing.T) { ok := func(store.Account) error { return nil } bad := func(store.Account) error { return errors.New("auth rejected") } d, buf := doctorDeps(t, []store.Account{rwAcc("work")}, ok, bad) err := DoctorCmd(d, "") if err == nil { t.Fatal("DoctorCmd must return error when a check fails (non-zero exit)") } out := string(*buf) if !strings.Contains(strings.ToLower(out), "fail") || !strings.Contains(out, "auth rejected") { t.Fatalf("failure not reported:\n%s", out) } } func TestDoctorSkipsSMTPForRO(t *testing.T) { ok := func(store.Account) error { return nil } smtpCalled := false smtp := func(store.Account) error { smtpCalled = true; return nil } d, _ := doctorDeps(t, []store.Account{roAcc("ro")}, ok, smtp) if err := DoctorCmd(d, ""); err != nil { t.Fatalf("DoctorCmd: %v", err) } if smtpCalled { t.Fatal("SMTP check must be skipped for RO accounts") } } func TestDoctorUsesDecryptedCredentials(t *testing.T) { // roAcc has Password "p". ListAccounts strips secrets, so doctor must // re-fetch the decrypted account before checking — otherwise the live // check runs with an empty password. var gotPassword string imap := func(a store.Account) error { gotPassword = a.Password; return nil } ok := func(store.Account) error { return nil } d, _ := doctorDeps(t, []store.Account{roAcc("work")}, imap, ok) if err := DoctorCmd(d, ""); err != nil { t.Fatalf("DoctorCmd: %v", err) } if gotPassword != "p" { t.Fatalf("check received password %q, want decrypted \"p\"", gotPassword) } } func TestDoctorFiltersByAccount(t *testing.T) { ok := func(store.Account) error { return nil } checked := map[string]bool{} imap := func(a store.Account) error { checked[a.Name] = true; return nil } d, _ := doctorDeps(t, []store.Account{roAcc("a"), roAcc("b")}, imap, ok) if err := DoctorCmd(d, "b"); err != nil { t.Fatalf("DoctorCmd: %v", err) } if checked["a"] || !checked["b"] { t.Fatalf("account filter wrong: %v", checked) } }