@@ -626,6 +626,80 @@ func TestEncodePartForcedCTEWithContentReader(t *testing.T) {
626626 assert .Nil (t , p .ContentReader , "ContentReader should have been consumed" )
627627}
628628
629+ // TestEncodePartForcedCTEEmptyContent verifies that a forced Content-Transfer-Encoding is still
630+ // applied to the header when the body is empty -- either nil/zero-length Content or a ContentReader
631+ // that yields zero bytes. Previously the override was gated on len(Content) > 0 and was silently
632+ // dropped, contradicting the documented "forces" contract.
633+ func TestEncodePartForcedCTEEmptyContent (t * testing.T ) {
634+ for _ , tc := range []struct {
635+ name string
636+ cte string
637+ part func () * enmime.Part
638+ }{
639+ {
640+ name : "7bit nil content" ,
641+ cte : "7bit" ,
642+ part : func () * enmime.Part {
643+ p := enmime .NewPart ("application/pgp-encrypted" )
644+ p .ContentTransferEncoding = "7bit"
645+ return p
646+ },
647+ },
648+ {
649+ name : "base64 nil content" ,
650+ cte : "base64" ,
651+ part : func () * enmime.Part {
652+ p := enmime .NewPart ("application/octet-stream" )
653+ p .ContentTransferEncoding = "base64"
654+ return p
655+ },
656+ },
657+ {
658+ name : "7bit zero-byte reader" ,
659+ cte : "7bit" ,
660+ part : func () * enmime.Part {
661+ p := enmime .NewPart ("application/pgp-encrypted" )
662+ p .ContentTransferEncoding = "7bit"
663+ p .ContentReader = bytes .NewReader (nil )
664+ return p
665+ },
666+ },
667+ {
668+ name : "base64 zero-byte reader" ,
669+ cte : "base64" ,
670+ part : func () * enmime.Part {
671+ p := enmime .NewPart ("application/octet-stream" )
672+ p .ContentTransferEncoding = "base64"
673+ p .ContentReader = bytes .NewReader (nil )
674+ return p
675+ },
676+ },
677+ } {
678+ t .Run (tc .name , func (t * testing.T ) {
679+ p := tc .part ()
680+ b := & bytes.Buffer {}
681+ if err := p .Encode (b ); err != nil {
682+ t .Fatal (err )
683+ }
684+ assert .Equal (t , tc .cte , p .Header .Get ("Content-Transfer-Encoding" ))
685+ })
686+ }
687+ }
688+
689+ // TestEncodePartAutoCTEEmptyContent verifies that, when no CTE override is set, an empty body still
690+ // results in no Content-Transfer-Encoding header (RFC 2045: 7bit is assumed when the header is
691+ // absent). This guards against the forced-override refactor accidentally emitting a header for
692+ // empty auto-detected parts.
693+ func TestEncodePartAutoCTEEmptyContent (t * testing.T ) {
694+ p := enmime .NewPart ("text/plain" )
695+ // No ContentTransferEncoding set; Content is empty.
696+ b := & bytes.Buffer {}
697+ if err := p .Encode (b ); err != nil {
698+ t .Fatal (err )
699+ }
700+ assert .Equal (t , "" , p .Header .Get ("Content-Transfer-Encoding" ))
701+ }
702+
629703// TestEncodePartForcedCTEPrecedenceOverEncoderOption verifies that ContentTransferEncoding on the part
630704// takes precedence over the encoder-level ForceQuotedPrintableCte option. Without the override, the
631705// non-ASCII text content combined with ForceQuotedPrintableCte(true) would be QP-encoded; the forced
0 commit comments