boulder/email/pardot_test.go

211 lines
6.3 KiB
Go

package email
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/test"
)
func defaultTokenHandler(w http.ResponseWriter, r *http.Request) {
err := json.NewEncoder(w).Encode(oauthTokenResp{
AccessToken: "dummy",
ExpiresIn: 3600,
})
if err != nil {
// This should never happen.
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("failed to encode token"))
return
}
}
func TestSendContactSuccess(t *testing.T) {
t.Parallel()
contactHandler := func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer dummy" {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
}
tokenSrv := httptest.NewServer(http.HandlerFunc(defaultTokenHandler))
defer tokenSrv.Close()
contactSrv := httptest.NewServer(http.HandlerFunc(contactHandler))
defer contactSrv.Close()
clk := clock.NewFake()
client, err := NewPardotClientImpl(clk, "biz-unit", "cid", "csec", tokenSrv.URL, contactSrv.URL)
test.AssertNotError(t, err, "failed to create client")
err = client.SendContact("test@example.com")
test.AssertNotError(t, err, "SendContact should succeed")
}
func TestSendContactUpdateTokenFails(t *testing.T) {
t.Parallel()
tokenHandlerThatAlwaysErrors := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "token error")
}
contactHandler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
tokenSrv := httptest.NewServer(http.HandlerFunc(tokenHandlerThatAlwaysErrors))
defer tokenSrv.Close()
contactSrv := httptest.NewServer(http.HandlerFunc(contactHandler))
defer contactSrv.Close()
clk := clock.NewFake()
client, err := NewPardotClientImpl(clk, "biz-unit", "cid", "csec", tokenSrv.URL, contactSrv.URL)
test.AssertNotError(t, err, "Failed to create client")
err = client.SendContact("test@example.com")
test.AssertError(t, err, "Expected token update to fail")
test.AssertContains(t, err.Error(), "failed to update token")
}
func TestSendContact4xx(t *testing.T) {
t.Parallel()
contactHandler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, err := io.WriteString(w, "bad request")
test.AssertNotError(t, err, "failed to write response")
}
tokenSrv := httptest.NewServer(http.HandlerFunc(defaultTokenHandler))
defer tokenSrv.Close()
contactSrv := httptest.NewServer(http.HandlerFunc(contactHandler))
defer contactSrv.Close()
clk := clock.NewFake()
client, err := NewPardotClientImpl(clk, "biz-unit", "cid", "csec", tokenSrv.URL, contactSrv.URL)
test.AssertNotError(t, err, "Failed to create client")
err = client.SendContact("test@example.com")
test.AssertError(t, err, "Should fail on 400")
test.AssertContains(t, err.Error(), "create contact request returned status 400")
}
func TestSendContactTokenExpiry(t *testing.T) {
t.Parallel()
// tokenHandler returns "old_token" on the first call and "new_token" on subsequent calls.
tokenRetrieved := false
tokenHandler := func(w http.ResponseWriter, r *http.Request) {
token := "new_token"
if !tokenRetrieved {
token = "old_token"
tokenRetrieved = true
}
err := json.NewEncoder(w).Encode(oauthTokenResp{
AccessToken: token,
ExpiresIn: 3600,
})
test.AssertNotError(t, err, "failed to encode token")
}
// contactHandler expects "old_token" for the first request and "new_token" for the next.
firstRequest := true
contactHandler := func(w http.ResponseWriter, r *http.Request) {
expectedToken := "new_token"
if firstRequest {
expectedToken = "old_token"
firstRequest = false
}
if r.Header.Get("Authorization") != "Bearer "+expectedToken {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
}
tokenSrv := httptest.NewServer(http.HandlerFunc(tokenHandler))
defer tokenSrv.Close()
contactSrv := httptest.NewServer(http.HandlerFunc(contactHandler))
defer contactSrv.Close()
clk := clock.NewFake()
client, err := NewPardotClientImpl(clk, "biz-unit", "cid", "csec", tokenSrv.URL, contactSrv.URL)
test.AssertNotError(t, err, "Failed to create client")
// First call uses the initial token ("old_token").
err = client.SendContact("test@example.com")
test.AssertNotError(t, err, "SendContact should succeed with the initial token")
// Advance time to force token expiry.
clk.Add(3601 * time.Second)
// Second call should refresh the token to "new_token".
err = client.SendContact("test@example.com")
test.AssertNotError(t, err, "SendContact should succeed after refreshing the token")
}
func TestSendContactServerErrorsAfterMaxAttempts(t *testing.T) {
t.Parallel()
gotAttempts := 0
contactHandler := func(w http.ResponseWriter, r *http.Request) {
gotAttempts++
w.WriteHeader(http.StatusServiceUnavailable)
}
tokenSrv := httptest.NewServer(http.HandlerFunc(defaultTokenHandler))
defer tokenSrv.Close()
contactSrv := httptest.NewServer(http.HandlerFunc(contactHandler))
defer contactSrv.Close()
client, _ := NewPardotClientImpl(clock.NewFake(), "biz-unit", "cid", "csec", tokenSrv.URL, contactSrv.URL)
err := client.SendContact("test@example.com")
test.AssertError(t, err, "Should fail after retrying all attempts")
test.AssertEquals(t, maxAttempts, gotAttempts)
test.AssertContains(t, err.Error(), "create contact request returned status 503")
}
func TestSendContactRedactsEmail(t *testing.T) {
t.Parallel()
emailToTest := "test@example.com"
contactHandler := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
// Intentionally include the request email in the response body.
resp := fmt.Sprintf("error: %s is invalid", emailToTest)
_, err := io.WriteString(w, resp)
test.AssertNotError(t, err, "failed to write response")
}
tokenSrv := httptest.NewServer(http.HandlerFunc(defaultTokenHandler))
defer tokenSrv.Close()
contactSrv := httptest.NewServer(http.HandlerFunc(contactHandler))
defer contactSrv.Close()
clk := clock.NewFake()
client, err := NewPardotClientImpl(clk, "biz-unit", "cid", "csec", tokenSrv.URL, contactSrv.URL)
test.AssertNotError(t, err, "failed to create client")
err = client.SendContact(emailToTest)
test.AssertError(t, err, "SendContact should fail")
test.AssertNotContains(t, err.Error(), emailToTest)
test.AssertContains(t, err.Error(), "[REDACTED]")
}