712 lines
21 KiB
Go
712 lines
21 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"google.golang.org/api/gmail/v1"
|
|
"google.golang.org/api/option"
|
|
|
|
"github.com/steipete/gogcli/internal/outfmt"
|
|
"github.com/steipete/gogcli/internal/ui"
|
|
)
|
|
|
|
func TestGmailDraftsListCmd_TextAndJSON(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts") && r.Method == http.MethodGet {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"drafts": []map[string]any{
|
|
{"id": "d1", "message": map[string]any{"id": "m1", "threadId": "t1"}},
|
|
{"id": "d2"},
|
|
},
|
|
"nextPageToken": "next",
|
|
})
|
|
return
|
|
}
|
|
http.NotFound(w, r)
|
|
}))
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
|
|
textOut := captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: os.Stdout, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{})
|
|
|
|
cmd := &GmailDraftsListCmd{}
|
|
if err := runKong(t, cmd, []string{}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
if !strings.Contains(textOut, "ID") || !strings.Contains(textOut, "d1") {
|
|
t.Fatalf("unexpected text: %q", textOut)
|
|
}
|
|
|
|
jsonOut := captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
|
|
|
cmd := &GmailDraftsListCmd{}
|
|
if err := runKong(t, cmd, []string{}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
|
|
var parsed struct {
|
|
Drafts []struct {
|
|
ID string `json:"id"`
|
|
MessageID string `json:"messageId"`
|
|
ThreadID string `json:"threadId"`
|
|
} `json:"drafts"`
|
|
NextPageToken string `json:"nextPageToken"`
|
|
}
|
|
if err := json.Unmarshal([]byte(jsonOut), &parsed); err != nil {
|
|
t.Fatalf("json parse: %v", err)
|
|
}
|
|
if len(parsed.Drafts) != 2 || parsed.Drafts[0].ID != "d1" || parsed.NextPageToken != "next" {
|
|
t.Fatalf("unexpected json: %#v", parsed)
|
|
}
|
|
}
|
|
|
|
func TestGmailDraftsGetCmd_Text(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
payloadText := base64.RawURLEncoding.EncodeToString([]byte("Hello"))
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts/d1") && r.Method == http.MethodGet {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{
|
|
"id": "m1",
|
|
"payload": map[string]any{
|
|
"mimeType": "multipart/mixed",
|
|
"headers": []map[string]any{
|
|
{"name": "To", "value": "a@example.com"},
|
|
{"name": "Cc", "value": "b@example.com"},
|
|
{"name": "Subject", "value": "Draft"},
|
|
},
|
|
"parts": []map[string]any{
|
|
{"mimeType": "text/plain", "body": map[string]any{"data": payloadText}},
|
|
{
|
|
"filename": "file.txt",
|
|
"mimeType": "text/plain",
|
|
"body": map[string]any{"attachmentId": "att1", "size": 10},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return
|
|
}
|
|
http.NotFound(w, r)
|
|
}))
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
|
|
out := captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: os.Stdout, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{})
|
|
|
|
cmd := &GmailDraftsGetCmd{}
|
|
if err := runKong(t, cmd, []string{"d1"}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
|
|
if !strings.Contains(out, "Draft-ID:") || !strings.Contains(out, "Subject:") {
|
|
t.Fatalf("unexpected output: %q", out)
|
|
}
|
|
if !strings.Contains(out, "Attachments:") || !strings.Contains(out, "file.txt") {
|
|
t.Fatalf("expected attachment output: %q", out)
|
|
}
|
|
}
|
|
|
|
func TestGmailDraftsDeleteCmd_JSON(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts/d1") && r.Method == http.MethodDelete {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
http.NotFound(w, r)
|
|
}))
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com", Force: true}
|
|
|
|
jsonOut := captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
|
|
|
cmd := &GmailDraftsDeleteCmd{}
|
|
if err := runKong(t, cmd, []string{"d1"}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
|
|
var parsed struct {
|
|
Deleted bool `json:"deleted"`
|
|
DraftID string `json:"draftId"`
|
|
}
|
|
if err := json.Unmarshal([]byte(jsonOut), &parsed); err != nil {
|
|
t.Fatalf("json parse: %v", err)
|
|
}
|
|
if !parsed.Deleted || parsed.DraftID != "d1" {
|
|
t.Fatalf("unexpected json: %#v", parsed)
|
|
}
|
|
}
|
|
|
|
func TestGmailDraftsSendCmd_Text(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts/send") && r.Method == http.MethodPost {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "m1",
|
|
"threadId": "t1",
|
|
})
|
|
return
|
|
}
|
|
http.NotFound(w, r)
|
|
}))
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
|
|
out := captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: os.Stdout, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{})
|
|
|
|
cmd := &GmailDraftsSendCmd{}
|
|
if err := runKong(t, cmd, []string{"d1"}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
|
|
if !strings.Contains(out, "message_id\tm1") || !strings.Contains(out, "thread_id\tt1") {
|
|
t.Fatalf("unexpected output: %q", out)
|
|
}
|
|
}
|
|
|
|
func TestGmailDraftsCreateCmd_JSON(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts") && r.Method == http.MethodPost {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{
|
|
"id": "m1",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
http.NotFound(w, r)
|
|
}))
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
|
|
jsonOut := captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
|
|
|
if err := runKong(t, &GmailDraftsCreateCmd{}, []string{"--to", "a@example.com", "--subject", "S", "--body", "Hello"}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
|
|
var parsed struct {
|
|
DraftID string `json:"draftId"`
|
|
ThreadID string `json:"threadId"`
|
|
}
|
|
if err := json.Unmarshal([]byte(jsonOut), &parsed); err != nil {
|
|
t.Fatalf("json parse: %v", err)
|
|
}
|
|
if parsed.DraftID != "d1" {
|
|
t.Fatalf("unexpected json: %#v", parsed)
|
|
}
|
|
}
|
|
|
|
func TestGmailDraftsCreateCmd_NoTo(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts") && r.Method == http.MethodPost {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("ReadAll: %v", err)
|
|
}
|
|
var draft gmail.Draft
|
|
if unmarshalErr := json.Unmarshal(body, &draft); unmarshalErr != nil {
|
|
t.Fatalf("unmarshal: %v body=%q", unmarshalErr, string(body))
|
|
}
|
|
if draft.Message == nil {
|
|
t.Fatalf("expected message in create")
|
|
}
|
|
raw, err := base64.RawURLEncoding.DecodeString(draft.Message.Raw)
|
|
if err != nil {
|
|
t.Fatalf("decode raw: %v", err)
|
|
}
|
|
s := string(raw)
|
|
if strings.Contains(s, "\r\nTo:") {
|
|
t.Fatalf("unexpected To header in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "Subject: S\r\n") {
|
|
t.Fatalf("missing Subject in raw:\n%s", s)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{
|
|
"id": "m1",
|
|
},
|
|
})
|
|
return
|
|
}
|
|
http.NotFound(w, r)
|
|
}))
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
|
|
_ = captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
|
|
|
if err := runKong(t, &GmailDraftsCreateCmd{}, []string{"--subject", "S", "--body", "Hello"}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGmailDraftsCreateCmd_WithFromAndReply(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
attachPath := filepath.Join(t.TempDir(), "note.txt")
|
|
if err := os.WriteFile(attachPath, []byte("hello"), 0o600); err != nil {
|
|
t.Fatalf("write attach: %v", err)
|
|
}
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "/settings/sendAs/alias@example.com") && r.Method == http.MethodGet:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"sendAsEmail": "alias@example.com",
|
|
"displayName": "Alias",
|
|
"verificationStatus": "accepted",
|
|
})
|
|
return
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/messages/m1") && r.Method == http.MethodGet:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "m1",
|
|
"threadId": "t1",
|
|
"payload": map[string]any{
|
|
"headers": []map[string]any{
|
|
{"name": "Message-ID", "value": "<msg@id>"},
|
|
{"name": "References", "value": "<ref@id>"},
|
|
},
|
|
},
|
|
})
|
|
return
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts") && r.Method == http.MethodPost:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{
|
|
"id": "m2",
|
|
},
|
|
})
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
|
|
|
_ = captureStdout(t, func() {
|
|
if err := runKong(t, &GmailDraftsCreateCmd{}, []string{
|
|
"--to", "a@example.com",
|
|
"--subject", "S",
|
|
"--body", "Hello",
|
|
"--from", "alias@example.com",
|
|
"--reply-to-message-id", "m1",
|
|
"--attach", attachPath,
|
|
}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGmailDraftsUpdateCmd_JSON(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
attData := []byte("attachment")
|
|
attachPath := filepath.Join(t.TempDir(), "note.txt")
|
|
if err := os.WriteFile(attachPath, attData, 0o600); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts/d1") && r.Method == http.MethodGet:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{"id": "m1", "threadId": "t1"},
|
|
})
|
|
return
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/threads/t1") && r.Method == http.MethodGet:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "t1",
|
|
"messages": []map[string]any{
|
|
{
|
|
"id": "m1",
|
|
"threadId": "t1",
|
|
"payload": map[string]any{
|
|
"headers": []map[string]any{
|
|
{"name": "Message-ID", "value": "<m1@example.com>"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts/d1") && r.Method == http.MethodPut:
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("ReadAll: %v", err)
|
|
}
|
|
var draft gmail.Draft
|
|
if unmarshalErr := json.Unmarshal(body, &draft); unmarshalErr != nil {
|
|
t.Fatalf("unmarshal: %v body=%q", unmarshalErr, string(body))
|
|
}
|
|
if draft.Message == nil {
|
|
t.Fatalf("expected message in update")
|
|
}
|
|
raw, err := base64.RawURLEncoding.DecodeString(draft.Message.Raw)
|
|
if err != nil {
|
|
t.Fatalf("decode raw: %v", err)
|
|
}
|
|
s := string(raw)
|
|
if !strings.Contains(s, "From: a@b.com\r\n") {
|
|
t.Fatalf("missing From in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "To: a@example.com\r\n") {
|
|
t.Fatalf("missing To in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "Cc: cc@example.com\r\n") {
|
|
t.Fatalf("missing Cc in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "Bcc: bcc@example.com\r\n") {
|
|
t.Fatalf("missing Bcc in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "Subject: Updated\r\n") {
|
|
t.Fatalf("missing Subject in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "Reply-To: reply@example.com\r\n") {
|
|
t.Fatalf("missing Reply-To in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "Hello") {
|
|
t.Fatalf("missing body in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, "Content-Disposition: attachment; filename=\"note.txt\"") {
|
|
t.Fatalf("missing attachment header in raw:\n%s", s)
|
|
}
|
|
if !strings.Contains(s, base64.StdEncoding.EncodeToString(attData)) {
|
|
t.Fatalf("missing attachment data in raw:\n%s", s)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{"id": "m2", "threadId": "t1"},
|
|
})
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
|
|
jsonOut := captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
|
|
|
if err := runKong(t, &GmailDraftsUpdateCmd{}, []string{
|
|
"d1",
|
|
"--to", "a@example.com",
|
|
"--cc", "cc@example.com",
|
|
"--bcc", "bcc@example.com",
|
|
"--subject", "Updated",
|
|
"--body", "Hello",
|
|
"--reply-to", "reply@example.com",
|
|
"--attach", attachPath,
|
|
}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
|
|
var parsed struct {
|
|
DraftID string `json:"draftId"`
|
|
ThreadID string `json:"threadId"`
|
|
}
|
|
if err := json.Unmarshal([]byte(jsonOut), &parsed); err != nil {
|
|
t.Fatalf("json parse: %v", err)
|
|
}
|
|
if parsed.DraftID != "d1" || parsed.ThreadID != "t1" {
|
|
t.Fatalf("unexpected json: %#v", parsed)
|
|
}
|
|
}
|
|
|
|
func TestGmailDraftsUpdateCmd_PreservesToWhenNotProvided(t *testing.T) {
|
|
origNew := newGmailService
|
|
t.Cleanup(func() { newGmailService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts/d1") && r.Method == http.MethodGet:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{
|
|
"id": "m1",
|
|
"threadId": "t1",
|
|
"payload": map[string]any{
|
|
"headers": []map[string]any{
|
|
{"name": "To", "value": "keep@example.com"},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/threads/t1") && r.Method == http.MethodGet:
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "t1",
|
|
"messages": []map[string]any{
|
|
{
|
|
"id": "m1",
|
|
"threadId": "t1",
|
|
"payload": map[string]any{
|
|
"headers": []map[string]any{
|
|
{"name": "Message-ID", "value": "<m1@example.com>"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return
|
|
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/drafts/d1") && r.Method == http.MethodPut:
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("ReadAll: %v", err)
|
|
}
|
|
var draft gmail.Draft
|
|
if unmarshalErr := json.Unmarshal(body, &draft); unmarshalErr != nil {
|
|
t.Fatalf("unmarshal: %v body=%q", unmarshalErr, string(body))
|
|
}
|
|
if draft.Message == nil {
|
|
t.Fatalf("expected message in update")
|
|
}
|
|
raw, err := base64.RawURLEncoding.DecodeString(draft.Message.Raw)
|
|
if err != nil {
|
|
t.Fatalf("decode raw: %v", err)
|
|
}
|
|
s := string(raw)
|
|
if !strings.Contains(s, "To: keep@example.com\r\n") {
|
|
t.Fatalf("expected preserved To in raw:\n%s", s)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"id": "d1",
|
|
"message": map[string]any{"id": "m2", "threadId": "t1"},
|
|
})
|
|
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 }
|
|
|
|
flags := &RootFlags{Account: "a@b.com"}
|
|
|
|
_ = captureStdout(t, func() {
|
|
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
|
if uiErr != nil {
|
|
t.Fatalf("ui.New: %v", uiErr)
|
|
}
|
|
ctx := ui.WithUI(context.Background(), u)
|
|
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
|
|
|
if err := runKong(t, &GmailDraftsUpdateCmd{}, []string{
|
|
"d1",
|
|
"--subject", "Updated",
|
|
"--body", "Hello",
|
|
}, ctx, flags); err != nil {
|
|
t.Fatalf("execute: %v", err)
|
|
}
|
|
})
|
|
}
|