Thanks @salmonumbrella. Co-authored-by: salmonumbrella <salmonumbrella@users.noreply.github.com>
251 lines
7.8 KiB
Go
251 lines
7.8 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"google.golang.org/api/gmail/v1"
|
|
"google.golang.org/api/option"
|
|
)
|
|
|
|
func TestGmailThreadGetAndAttachments_JSON(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
attachmentData := base64.RawURLEncoding.EncodeToString([]byte("payload"))
|
|
threadResp := map[string]any{
|
|
"id": "t1",
|
|
"messages": []map[string]any{
|
|
{
|
|
"id": "m1",
|
|
"payload": map[string]any{
|
|
"headers": []map[string]any{
|
|
{"name": "From", "value": "a@example.com"},
|
|
{"name": "To", "value": "b@example.com"},
|
|
{"name": "Subject", "value": "Hi"},
|
|
{"name": "Date", "value": "Mon, 1 Jan 2025 00:00:00 +0000"},
|
|
},
|
|
"mimeType": "multipart/mixed",
|
|
"parts": []map[string]any{
|
|
{
|
|
"mimeType": "text/plain",
|
|
"body": map[string]any{
|
|
"data": base64.RawURLEncoding.EncodeToString([]byte("hello")),
|
|
},
|
|
},
|
|
{
|
|
"filename": "note.txt",
|
|
"mimeType": "text/plain",
|
|
"body": map[string]any{
|
|
"attachmentId": "att1",
|
|
"size": 7,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
emptyThreadResp := map[string]any{
|
|
"id": "empty",
|
|
"messages": []map[string]any{},
|
|
}
|
|
noAttsThreadResp := map[string]any{
|
|
"id": "noatts",
|
|
"messages": []map[string]any{
|
|
{
|
|
"id": "m2",
|
|
"payload": map[string]any{
|
|
"mimeType": "text/plain",
|
|
"body": map[string]any{
|
|
"data": base64.RawURLEncoding.EncodeToString([]byte("hello")),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path := strings.TrimPrefix(r.URL.Path, "/gmail/v1")
|
|
switch {
|
|
case r.Method == http.MethodGet && path == "/users/me/threads/t1":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(threadResp)
|
|
return
|
|
case r.Method == http.MethodGet && path == "/users/me/threads/empty":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(emptyThreadResp)
|
|
return
|
|
case r.Method == http.MethodGet && path == "/users/me/threads/noatts":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(noAttsThreadResp)
|
|
return
|
|
case r.Method == http.MethodGet && path == "/users/me/messages/m1/attachments/att1":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"data": attachmentData,
|
|
})
|
|
return
|
|
default:
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
svc, err := gmail.NewService(context.Background(),
|
|
option.WithoutAuthentication(),
|
|
option.WithHTTPClient(srv.Client()),
|
|
option.WithEndpoint(srv.URL+"/"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewService: %v", err)
|
|
}
|
|
newGmailService = func(context.Context, string) (*gmail.Service, error) { return svc, nil }
|
|
|
|
outDir := t.TempDir()
|
|
getOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--json", "--account", "a@b.com", "gmail", "thread", "get", "t1", "--download", "--out-dir", outDir}); err != nil {
|
|
t.Fatalf("Execute thread get: %v", err)
|
|
}
|
|
})
|
|
})
|
|
|
|
var payload struct {
|
|
Thread map[string]any `json:"thread"`
|
|
Downloaded []map[string]any `json:"downloaded"`
|
|
}
|
|
if err := json.Unmarshal([]byte(getOut), &payload); err != nil {
|
|
t.Fatalf("decode thread json: %v", err)
|
|
}
|
|
if payload.Thread == nil || len(payload.Downloaded) != 1 {
|
|
t.Fatalf("unexpected thread payload: %#v", payload)
|
|
}
|
|
path, ok := payload.Downloaded[0]["path"].(string)
|
|
if !ok || path == "" {
|
|
t.Fatalf("expected download path, got: %#v", payload.Downloaded)
|
|
}
|
|
if _, statErr := os.Stat(path); statErr != nil {
|
|
t.Fatalf("expected downloaded file: %v", statErr)
|
|
}
|
|
|
|
attachmentsOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--json", "--account", "a@b.com", "gmail", "thread", "attachments", "t1"}); err != nil {
|
|
t.Fatalf("Execute attachments: %v", err)
|
|
}
|
|
})
|
|
})
|
|
var attachments struct {
|
|
ThreadID string `json:"threadId"`
|
|
Attachments []map[string]any `json:"attachments"`
|
|
}
|
|
if err := json.Unmarshal([]byte(attachmentsOut), &attachments); err != nil {
|
|
t.Fatalf("decode attachments json: %v", err)
|
|
}
|
|
if attachments.ThreadID != "t1" || len(attachments.Attachments) != 1 {
|
|
t.Fatalf("unexpected attachments payload: %#v", attachments)
|
|
}
|
|
if attachments.Attachments[0]["filename"] != "note.txt" {
|
|
t.Fatalf("unexpected attachment filename: %#v", attachments.Attachments[0])
|
|
}
|
|
|
|
attachmentsDownloadOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--json", "--account", "a@b.com", "gmail", "thread", "attachments", "t1", "--download", "--out-dir", outDir}); err != nil {
|
|
t.Fatalf("Execute attachments download: %v", err)
|
|
}
|
|
})
|
|
})
|
|
var attachmentsDownloaded struct {
|
|
Attachments []map[string]any `json:"attachments"`
|
|
}
|
|
if err := json.Unmarshal([]byte(attachmentsDownloadOut), &attachmentsDownloaded); err != nil {
|
|
t.Fatalf("decode attachments download: %v", err)
|
|
}
|
|
if len(attachmentsDownloaded.Attachments) != 1 {
|
|
t.Fatalf("unexpected download attachments: %#v", attachmentsDownloaded.Attachments)
|
|
}
|
|
if _, ok := attachmentsDownloaded.Attachments[0]["path"]; !ok {
|
|
t.Fatalf("expected download path in attachments: %#v", attachmentsDownloaded.Attachments[0])
|
|
}
|
|
|
|
plainOutDir := t.TempDir()
|
|
plainDownloadOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--account", "a@b.com", "gmail", "thread", "attachments", "t1", "--download", "--out-dir", plainOutDir}); err != nil {
|
|
t.Fatalf("Execute attachments download plain: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(plainDownloadOut, "Saved") {
|
|
t.Fatalf("unexpected download output: %q", plainDownloadOut)
|
|
}
|
|
|
|
cachedOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--account", "a@b.com", "gmail", "thread", "attachments", "t1", "--download", "--out-dir", plainOutDir}); err != nil {
|
|
t.Fatalf("Execute attachments cached: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(cachedOut, "Cached") {
|
|
t.Fatalf("unexpected cached output: %q", cachedOut)
|
|
}
|
|
|
|
// Ensure path is within the requested output dir when downloading attachments.
|
|
if !strings.HasPrefix(path, filepath.Clean(outDir)+string(os.PathSeparator)) {
|
|
t.Fatalf("unexpected download path: %s", path)
|
|
}
|
|
|
|
plainOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--account", "a@b.com", "gmail", "thread", "get", "t1"}); err != nil {
|
|
t.Fatalf("Execute thread get plain: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(plainOut, "Thread contains") {
|
|
t.Fatalf("unexpected plain output: %q", plainOut)
|
|
}
|
|
|
|
emptyErr := captureStderr(t, func() {
|
|
if err := Execute([]string{"--account", "a@b.com", "gmail", "thread", "get", "empty"}); err != nil {
|
|
t.Fatalf("Execute empty thread: %v", err)
|
|
}
|
|
})
|
|
if !strings.Contains(emptyErr, "Empty thread") {
|
|
t.Fatalf("unexpected empty thread stderr: %q", emptyErr)
|
|
}
|
|
|
|
noAttsOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--account", "a@b.com", "gmail", "thread", "attachments", "noatts"}); err != nil {
|
|
t.Fatalf("Execute no attachments: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(noAttsOut, "No attachments found") {
|
|
t.Fatalf("unexpected no attachments output: %q", noAttsOut)
|
|
}
|
|
|
|
emptyAttachOut := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--json", "--account", "a@b.com", "gmail", "thread", "attachments", "empty"}); err != nil {
|
|
t.Fatalf("Execute empty attachments json: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(emptyAttachOut, "\"attachments\"") {
|
|
t.Fatalf("unexpected empty attachments output: %q", emptyAttachOut)
|
|
}
|
|
}
|