@@ -61,8 +61,8 @@ func TestExecute_GmailForward_DefaultSubjectAndAttachments(t *testing.T) {
6161 t .Fatalf ("ReadAll: %v" , err )
6262 }
6363 var msg gmail.Message
64- if err := json .Unmarshal (body , & msg ); err != nil {
65- t .Fatalf ("unmarshal: %v body=%q" , err , string (body ))
64+ if unmarshalErr := json .Unmarshal (body , & msg ); unmarshalErr != nil {
65+ t .Fatalf ("unmarshal: %v body=%q" , unmarshalErr , string (body ))
6666 }
6767 raw , err := base64 .RawURLEncoding .DecodeString (msg .Raw )
6868 if err != nil {
@@ -131,3 +131,279 @@ func TestExecute_GmailForward_DefaultSubjectAndAttachments(t *testing.T) {
131131 })
132132 })
133133}
134+
135+ func TestExecute_GmailForward_HTMLOnlyMessage (t * testing.T ) {
136+ origNew := newGmailService
137+ t .Cleanup (func () { newGmailService = origNew })
138+
139+ htmlBody := "<p>HTML <strong>body</strong></p>"
140+ htmlEncoded := base64 .RawURLEncoding .EncodeToString ([]byte (htmlBody ))
141+
142+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
143+ switch {
144+ case r .Method == http .MethodGet && strings .Contains (r .URL .Path , "/gmail/v1/users/me/messages/m2" ):
145+ w .Header ().Set ("Content-Type" , "application/json" )
146+ _ = json .NewEncoder (w ).Encode (map [string ]any {
147+ "id" : "m2" ,
148+ "threadId" : "t2" ,
149+ "payload" : map [string ]any {
150+ "headers" : []map [string ]any {
151+ {"name" : "From" , "value" : "sender@example.com" },
152+ {"name" : "To" , "value" : "you@example.com" },
153+ {"name" : "Subject" , "value" : "HTML Email" },
154+ {"name" : "Date" , "value" : "Wed, 17 Dec 2025 15:00:00 -0800" },
155+ },
156+ "parts" : []map [string ]any {
157+ {
158+ "mimeType" : "text/html" ,
159+ "body" : map [string ]any {"data" : htmlEncoded },
160+ },
161+ },
162+ },
163+ })
164+ return
165+ case r .Method == http .MethodPost && strings .Contains (r .URL .Path , "/gmail/v1/users/me/messages/send" ):
166+ body , err := io .ReadAll (r .Body )
167+ if err != nil {
168+ t .Fatalf ("ReadAll: %v" , err )
169+ }
170+ var msg gmail.Message
171+ if unmarshalErr := json .Unmarshal (body , & msg ); unmarshalErr != nil {
172+ t .Fatalf ("unmarshal: %v body=%q" , unmarshalErr , string (body ))
173+ }
174+ raw , err := base64 .RawURLEncoding .DecodeString (msg .Raw )
175+ if err != nil {
176+ t .Fatalf ("decode raw: %v" , err )
177+ }
178+ s := string (raw )
179+ if ! strings .Contains (s , "Subject: Fwd: HTML Email\r \n " ) {
180+ t .Fatalf ("missing forward subject in raw:\n %s" , s )
181+ }
182+ if ! strings .Contains (s , "-------- Forwarded message --------" ) {
183+ t .Fatalf ("missing forwarded header in raw:\n %s" , s )
184+ }
185+ if ! strings .Contains (s , "HTML body" ) {
186+ t .Fatalf ("missing stripped HTML content in plain part:\n %s" , s )
187+ }
188+ if ! strings .Contains (s , htmlBody ) {
189+ t .Fatalf ("missing original HTML in HTML part:\n %s" , s )
190+ }
191+ w .Header ().Set ("Content-Type" , "application/json" )
192+ _ = json .NewEncoder (w ).Encode (map [string ]any {"id" : "s2" , "threadId" : "t2" })
193+ return
194+ default :
195+ http .NotFound (w , r )
196+ return
197+ }
198+ }))
199+ defer srv .Close ()
200+
201+ svc , err := gmail .NewService (context .Background (),
202+ option .WithoutAuthentication (),
203+ option .WithHTTPClient (srv .Client ()),
204+ option .WithEndpoint (srv .URL + "/" ),
205+ )
206+ if err != nil {
207+ t .Fatalf ("NewService: %v" , err )
208+ }
209+ newGmailService = func (context.Context , string ) (* gmail.Service , error ) { return svc , nil }
210+
211+ _ = captureStdout (t , func () {
212+ _ = captureStderr (t , func () {
213+ if err := Execute ([]string {
214+ "--json" ,
215+ "--account" , "a@b.com" ,
216+ "gmail" , "forward" , "m2" , "to@example.com" ,
217+ "--body" , "Check this out." ,
218+ }); err != nil {
219+ t .Fatalf ("Execute: %v" , err )
220+ }
221+ })
222+ })
223+ }
224+
225+ func TestExecute_GmailForward_CustomSubject (t * testing.T ) {
226+ origNew := newGmailService
227+ t .Cleanup (func () { newGmailService = origNew })
228+
229+ bodyText := "Original body"
230+ bodyEncoded := base64 .RawURLEncoding .EncodeToString ([]byte (bodyText ))
231+
232+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
233+ switch {
234+ case r .Method == http .MethodGet && strings .Contains (r .URL .Path , "/gmail/v1/users/me/messages/m3" ):
235+ w .Header ().Set ("Content-Type" , "application/json" )
236+ _ = json .NewEncoder (w ).Encode (map [string ]any {
237+ "id" : "m3" ,
238+ "threadId" : "t3" ,
239+ "payload" : map [string ]any {
240+ "headers" : []map [string ]any {
241+ {"name" : "From" , "value" : "sender@example.com" },
242+ {"name" : "To" , "value" : "you@example.com" },
243+ {"name" : "Subject" , "value" : "Original Subject" },
244+ {"name" : "Date" , "value" : "Wed, 17 Dec 2025 16:00:00 -0800" },
245+ },
246+ "parts" : []map [string ]any {
247+ {
248+ "mimeType" : "text/plain" ,
249+ "body" : map [string ]any {"data" : bodyEncoded },
250+ },
251+ },
252+ },
253+ })
254+ return
255+ case r .Method == http .MethodPost && strings .Contains (r .URL .Path , "/gmail/v1/users/me/messages/send" ):
256+ body , err := io .ReadAll (r .Body )
257+ if err != nil {
258+ t .Fatalf ("ReadAll: %v" , err )
259+ }
260+ var msg gmail.Message
261+ if unmarshalErr := json .Unmarshal (body , & msg ); unmarshalErr != nil {
262+ t .Fatalf ("unmarshal: %v body=%q" , unmarshalErr , string (body ))
263+ }
264+ raw , err := base64 .RawURLEncoding .DecodeString (msg .Raw )
265+ if err != nil {
266+ t .Fatalf ("decode raw: %v" , err )
267+ }
268+ s := string (raw )
269+ if ! strings .Contains (s , "Subject: Custom Forward Subject\r \n " ) {
270+ t .Fatalf ("missing custom subject in raw:\n %s" , s )
271+ }
272+ if strings .Contains (s , "Subject: Fwd: Original Subject\r \n " ) {
273+ t .Fatalf ("should not contain default Fwd: subject:\n %s" , s )
274+ }
275+ if ! strings .Contains (s , "-------- Forwarded message --------" ) {
276+ t .Fatalf ("missing forwarded header in raw:\n %s" , s )
277+ }
278+ if ! strings .Contains (s , bodyText ) {
279+ t .Fatalf ("missing original body in raw:\n %s" , s )
280+ }
281+ w .Header ().Set ("Content-Type" , "application/json" )
282+ _ = json .NewEncoder (w ).Encode (map [string ]any {"id" : "s3" , "threadId" : "t3" })
283+ return
284+ default :
285+ http .NotFound (w , r )
286+ return
287+ }
288+ }))
289+ defer srv .Close ()
290+
291+ svc , err := gmail .NewService (context .Background (),
292+ option .WithoutAuthentication (),
293+ option .WithHTTPClient (srv .Client ()),
294+ option .WithEndpoint (srv .URL + "/" ),
295+ )
296+ if err != nil {
297+ t .Fatalf ("NewService: %v" , err )
298+ }
299+ newGmailService = func (context.Context , string ) (* gmail.Service , error ) { return svc , nil }
300+
301+ _ = captureStdout (t , func () {
302+ _ = captureStderr (t , func () {
303+ if err := Execute ([]string {
304+ "--json" ,
305+ "--account" , "a@b.com" ,
306+ "gmail" , "forward" , "m3" , "to@example.com" ,
307+ "--subject" , "Custom Forward Subject" ,
308+ "--body" , "FYI." ,
309+ }); err != nil {
310+ t .Fatalf ("Execute: %v" , err )
311+ }
312+ })
313+ })
314+ }
315+
316+ func TestExecute_GmailForward_CcAndBcc (t * testing.T ) {
317+ origNew := newGmailService
318+ t .Cleanup (func () { newGmailService = origNew })
319+
320+ bodyText := "Original body"
321+ bodyEncoded := base64 .RawURLEncoding .EncodeToString ([]byte (bodyText ))
322+
323+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
324+ switch {
325+ case r .Method == http .MethodGet && strings .Contains (r .URL .Path , "/gmail/v1/users/me/messages/m4" ):
326+ w .Header ().Set ("Content-Type" , "application/json" )
327+ _ = json .NewEncoder (w ).Encode (map [string ]any {
328+ "id" : "m4" ,
329+ "threadId" : "t4" ,
330+ "payload" : map [string ]any {
331+ "headers" : []map [string ]any {
332+ {"name" : "From" , "value" : "sender@example.com" },
333+ {"name" : "To" , "value" : "you@example.com" },
334+ {"name" : "Subject" , "value" : "Test Message" },
335+ {"name" : "Date" , "value" : "Wed, 17 Dec 2025 17:00:00 -0800" },
336+ },
337+ "parts" : []map [string ]any {
338+ {
339+ "mimeType" : "text/plain" ,
340+ "body" : map [string ]any {"data" : bodyEncoded },
341+ },
342+ },
343+ },
344+ })
345+ return
346+ case r .Method == http .MethodPost && strings .Contains (r .URL .Path , "/gmail/v1/users/me/messages/send" ):
347+ body , err := io .ReadAll (r .Body )
348+ if err != nil {
349+ t .Fatalf ("ReadAll: %v" , err )
350+ }
351+ var msg gmail.Message
352+ if unmarshalErr := json .Unmarshal (body , & msg ); unmarshalErr != nil {
353+ t .Fatalf ("unmarshal: %v body=%q" , unmarshalErr , string (body ))
354+ }
355+ raw , err := base64 .RawURLEncoding .DecodeString (msg .Raw )
356+ if err != nil {
357+ t .Fatalf ("decode raw: %v" , err )
358+ }
359+ s := string (raw )
360+ if ! strings .Contains (s , "To: to@example.com\r \n " ) {
361+ t .Fatalf ("missing To header in raw:\n %s" , s )
362+ }
363+ if ! strings .Contains (s , "Cc: cc1@example.com, cc2@example.com\r \n " ) {
364+ t .Fatalf ("missing Cc header in raw:\n %s" , s )
365+ }
366+ if ! strings .Contains (s , "Bcc: bcc@example.com\r \n " ) {
367+ t .Fatalf ("missing Bcc header in raw:\n %s" , s )
368+ }
369+ if ! strings .Contains (s , "-------- Forwarded message --------" ) {
370+ t .Fatalf ("missing forwarded header in raw:\n %s" , s )
371+ }
372+ if ! strings .Contains (s , bodyText ) {
373+ t .Fatalf ("missing original body in raw:\n %s" , s )
374+ }
375+ w .Header ().Set ("Content-Type" , "application/json" )
376+ _ = json .NewEncoder (w ).Encode (map [string ]any {"id" : "s4" , "threadId" : "t4" })
377+ return
378+ default :
379+ http .NotFound (w , r )
380+ return
381+ }
382+ }))
383+ defer srv .Close ()
384+
385+ svc , err := gmail .NewService (context .Background (),
386+ option .WithoutAuthentication (),
387+ option .WithHTTPClient (srv .Client ()),
388+ option .WithEndpoint (srv .URL + "/" ),
389+ )
390+ if err != nil {
391+ t .Fatalf ("NewService: %v" , err )
392+ }
393+ newGmailService = func (context.Context , string ) (* gmail.Service , error ) { return svc , nil }
394+
395+ _ = captureStdout (t , func () {
396+ _ = captureStderr (t , func () {
397+ if err := Execute ([]string {
398+ "--json" ,
399+ "--account" , "a@b.com" ,
400+ "gmail" , "forward" , "m4" , "to@example.com" ,
401+ "--cc" , "cc1@example.com,cc2@example.com" ,
402+ "--bcc" , "bcc@example.com" ,
403+ "--body" , "Important message." ,
404+ }); err != nil {
405+ t .Fatalf ("Execute: %v" , err )
406+ }
407+ })
408+ })
409+ }
0 commit comments