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 (
|
import (
|
||||||
"git.ctrlz.es/mgdelacroix/birthdaybot/model"
|
"git.ctrlz.es/mgdelacroix/birthdaybot/model"
|
||||||
|
"git.ctrlz.es/mgdelacroix/birthdaybot/notification"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,3 +21,24 @@ func WithLogger(logger *log.Logger) Option {
|
||||||
return server
|
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"
|
"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 {
|
type Server struct {
|
||||||
Logger *log.Logger
|
Logger *log.Logger
|
||||||
config *model.Config
|
config *model.Config
|
||||||
workers []*Worker
|
workers []Worker
|
||||||
birthdays []*model.Birthday
|
birthdays []*model.Birthday
|
||||||
notificationServices []notification.NotificationService
|
notificationServices []notification.NotificationService
|
||||||
}
|
}
|
||||||
|
@ -44,33 +48,43 @@ func New(options ...Option) (*Server, error) {
|
||||||
srv = option(srv)
|
srv = option(srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.Logger.Debug("parsing CSV file", "birthdayFile", srv.config.BirthdayFile)
|
if srv.Logger == nil {
|
||||||
|
return nil, ErrNoLogger
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.Logger.Debug("creating notification services from config")
|
if srv.config == nil {
|
||||||
|
return nil, ErrNoConfig
|
||||||
notificationServices, err := createNotificationServices(srv.Logger, srv.config)
|
|
||||||
if err != nil {
|
|
||||||
srv.Logger.Error("error creating notification services", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.Logger.Info("creating server")
|
if len(srv.birthdays) == 0 {
|
||||||
|
srv.Logger.Debug("parsing CSV file", "birthdayFile", srv.config.BirthdayFile)
|
||||||
|
|
||||||
server := &Server{
|
var err error
|
||||||
Logger: srv.Logger,
|
srv.birthdays, err = parser.ParseCSV(srv.config.BirthdayFile)
|
||||||
config: srv.config,
|
if err != nil {
|
||||||
birthdays: birthdays,
|
srv.Logger.Error("cannot parse CSV file", "birthdayFile", srv.config.BirthdayFile, "error", err)
|
||||||
notificationServices: notificationServices,
|
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() {
|
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
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,15 +8,20 @@ import (
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Worker struct {
|
type Worker interface {
|
||||||
|
Start()
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimpleWorker struct {
|
||||||
server *Server
|
server *Server
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
stop chan bool
|
stop chan bool
|
||||||
stopped chan bool
|
stopped chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWorker(server *Server) *Worker {
|
func NewSimpleWorker(server *Server) *SimpleWorker {
|
||||||
return &Worker{
|
return &SimpleWorker{
|
||||||
server: server,
|
server: server,
|
||||||
logger: server.Logger,
|
logger: server.Logger,
|
||||||
stop: make(chan bool, 1),
|
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")
|
w.logger.Info("starting worker")
|
||||||
go w.run()
|
go w.run()
|
||||||
w.logger.Info("worker started")
|
w.logger.Info("worker started")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) Stop() {
|
func (w *SimpleWorker) Stop() {
|
||||||
w.logger.Info("stopping worker")
|
w.logger.Info("stopping worker")
|
||||||
w.stop <- true
|
w.stop <- true
|
||||||
<-w.stopped
|
<-w.stopped
|
||||||
w.logger.Info("worker 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)
|
birthdays := model.FilterByDate(w.server.Birthdays(), year, month, day)
|
||||||
w.logger.Info("notifying for date", "birthdays", len(birthdays), "year", year, "month", month, "day", 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
|
// first we calculate the delta with 23:00
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
eleven := time.Date(now.Year(), now.Month(), now.Day(), 23, 0, 0, 0, now.Location())
|
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