gogcli/internal/cmd/gmail_send_tracking_test.go
Terry Li 8acdc43419
feat: add --quote flag to include original message in replies (#169)
* 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>
2026-02-14 21:51:09 +01:00

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)
}
}