mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Rather than having the services subscribe to each individual event type, have only one subscriber for the whole event object. This spreads the logic to the service through the `EventHandler` type. This is important for the next stage where the IMAP service will be required to apply events from the past.
204 lines
5.9 KiB
Go
204 lines
5.9 KiB
Go
// Copyright (c) 2023 Proton AG
|
|
//
|
|
// This file is part of Proton Mail Bridge.
|
|
//
|
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package userevents
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/gluon/async"
|
|
"github.com/ProtonMail/go-proton-api"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/events/mocks"
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestServiceHandleEventError_SubscriberEventUnwrapping(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
eventPublisher := mocks.NewMockEventPublisher(mockCtrl)
|
|
eventIDStore := NewInMemoryEventIDStore()
|
|
|
|
service := NewService(
|
|
"foo",
|
|
&NullEventSource{},
|
|
eventIDStore,
|
|
eventPublisher,
|
|
100*time.Millisecond,
|
|
time.Millisecond,
|
|
time.Second,
|
|
async.NoopPanicHandler{},
|
|
)
|
|
|
|
lastEventID := "PrevEvent"
|
|
event := proton.Event{EventID: "MyEvent"}
|
|
subscriber := &noOpSubscriber[proton.Event]{}
|
|
|
|
err := &eventPublishError{
|
|
subscriber: subscriber,
|
|
error: &proton.NetError{},
|
|
}
|
|
|
|
subscriberName, unpackedErr := service.handleEventError(context.Background(), lastEventID, event, err)
|
|
require.Equal(t, subscriber.name(), subscriberName)
|
|
protonNetErr := new(proton.NetError)
|
|
require.True(t, errors.As(unpackedErr, &protonNetErr))
|
|
|
|
err2 := &proton.APIError{Status: 500}
|
|
subscriberName2, unpackedErr2 := service.handleEventError(context.Background(), lastEventID, event, err2)
|
|
require.Equal(t, "", subscriberName2)
|
|
protonAPIErr := new(proton.APIError)
|
|
require.True(t, errors.As(unpackedErr2, &protonAPIErr))
|
|
}
|
|
|
|
func TestServiceHandleEventError_BadEventPutsServiceOnPause(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
eventPublisher := mocks.NewMockEventPublisher(mockCtrl)
|
|
eventIDStore := NewInMemoryEventIDStore()
|
|
|
|
service := NewService(
|
|
"foo",
|
|
&NullEventSource{},
|
|
eventIDStore,
|
|
eventPublisher,
|
|
100*time.Millisecond,
|
|
time.Millisecond,
|
|
time.Second,
|
|
async.NoopPanicHandler{},
|
|
)
|
|
service.Resume()
|
|
lastEventID := "PrevEvent"
|
|
event := proton.Event{EventID: "MyEvent"}
|
|
|
|
err := &proton.APIError{}
|
|
|
|
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserBadEvent{
|
|
UserID: service.userID,
|
|
OldEventID: lastEventID,
|
|
NewEventID: event.EventID,
|
|
EventInfo: event.String(),
|
|
Error: err,
|
|
})).Times(1)
|
|
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, err)
|
|
require.True(t, service.IsPaused())
|
|
}
|
|
|
|
func TestServiceHandleEventError_BadEventFromPublishTimeout(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
eventPublisher := mocks.NewMockEventPublisher(mockCtrl)
|
|
eventIDStore := NewInMemoryEventIDStore()
|
|
|
|
service := NewService(
|
|
"foo",
|
|
&NullEventSource{},
|
|
eventIDStore,
|
|
eventPublisher,
|
|
100*time.Millisecond,
|
|
time.Millisecond,
|
|
time.Second,
|
|
async.NoopPanicHandler{},
|
|
)
|
|
lastEventID := "PrevEvent"
|
|
event := proton.Event{EventID: "MyEvent"}
|
|
err := ErrPublishTimeoutExceeded
|
|
|
|
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UserBadEvent{
|
|
UserID: service.userID,
|
|
OldEventID: lastEventID,
|
|
NewEventID: event.EventID,
|
|
EventInfo: event.String(),
|
|
Error: err,
|
|
})).Times(1)
|
|
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, err)
|
|
}
|
|
|
|
func TestServiceHandleEventError_NoBadEventCheck(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
eventPublisher := mocks.NewMockEventPublisher(mockCtrl)
|
|
eventIDStore := NewInMemoryEventIDStore()
|
|
|
|
service := NewService(
|
|
"foo",
|
|
&NullEventSource{},
|
|
eventIDStore,
|
|
eventPublisher,
|
|
100*time.Millisecond,
|
|
time.Millisecond,
|
|
time.Second,
|
|
async.NoopPanicHandler{},
|
|
)
|
|
lastEventID := "PrevEvent"
|
|
event := proton.Event{EventID: "MyEvent"}
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, context.Canceled)
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, &proton.NetError{})
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, &net.OpError{})
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, io.ErrUnexpectedEOF)
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, &proton.APIError{Status: 500})
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, &proton.APIError{Status: 429})
|
|
}
|
|
|
|
func TestServiceHandleEventError_JsonUnmarshalEventProducesUncategorizedErrorEvent(t *testing.T) {
|
|
mockCtrl := gomock.NewController(t)
|
|
eventPublisher := mocks.NewMockEventPublisher(mockCtrl)
|
|
eventIDStore := NewInMemoryEventIDStore()
|
|
|
|
service := NewService(
|
|
"foo",
|
|
&NullEventSource{},
|
|
eventIDStore,
|
|
eventPublisher,
|
|
100*time.Millisecond,
|
|
time.Millisecond,
|
|
time.Second,
|
|
async.NoopPanicHandler{},
|
|
)
|
|
lastEventID := "PrevEvent"
|
|
event := proton.Event{EventID: "MyEvent"}
|
|
err := &json.UnmarshalTypeError{}
|
|
|
|
eventPublisher.EXPECT().PublishEvent(gomock.Any(), gomock.Eq(events.UncategorizedEventError{
|
|
UserID: service.userID,
|
|
Error: err,
|
|
})).Times(1)
|
|
|
|
_, _ = service.handleEventError(context.Background(), lastEventID, event, err)
|
|
}
|
|
|
|
type noOpSubscriber[T any] struct{}
|
|
|
|
func (n noOpSubscriber[T]) name() string { //nolint:unused
|
|
return "NoopSubscriber"
|
|
}
|
|
|
|
func (n noOpSubscriber[T]) handle(_ context.Context, _ T) error { //nolint:unused
|
|
return nil
|
|
}
|
|
|
|
//nolint:unused
|
|
func (n noOpSubscriber[T]) close() {} //
|
|
|
|
//nolint:unused
|
|
func (n noOpSubscriber[T]) cancel() {}
|