| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- package agi
- import (
- "encoding/json"
- "errors"
- "strings"
- "testing"
- "github.com/robertkrimen/otto"
- "imuslab.com/arozos/mod/agi/static"
- "imuslab.com/arozos/mod/email"
- "imuslab.com/arozos/mod/permission"
- user "imuslab.com/arozos/mod/user"
- )
- // newEmailTestVM injects the email library for a user into a fresh VM.
- func newEmailTestVM(t *testing.T, manager *email.Manager, u *user.User) *otto.Otto {
- t.Helper()
- g := &Gateway{Option: &AgiSysInfo{EmailManager: manager}}
- vm := otto.New()
- g.injectEmailLibFunctions(&static.AgiLibInjectionPayload{VM: vm, User: u})
- return vm
- }
- func newEmailTestManager(t *testing.T) *email.Manager {
- t.Helper()
- manager, err := email.NewManager(email.Options{DataDir: t.TempDir(), DisableBackground: true})
- if err != nil {
- t.Fatalf("email.NewManager: %v", err)
- }
- t.Cleanup(manager.Close)
- return manager
- }
- // runEmailScript evaluates an expression and decodes its JSON result.
- func runEmailScript(t *testing.T, vm *otto.Otto, script string) map[string]interface{} {
- t.Helper()
- value, err := vm.Run("JSON.stringify(" + script + ")")
- if err != nil {
- t.Fatalf("script %q failed: %v", script, err)
- }
- text, _ := value.ToString()
- decoded := map[string]interface{}{}
- if err := json.Unmarshal([]byte(text), &decoded); err != nil {
- t.Fatalf("script %q returned %q: %v", script, text, err)
- }
- return decoded
- }
- func TestEmailLibWrapperDefinesEveryFunction(t *testing.T) {
- vm := newEmailTestVM(t, newEmailTestManager(t), &user.User{Username: "alice"})
- names := []string{
- "providers", "discover", "oauthProviders", "oauthStart", "oauthComplete", "oauthStatus", "oauthCancel",
- "listAccounts", "getAccount", "testAccount", "addAccount", "updateAccount", "removeAccount", "reorderAccounts",
- "folders", "createFolder", "renameFolder", "deleteFolder", "emptyFolder", "markAllRead",
- "list", "unified", "get", "rawSource", "setFlag", "move", "copy", "moveToRole", "remove", "locate",
- "checkInboxes", "newSince", "saveMessage", "saveAttachment", "saveAllAttachments", "openEml",
- "saveEmlAttachment", "importEml", "tempFolder", "send", "saveDraft", "deleteDraft", "outbox",
- "outboxCancel", "outboxSendNow", "contacts", "searchContacts", "saveContact", "deleteContact",
- "importContacts", "labels", "saveLabels", "setLabels", "labelMessages", "snooze", "unsnooze",
- "snoozed", "settings", "saveSettings", "trustSender", "isAdmin", "adminConfig", "setAdminConfig",
- }
- for _, name := range names {
- value, err := vm.Run("typeof email." + name)
- if err != nil {
- t.Fatalf("typeof email.%s: %v", name, err)
- }
- if kind, _ := value.ToString(); kind != "function" {
- t.Errorf("email.%s is %s, want function", name, kind)
- }
- }
- }
- func TestEmailLibEnvelopes(t *testing.T) {
- vm := newEmailTestVM(t, newEmailTestManager(t), &user.User{Username: "alice"})
- providers := runEmailScript(t, vm, `email.providers()`)
- if providers["success"] != true {
- t.Fatalf("providers = %v", providers)
- }
- if list, ok := providers["data"].([]interface{}); !ok || len(list) < 5 {
- t.Errorf("providers data = %v", providers["data"])
- }
- discovered := runEmailScript(t, vm, `email.discover("someone@gmail.com")`)
- data, _ := discovered["data"].(map[string]interface{})
- if discovered["success"] != true || data["provider"] != "gmail" {
- t.Errorf("discover = %v", discovered)
- }
- accounts := runEmailScript(t, vm, `email.listAccounts()`)
- if accounts["success"] != true {
- t.Errorf("listAccounts = %v", accounts)
- }
- missing := runEmailScript(t, vm, `email.list("no-such-account", {folder: "INBOX"})`)
- if missing["success"] != false || missing["code"] != "notfound" {
- t.Errorf("unknown account should fail with notfound, got %v", missing)
- }
- saved := runEmailScript(t, vm, `email.saveSettings({density: "compact", undoSendSeconds: 10})`)
- if saved["success"] != true {
- t.Fatalf("saveSettings = %v", saved)
- }
- settings := runEmailScript(t, vm, `email.settings()`)
- values, _ := settings["data"].(map[string]interface{})
- if values["density"] != "compact" || values["undoSendSeconds"] != float64(10) || values["theme"] != "system" {
- t.Errorf("settings = %v", values)
- }
- }
- func TestEmailLibAdminGate(t *testing.T) {
- manager := newEmailTestManager(t)
- regular := newEmailTestVM(t, manager, &user.User{Username: "bob"})
- if result := runEmailScript(t, regular, `email.adminConfig()`); result["success"] != false {
- t.Errorf("a regular user read the admin configuration: %v", result)
- }
- if result := runEmailScript(t, regular, `email.setAdminConfig({allowPrivateHosts: true})`); result["success"] != false {
- t.Errorf("a regular user changed the admin configuration: %v", result)
- }
- admin := newEmailTestVM(t, manager, &user.User{Username: "root", PermissionGroup: []*permission.PermissionGroup{{Name: "administrator", IsAdmin: true}}})
- if result := runEmailScript(t, admin, `email.setAdminConfig({allowPrivateHosts: true, microsoft: {enabled: true, clientId: "abc", flow: "device"}})`); result["success"] != true {
- t.Fatalf("admin setAdminConfig = %v", result)
- }
- config := runEmailScript(t, admin, `email.adminConfig()`)
- data, _ := config["data"].(map[string]interface{})
- if data["allowPrivateHosts"] != true {
- t.Errorf("admin config = %v", data)
- }
- if providers := runEmailScript(t, regular, `email.oauthProviders()`); !strings.Contains(mustJSON(providers), `"enabled":true`) {
- t.Errorf("the configured Microsoft sign-in is not offered: %v", providers)
- }
- }
- func TestEmailLibWithoutManager(t *testing.T) {
- vm := newEmailTestVM(t, nil, &user.User{Username: "alice"})
- result := runEmailScript(t, vm, `email.listAccounts()`)
- if result["success"] != false || !strings.Contains(result["error"].(string), "not enabled") {
- t.Errorf("listAccounts without a manager = %v", result)
- }
- }
- func TestEmailFailureClassification(t *testing.T) {
- vm := otto.New()
- tests := []struct {
- name string
- err error
- check func(map[string]interface{}) bool
- }{
- {"auth error carries hint", &email.AuthError{Server: "IMAP", Detail: "bad", Hint: "use an app password"}, func(m map[string]interface{}) bool {
- return m["authFailed"] == true && m["hint"] == "use an app password"
- }},
- {"not found", email.ErrMessageNotFound, func(m map[string]interface{}) bool { return m["code"] == "notfound" }},
- {"blocked", email.ErrBlockedAddress, func(m map[string]interface{}) bool { return m["code"] == "blocked" }},
- {"plain error", errors.New("boom"), func(m map[string]interface{}) bool {
- _, hasCode := m["code"]
- return m["error"] == "boom" && !hasCode && m["authFailed"] == nil
- }},
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- text, _ := emailFailureWith(vm, test.err, map[string]interface{}{"authFailed": false, "hint": ""}).ToString()
- decoded := map[string]interface{}{}
- if err := json.Unmarshal([]byte(text), &decoded); err != nil {
- t.Fatalf("invalid JSON %q", text)
- }
- if decoded["success"] != false || !test.check(decoded) {
- t.Errorf("envelope = %v", decoded)
- }
- })
- }
- }
- func TestEmailSafeName(t *testing.T) {
- tests := map[string]string{
- "../../secret.txt": "_.._secret.txt",
- "report.pdf": "report.pdf",
- " .. ": "attachment",
- "a\\b/c.txt": "a_b_c.txt",
- }
- for input, want := range tests {
- if got := emailSafeName(input); got != want {
- t.Errorf("emailSafeName(%q) = %q, want %q", input, got, want)
- }
- }
- }
- func mustJSON(value interface{}) string {
- encoded, _ := json.Marshal(value)
- return string(encoded)
- }
|