Skip to content

Commit

Permalink
Merge 80b4e62 into 7a03946
Browse files Browse the repository at this point in the history
  • Loading branch information
hawx authored Dec 11, 2023
2 parents 7a03946 + 80b4e62 commit ba3f2ab
Show file tree
Hide file tree
Showing 24 changed files with 262 additions and 411 deletions.
3 changes: 2 additions & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ coverage:
- "./cmd/mock-onelogin/main.go"
- "./cmd/mock-os-api/main.go"
- "./internal/identity/yoti*"
- "./internal/notify/emails.go"
- "./internal/notify/email.go"
- "./internal/notify/sms.go"
- "./internal/page/fixtures"
- "./internal/telemetry"
- "./mocks/*"
Expand Down
90 changes: 25 additions & 65 deletions internal/notify/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,13 @@ import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"strings"
"time"

"github.com/golang-jwt/jwt/v4"
)

type Template uint8

const (
CertificateProviderActingDigitallyHasNotConfirmedPersonalDetailsLPADetailsChangedPromptSMS Template = iota
CertificateProviderActingDigitallyHasConfirmedPersonalDetailsLPADetailsChangedPromptSMS
CertificateProviderActingOnPaperDetailsChangedSMS
CertificateProviderActingOnPaperMeetingPromptSMS
WitnessCodeSMS
)

var (
productionTemplates = map[Template]string{
CertificateProviderActingDigitallyHasNotConfirmedPersonalDetailsLPADetailsChangedPromptSMS: "19948d7d-a2df-4e85-930b-5d800978f41f",
CertificateProviderActingDigitallyHasConfirmedPersonalDetailsLPADetailsChangedPromptSMS: "71d21daa-11f9-4a2a-9ae2-bb5c2247bfb7",
CertificateProviderActingOnPaperDetailsChangedSMS: "ab90c6be-806e-411a-a354-de10f7a70c47",
CertificateProviderActingOnPaperMeetingPromptSMS: "b5cd2c1b-e9b4-4f3e-8cf1-504aff93b16d",
WitnessCodeSMS: "e39849c0-ecab-4e16-87ec-6b22afb9d535",
}
testingTemplates = map[Template]string{
CertificateProviderActingDigitallyHasNotConfirmedPersonalDetailsLPADetailsChangedPromptSMS: "d7513751-49ba-4276-aef5-ad67361d29c4",
CertificateProviderActingDigitallyHasConfirmedPersonalDetailsLPADetailsChangedPromptSMS: "359fffa0-e1ec-444c-a886-6f046af374ab",
CertificateProviderActingOnPaperDetailsChangedSMS: "94477364-281a-4032-9a88-b215f969cd12",
CertificateProviderActingOnPaperMeetingPromptSMS: "ee39cd81-5802-44bb-b967-27da7e25e897",
WitnessCodeSMS: "dfa15e16-1f23-494a-bffb-a475513df6cc",
}
)

//go:generate mockery --testonly --inpackage --name Doer --structname mockDoer
type Doer interface {
Do(*http.Request) (*http.Response, error)
Expand All @@ -51,7 +23,6 @@ type Client struct {
issuer string
secretKey []byte
now func() time.Time
templates map[Template]string
isProduction bool
}

Expand All @@ -61,18 +32,12 @@ func New(isProduction bool, baseURL, apiKey string, httpClient Doer) (*Client, e
return nil, errors.New("invalid apiKey format")
}

templates := testingTemplates
if isProduction {
templates = productionTemplates
}

return &Client{
baseURL: baseURL,
doer: httpClient,
issuer: strings.Join(keyParts[1:6], "-"),
secretKey: []byte(strings.Join(keyParts[6:11], "-")),
now: time.Now,
templates: templates,
isProduction: isProduction,
}, nil
}
Expand Down Expand Up @@ -105,84 +70,79 @@ type errorItem struct {
Message string `json:"message"`
}

func (c *Client) TemplateID(id Template) string {
return c.templates[id]
}

type Email interface {
emailID(bool) string
}

type emailWrapper struct {
EmailAddress string `json:"email_address"`
TemplateID string `json:"template_id"`
Personalisation any `json:"personalisation,omitempty"`
}

func (c *Client) SendEmail(ctx context.Context, to string, email Email) (string, error) {
wrapper := emailWrapper{
req, err := c.newRequest(ctx, "/v2/notifications/email", emailWrapper{
EmailAddress: to,
TemplateID: email.emailID(c.isProduction),
Personalisation: email,
}

var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(wrapper); err != nil {
return "", err
}

req, err := c.request(ctx, "/v2/notifications/email", &buf)
})
if err != nil {
return "", err
}

resp, err := c.doRequest(req)
resp, err := c.do(req)
if err != nil {
return "", err
}

return resp.ID, nil
}

func (c *Client) Sms(ctx context.Context, sms Sms) (string, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(sms); err != nil {
return "", err
}
type smsWrapper struct {
PhoneNumber string `json:"phone_number"`
TemplateID string `json:"template_id"`
Personalisation any `json:"personalisation,omitempty"`
}

req, err := c.request(ctx, "/v2/notifications/sms", &buf)
func (c *Client) SendSMS(ctx context.Context, to string, sms SMS) (string, error) {
req, err := c.newRequest(ctx, "/v2/notifications/sms", smsWrapper{
PhoneNumber: to,
TemplateID: sms.smsID(c.isProduction),
Personalisation: sms,
})
if err != nil {
return "", err
}

resp, err := c.doRequest(req)
resp, err := c.do(req)
if err != nil {
return "", err
}

return resp.ID, nil
}

func (c *Client) request(ctx context.Context, url string, body io.Reader) (*http.Request, error) {
func (c *Client) newRequest(ctx context.Context, url string, wrapper any) (*http.Request, error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(wrapper); err != nil {
return nil, err
}

token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.RegisteredClaims{
Issuer: c.issuer,
IssuedAt: jwt.NewNumericDate(c.now()),
}).SignedString(c.secretKey)
if err != nil {
return &http.Request{}, err
return nil, err
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+url, body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+url, &buf)
if err != nil {
return &http.Request{}, err
return nil, err
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer "+token)

return req, nil
}

func (c *Client) doRequest(req *http.Request) (response, error) {
func (c *Client) do(req *http.Request) (response, error) {
var r response

resp, err := c.doer.Do(req)
Expand Down
66 changes: 29 additions & 37 deletions internal/notify/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,28 +85,15 @@ func TestSendEmailWhenError(t *testing.T) {
assert.Equal(`error sending message: This happened: Plus this`, err.Error())
}

func TestTemplateID(t *testing.T) {
production, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", nil)
assert.Equal(t, "e39849c0-ecab-4e16-87ec-6b22afb9d535", production.TemplateID(WitnessCodeSMS))
assert.Equal(t, "", production.TemplateID(Template(200)))

test, _ := New(false, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", nil)
assert.Equal(t, "dfa15e16-1f23-494a-bffb-a475513df6cc", test.TemplateID(WitnessCodeSMS))
assert.Equal(t, "", test.TemplateID(Template(200)))
}

func TestRequest(t *testing.T) {
func TestNewRequest(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
doer := newMockDoer(t)

var jsonBody bytes.Buffer
jsonBody.WriteString(`{"some": "json"}`)

client, _ := New(true, "http://base", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)
client.now = func() time.Time { return time.Date(2020, time.January, 2, 3, 4, 5, 6, time.UTC) }

req, err := client.request(ctx, "/an/url", &jsonBody)
req, err := client.newRequest(ctx, "/an/url", map[string]string{"some": "json"})

assert.Nil(err)
assert.Equal(http.MethodPost, req.Method)
Expand All @@ -115,22 +102,19 @@ func TestRequest(t *testing.T) {
assert.Equal("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmMzM1MTdmZi0yYTg4LTRmNmUtYjg1NS1jNTUwMjY4Y2UwOGEiLCJpYXQiOjE1Nzc5MzQyNDV9.V0iR-Foo_twZdWttAxy4koJoSYJzyZHMr-tJIBwZj8k", req.Header.Get("Authorization"))
}

func TestRequestWhenNewRequestError(t *testing.T) {
func TestNewRequestWhenNewRequestError(t *testing.T) {
assert := assert.New(t)
doer := newMockDoer(t)

var jsonBody bytes.Buffer
jsonBody.WriteString(`{"some": "json"}`)

client, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)
client.now = func() time.Time { return time.Now().Add(-time.Minute) }

_, err := client.request(nil, "/an/url", &jsonBody)
_, err := client.newRequest(nil, "/an/url", map[string]string{"some": "json"})

assert.Equal(errors.New("net/http: nil Context"), err)
}

func TestDoRequest(t *testing.T) {
func TestDo(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
jsonString := `{"id": "123", "status_code": 400}`
Expand All @@ -148,16 +132,16 @@ func TestDoRequest(t *testing.T) {
client, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)
client.now = func() time.Time { return time.Date(2020, time.January, 2, 3, 4, 5, 6, time.UTC) }

req, _ := client.request(ctx, "/an/url", &jsonBody)
req, _ := client.newRequest(ctx, "/an/url", &jsonBody)

response, err := client.doRequest(req)
response, err := client.do(req)

assert.Nil(err)
assert.Equal(response.ID, "123")
assert.Equal(response.StatusCode, 400)
}

func TestDoRequestWhenContainsErrorList(t *testing.T) {
func TestDoWhenContainsErrorList(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
jsonString := `{"id": "123", "status_code": 400, "errors": [{"error":"SomeError","message":"This happened"}, {"error":"AndError","message":"Plus this"}]}`
Expand All @@ -175,9 +159,9 @@ func TestDoRequestWhenContainsErrorList(t *testing.T) {
client, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)
client.now = func() time.Time { return time.Date(2020, time.January, 2, 3, 4, 5, 6, time.UTC) }

req, _ := client.request(ctx, "/an/url", &jsonBody)
req, _ := client.newRequest(ctx, "/an/url", &jsonBody)

response, err := client.doRequest(req)
response, err := client.do(req)

assert.Equal(errorsList{
errorItem{
Expand Down Expand Up @@ -220,9 +204,9 @@ func TestDoRequestWhenRequestError(t *testing.T) {
client, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)
client.now = func() time.Time { return time.Date(2020, time.January, 2, 3, 4, 5, 6, time.UTC) }

req, _ := client.request(ctx, "/an/url", &jsonBody)
req, _ := client.newRequest(ctx, "/an/url", &jsonBody)

resp, err := client.doRequest(req)
resp, err := client.do(req)

assert.Equal(errors.New("err"), err)
assert.Equal(response{}, resp)
Expand All @@ -245,15 +229,21 @@ func TestDoRequestWhenJsonDecodeFails(t *testing.T) {
client, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)
client.now = func() time.Time { return time.Date(2020, time.January, 2, 3, 4, 5, 6, time.UTC) }

req, _ := client.request(ctx, "/an/url", &jsonBody)
req, _ := client.newRequest(ctx, "/an/url", &jsonBody)

resp, err := client.doRequest(req)
resp, err := client.do(req)

assert.IsType(&json.SyntaxError{}, err)
assert.Equal(response{}, resp)
}

func TestSms(t *testing.T) {
type testSMS struct {
A string
}

func (e testSMS) smsID(bool) string { return "template-id" }

func TestSendSMS(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()

Expand All @@ -264,11 +254,13 @@ func TestSms(t *testing.T) {
io.Copy(&buf, req.Body)
req.Body = ioutil.NopCloser(&buf)

var v map[string]string
var v map[string]any
json.Unmarshal(buf.Bytes(), &v)

return assert.Equal("+447535111111", v["phone_number"]) &&
assert.Equal("template-123", v["template_id"])
return assert.Equal("+447535111111", v["phone_number"].(string)) &&
assert.Equal("template-id", v["template_id"].(string)) &&
assert.Equal(map[string]any{"A": "value"}, v["personalisation"].(map[string]any))

})).
Return(&http.Response{
Body: io.NopCloser(strings.NewReader(`{"id":"xyz"}`)),
Expand All @@ -277,13 +269,13 @@ func TestSms(t *testing.T) {
client, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)
client.now = func() time.Time { return time.Date(2020, time.January, 2, 3, 4, 5, 6, time.UTC) }

id, err := client.Sms(ctx, Sms{PhoneNumber: "+447535111111", TemplateID: "template-123"})
id, err := client.SendSMS(ctx, "+447535111111", testSMS{A: "value"})

assert.Nil(err)
assert.Equal("xyz", id)
}

func TestSmsWhenError(t *testing.T) {
func TestSendSMSWhenError(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()

Expand All @@ -296,6 +288,6 @@ func TestSmsWhenError(t *testing.T) {

client, _ := New(true, "", "my_client-f33517ff-2a88-4f6e-b855-c550268ce08a-740e5834-3a29-46b4-9a6f-16142fde533a", doer)

_, err := client.Sms(ctx, Sms{PhoneNumber: "+447535111111", TemplateID: "template-123"})
_, err := client.SendSMS(ctx, "+447535111111", testSMS{})
assert.Equal(`error sending message: This happened: Plus this`, err.Error())
}
4 changes: 4 additions & 0 deletions internal/notify/emails.go → internal/notify/email.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package notify

type Email interface {
emailID(bool) string
}

type InitialOriginalAttorneyEmail struct {
DonorFullName string
LpaType string
Expand Down
Loading

0 comments on commit ba3f2ab

Please sign in to comment.