package service_test import ( "context" "errors" "testing" "time" "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" "gitea.dwysokinski.me/twhelp/dcbot/internal/service" "gitea.dwysokinski.me/twhelp/dcbot/internal/service/internal/mock" "gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) func TestMonitor_Create(t *testing.T) { t.Parallel() t.Run("OK", func(t *testing.T) { t.Parallel() repo := &mock.FakeMonitorRepository{} repo.ListReturns(nil, nil) repo.CreateCalls(func(ctx context.Context, params domain.CreateMonitorParams) (domain.Monitor, error) { return domain.Monitor{ ID: uuid.NewString(), TribeID: params.TribeID(), GroupID: params.GroupID(), CreatedAt: time.Now(), }, nil }) groupSvc := &mock.FakeGroupReader{} groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) { return domain.Group{ ID: groupID, ServerID: serverID, ChannelGains: "", ChannelLosses: "", ServerKey: "pl151", VersionCode: "pl", CreatedAt: time.Now(), }, nil }) client := &mock.FakeTWHelpClient{} tribe := twhelp.Tribe{ ID: 150, Tag: "*TAG*", Name: uuid.NewString(), ProfileURL: "https://pl151.plemiona.pl/game.php?screen=info_player&id=150", DeletedAt: time.Time{}, } client.GetTribeByTagReturns(tribe, nil) groupID := uuid.NewString() monitor, err := service.NewMonitor(repo, groupSvc, client, 10). Create(context.Background(), groupID, uuid.NewString(), tribe.Tag) assert.NoError(t, err) assert.NotEmpty(t, monitor.ID) assert.Equal(t, groupID, monitor.GroupID) assert.Equal(t, tribe.ID, monitor.TribeID) assert.NotEmpty(t, monitor.CreatedAt) }) t.Run("ERR: group doesn't exist", func(t *testing.T) { t.Parallel() groupID := uuid.NewString() groupSvc := &mock.FakeGroupReader{} groupSvc.GetReturns(domain.Group{}, domain.GroupNotFoundError{ID: groupID}) monitor, err := service.NewMonitor(nil, groupSvc, nil, 10). Create(context.Background(), groupID, uuid.NewString(), "tag") assert.ErrorIs(t, err, domain.GroupDoesNotExistError{ ID: groupID, }) assert.Zero(t, monitor) }) t.Run("ERR: monitor limit has been reached", func(t *testing.T) { t.Parallel() const maxMonitorsPerGroup = 10 repo := &mock.FakeMonitorRepository{} repo.ListReturns(make([]domain.Monitor, maxMonitorsPerGroup), nil) groupSvc := &mock.FakeGroupReader{} groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) { return domain.Group{ ID: groupID, ServerID: serverID, ChannelGains: "", ChannelLosses: "", ServerKey: "pl151", VersionCode: "pl", CreatedAt: time.Now(), }, nil }) monitor, err := service.NewMonitor(repo, groupSvc, nil, 10). Create(context.Background(), uuid.NewString(), uuid.NewString(), "TAG") assert.ErrorIs(t, err, domain.MonitorLimitReachedError{ Current: maxMonitorsPerGroup, Limit: maxMonitorsPerGroup, }) assert.Zero(t, monitor) }) t.Run("ERR: tribe doesn't exist", func(t *testing.T) { t.Parallel() t.Run("API error", func(t *testing.T) { t.Parallel() repo := &mock.FakeMonitorRepository{} repo.ListReturns(nil, nil) repo.CreateCalls(func(ctx context.Context, params domain.CreateMonitorParams) (domain.Monitor, error) { return domain.Monitor{ ID: uuid.NewString(), TribeID: params.TribeID(), GroupID: params.GroupID(), CreatedAt: time.Now(), }, nil }) groupSvc := &mock.FakeGroupReader{} groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) { return domain.Group{ ID: groupID, ServerID: serverID, ChannelGains: "", ChannelLosses: "", ServerKey: "pl151", VersionCode: "pl", CreatedAt: time.Now(), }, nil }) client := &mock.FakeTWHelpClient{} client.GetTribeByTagReturns(twhelp.Tribe{}, twhelp.APIError{ Code: twhelp.ErrorCodeEntityNotFound, Message: "tribe not found", }) tag := "TAG" monitor, err := service.NewMonitor(repo, groupSvc, client, 10). Create(context.Background(), uuid.NewString(), uuid.NewString(), tag) assert.ErrorIs(t, err, domain.TribeDoesNotExistError{ Tag: tag, }) assert.Zero(t, monitor) }) t.Run("tribe is deleted", func(t *testing.T) { t.Parallel() repo := &mock.FakeMonitorRepository{} repo.ListReturns(nil, nil) groupSvc := &mock.FakeGroupReader{} groupSvc.GetCalls(func(ctx context.Context, groupID, serverID string) (domain.Group, error) { return domain.Group{ ID: groupID, ServerID: serverID, ChannelGains: "", ChannelLosses: "", ServerKey: "pl151", VersionCode: "pl", CreatedAt: time.Now(), }, nil }) client := &mock.FakeTWHelpClient{} tribe := twhelp.Tribe{ ID: 150, Tag: "*TAG*", Name: uuid.NewString(), ProfileURL: "https://pl151.plemiona.pl/game.php?screen=info_player&id=150", DeletedAt: time.Now(), } client.GetTribeByTagReturns(tribe, nil) monitor, err := service.NewMonitor(repo, groupSvc, client, 10). Create(context.Background(), uuid.NewString(), uuid.NewString(), tribe.Tag) assert.ErrorIs(t, err, domain.TribeDoesNotExistError{ Tag: tribe.Tag, }) assert.Zero(t, monitor) }) }) } func TestMonitor_Execute(t *testing.T) { t.Parallel() t.Run("OK", func(t *testing.T) { t.Parallel() client := &mock.FakeTWHelpClient{} villages := map[string][]twhelp.VillageMeta{ "pl:pl181": { { ID: 1, FullName: uuid.NewString(), ProfileURL: uuid.NewString(), }, { ID: 2, FullName: uuid.NewString(), ProfileURL: uuid.NewString(), }, { ID: 3, FullName: uuid.NewString(), ProfileURL: uuid.NewString(), }, { ID: 4, FullName: uuid.NewString(), ProfileURL: uuid.NewString(), }, }, "en:en130": { { ID: 100, FullName: uuid.NewString(), ProfileURL: uuid.NewString(), }, { ID: 101, FullName: uuid.NewString(), ProfileURL: uuid.NewString(), }, }, "pl:pl180": { { ID: 200, FullName: uuid.NewString(), ProfileURL: uuid.NewString(), }, }, } tribes := map[string][]twhelp.TribeMeta{ "pl:pl181": { { ID: 1, Name: uuid.NewString(), Tag: uuid.NewString(), ProfileURL: uuid.NewString(), }, { ID: 2, Name: uuid.NewString(), Tag: uuid.NewString(), ProfileURL: uuid.NewString(), }, { ID: 3, Name: uuid.NewString(), Tag: uuid.NewString(), ProfileURL: uuid.NewString(), }, }, "en:en130": { { ID: 100, Name: uuid.NewString(), Tag: uuid.NewString(), ProfileURL: uuid.NewString(), }, { ID: 101, Name: uuid.NewString(), Tag: uuid.NewString(), ProfileURL: uuid.NewString(), }, }, "pl:pl180": { { ID: 200, Name: uuid.NewString(), Tag: uuid.NewString(), ProfileURL: uuid.NewString(), }, }, } players := map[string][]twhelp.PlayerMeta{ "pl:pl181": { { ID: 1, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{ Tribe: tribes["pl:pl181"][0], Valid: true, }, }, { ID: 2, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{ Tribe: tribes["pl:pl181"][1], Valid: true, }, }, { ID: 3, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{ Tribe: tribes["pl:pl181"][2], Valid: true, }, }, { ID: 4, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{}, }, }, "en:en130": { { ID: 100, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{ Tribe: tribes["en:en130"][0], Valid: true, }, }, { ID: 101, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{ Tribe: tribes["en:en130"][1], Valid: true, }, }, { ID: 102, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{}, }, }, "pl:pl180": { { ID: 200, Name: uuid.NewString(), ProfileURL: uuid.NewString(), Tribe: twhelp.NullTribeMeta{ Tribe: tribes["pl:pl180"][0], Valid: true, }, }, }, } ennoblements := map[string][]twhelp.Ennoblement{ "pl:pl181": { { ID: 1, Village: villages["pl:pl181"][0], NewOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][0], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][1], Valid: true, }, CreatedAt: time.Now().Add(-5 * time.Minute), }, { ID: 2, // self conquer, should be skipped Village: villages["pl:pl181"][0], NewOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][0], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][0], Valid: true, }, CreatedAt: time.Now().Add(-4 * time.Minute), }, { ID: 3, // internal conquer, should be skipped Village: villages["pl:pl181"][1], NewOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][0], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][2], Valid: true, }, CreatedAt: time.Now().Add(-3 * time.Minute), }, { ID: 4, // barbarian Village: villages["pl:pl181"][2], NewOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][0], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{}, CreatedAt: time.Now().Add(-5 * time.Minute), }, { ID: 5, // disabled notifications about gains, should be skipped Village: villages["pl:pl181"][3], NewOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][1], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{}, CreatedAt: time.Now().Add(-5 * time.Minute), }, { ID: 6, // disabled notifications about losses, should be skipped Village: villages["pl:pl181"][3], NewOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][3], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl181"][0], Valid: true, }, CreatedAt: time.Now().Add(-5 * time.Minute), }, }, "en:en130": { { ID: 100, // no monitor for these tribes, should be skipped Village: villages["en:en130"][0], NewOwner: twhelp.NullPlayerMeta{ Player: players["en:en130"][0], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{}, CreatedAt: time.Now().Add(-5 * time.Minute), }, { ID: 101, // no monitor for these tribes, should be skipped Village: villages["en:en130"][0], NewOwner: twhelp.NullPlayerMeta{ Player: players["en:en130"][2], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{ Player: players["en:en130"][0], Valid: true, }, CreatedAt: time.Now().Add(-5 * time.Minute), }, { ID: 102, Village: villages["en:en130"][1], NewOwner: twhelp.NullPlayerMeta{ Player: players["en:en130"][1], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{ Player: players["en:en130"][2], Valid: true, }, CreatedAt: time.Now().Add(-5 * time.Minute), }, }, "pl:pl180": { { ID: 200, // api error, should be skipped Village: villages["pl:pl180"][0], NewOwner: twhelp.NullPlayerMeta{ Player: players["pl:pl180"][0], Valid: true, }, OldOwner: twhelp.NullPlayerMeta{}, CreatedAt: time.Now().Add(-5 * time.Minute), }, }, } client.ListEnnoblementsCalls( func( ctx context.Context, version string, server string, _ twhelp.ListEnnoblementsQueryParams, ) ([]twhelp.Ennoblement, error) { if version == "pl" && server == "pl180" { return nil, errors.New("random error") } return ennoblements[version+":"+server], nil }, ) groupSvc := &mock.FakeGroupReader{} groups := []domain.Group{ { ID: uuid.NewString(), ServerID: uuid.NewString(), ChannelGains: "", // should be skipped - ChannelGains and ChannelLosses are empty strings ChannelLosses: "", ServerKey: "pl181", VersionCode: "pl", CreatedAt: time.Now(), }, { ID: uuid.NewString(), ServerID: uuid.NewString(), ChannelGains: uuid.NewString(), ChannelLosses: "", ServerKey: "pl181", VersionCode: "pl", CreatedAt: time.Now(), }, { ID: uuid.NewString(), ServerID: uuid.NewString(), ChannelGains: "", ChannelLosses: uuid.NewString(), ServerKey: "pl181", VersionCode: "pl", CreatedAt: time.Now(), }, { ID: uuid.NewString(), ServerID: uuid.NewString(), ChannelGains: uuid.NewString(), ChannelLosses: uuid.NewString(), ServerKey: "en130", VersionCode: "en", CreatedAt: time.Now(), }, { ID: uuid.NewString(), ServerID: uuid.NewString(), ChannelGains: uuid.NewString(), ChannelLosses: uuid.NewString(), ServerKey: "pl180", VersionCode: "pl", CreatedAt: time.Now(), }, } groupSvc.ListReturns(groups, nil) repo := &mock.FakeMonitorRepository{} monitors := map[string][]domain.Monitor{ groups[1].ID: { { ID: uuid.NewString(), TribeID: tribes["pl:pl181"][0].ID, GroupID: groups[1].ID, CreatedAt: time.Now(), }, { ID: uuid.NewString(), TribeID: tribes["pl:pl181"][2].ID, GroupID: groups[1].ID, CreatedAt: time.Now(), }, }, groups[2].ID: { { ID: uuid.NewString(), TribeID: tribes["pl:pl181"][1].ID, GroupID: groups[2].ID, CreatedAt: time.Now(), }, }, groups[3].ID: { { ID: uuid.NewString(), TribeID: tribes["en:en130"][1].ID, GroupID: groups[3].ID, CreatedAt: time.Now(), }, }, groups[4].ID: { { ID: uuid.NewString(), TribeID: tribes["pl:pl180"][0].ID, GroupID: groups[4].ID, CreatedAt: time.Now(), }, }, } repo.ListCalls(func(ctx context.Context, groupID string) ([]domain.Monitor, error) { return monitors[groupID], nil }) notifications, err := service.NewMonitor(repo, groupSvc, client, 10). Execute(context.Background()) assert.NoError(t, err) expectedNotifications := []domain.EnnoblementNotification{ { Type: domain.EnnoblementNotificationTypeGain, ServerID: groups[1].ServerID, ChannelID: groups[1].ChannelGains, Ennoblement: ennoblementToDomainModel(ennoblements["pl:pl181"][0]), }, { Type: domain.EnnoblementNotificationTypeGain, ServerID: groups[1].ServerID, ChannelID: groups[1].ChannelGains, Ennoblement: ennoblementToDomainModel(ennoblements["pl:pl181"][3]), }, { Type: domain.EnnoblementNotificationTypeLoss, ServerID: groups[2].ServerID, ChannelID: groups[2].ChannelLosses, Ennoblement: ennoblementToDomainModel(ennoblements["pl:pl181"][0]), }, { Type: domain.EnnoblementNotificationTypeGain, ServerID: groups[3].ServerID, ChannelID: groups[3].ChannelGains, Ennoblement: ennoblementToDomainModel(ennoblements["en:en130"][2]), }, } assert.Len(t, notifications, len(expectedNotifications)) for _, n := range expectedNotifications { assert.Contains(t, notifications, n) } }) } func ennoblementToDomainModel(e twhelp.Ennoblement) domain.Ennoblement { return domain.Ennoblement{ ID: e.ID, Village: domain.VillageMeta(e.Village), NewOwner: domain.NullPlayerMeta{ Player: domain.PlayerMeta{ ID: e.NewOwner.Player.ID, Name: e.NewOwner.Player.Name, ProfileURL: e.NewOwner.Player.ProfileURL, Tribe: domain.NullTribeMeta{ Tribe: domain.TribeMeta(e.NewOwner.Player.Tribe.Tribe), Valid: e.NewOwner.Player.Tribe.Valid, }, }, Valid: e.NewOwner.Valid, }, OldOwner: domain.NullPlayerMeta{ Player: domain.PlayerMeta{ ID: e.OldOwner.Player.ID, Name: e.OldOwner.Player.Name, ProfileURL: e.OldOwner.Player.ProfileURL, Tribe: domain.NullTribeMeta{ Tribe: domain.TribeMeta(e.OldOwner.Player.Tribe.Tribe), Valid: e.OldOwner.Player.Tribe.Valid, }, }, Valid: e.OldOwner.Valid, }, CreatedAt: e.CreatedAt, } }