Create a test to notify from the server, and all necessary structure
This commit is contained in:
parent
977e2d993e
commit
15f90c1e19
6 changed files with 239 additions and 29 deletions
73
server/helpers_test.go
Normal file
73
server/helpers_test.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.ctrlz.es/mgdelacroix/birthdaybot/model"
|
||||
"git.ctrlz.es/mgdelacroix/birthdaybot/notification"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
t *testing.T
|
||||
ctrl *gomock.Controller
|
||||
mockNotificationService *notification.MockNotificationService
|
||||
mockWorker *MockWorker
|
||||
srv *Server
|
||||
}
|
||||
|
||||
func testConfig(t *testing.T) *model.Config {
|
||||
f, err := ioutil.TempFile("", "birthdaybot-")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Close())
|
||||
require.NoError(t, os.Remove(f.Name()))
|
||||
|
||||
return &model.Config{BirthdayFile: f.Name()}
|
||||
}
|
||||
|
||||
func SetupTestHelper(t *testing.T) *TestHelper {
|
||||
th := &TestHelper{t: t}
|
||||
th.ctrl = gomock.NewController(t)
|
||||
|
||||
th.mockNotificationService = notification.NewMockNotificationService(th.ctrl)
|
||||
notificationServices := []notification.NotificationService{th.mockNotificationService}
|
||||
|
||||
th.mockWorker = NewMockWorker(th.ctrl)
|
||||
workers := []Worker{th.mockWorker}
|
||||
th.mockWorker.EXPECT().Start().Times(1)
|
||||
th.mockWorker.EXPECT().Stop().Times(1)
|
||||
|
||||
birthdays := []*model.Birthday{
|
||||
{
|
||||
Name: "John",
|
||||
Email: "john@doe.com",
|
||||
Phone: "1234",
|
||||
YearOfBirth: 2022,
|
||||
MonthOfBirth: 1,
|
||||
DayOfBirth: 1,
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
th.srv, err = New(
|
||||
WithConfig(testConfig(t)),
|
||||
WithLogger(log.New(os.Stderr)),
|
||||
WithBirthdays(birthdays),
|
||||
WithNotificationServices(notificationServices),
|
||||
WithWorkers(workers),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.srv.Start()
|
||||
|
||||
return th
|
||||
}
|
||||
|
||||
func (th *TestHelper) TearDown() {
|
||||
th.srv.Stop()
|
||||
th.ctrl.Finish()
|
||||
}
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"git.ctrlz.es/mgdelacroix/birthdaybot/model"
|
||||
"git.ctrlz.es/mgdelacroix/birthdaybot/notification"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
|
@ -20,3 +21,24 @@ func WithLogger(logger *log.Logger) Option {
|
|||
return server
|
||||
}
|
||||
}
|
||||
|
||||
func WithBirthdays(birthdays []*model.Birthday) Option {
|
||||
return func(server *Server) *Server {
|
||||
server.birthdays = birthdays
|
||||
return server
|
||||
}
|
||||
}
|
||||
|
||||
func WithNotificationServices(notificationServices []notification.NotificationService) Option {
|
||||
return func(server *Server) *Server {
|
||||
server.notificationServices = notificationServices
|
||||
return server
|
||||
}
|
||||
}
|
||||
|
||||
func WithWorkers(workers []Worker) Option {
|
||||
return func(server *Server) *Server {
|
||||
server.workers = workers
|
||||
return server
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,16 @@ import (
|
|||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
var ErrNoNotificationServices = errors.New("there are no notification services configured")
|
||||
var (
|
||||
ErrNoNotificationServices = errors.New("there are no notification services configured")
|
||||
ErrNoLogger = errors.New("there is no logger configured")
|
||||
ErrNoConfig = errors.New("configuration is required to create a server")
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Logger *log.Logger
|
||||
config *model.Config
|
||||
workers []*Worker
|
||||
workers []Worker
|
||||
birthdays []*model.Birthday
|
||||
notificationServices []notification.NotificationService
|
||||
}
|
||||
|
@ -44,33 +48,43 @@ func New(options ...Option) (*Server, error) {
|
|||
srv = option(srv)
|
||||
}
|
||||
|
||||
srv.Logger.Debug("parsing CSV file", "birthdayFile", srv.config.BirthdayFile)
|
||||
|
||||
birthdays, err := parser.ParseCSV(srv.config.BirthdayFile)
|
||||
if err != nil {
|
||||
srv.Logger.Error("cannot parse CSV file", "birthdayFile", srv.config.BirthdayFile, "error", err)
|
||||
return nil, fmt.Errorf("cannot parse CSV file %s: %w", srv.config.BirthdayFile, err)
|
||||
if srv.Logger == nil {
|
||||
return nil, ErrNoLogger
|
||||
}
|
||||
|
||||
srv.Logger.Debug("creating notification services from config")
|
||||
|
||||
notificationServices, err := createNotificationServices(srv.Logger, srv.config)
|
||||
if err != nil {
|
||||
srv.Logger.Error("error creating notification services", "error", err)
|
||||
return nil, err
|
||||
if srv.config == nil {
|
||||
return nil, ErrNoConfig
|
||||
}
|
||||
|
||||
srv.Logger.Info("creating server")
|
||||
if len(srv.birthdays) == 0 {
|
||||
srv.Logger.Debug("parsing CSV file", "birthdayFile", srv.config.BirthdayFile)
|
||||
|
||||
server := &Server{
|
||||
Logger: srv.Logger,
|
||||
config: srv.config,
|
||||
birthdays: birthdays,
|
||||
notificationServices: notificationServices,
|
||||
var err error
|
||||
srv.birthdays, err = parser.ParseCSV(srv.config.BirthdayFile)
|
||||
if err != nil {
|
||||
srv.Logger.Error("cannot parse CSV file", "birthdayFile", srv.config.BirthdayFile, "error", err)
|
||||
return nil, fmt.Errorf("cannot parse CSV file %s: %w", srv.config.BirthdayFile, err)
|
||||
}
|
||||
}
|
||||
server.workers = []*Worker{NewWorker(server)}
|
||||
|
||||
return server, nil
|
||||
if len(srv.notificationServices) == 0 {
|
||||
srv.Logger.Debug("creating notification services from config")
|
||||
|
||||
var err error
|
||||
srv.notificationServices, err = createNotificationServices(srv.Logger, srv.config)
|
||||
if err != nil {
|
||||
srv.Logger.Error("error creating notification services", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(srv.workers) == 0 {
|
||||
srv.Logger.Info("creating server workers")
|
||||
|
||||
srv.workers = []Worker{NewSimpleWorker(srv)}
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() {
|
||||
|
|
37
server/server_test.go
Normal file
37
server/server_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNotify(t *testing.T) {
|
||||
th := SetupTestHelper(t)
|
||||
defer th.TearDown()
|
||||
t.Run("should correctly use the notification services to notify", func(t *testing.T) {
|
||||
birthday := th.srv.birthdays[0]
|
||||
th.mockNotificationService.
|
||||
EXPECT().
|
||||
Notify(birthday).
|
||||
Return(nil).
|
||||
Times(1)
|
||||
|
||||
err := th.srv.Notify(birthday)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should return an error if a service fails", func(t *testing.T) {
|
||||
mockErr := errors.New("failed to notify")
|
||||
birthday := th.srv.birthdays[0]
|
||||
th.mockNotificationService.
|
||||
EXPECT().
|
||||
Notify(birthday).
|
||||
Return(mockErr).
|
||||
Times(1)
|
||||
|
||||
err := th.srv.Notify(birthday)
|
||||
require.ErrorIs(t, err, mockErr)
|
||||
})
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
//go:generate mockgen -source=worker.go -destination=worker_mock.go -package=server
|
||||
package server
|
||||
|
||||
import (
|
||||
|
@ -7,15 +8,20 @@ import (
|
|||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
type Worker interface {
|
||||
Start()
|
||||
Stop()
|
||||
}
|
||||
|
||||
type SimpleWorker struct {
|
||||
server *Server
|
||||
logger *log.Logger
|
||||
stop chan bool
|
||||
stopped chan bool
|
||||
}
|
||||
|
||||
func NewWorker(server *Server) *Worker {
|
||||
return &Worker{
|
||||
func NewSimpleWorker(server *Server) *SimpleWorker {
|
||||
return &SimpleWorker{
|
||||
server: server,
|
||||
logger: server.Logger,
|
||||
stop: make(chan bool, 1),
|
||||
|
@ -23,20 +29,20 @@ func NewWorker(server *Server) *Worker {
|
|||
}
|
||||
}
|
||||
|
||||
func (w *Worker) Start() {
|
||||
func (w *SimpleWorker) Start() {
|
||||
w.logger.Info("starting worker")
|
||||
go w.run()
|
||||
w.logger.Info("worker started")
|
||||
}
|
||||
|
||||
func (w *Worker) Stop() {
|
||||
func (w *SimpleWorker) Stop() {
|
||||
w.logger.Info("stopping worker")
|
||||
w.stop <- true
|
||||
<-w.stopped
|
||||
w.logger.Info("worker stopped")
|
||||
}
|
||||
|
||||
func (w *Worker) notifyDay(year, month, day int) {
|
||||
func (w *SimpleWorker) notifyDay(year, month, day int) {
|
||||
birthdays := model.FilterByDate(w.server.Birthdays(), year, month, day)
|
||||
w.logger.Info("notifying for date", "birthdays", len(birthdays), "year", year, "month", month, "day", day)
|
||||
|
||||
|
@ -46,7 +52,7 @@ func (w *Worker) notifyDay(year, month, day int) {
|
|||
}
|
||||
}
|
||||
|
||||
func (w *Worker) run() {
|
||||
func (w *SimpleWorker) run() {
|
||||
// first we calculate the delta with 23:00
|
||||
now := time.Now()
|
||||
eleven := time.Date(now.Year(), now.Month(), now.Day(), 23, 0, 0, 0, now.Location())
|
||||
|
|
58
server/worker_mock.go
Normal file
58
server/worker_mock.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: worker.go
|
||||
|
||||
// Package server is a generated GoMock package.
|
||||
package server
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockWorker is a mock of Worker interface.
|
||||
type MockWorker struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockWorkerMockRecorder
|
||||
}
|
||||
|
||||
// MockWorkerMockRecorder is the mock recorder for MockWorker.
|
||||
type MockWorkerMockRecorder struct {
|
||||
mock *MockWorker
|
||||
}
|
||||
|
||||
// NewMockWorker creates a new mock instance.
|
||||
func NewMockWorker(ctrl *gomock.Controller) *MockWorker {
|
||||
mock := &MockWorker{ctrl: ctrl}
|
||||
mock.recorder = &MockWorkerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockWorker) EXPECT() *MockWorkerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Start mocks base method.
|
||||
func (m *MockWorker) Start() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Start")
|
||||
}
|
||||
|
||||
// Start indicates an expected call of Start.
|
||||
func (mr *MockWorkerMockRecorder) Start() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockWorker)(nil).Start))
|
||||
}
|
||||
|
||||
// Stop mocks base method.
|
||||
func (m *MockWorker) Stop() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Stop")
|
||||
}
|
||||
|
||||
// Stop indicates an expected call of Stop.
|
||||
func (mr *MockWorkerMockRecorder) Stop() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockWorker)(nil).Stop))
|
||||
}
|
Loading…
Reference in a new issue