Files
larksuite-cli/shortcuts/mail/draft/patch_header_test.go
tuxedomm e64d24580a refactor: migrate mail shortcuts to FileIO (#356)
* refactor: migrate mail shortcuts to FileIO

- DraftSnapshot.FIO: inject FileIO into draft snapshot for patch ops
  (addAttachment, loadAndAttachInline, replaceInline)
- emlbuilder.Builder.fio: inject via WithFileIO(), readFile uses FileIO.Open
- mail_draft_edit: loadPatchFile uses runtime.FileIO().Open
- helpers: checkAttachmentSizeLimit takes fio param, uses FileIO.Stat
- validateComposeInlineAndAttachments: pass fio through to size check
- All mail entry points (send/reply/reply_all/forward/draft_create):
  pass runtime.FileIO() to builder and size limit checks
2026-04-09 17:40:30 +08:00

208 lines
5.9 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package draft
import (
"strings"
"testing"
)
// ---------------------------------------------------------------------------
// set_reply_to / clear_reply_to
// ---------------------------------------------------------------------------
func TestApplySetReplyTo(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{
Op: "set_reply_to",
Addresses: []Address{{Name: "Support", Address: "support@example.com"}},
}},
})
if err != nil {
t.Fatalf("Apply() error = %v", err)
}
if len(snapshot.ReplyTo) != 1 || snapshot.ReplyTo[0].Address != "support@example.com" {
t.Fatalf("ReplyTo = %#v", snapshot.ReplyTo)
}
}
func TestApplyClearReplyTo(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
Reply-To: Support <support@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
if len(snapshot.ReplyTo) == 0 {
t.Fatalf("ReplyTo should be set before clear")
}
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "clear_reply_to"}},
})
if err != nil {
t.Fatalf("Apply() error = %v", err)
}
if len(snapshot.ReplyTo) != 0 {
t.Fatalf("ReplyTo = %#v, want empty", snapshot.ReplyTo)
}
if got := headerValue(snapshot.Headers, "Reply-To"); got != "" {
t.Fatalf("Reply-To header still present: %q", got)
}
}
// ---------------------------------------------------------------------------
// remove_header
// ---------------------------------------------------------------------------
func TestApplyRemoveHeader(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
X-Priority: 1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "remove_header", Name: "X-Priority"}},
})
if err != nil {
t.Fatalf("Apply() error = %v", err)
}
if got := headerValue(snapshot.Headers, "X-Priority"); got != "" {
t.Fatalf("X-Priority still present: %q", got)
}
}
func TestApplyRemoveProtectedHeaderBlocked(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "remove_header", Name: "Content-Type"}},
})
if err == nil || !strings.Contains(err.Error(), "protected") {
t.Fatalf("error = %v, want protected header error", err)
}
}
func TestApplyRemoveProtectedHeaderWithOverride(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
Reply-To: Old <old@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "remove_header", Name: "Reply-To"}},
Options: PatchOptions{AllowProtectedHeaderEdits: true},
})
if err != nil {
t.Fatalf("Apply() error = %v", err)
}
if got := headerValue(snapshot.Headers, "Reply-To"); got != "" {
t.Fatalf("Reply-To still present: %q", got)
}
}
// ---------------------------------------------------------------------------
// set_header validation — name with colon, CR/LF
// ---------------------------------------------------------------------------
func TestApplySetHeaderRejectsNameWithColon(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "set_header", Name: "Bad:Name", Value: "value"}},
})
if err == nil || !strings.Contains(err.Error(), "must not contain") {
t.Fatalf("error = %v, want header name rejection", err)
}
}
func TestApplySetHeaderRejectsValueWithCRLF(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "set_header", Name: "X-Custom", Value: "val\r\ninjected"}},
})
if err == nil || !strings.Contains(err.Error(), "must not contain") {
t.Fatalf("error = %v, want header value rejection", err)
}
}
// ---------------------------------------------------------------------------
// set_subject with CR/LF
// ---------------------------------------------------------------------------
func TestApplySetSubjectRejectsCRLF(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "set_subject", Value: "Subject\ninjection"}},
})
if err == nil || !strings.Contains(err.Error(), "must not contain") {
t.Fatalf("error = %v, want subject rejection", err)
}
}
// ---------------------------------------------------------------------------
// unsupported op
// ---------------------------------------------------------------------------
func TestApplyUnsupportedOpReturnsError(t *testing.T) {
snapshot := mustParseFixtureDraft(t, `Subject: Test
From: Alice <alice@example.com>
To: Bob <bob@example.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
hello
`)
err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{
Ops: []PatchOp{{Op: "unknown_op"}},
})
if err == nil || !strings.Contains(err.Error(), "unsupported") {
t.Fatalf("error = %v, want unsupported op error", err)
}
}