diff --git a/pkg/app.go b/pkg/app.go index 97eca84..87a31d7 100644 --- a/pkg/app.go +++ b/pkg/app.go @@ -18,13 +18,12 @@ import ( ) type App struct { - Logger *zerolog.Logger - Config *types.Config - StateManager *state.Manager - MutesManager *mutes.Manager - ReportGenerator *report.Generator - StateGenerator *state.Generator - Reporters []reportersPkg.Reporter + Logger *zerolog.Logger + Config *types.Config + StateManager *state.Manager + ReportGenerator *report.Generator + StateGenerator *state.Generator + ReportDispatcher *report.Dispatcher } func NewApp(configPath string, filesystem fs.FS, version string) *App { @@ -47,31 +46,27 @@ func NewApp(configPath string, filesystem fs.FS, version string) *App { timeZone, _ := time.LoadLocation(config.Timezone) + reporters := []reportersPkg.Reporter{ + pagerduty.NewPagerDutyReporter(config.PagerDutyConfig, log), + telegram.NewTelegramReporter(config.TelegramConfig, mutesManager, stateGenerator, dataManager, log, version, timeZone), + } + + reportDispatcher := report.NewDispatcher(log, mutesManager, reporters) + return &App{ - Logger: log, - Config: config, - StateManager: stateManager, - MutesManager: mutesManager, - ReportGenerator: reportGenerator, - StateGenerator: stateGenerator, - Reporters: []reportersPkg.Reporter{ - pagerduty.NewPagerDutyReporter(config.PagerDutyConfig, log), - telegram.NewTelegramReporter(config.TelegramConfig, mutesManager, stateGenerator, dataManager, log, version, timeZone), - }, + Logger: log, + Config: config, + StateManager: stateManager, + ReportGenerator: reportGenerator, + StateGenerator: stateGenerator, + ReportDispatcher: reportDispatcher, } } func (a *App) Start() { a.StateManager.Load() - a.MutesManager.Load() - - for _, reporter := range a.Reporters { - if err := reporter.Init(); err != nil { - a.Logger.Fatal().Err(err).Str("name", reporter.Name()).Msg("Error initializing reporter") - } - if reporter.Enabled() { - a.Logger.Info().Str("name", reporter.Name()).Msg("Init reporter") - } + if err := a.ReportDispatcher.Init(); err != nil { + a.Logger.Panic().Err(err).Msg("Error initializing reporters") } c := cron.New() @@ -89,20 +84,5 @@ func (a *App) Start() { func (a *App) Report() { newState := a.StateGenerator.GetState(a.StateManager.State) generatedReport := a.ReportGenerator.GenerateReport(a.StateManager.State, newState) - - if generatedReport.Empty() { - a.Logger.Debug().Msg("Empty report, not sending.") - return - } - - a.Logger.Debug().Int("len", len(generatedReport.Entries)).Msg("Got non-empty report") - - for _, reporter := range a.Reporters { - if reporter.Enabled() { - a.Logger.Debug().Str("name", reporter.Name()).Msg("Sending report...") - if err := reporter.SendReport(generatedReport); err != nil { - a.Logger.Error().Err(err).Str("name", reporter.Name()).Msg("Failed to send report") - } - } - } + a.ReportDispatcher.SendReport(generatedReport) } diff --git a/pkg/mutes/manager.go b/pkg/mutes/manager.go index 2586626..c7572ec 100644 --- a/pkg/mutes/manager.go +++ b/pkg/mutes/manager.go @@ -3,6 +3,7 @@ package mutesmanager import ( "encoding/json" "main/pkg/fs" + "main/pkg/report/entry" "main/pkg/utils" "github.com/rs/zerolog" @@ -59,12 +60,19 @@ func (m *Manager) Save() { } } -func (m *Manager) IsMuted(chain string, proposalID string) bool { +func (m *Manager) IsEntryMuted(reportEntry entry.ReportEntry) bool { + entryConverted, ok := reportEntry.(entry.ReportEntryNotError) + if !ok { + return false + } + if m.MutesPath == "" { return false } - return m.Mutes.IsMuted(chain, proposalID) + chain := entryConverted.GetChain() + proposal := entryConverted.GetProposal() + return m.Mutes.IsMuted(chain.Name, proposal.ID) } func (m *Manager) AddMute(mute *Mute) { diff --git a/pkg/mutes/manager_test.go b/pkg/mutes/manager_test.go index 79d50fb..4186d52 100644 --- a/pkg/mutes/manager_test.go +++ b/pkg/mutes/manager_test.go @@ -1,8 +1,10 @@ package mutesmanager import ( + "main/pkg/events" "main/pkg/fs" "main/pkg/logger" + "main/pkg/types" "testing" "time" @@ -110,8 +112,14 @@ func TestMuteManagerAddMuteIsMuted(t *testing.T) { Expires: time.Now().Add(time.Hour), }) - assert.True(t, manager.IsMuted("chain", "proposal")) - assert.False(t, manager.IsMuted("chain2", "proposal")) + assert.True(t, manager.IsEntryMuted(events.VotedEvent{ + Chain: &types.Chain{Name: "chain"}, + Proposal: types.Proposal{ID: "proposal"}, + })) + assert.False(t, manager.IsEntryMuted(events.VotedEvent{ + Chain: &types.Chain{Name: "chain2"}, + Proposal: types.Proposal{ID: "proposal"}, + })) } func TestMuteManagerIsMutedNoPath(t *testing.T) { @@ -128,5 +136,22 @@ func TestMuteManagerIsMutedNoPath(t *testing.T) { Expires: time.Now().Add(time.Hour), }) - assert.False(t, manager.IsMuted("chain", "proposal")) + assert.False(t, manager.IsEntryMuted(events.VotedEvent{ + Chain: &types.Chain{Name: "chain"}, + Proposal: types.Proposal{ID: "proposal"}, + })) +} + +func TestMuteManagerIsNotAlert(t *testing.T) { + t.Parallel() + + log := logger.GetNopLogger() + filesystem := &fs.TestFS{} + + manager := NewMutesManager("", filesystem, log) + manager.Load() + + assert.False(t, manager.IsEntryMuted(events.ProposalsQueryErrorEvent{ + Chain: &types.Chain{Name: "chain"}, + })) } diff --git a/pkg/report/dispatcher.go b/pkg/report/dispatcher.go new file mode 100644 index 0000000..5cc2dac --- /dev/null +++ b/pkg/report/dispatcher.go @@ -0,0 +1,83 @@ +package report + +import ( + mutes "main/pkg/mutes" + reportersPkg "main/pkg/reporters" + + "github.com/rs/zerolog" +) + +type Dispatcher struct { + Logger zerolog.Logger + MutesManager *mutes.Manager + Reporters []reportersPkg.Reporter +} + +func NewDispatcher( + logger *zerolog.Logger, + mutesManager *mutes.Manager, + reporters []reportersPkg.Reporter, +) *Dispatcher { + return &Dispatcher{ + Logger: logger.With().Str("component", "report_dispatcher").Logger(), + MutesManager: mutesManager, + Reporters: reporters, + } +} + +func (d *Dispatcher) Init() error { + d.MutesManager.Load() + + for _, reporter := range d.Reporters { + if err := reporter.Init(); err != nil { + d.Logger.Error().Err(err). + Str("name", reporter.Name()). + Msg("Error initializing reporter") + return err + } + if reporter.Enabled() { + d.Logger.Info().Str("name", reporter.Name()).Msg("Init reporter") + } + } + + return nil +} + +func (d *Dispatcher) SendReport(report reportersPkg.Report) { + if report.Empty() { + d.Logger.Debug().Msg("Empty report, not sending.") + return + } + + d.Logger.Debug().Int("len", len(report.Entries)).Msg("Got non-empty report") + + for _, reporter := range d.Reporters { + if !reporter.Enabled() { + d.Logger.Debug(). + Str("name", reporter.Name()). + Msg("Reporter is disabled, not sending report") + continue + } + + d.Logger.Debug(). + Str("name", reporter.Name()). + Msg("Sending report...") + + for _, reportEntry := range report.Entries { + if d.MutesManager.IsEntryMuted(reportEntry) { + d.Logger.Debug(). + Str("entry", reportEntry.Name()). + Msg("Notifications are muted, not sending.") + continue + } + + if err := reporter.SendReportEntry(reportEntry); err != nil { + d.Logger.Error(). + Err(err). + Str("name", reporter.Name()). + Str("entry", reportEntry.Name()). + Msg("Failed to send report entry") + } + } + } +} diff --git a/pkg/report/dispatcher_test.go b/pkg/report/dispatcher_test.go new file mode 100644 index 0000000..86c08e6 --- /dev/null +++ b/pkg/report/dispatcher_test.go @@ -0,0 +1,122 @@ +package report + +import ( + "main/pkg/events" + "main/pkg/fs" + "main/pkg/logger" + mutes "main/pkg/mutes" + "main/pkg/report/entry" + reportersPkg "main/pkg/reporters" + "main/pkg/types" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestReportDispatcherInitFail(t *testing.T) { + t.Parallel() + + mutesManager := mutes.NewMutesManager("./state.json", &fs.TestFS{}, logger.GetNopLogger()) + dispatcher := NewDispatcher(logger.GetNopLogger(), mutesManager, []reportersPkg.Reporter{ + &reportersPkg.TestReporter{WithInitFail: true}, + }) + + err := dispatcher.Init() + require.Error(t, err) +} + +func TestReportDispatcherInitOk(t *testing.T) { + t.Parallel() + + mutesManager := mutes.NewMutesManager("./state.json", &fs.TestFS{}, logger.GetNopLogger()) + dispatcher := NewDispatcher(logger.GetNopLogger(), mutesManager, []reportersPkg.Reporter{ + &reportersPkg.TestReporter{}, + }) + + err := dispatcher.Init() + require.NoError(t, err) +} + +func TestReportDispatcherSendEmptyReport(t *testing.T) { + t.Parallel() + + mutesManager := mutes.NewMutesManager("./state.json", &fs.TestFS{}, logger.GetNopLogger()) + dispatcher := NewDispatcher(logger.GetNopLogger(), mutesManager, []reportersPkg.Reporter{ + &reportersPkg.TestReporter{}, + }) + + err := dispatcher.Init() + require.NoError(t, err) + + dispatcher.SendReport(reportersPkg.Report{Entries: make([]entry.ReportEntry, 0)}) +} + +func TestReportDispatcherSendReportDisabledReporter(t *testing.T) { + t.Parallel() + + mutesManager := mutes.NewMutesManager("./state.json", &fs.TestFS{}, logger.GetNopLogger()) + dispatcher := NewDispatcher(logger.GetNopLogger(), mutesManager, []reportersPkg.Reporter{ + &reportersPkg.TestReporter{WithDisabled: true}, + }) + + err := dispatcher.Init() + require.NoError(t, err) + + dispatcher.SendReport(reportersPkg.Report{Entries: []entry.ReportEntry{ + events.ProposalsQueryErrorEvent{}, + }}) +} + +func TestReportDispatcherSendReportMuted(t *testing.T) { + t.Parallel() + + mutesManager := mutes.NewMutesManager("./state.json", &fs.TestFS{}, logger.GetNopLogger()) + dispatcher := NewDispatcher(logger.GetNopLogger(), mutesManager, []reportersPkg.Reporter{ + &reportersPkg.TestReporter{}, + }) + + err := dispatcher.Init() + require.NoError(t, err) + + mutesManager.AddMute(&mutes.Mute{Expires: time.Now().Add(time.Minute)}) + + dispatcher.SendReport(reportersPkg.Report{Entries: []entry.ReportEntry{ + events.NotVotedEvent{ + Chain: &types.Chain{Name: "chain"}, + Proposal: types.Proposal{ID: "proposal"}, + }, + }}) +} + +func TestReportDispatcherSendReportErrorSending(t *testing.T) { + t.Parallel() + + mutesManager := mutes.NewMutesManager("./state.json", &fs.TestFS{}, logger.GetNopLogger()) + dispatcher := NewDispatcher(logger.GetNopLogger(), mutesManager, []reportersPkg.Reporter{ + &reportersPkg.TestReporter{WithErrorSending: true}, + }) + + err := dispatcher.Init() + require.NoError(t, err) + + dispatcher.SendReport(reportersPkg.Report{Entries: []entry.ReportEntry{ + events.ProposalsQueryErrorEvent{}, + }}) +} + +func TestReportDispatcherSendReportOk(t *testing.T) { + t.Parallel() + + mutesManager := mutes.NewMutesManager("./state.json", &fs.TestFS{}, logger.GetNopLogger()) + dispatcher := NewDispatcher(logger.GetNopLogger(), mutesManager, []reportersPkg.Reporter{ + &reportersPkg.TestReporter{}, + }) + + err := dispatcher.Init() + require.NoError(t, err) + + dispatcher.SendReport(reportersPkg.Report{Entries: []entry.ReportEntry{ + events.ProposalsQueryErrorEvent{}, + }}) +} diff --git a/pkg/reporters/pagerduty/pagerduty.go b/pkg/reporters/pagerduty/pagerduty.go index 64122ef..85feee1 100644 --- a/pkg/reporters/pagerduty/pagerduty.go +++ b/pkg/reporters/pagerduty/pagerduty.go @@ -12,8 +12,6 @@ import ( "os" "time" - "main/pkg/reporters" - "github.com/rs/zerolog" ) @@ -132,26 +130,17 @@ func (r Reporter) Name() string { return "pagerduty-reporter" } -func (r Reporter) SendReport(report reporters.Report) error { - var err error - - for _, reportEntry := range report.Entries { - if !reportEntry.IsAlert() { - continue - } - - alert, alertCreateErr := r.NewAlertFromReportEntry(reportEntry) - if alertCreateErr != nil { - err = alertCreateErr - continue - } +func (r Reporter) SendReportEntry(reportEntry entry.ReportEntry) error { + if !reportEntry.IsAlert() { + return nil + } - if alertErr := r.SendAlert(alert); alertErr != nil { - err = alertErr - } + alert, alertCreateErr := r.NewAlertFromReportEntry(reportEntry) + if alertCreateErr != nil { + return alertCreateErr } - return err + return r.SendAlert(alert) } func (r Reporter) SendAlert(alert Alert) error { diff --git a/pkg/reporters/reporter.go b/pkg/reporters/reporter.go index 6c87b78..21d3775 100644 --- a/pkg/reporters/reporter.go +++ b/pkg/reporters/reporter.go @@ -7,7 +7,7 @@ import ( type Reporter interface { Init() error Enabled() bool - SendReport(report Report) error + SendReportEntry(entry entry.ReportEntry) error Name() string } diff --git a/pkg/reporters/telegram/telegram.go b/pkg/reporters/telegram/telegram.go index ddfa405..777f21a 100644 --- a/pkg/reporters/telegram/telegram.go +++ b/pkg/reporters/telegram/telegram.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "main/pkg/reporters" "main/pkg/types" "github.com/rs/zerolog" @@ -94,38 +93,24 @@ func (reporter *Reporter) SerializeReportEntry(e entry.ReportEntry) (string, err return reporter.TemplatesManager.Render(e.Name(), e) } -func (reporter *Reporter) SendReport(report reporters.Report) error { - for _, reportEntry := range report.Entries { - if entryConverted, ok := reportEntry.(entry.ReportEntryNotError); ok { - chain := entryConverted.GetChain() - proposal := entryConverted.GetProposal() - if reporter.MutesManager.IsMuted(chain.Name, proposal.ID) { - reporter.Logger.Debug(). - Str("chain", chain.Name). - Str("proposal", proposal.ID). - Msg("Notifications are muted, not sending.") - continue - } - } - - serializedEntry, err := reporter.SerializeReportEntry(reportEntry) - if err != nil { - reporter.Logger.Err(err).Msg("Could not serialize report entry") - return err - } +func (reporter *Reporter) SendReportEntry(reportEntry entry.ReportEntry) error { + serializedEntry, err := reporter.SerializeReportEntry(reportEntry) + if err != nil { + reporter.Logger.Err(err).Msg("Could not serialize report entry") + return err + } - _, err = reporter.TelegramBot.Send( - &tele.User{ - ID: reporter.TelegramChat, - }, - serializedEntry, - tele.ModeHTML, - tele.NoPreview, - ) - if err != nil { - reporter.Logger.Err(err).Msg("Could not send Telegram message") - return err - } + _, err = reporter.TelegramBot.Send( + &tele.User{ + ID: reporter.TelegramChat, + }, + serializedEntry, + tele.ModeHTML, + tele.NoPreview, + ) + if err != nil { + reporter.Logger.Err(err).Msg("Could not send Telegram message") + return err } return nil diff --git a/pkg/reporters/test_reporter.go b/pkg/reporters/test_reporter.go new file mode 100644 index 0000000..2146a98 --- /dev/null +++ b/pkg/reporters/test_reporter.go @@ -0,0 +1,36 @@ +package reporters + +import ( + "errors" + "main/pkg/report/entry" +) + +type TestReporter struct { + WithInitFail bool + WithDisabled bool + WithErrorSending bool +} + +func (r *TestReporter) Init() error { + if r.WithInitFail { + return errors.New("fail") + } + + return nil +} + +func (r *TestReporter) Enabled() bool { + return !r.WithDisabled +} + +func (r *TestReporter) SendReportEntry(entry entry.ReportEntry) error { + if r.WithErrorSending { + return errors.New("fail") + } + + return nil +} + +func (r *TestReporter) Name() string { + return "test-reporter" +}