dcbot/internal/service/monitor_test.go
Dawid Wysokiński 28c4e377f5
All checks were successful
continuous-integration/drone/push Build is passing
refactor: twhelp - ennoblements - new response format
2023-01-22 10:59:31 +01:00

812 lines
20 KiB
Go

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"
"go.uber.org/zap"
)
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.ListTribesReturns([]twhelp.Tribe{tribe}, nil)
groupID := uuid.NewString()
monitor, err := service.NewMonitor(repo, groupSvc, client, zap.NewNop(), 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, zap.NewNop(), 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, zap.NewNop(), maxMonitorsPerGroup).
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()
tests := []struct {
name string
err error
}{
{
name: "err=null",
},
{
name: "err!=null",
err: twhelp.APIError{
Code: twhelp.ErrorCodeInternalServerError,
Message: "internal server error",
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, 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.ListTribesReturns(nil, tt.err)
tag := "TAG"
monitor, err := service.NewMonitor(repo, groupSvc, client, zap.NewNop(), 10).
Create(context.Background(), uuid.NewString(), uuid.NewString(), tag)
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
Tag: tag,
})
assert.Zero(t, monitor)
})
}
})
}
func TestMonitor_List(t *testing.T) {
t.Parallel()
groupID := uuid.NewString()
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
})
repo := &mock.FakeMonitorRepository{}
monitors := []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: 124,
GroupID: groupID,
CreatedAt: time.Now(),
},
{
ID: uuid.NewString(),
TribeID: 125,
GroupID: groupID,
CreatedAt: time.Now(),
},
}
repo.ListReturns(monitors, nil)
client := &mock.FakeTWHelpClient{}
client.GetTribeByIDCalls(func(ctx context.Context, _ string, _ string, id int64) (twhelp.Tribe, error) {
return twhelp.Tribe{
ID: id,
Tag: uuid.NewString(),
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
DeletedAt: time.Time{},
}, nil
})
res, err := service.NewMonitor(repo, groupSvc, client, zap.NewNop(), 10).
List(context.Background(), groupID, uuid.NewString())
assert.NoError(t, err)
assert.Len(t, res, len(monitors))
for i, m := range monitors {
assert.Equal(t, m, res[i].Monitor)
assert.Equal(t, m.TribeID, res[i].Tribe.ID)
}
}
func TestMonitor_Execute(t *testing.T) {
t.Parallel()
client := &mock.FakeTWHelpClient{}
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(),
},
},
"de:de200": {
{
ID: 300,
Name: uuid.NewString(),
Tag: uuid.NewString(),
ProfileURL: uuid.NewString(),
},
{
ID: 301,
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,
},
},
},
"de:de200": {
{
ID: 300,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: tribes["de:de200"][0],
Valid: true,
},
},
{
ID: 301,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: tribes["de:de200"][0],
Valid: true,
},
},
{
ID: 302,
Name: uuid.NewString(),
ProfileURL: uuid.NewString(),
Tribe: twhelp.NullTribeMeta{
Tribe: tribes["de:de200"][1],
Valid: true,
},
},
},
}
villages := map[string][]twhelp.VillageMeta{
"pl:pl181": {
{
ID: 1,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][1],
Valid: true,
},
},
{
ID: 2,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
},
{
ID: 3,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][2],
Valid: true,
},
},
{
ID: 4,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
},
{
ID: 5,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
},
},
"en:en130": {
{
ID: 100,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
},
{
ID: 101,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["en:en130"][0],
Valid: true,
},
},
{
ID: 102,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["en:en130"][2],
Valid: true,
},
},
},
"pl:pl180": {
{
ID: 200,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
},
},
"de:de200": {
{
ID: 300,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
},
{
ID: 301,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["de:de200"][1],
Valid: true,
},
},
{
ID: 302,
FullName: uuid.NewString(),
ProfileURL: uuid.NewString(),
Player: twhelp.NullPlayerMeta{
Player: players["de:de200"][2],
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,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
{
ID: 2, // self conquer, should be skipped
Village: villages["pl:pl181"][1],
NewOwner: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
CreatedAt: time.Now().Add(-4 * time.Minute),
},
{
ID: 3, // internal, should be skipped (internals disabled)
Village: villages["pl:pl181"][2],
NewOwner: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
CreatedAt: time.Now().Add(-3 * time.Minute),
},
{
ID: 4, // barbarian, shouldn't be skipped (barbarians enabled)
Village: villages["pl:pl181"][3],
NewOwner: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][0],
Valid: true,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
{
ID: 5, // disabled notifications about gains, should be skipped
Village: villages["pl:pl181"][4],
NewOwner: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][1],
Valid: true,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
{
ID: 6, // disabled notifications about losses, should be skipped
Village: villages["pl:pl181"][4],
NewOwner: twhelp.NullPlayerMeta{
Player: players["pl:pl181"][3],
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,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
{
ID: 101, // no monitor for these tribes, should be skipped
Village: villages["en:en130"][1],
NewOwner: twhelp.NullPlayerMeta{
Player: players["en:en130"][2],
Valid: true,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
{
ID: 102,
Village: villages["en:en130"][2],
NewOwner: twhelp.NullPlayerMeta{
Player: players["en:en130"][1],
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,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
},
"de:de200": {
{
ID: 300, // barbarian, should be skipped (barbarians disabled)
Village: villages["de:de200"][0],
NewOwner: twhelp.NullPlayerMeta{
Player: players["de:de200"][0],
Valid: true,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
{
ID: 301, // internal, shouldn't be skipped (internals enabled)
Village: villages["de:de200"][1],
NewOwner: twhelp.NullPlayerMeta{
Player: players["de:de200"][0],
Valid: true,
},
CreatedAt: time.Now().Add(-5 * time.Minute),
},
{
ID: 302, // internal, shouldn't be skipped (internals enabled)
Village: villages["de:de200"][2],
NewOwner: twhelp.NullPlayerMeta{
Player: players["de:de200"][0],
Valid: true,
},
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: uuid.NewString(),
ChannelLosses: "",
Barbarians: true,
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(),
Barbarians: true,
ServerKey: "pl180",
VersionCode: "pl",
CreatedAt: time.Now(),
},
{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Barbarians: false,
Internals: true,
ServerKey: "de200",
VersionCode: "de",
CreatedAt: time.Now(),
},
}
groupSvc.ListReturns(groups, nil)
repo := &mock.FakeMonitorRepository{}
monitors := map[string][]domain.Monitor{
groups[0].ID: {
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl181"][0].ID,
GroupID: groups[0].ID,
CreatedAt: time.Now(),
},
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl181"][2].ID,
GroupID: groups[0].ID,
CreatedAt: time.Now(),
},
},
groups[1].ID: {
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl181"][1].ID,
GroupID: groups[1].ID,
CreatedAt: time.Now(),
},
},
groups[2].ID: {
{
ID: uuid.NewString(),
TribeID: tribes["en:en130"][1].ID,
GroupID: groups[2].ID,
CreatedAt: time.Now(),
},
},
groups[3].ID: {
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl180"][0].ID,
GroupID: groups[3].ID,
CreatedAt: time.Now(),
},
},
groups[4].ID: {
{
ID: uuid.NewString(),
TribeID: tribes["de:de200"][0].ID,
GroupID: groups[4].ID,
CreatedAt: time.Now(),
},
{
ID: uuid.NewString(),
TribeID: tribes["de:de200"][1].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, zap.NewNop(), 10).
Execute(context.Background())
assert.NoError(t, err)
expectedNotifications := []domain.EnnoblementNotification{
newNotification(domain.EnnoblementNotificationTypeGain, groups[0].ServerID, groups[0].ChannelGains, ennoblements["pl:pl181"][0]),
newNotification(domain.EnnoblementNotificationTypeGain, groups[0].ServerID, groups[0].ChannelGains, ennoblements["pl:pl181"][3]),
newNotification(domain.EnnoblementNotificationTypeLoss, groups[1].ServerID, groups[1].ChannelLosses, ennoblements["pl:pl181"][0]),
newNotification(domain.EnnoblementNotificationTypeGain, groups[2].ServerID, groups[2].ChannelGains, ennoblements["en:en130"][2]),
newNotification(domain.EnnoblementNotificationTypeGain, groups[4].ServerID, groups[4].ChannelGains, ennoblements["de:de200"][1]),
newNotification(domain.EnnoblementNotificationTypeGain, groups[4].ServerID, groups[4].ChannelGains, ennoblements["de:de200"][2]),
}
assert.Len(t, notifications, len(expectedNotifications))
for _, n := range expectedNotifications {
assert.Contains(t, notifications, n)
}
}
func newNotification(typ domain.EnnoblementNotificationType, serverID, channelID string, e twhelp.Ennoblement) domain.EnnoblementNotification {
return domain.EnnoblementNotification{
Type: typ,
ServerID: serverID,
ChannelID: channelID,
Ennoblement: domain.Ennoblement{
ID: e.ID,
Village: domain.VillageMeta{
ID: e.Village.ID,
FullName: e.Village.FullName,
ProfileURL: e.Village.ProfileURL,
Player: domain.NullPlayerMeta{
Player: domain.PlayerMeta{
ID: e.Village.Player.Player.ID,
Name: e.Village.Player.Player.Name,
ProfileURL: e.Village.Player.Player.ProfileURL,
Tribe: domain.NullTribeMeta{
Tribe: domain.TribeMeta(e.Village.Player.Player.Tribe.Tribe),
Valid: e.Village.Player.Player.Tribe.Valid,
},
},
Valid: e.Village.Player.Valid,
},
},
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,
},
CreatedAt: e.CreatedAt,
},
}
}