diff --git a/api/api.go b/api/api.go index 8bd964c..22d975a 100644 --- a/api/api.go +++ b/api/api.go @@ -6,15 +6,16 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" helmes "github.com/rugwirobaker/helmes" + "github.com/rugwirobaker/helmes/api/handlers" ) // Server ... type Server struct { - Service helmes.Service + Service helmes.SendService } // New api Server instance -func New(svc helmes.Service) *Server { +func New(svc helmes.SendService) *Server { return &Server{Service: svc} } @@ -31,9 +32,9 @@ func (s Server) Handler() http.Handler { w.Write([]byte("Welcome to helmes")) }) - r.Get("/version", VersionHandler(s.Service)) - r.Post("/send", SMSHandler(s.Service)) - r.Get("/healthz", HealthHandler()) + r.Get("/healthz", handlers.HealthHandler()) + r.Get("/version", handlers.VersionHandler()) + r.Post("/send", handlers.SendHandler(s.Service)) return r } diff --git a/api/handlers.go b/api/handlers/handlers.go similarity index 69% rename from api/handlers.go rename to api/handlers/handlers.go index 1f53a16..f57adbe 100644 --- a/api/handlers.go +++ b/api/handlers/handlers.go @@ -1,4 +1,4 @@ -package api +package handlers import ( "encoding/json" @@ -12,8 +12,8 @@ import ( var startTime = time.Now() -// SMSHandler ... -func SMSHandler(svc helmes.Service) http.HandlerFunc { +// SendHandler ... +func SendHandler(svc helmes.SendService) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { in := new(helmes.SMS) @@ -35,25 +35,18 @@ func SMSHandler(svc helmes.Service) http.HandlerFunc { } } -// VersionHandler ... -func VersionHandler(svc helmes.Service) http.HandlerFunc { +// VersionHandler handles version reporting +func VersionHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - build, err := svc.Version(r.Context()) - if err != nil { - http.Error(w, err.Error(), 500) - } - JSON(w, build, http.StatusOK) + res := helmes.Data() + JSON(w, res, http.StatusOK) } } -// HealthHandler reports the health of the application +// HealthHandler handles application health reporting func HealthHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - res := struct { - GitRev string `json:"git_rev"` - Uptime float64 `json:"uptime"` - Goroutines int `json:"goroutines"` - }{ + res := &helmes.Health{ GitRev: helmes.Data().Version, Uptime: time.Since(startTime).Seconds(), Goroutines: runtime.NumGoroutine(), diff --git a/api/handlers/handlers_test.go b/api/handlers/handlers_test.go new file mode 100644 index 0000000..e27efb0 --- /dev/null +++ b/api/handlers/handlers_test.go @@ -0,0 +1,81 @@ +package handlers_test + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/go-cmp/cmp" + "github.com/rugwirobaker/helmes" + "github.com/rugwirobaker/helmes/api/handlers" + "github.com/rugwirobaker/helmes/mock" +) + +var ( + dummyMessage = &helmes.SMS{ + Payload: "Hello", + Recipient: "User_Phone", + } + dummyReport = &helmes.Report{ + ID: "message id", + Cost: 1, + } +) + +func TestSendHander(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + sender := mock.NewMockSendService(controller) + sender.EXPECT().Send(gomock.Any(), gomock.Any()).Return(dummyReport, nil) + + in := new(bytes.Buffer) + + _ = json.NewEncoder(in).Encode(dummyMessage) + + w := httptest.NewRecorder() + r := httptest.NewRequest("POST", "/", in) + + handlers.SendHandler(sender).ServeHTTP(w, r) + if got, want := w.Code, http.StatusOK; want != got { + t.Errorf("Want response code %d, got %d", want, got) + } + + got, want := &helmes.Report{}, dummyReport + json.NewDecoder(w.Body).Decode(got) + if diff := cmp.Diff(got, want); len(diff) != 0 { + t.Errorf(diff) + } + +} + +func TestHealthHandler(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/healthz", nil) + + handlers.HealthHandler().ServeHTTP(w, r) + + if got, want := w.Code, 200; want != got { + t.Errorf("Want response code %d, got %d", want, got) + } +} + +func TestVersionHandler(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/version", nil) + + handlers.VersionHandler().ServeHTTP(w, r) + + if got, want := w.Code, 200; want != got { + t.Errorf("Want response code %d, got %d", want, got) + } + + got, want := &helmes.Build{}, helmes.Data() + json.NewDecoder(w.Body).Decode(got) + if diff := cmp.Diff(got, want); len(diff) != 0 { + t.Errorf(diff) + } +} diff --git a/go.mod b/go.mod index 4a60eeb..d20975d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.14 require ( github.com/go-chi/chi v4.1.2+incompatible + github.com/golang/mock v1.4.4 + github.com/google/go-cmp v0.5.1 github.com/google/uuid v1.1.2 - github.com/quarksgroup/sms-client v0.0.0-20200916024156-cec3c804e5f8 + github.com/quarksgroup/sms-client v1.0.0 golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect ) diff --git a/go.sum b/go.sum index 51afd0d..f8bff29 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= @@ -18,19 +20,24 @@ github.com/quarksgroup/sms-client v0.0.0-20200911144732-8cbf6e85ed3a h1:Prr/Ac/a github.com/quarksgroup/sms-client v0.0.0-20200911144732-8cbf6e85ed3a/go.mod h1:XcPDoSKQvB0PT2pXePS3pqVd7uCHTBhZ/zBdnSFBYOw= github.com/quarksgroup/sms-client v0.0.0-20200916024156-cec3c804e5f8 h1:AehGo7q3SYg9btSTVFPAUx0ToP3vWu1k6qccpHs+vzk= github.com/quarksgroup/sms-client v0.0.0-20200916024156-cec3c804e5f8/go.mod h1:XcPDoSKQvB0PT2pXePS3pqVd7uCHTBhZ/zBdnSFBYOw= +github.com/quarksgroup/sms-client v1.0.0 h1:JKKsozedN6vbsAvqciC7A4SnnrMiNX2xSTknKjn1TA8= +github.com/quarksgroup/sms-client v1.0.0/go.mod h1:XcPDoSKQvB0PT2pXePS3pqVd7uCHTBhZ/zBdnSFBYOw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/health.go b/health.go new file mode 100644 index 0000000..4356801 --- /dev/null +++ b/health.go @@ -0,0 +1,8 @@ +package helmes + +// Health report +type Health struct { + GitRev string `json:"git_rev"` + Uptime float64 `json:"uptime"` + Goroutines int `json:"goroutines"` +} diff --git a/helmes.go b/helmes.go index 0b0667d..ec336c3 100644 --- a/helmes.go +++ b/helmes.go @@ -19,13 +19,10 @@ type Report struct { Cost int64 `json:"cost"` } -// Service defines the capabilties of helmes -type Service interface { +// SendService defines the capabilties of helmes +type SendService interface { // Send an sms message and return it's Send(context.Context, *SMS) (*Report, error) - - //Version returns helmes's current running version - Version(context.Context) (*Build, error) } type service struct { @@ -36,7 +33,7 @@ type service struct { } // New instance of service -func New(cli *sms.Client, id, secret, sender, callback string) (Service, error) { +func New(cli *sms.Client, id, secret, sender, callback string) (SendService, error) { token, _, err := cli.Auth.Login(context.Background(), id, secret) if err != nil { return nil, err @@ -54,6 +51,7 @@ func (s *service) Send(ctx context.Context, message *SMS) (*Report, error) { if err != nil { return nil, err } + s.token = token ctx = context.WithValue(ctx, sms.TokenKey{}, &sms.Token{ Token: token.Token, Refresh: token.Refresh, @@ -74,10 +72,6 @@ func (s *service) Send(ctx context.Context, message *SMS) (*Report, error) { return convertReport(report), nil } -func (s *service) Version(ctx context.Context) (*Build, error) { - return Data(), nil -} - func convertReport(report *sms.Report) *Report { return &Report{ ID: report.ID, diff --git a/helmes_test.go b/helmes_test.go new file mode 100644 index 0000000..e238345 --- /dev/null +++ b/helmes_test.go @@ -0,0 +1,53 @@ +package helmes_test + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/go-cmp/cmp" + "github.com/quarksgroup/sms-client/sms" + "github.com/rugwirobaker/helmes" + "github.com/rugwirobaker/helmes/mock/mocksmc" +) + +var noContext = context.Background() + +func TestSend(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + mockReport := &sms.Report{ + ID: "fake_id", + Cost: 1, + } + mockToken := &sms.Token{} + mockSMS := &helmes.SMS{} + + mockSendService := mocksmc.NewMockSendService(controller) + mockSendService.EXPECT().Send(gomock.Any(), gomock.Any()).Return(mockReport, nil, nil) + + mockAuthService := mocksmc.NewMockAuthService(controller) + mockAuthService.EXPECT().Refresh(gomock.Any(), gomock.Any(), false).Return(mockToken, nil, nil) + mockAuthService.EXPECT().Login(gomock.Any(), "id", "secret").Return(mockToken, nil, nil) + + client := new(sms.Client) + client.Message = mockSendService + client.Auth = mockAuthService + + service, _ := helmes.New(client, "id", "secret", "sender", "callback") + + want := &helmes.Report{ + ID: "fake_id", + Cost: 1, + } + + got, err := service.Send(noContext, mockSMS) + if err != nil { + t.Error(err) + } + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf(diff) + } + +} diff --git a/mock/mock.go b/mock/mock.go new file mode 100644 index 0000000..d110a44 --- /dev/null +++ b/mock/mock.go @@ -0,0 +1,3 @@ +package mock + +//go:generate mockgen -package=mock -destination=mock_gen.go github.com/rugwirobaker/helmes SendService diff --git a/mock/mock_gen.go b/mock/mock_gen.go new file mode 100644 index 0000000..2c0d216 --- /dev/null +++ b/mock/mock_gen.go @@ -0,0 +1,50 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/rugwirobaker/helmes (interfaces: SendService) + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + helmes "github.com/rugwirobaker/helmes" + reflect "reflect" +) + +// MockSendService is a mock of SendService interface +type MockSendService struct { + ctrl *gomock.Controller + recorder *MockSendServiceMockRecorder +} + +// MockSendServiceMockRecorder is the mock recorder for MockSendService +type MockSendServiceMockRecorder struct { + mock *MockSendService +} + +// NewMockSendService creates a new mock instance +func NewMockSendService(ctrl *gomock.Controller) *MockSendService { + mock := &MockSendService{ctrl: ctrl} + mock.recorder = &MockSendServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockSendService) EXPECT() *MockSendServiceMockRecorder { + return m.recorder +} + +// Send mocks base method +func (m *MockSendService) Send(arg0 context.Context, arg1 *helmes.SMS) (*helmes.Report, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", arg0, arg1) + ret0, _ := ret[0].(*helmes.Report) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Send indicates an expected call of Send +func (mr *MockSendServiceMockRecorder) Send(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockSendService)(nil).Send), arg0, arg1) +} diff --git a/mock/mocksmc/mock.go b/mock/mocksmc/mock.go new file mode 100644 index 0000000..2ebcb32 --- /dev/null +++ b/mock/mocksmc/mock.go @@ -0,0 +1,3 @@ +package mocksmc + +//go:generate mockgen -package=mocksmc -destination=mock_gen.go github.com/quarksgroup/sms-client/sms SendService,AuthService diff --git a/mock/mocksmc/mock_gen.go b/mock/mocksmc/mock_gen.go new file mode 100644 index 0000000..072692e --- /dev/null +++ b/mock/mocksmc/mock_gen.go @@ -0,0 +1,106 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/quarksgroup/sms-client/sms (interfaces: SendService,AuthService) + +// Package mocksmc is a generated GoMock package. +package mocksmc + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + sms "github.com/quarksgroup/sms-client/sms" + reflect "reflect" +) + +// MockSendService is a mock of SendService interface +type MockSendService struct { + ctrl *gomock.Controller + recorder *MockSendServiceMockRecorder +} + +// MockSendServiceMockRecorder is the mock recorder for MockSendService +type MockSendServiceMockRecorder struct { + mock *MockSendService +} + +// NewMockSendService creates a new mock instance +func NewMockSendService(ctrl *gomock.Controller) *MockSendService { + mock := &MockSendService{ctrl: ctrl} + mock.recorder = &MockSendServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockSendService) EXPECT() *MockSendServiceMockRecorder { + return m.recorder +} + +// Send mocks base method +func (m *MockSendService) Send(arg0 context.Context, arg1 sms.Message) (*sms.Report, *sms.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", arg0, arg1) + ret0, _ := ret[0].(*sms.Report) + ret1, _ := ret[1].(*sms.Response) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Send indicates an expected call of Send +func (mr *MockSendServiceMockRecorder) Send(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockSendService)(nil).Send), arg0, arg1) +} + +// MockAuthService is a mock of AuthService interface +type MockAuthService struct { + ctrl *gomock.Controller + recorder *MockAuthServiceMockRecorder +} + +// MockAuthServiceMockRecorder is the mock recorder for MockAuthService +type MockAuthServiceMockRecorder struct { + mock *MockAuthService +} + +// NewMockAuthService creates a new mock instance +func NewMockAuthService(ctrl *gomock.Controller) *MockAuthService { + mock := &MockAuthService{ctrl: ctrl} + mock.recorder = &MockAuthServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockAuthService) EXPECT() *MockAuthServiceMockRecorder { + return m.recorder +} + +// Login mocks base method +func (m *MockAuthService) Login(arg0 context.Context, arg1, arg2 string) (*sms.Token, *sms.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Login", arg0, arg1, arg2) + ret0, _ := ret[0].(*sms.Token) + ret1, _ := ret[1].(*sms.Response) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Login indicates an expected call of Login +func (mr *MockAuthServiceMockRecorder) Login(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockAuthService)(nil).Login), arg0, arg1, arg2) +} + +// Refresh mocks base method +func (m *MockAuthService) Refresh(arg0 context.Context, arg1 *sms.Token, arg2 bool) (*sms.Token, *sms.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Refresh", arg0, arg1, arg2) + ret0, _ := ret[0].(*sms.Token) + ret1, _ := ret[1].(*sms.Response) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Refresh indicates an expected call of Refresh +func (mr *MockAuthServiceMockRecorder) Refresh(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Refresh", reflect.TypeOf((*MockAuthService)(nil).Refresh), arg0, arg1, arg2) +}