From 0eed29747cb9b38c8f65f3c6e2041d34f3fef624 Mon Sep 17 00:00:00 2001 From: Zach Blake Date: Sun, 11 Dec 2022 19:11:48 -0800 Subject: [PATCH 1/3] feat(service): add gotify service --- service/gotify/gotify.go | 71 +++++++++++++++++++++++++++ service/gotify/gotify_test.go | 47 ++++++++++++++++++ service/gotify/mock_gotify_service.go | 43 ++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 service/gotify/gotify.go create mode 100644 service/gotify/gotify_test.go create mode 100644 service/gotify/mock_gotify_service.go diff --git a/service/gotify/gotify.go b/service/gotify/gotify.go new file mode 100644 index 00000000..eebdf296 --- /dev/null +++ b/service/gotify/gotify.go @@ -0,0 +1,71 @@ +package gotify + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +//go:generate mockery --name=gotifyService --output=. --case=underscore --inpackage +type gotifyService interface { + Send(ctx context.Context, subject, message string) error +} + +// Gotify struct holds necessary data to communicate with Gotify API +type Gotify struct { + httpClient *http.Client + baseUrl string + appToken string +} + +func New(appToken, baseUrl string) *Gotify { + g := &Gotify{ + httpClient: http.DefaultClient, + baseUrl: baseUrl, + appToken: appToken, + } + + return g +} + +type newMessageRequestBody struct { + Title string + Message string + Priority int +} + +func (g *Gotify) Send(ctx context.Context, subject, message string) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + reqBody := &newMessageRequestBody{ + Title: subject, + Message: message, + Priority: 1, + } + + body, err := json.Marshal(reqBody) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/message", g.baseUrl), bytes.NewReader(body)) + if err != nil { + return err + } + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", g.appToken)) + + _, err = g.httpClient.Do(req) + if err != nil { + return errors.Wrapf(err, "failed to send message to gotify server") + } + + return nil + } +} diff --git a/service/gotify/gotify_test.go b/service/gotify/gotify_test.go new file mode 100644 index 00000000..24f5d477 --- /dev/null +++ b/service/gotify/gotify_test.go @@ -0,0 +1,47 @@ +package gotify + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGotify_New(t *testing.T) { + t.Parallel() + + assert := require.New(t) + assert.NotNil(New( + "testingAppToken", + "baseUrl", + )) +} + +func TestGotify_SendSuccess(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + mock := newMockGotifyService(t) + mock.On("Send", context.Background(), "testing", "hello world!").Return(nil) + + err := mock.Send(context.Background(), "testing", "hello world!") + assert.Nil(err) + + mock.AssertCalled(t, "Send", context.Background(), "testing", "hello world!") +} + +func TestGotify_SendFailure(t *testing.T) { + t.Parallel() + + assert := require.New(t) + + mock := newMockGotifyService(t) + mock.On("Send", context.Background(), "testing", "hello world!").Return(errors.New("failed to send message")) + + err := mock.Send(context.Background(), "testing", "hello world!") + assert.NotNil(err) + + mock.AssertCalled(t, "Send", context.Background(), "testing", "hello world!") +} diff --git a/service/gotify/mock_gotify_service.go b/service/gotify/mock_gotify_service.go new file mode 100644 index 00000000..d993c04c --- /dev/null +++ b/service/gotify/mock_gotify_service.go @@ -0,0 +1,43 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package gotify + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// mockGotifyService is an autogenerated mock type for the gotifyService type +type mockGotifyService struct { + mock.Mock +} + +// Send provides a mock function with given fields: ctx, subject, message +func (_m *mockGotifyService) Send(ctx context.Context, subject string, message string) error { + ret := _m.Called(ctx, subject, message) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, subject, message) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTnewMockGotifyService interface { + mock.TestingT + Cleanup(func()) +} + +// newMockGotifyService creates a new instance of mockGotifyService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func newMockGotifyService(t mockConstructorTestingTnewMockGotifyService) *mockGotifyService { + mock := &mockGotifyService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 1976ff75bfab54a58e14727049f3d5b36ce7092f Mon Sep 17 00:00:00 2001 From: Zach Blake Date: Sun, 11 Dec 2022 19:14:48 -0800 Subject: [PATCH 2/3] fix(service): add compile time interface check doing this to silence the linter --- service/gotify/gotify.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/service/gotify/gotify.go b/service/gotify/gotify.go index eebdf296..5ddcc72c 100644 --- a/service/gotify/gotify.go +++ b/service/gotify/gotify.go @@ -15,6 +15,8 @@ type gotifyService interface { Send(ctx context.Context, subject, message string) error } +var _ gotifyService = &Gotify{} + // Gotify struct holds necessary data to communicate with Gotify API type Gotify struct { httpClient *http.Client From 8ae87d16af54aeaefdb4e9e09a1e5d6da9909a81 Mon Sep 17 00:00:00 2001 From: Zach Blake Date: Sun, 11 Dec 2022 19:21:36 -0800 Subject: [PATCH 3/3] fix(service): silence ci linter --- service/gotify/gotify.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/service/gotify/gotify.go b/service/gotify/gotify.go index 5ddcc72c..07d936e8 100644 --- a/service/gotify/gotify.go +++ b/service/gotify/gotify.go @@ -20,14 +20,14 @@ var _ gotifyService = &Gotify{} // Gotify struct holds necessary data to communicate with Gotify API type Gotify struct { httpClient *http.Client - baseUrl string + baseURL string appToken string } -func New(appToken, baseUrl string) *Gotify { +func New(appToken, baseURL string) *Gotify { g := &Gotify{ httpClient: http.DefaultClient, - baseUrl: baseUrl, + baseURL: baseURL, appToken: appToken, } @@ -56,14 +56,15 @@ func (g *Gotify) Send(ctx context.Context, subject, message string) error { return err } - req, err := http.NewRequest("POST", fmt.Sprintf("%s/message", g.baseUrl), bytes.NewReader(body)) + req, err := http.NewRequestWithContext(context.Background(), "POST", fmt.Sprintf("%s/message", g.baseURL), bytes.NewReader(body)) if err != nil { return err } req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", g.appToken)) - _, err = g.httpClient.Do(req) + b, err := g.httpClient.Do(req) + b.Body.Close() if err != nil { return errors.Wrapf(err, "failed to send message to gotify server") }