blob: 87330a7bff699e2a772058bf8c9c63a10a99136b [file] [log] [blame]
package email
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/json"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/testutils/unittest"
"google.golang.org/api/gmail/v1"
)
const subject = "An Alert!"
const expectedMessage = `From: Alert Service <alerts@skia.org>
To: test@example.com
Subject: An Alert!
Content-Type: text/html; charset=UTF-8
References: <some-reference-id>
In-Reply-To: <some-reference-id>
<html>
<body>
<div itemscope itemtype="http://schema.org/EmailMessage">
<div itemprop="potentialAction" itemscope itemtype="http://schema.org/ViewAction">
<link itemprop="target" href="https://example.com"/>
<meta itemprop="name" content="Example"/>
</div>
<meta itemprop="description" content="Click the link"/>
</div>
<h1>Something happened</h1>
</body>
</html>
`
// Mock out the http.Client by supplying our own http.RoundTripper.
type myTransport struct {
t *testing.T
}
// RoundTrip implements http.RoundTripper.
func (m *myTransport) RoundTrip(r *http.Request) (*http.Response, error) {
// Check the URL.
require.Equal(m.t, "https://gmail.googleapis.com/gmail/v1/users/alerts%40skia.org/messages/send?alt=json&prettyPrint=false", r.URL.String())
// Confirm the body, which is a JSON encoded gmail.Message, is correct.
var msg gmail.Message
err := json.NewDecoder(r.Body).Decode(&msg)
require.NoError(m.t, err)
require.Equal(m.t, subject, msg.Snippet)
require.Equal(m.t, int64(563), msg.SizeEstimate)
// The actual RFC2822 message is base64 encoded in the .Raw member.
b, err := base64.URLEncoding.DecodeString(msg.Raw)
require.NoError(m.t, err)
require.Equal(m.t, expectedMessage, string(b))
// Now supply a response that the GMail client library will accept.
buf := bytes.NewBufferString(`HTTP/1.1 200 OK
Date: Mon, 27 Jul 2020 12:28:53 GMT
Last-Modified: Wed, 22 Jul 2020 19:15:56 GMT
Content-Type: text/html
{
"id": "some-email-id"
}`)
return http.ReadResponse(bufio.NewReader(buf), r)
}
var _ http.RoundTripper = (*myTransport)(nil)
func TestGMailSendWithMarkup(t *testing.T) {
unittest.SmallTest(t)
c := httputils.DefaultClientConfig().Client()
// Swap in our mock transport.
c.Transport = &myTransport{t: t}
// Construct a new *GMail instance.
service, err := gmail.New(c)
require.NoError(t, err)
gm := &GMail{
service: service,
from: "alerts@skia.org",
}
markup, err := GetViewActionMarkup("https://example.com", "Example", "Click the link")
require.NoError(t, err)
ref := "<some-reference-id>"
// Send email, the validation happens in myTransport.
_, err = gm.SendWithMarkup("Alert Service", []string{"test@example.com"}, subject, "<h1>Something happened</h1>", markup, ref)
require.NoError(t, err)
}
func TestParseRFC2822Message_HappyPath(t *testing.T) {
unittest.SmallTest(t)
from, to, subject, body, err := ParseRFC2822Message([]byte(`From: Alerts <alerts@skia.org>
To: A Display Name <a@example.com>, B <b@example.org>,,
Subject: My Stuff
Hi!
`))
require.NoError(t, err)
require.Equal(t, "Alerts <alerts@skia.org>", from)
require.Equal(t, []string{"A Display Name <a@example.com>", "B <b@example.org>"}, to)
require.Equal(t, "My Stuff", subject)
require.Equal(t, "Hi!\n", body)
}
func TestParseRFC2822Message_EmptyInput_ReturnsError(t *testing.T) {
unittest.SmallTest(t)
_, _, _, _, err := ParseRFC2822Message([]byte(``))
require.Contains(t, err.Error(), "Failed to find a From: line")
}
func TestParseRFC2822Message_EmptyToLine_ReturnsError(t *testing.T) {
unittest.SmallTest(t)
_, _, _, _, err := ParseRFC2822Message([]byte(`From: Alerts <alerts@skia.org>
To: ,,,
Subject: My Stuff
Hi!
`))
require.Contains(t, err.Error(), "Failed to find any To: addresses")
}
func TestParseRFC2822Message_MissingSubject_DefaultSubjectIsReturned(t *testing.T) {
unittest.SmallTest(t)
from, to, subject, body, err := ParseRFC2822Message([]byte(`From: Alerts <alerts@skia.org>
To: someone@example.org
Hi!
`))
require.NoError(t, err)
require.Equal(t, "Alerts <alerts@skia.org>", from)
require.Equal(t, []string{"someone@example.org"}, to)
require.Equal(t, "(no subject)", subject)
require.Equal(t, "Hi!\n", body)
}
func TestFormatAsRFC2822_HappyPath(t *testing.T) {
unittest.SmallTest(t)
body := `<h1>Testing</h1>`
ref := "<some-reference-id>"
messageID := "<foo-bar-baz@skia.org>"
markup, err := GetViewActionMarkup("https://example.com", "Example", "Click the link")
require.NoError(t, err)
actual, err := FormatAsRFC2822("Alerts", "alerts@skia.org", []string{"someone@example.org"}, "Your Alert", body, markup, ref, messageID)
require.NoError(t, err)
expected := `From: Alerts <alerts@skia.org>
To: someone@example.org
Subject: Your Alert
Content-Type: text/html; charset=UTF-8
References: <some-reference-id>
In-Reply-To: <some-reference-id>
Message-ID: <foo-bar-baz@skia.org>
<html>
<body>
<div itemscope itemtype="http://schema.org/EmailMessage">
<div itemprop="potentialAction" itemscope itemtype="http://schema.org/ViewAction">
<link itemprop="target" href="https://example.com"/>
<meta itemprop="name" content="Example"/>
</div>
<meta itemprop="description" content="Click the link"/>
</div>
<h1>Testing</h1>
</body>
</html>
`
require.Equal(t, expected, actual.String())
}
func TestFormatAsRFC2822_NoThreadID_MessageDoesNotContainInReplyToOrReferencesHeaders(t *testing.T) {
unittest.SmallTest(t)
body := `<h1>Testing</h1>`
ref := ""
messageID := "<foo-bar-baz@skia.org>"
markup, err := GetViewActionMarkup("https://example.com", "Example", "Click the link")
require.NoError(t, err)
actual, err := FormatAsRFC2822("Alerts", "alerts@skia.org", []string{"someone@example.org"}, "Your Alert", body, markup, ref, messageID)
require.NoError(t, err)
expected := `From: Alerts <alerts@skia.org>
To: someone@example.org
Subject: Your Alert
Content-Type: text/html; charset=UTF-8
Message-ID: <foo-bar-baz@skia.org>
<html>
<body>
<div itemscope itemtype="http://schema.org/EmailMessage">
<div itemprop="potentialAction" itemscope itemtype="http://schema.org/ViewAction">
<link itemprop="target" href="https://example.com"/>
<meta itemprop="name" content="Example"/>
</div>
<meta itemprop="description" content="Click the link"/>
</div>
<h1>Testing</h1>
</body>
</html>
`
require.Equal(t, expected, actual.String())
}
func TestFormatAsRFC2822_NoThreadIDOrMessageID_MessageDoesNotContainInReplyToOrReferenceOrMessageIDHeaders(t *testing.T) {
unittest.SmallTest(t)
body := `<h1>Testing</h1>`
ref := ""
messageID := ""
markup, err := GetViewActionMarkup("https://example.com", "Example", "Click the link")
require.NoError(t, err)
actual, err := FormatAsRFC2822("Alerts", "alerts@skia.org", []string{"someone@example.org"}, "Your Alert", body, markup, ref, messageID)
require.NoError(t, err)
expected := `From: Alerts <alerts@skia.org>
To: someone@example.org
Subject: Your Alert
Content-Type: text/html; charset=UTF-8
<html>
<body>
<div itemscope itemtype="http://schema.org/EmailMessage">
<div itemprop="potentialAction" itemscope itemtype="http://schema.org/ViewAction">
<link itemprop="target" href="https://example.com"/>
<meta itemprop="name" content="Example"/>
</div>
<meta itemprop="description" content="Click the link"/>
</div>
<h1>Testing</h1>
</body>
</html>
`
require.Equal(t, expected, actual.String())
}
func TestFormatAsRFC2822_NoMessageID_MessageDoesNotContainMessageIDHeader(t *testing.T) {
unittest.SmallTest(t)
body := `<h1>Testing</h1>`
ref := "<some-reference-id>"
messageID := ""
markup, err := GetViewActionMarkup("https://example.com", "Example", "Click the link")
require.NoError(t, err)
actual, err := FormatAsRFC2822("Alerts", "alerts@skia.org", []string{"someone@example.org"}, "Your Alert", body, markup, ref, messageID)
require.NoError(t, err)
expected := `From: Alerts <alerts@skia.org>
To: someone@example.org
Subject: Your Alert
Content-Type: text/html; charset=UTF-8
References: <some-reference-id>
In-Reply-To: <some-reference-id>
<html>
<body>
<div itemscope itemtype="http://schema.org/EmailMessage">
<div itemprop="potentialAction" itemscope itemtype="http://schema.org/ViewAction">
<link itemprop="target" href="https://example.com"/>
<meta itemprop="name" content="Example"/>
</div>
<meta itemprop="description" content="Click the link"/>
</div>
<h1>Testing</h1>
</body>
</html>
`
require.Equal(t, expected, actual.String())
}