* feat: add --quote flag to include original message in replies Adds a --quote flag to gog gmail send that automatically quotes the original message when replying. Changes: - Added --quote flag to GmailSendCmd struct - Modified fetchReplyInfo to optionally fetch message body - Added formatQuotedMessage function for attribution and quoting - Updated validation to require reply target when --quote is used * feat(gmail): add --quote flag to gmail send command Add automatic message quoting when replying to emails. The new --quote flag includes the original message body with proper attribution line and quote prefixes when using --reply-to-message-id or --thread-id. Changes: - Add Quote bool flag to GmailSendCmd struct - Validate --quote requires reply target (--reply-to-message-id or --thread-id) - Modify fetchReplyInfo to support fetching full message format for quoting - Add Date and Body fields to replyInfo struct - Add formatQuotedMessage function to format original message as quote - Append quoted message to user's body when --quote is set - Update test calls to replyInfoFromMessage with new includeBody parameter - Use gmailFormatFull/gmailFormatMetadata constants instead of string literals - Use body += for appending instead of body = body + * feat(gmail): add HTML blockquote support for --quote feature * fix(gmail): include user body in HTML when using --quote * feat(gmail): preserve original HTML formatting when quoting * fix(gmail): handle --body-file and --body-html edge cases with --quote * fix(gmail): handle HTML-only messages when using --quote - Quote now works when original message has only HTML content (no plain text body) - Removed unused formatQuotedMessageHTML function Fixes edge cases where --quote would skip messages with HTML-only bodies. * fix(gmail): harden --quote body selection + watch includeBody format (#169) (thanks @terry-li-hm) --------- Co-authored-by: OpenCode <opencode@local> Co-authored-by: Peter Steinberger <steipete@gmail.com>
94 lines
2.7 KiB
Go
94 lines
2.7 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/steipete/gogcli/internal/outfmt"
|
|
"github.com/steipete/gogcli/internal/tracking"
|
|
"github.com/steipete/gogcli/internal/ui"
|
|
)
|
|
|
|
func TestResolveTrackingConfig(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "xdg"))
|
|
|
|
cmd := &GmailSendCmd{}
|
|
cmd.BodyHTML = "<html></html>"
|
|
|
|
// Multiple recipients without split should fail.
|
|
if _, err := cmd.resolveTrackingConfig("a@b.com", []string{"a@b.com", "b@b.com"}, nil, nil, cmd.BodyHTML); err == nil {
|
|
t.Fatalf("expected error for multiple recipients without split")
|
|
}
|
|
|
|
cmd.TrackSplit = true
|
|
cmd.BodyHTML = ""
|
|
if _, err := cmd.resolveTrackingConfig("a@b.com", []string{"a@b.com"}, nil, nil, cmd.BodyHTML); err == nil {
|
|
t.Fatalf("expected error for missing body html")
|
|
}
|
|
|
|
cmd.BodyHTML = "<html></html>"
|
|
if _, err := cmd.resolveTrackingConfig("a@b.com", []string{"a@b.com"}, nil, nil, cmd.BodyHTML); err == nil {
|
|
t.Fatalf("expected error for unconfigured tracking")
|
|
}
|
|
|
|
key, err := tracking.GenerateKey()
|
|
if err != nil {
|
|
t.Fatalf("GenerateKey: %v", err)
|
|
}
|
|
cfg := &tracking.Config{
|
|
Enabled: true,
|
|
WorkerURL: "https://example.com",
|
|
TrackingKey: key,
|
|
AdminKey: "admin",
|
|
}
|
|
err = tracking.SaveConfig("a@b.com", cfg)
|
|
if err != nil {
|
|
t.Fatalf("SaveConfig: %v", err)
|
|
}
|
|
|
|
got, err := cmd.resolveTrackingConfig("a@b.com", []string{"a@b.com"}, nil, nil, cmd.BodyHTML)
|
|
if err != nil {
|
|
t.Fatalf("resolveTrackingConfig: %v", err)
|
|
}
|
|
if got == nil || !got.IsConfigured() {
|
|
t.Fatalf("expected configured tracking, got %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestFirstRecipient(t *testing.T) {
|
|
if got := firstRecipient([]string{"a"}, []string{"b"}, []string{"c"}); got != "a" {
|
|
t.Fatalf("unexpected first recipient: %q", got)
|
|
}
|
|
if got := firstRecipient(nil, []string{"b"}, nil); got != "b" {
|
|
t.Fatalf("unexpected first recipient: %q", got)
|
|
}
|
|
if got := firstRecipient(nil, nil, []string{"c"}); got != "c" {
|
|
t.Fatalf("unexpected first recipient: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestWriteSendResults_JSONMultiple(t *testing.T) {
|
|
out := captureStdout(t, func() {
|
|
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
|
|
if err != nil {
|
|
t.Fatalf("ui.New: %v", err)
|
|
}
|
|
ctx := outfmt.WithMode(ui.WithUI(context.Background(), u), outfmt.Mode{JSON: true})
|
|
|
|
if err := writeSendResults(ctx, u, "from@example.com", []sendResult{
|
|
{MessageID: "m1", ThreadID: "t1", To: "a@example.com"},
|
|
{MessageID: "m2", ThreadID: "t2", To: "b@example.com"},
|
|
}); err != nil {
|
|
t.Fatalf("writeSendResults: %v", err)
|
|
}
|
|
})
|
|
if !strings.Contains(out, "\"messages\"") {
|
|
t.Fatalf("unexpected json output: %q", out)
|
|
}
|
|
}
|