dcbot/internal/service/group_test.go

1926 lines
45 KiB
Go

package service_test
import (
"context"
"errors"
"fmt"
"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"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"golang.org/x/text/language"
)
func TestGroup_Create(t *testing.T) {
t.Parallel()
newParams := func(tb testing.TB) domain.CreateGroupParams {
tb.Helper()
params, err := domain.NewCreateGroupParams(
"592292203234328587",
"en",
"en113",
language.English.String(),
"1234",
"1235",
true,
true,
)
require.NoError(t, err)
return params
}
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.CreateCalls(func(_ context.Context, p domain.CreateGroupParams) (domain.GroupWithMonitors, error) {
return domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: p.ServerID(),
ChannelGains: p.ChannelGains(),
ChannelLosses: p.ChannelLosses(),
Barbarians: p.Barbarians(),
Internals: p.Internals(),
ServerKey: p.ServerKey(),
VersionCode: p.VersionCode(),
CreatedAt: time.Now(),
},
}, nil
})
client := &mock.FakeTWHelpClient{}
client.GetServerCalls(func(_ context.Context, _ string, server string) (twhelp.Server, error) {
return twhelp.Server{
Key: server,
URL: fmt.Sprintf("https://%s.tribalwars.net", server),
Open: true,
}, nil
})
params := newParams(t)
g, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).Create(context.Background(), params)
assert.NoError(t, err)
assert.NotEmpty(t, g.ID)
assert.Equal(t, params.ServerID(), g.ServerID)
assert.Equal(t, params.ServerKey(), g.ServerKey)
assert.Equal(t, params.VersionCode(), g.VersionCode)
assert.Equal(t, params.ChannelGains(), g.ChannelGains)
assert.Equal(t, params.ChannelLosses(), g.ChannelLosses)
assert.Equal(t, params.Barbarians(), g.Barbarians)
assert.Equal(t, params.Internals(), g.Barbarians)
assert.NotEmpty(t, g.CreatedAt)
})
t.Run("ERR: group limit has been reached", func(t *testing.T) {
t.Parallel()
const maxGroupsPerServer = 10
repo := &mock.FakeGroupRepository{}
repo.ListReturns(make([]domain.GroupWithMonitors, maxGroupsPerServer), nil)
g, err := service.NewGroup(repo, nil, zap.NewNop(), maxGroupsPerServer, 1).Create(context.Background(), newParams(t))
assert.ErrorIs(t, err, domain.GroupLimitReachedError{
Current: maxGroupsPerServer,
Limit: maxGroupsPerServer,
})
assert.Zero(t, g)
})
t.Run("ERR: server doesn't exist", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
client := &mock.FakeTWHelpClient{}
client.GetServerCalls(func(_ context.Context, _ string, _ string) (twhelp.Server, error) {
return twhelp.Server{}, twhelp.APIError{
Code: twhelp.ErrorCodeEntityNotFound,
Message: "server not found",
}
})
params := newParams(t)
g, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).Create(context.Background(), params)
assert.ErrorIs(t, err, domain.TWServerDoesNotExistError{
VersionCode: params.VersionCode(),
Key: params.ServerKey(),
})
assert.Zero(t, g)
})
t.Run("ERR: server is closed", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
client := &mock.FakeTWHelpClient{}
client.GetServerCalls(func(ctx context.Context, _ string, server string) (twhelp.Server, error) {
return twhelp.Server{
Key: server,
URL: fmt.Sprintf("https://%s.tribalwars.net", server),
Open: false,
}, nil
})
params := newParams(t)
g, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).Create(context.Background(), params)
assert.ErrorIs(t, err, domain.TWServerIsClosedError{
VersionCode: params.VersionCode(),
Key: params.ServerKey(),
})
assert.Zero(t, g)
})
}
func TestGroup_AddTribe(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
repo.AddMonitorCalls(func(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error) {
group.Monitors = append(group.Monitors, domain.Monitor{
ID: uuid.NewString(),
GroupID: id,
TribeID: tribeID,
})
return group, nil
})
client := &mock.FakeTWHelpClient{}
tribe := twhelp.Tribe{
ID: 1234,
Tag: "*TAG*",
Name: "Name",
ProfileURL: "Profile",
DeletedAt: time.Time{},
}
client.ListTribesReturns([]twhelp.Tribe{tribe}, nil)
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
AddTribe(context.Background(), group.ID, group.ServerID, tribe.Tag)
assert.NoError(t, err)
require.Len(t, g.Monitors, 1)
assert.NotZero(t, g.Monitors[0].ID)
assert.Equal(t, group.ID, g.Monitors[0].GroupID)
assert.Equal(t, tribe.ID, g.Monitors[0].TribeID)
})
t.Run("ERR: group doesn't exist", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
serverID: uuid.NewString(),
group: group,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
AddTribe(context.Background(), group.ID, uuid.NewString(), "*TAG*")
assert.ErrorIs(t, err, domain.GroupDoesNotExistError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
t.Run("ERR: monitor limit has been reached", func(t *testing.T) {
t.Parallel()
const maxMonitorsPerGroup = 10
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
Monitors: make([]domain.Monitor, maxMonitorsPerGroup),
}
repo.GetReturns(group, nil)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, maxMonitorsPerGroup).
AddTribe(context.Background(), group.ID, group.ServerID, "*TAG*")
assert.ErrorIs(t, err, domain.MonitorLimitReachedError{
Current: len(group.Monitors),
Limit: maxMonitorsPerGroup,
})
assert.Zero(t, g)
})
t.Run("ERR: tribe doesn't exist", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
client := &mock.FakeTWHelpClient{}
client.ListTribesReturns(nil, nil)
tribeTag := "*TAG*"
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
AddTribe(context.Background(), group.ID, group.ServerID, tribeTag)
assert.ErrorIs(t, err, domain.TribeDoesNotExistError{
Tag: tribeTag,
})
assert.Zero(t, g)
})
}
func TestGroup_RemoveTribe(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
groupID := uuid.NewString()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: groupID,
ServerID: uuid.NewString(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: 1234,
},
{
ID: uuid.NewString(),
TribeID: 1237,
},
},
}
repo.GetReturns(group, nil)
repo.DeleteMonitorsCalls(func(ctx context.Context, id string, monitorIDs ...string) (domain.GroupWithMonitors, error) {
monitors := make([]domain.Monitor, 0, len(group.Monitors)-1)
for _, m := range group.Monitors {
var skip bool
for _, monitorID := range monitorIDs {
if m.ID == monitorID {
skip = true
break
}
}
if skip {
continue
}
monitors = append(monitors, m)
}
return domain.GroupWithMonitors{
Monitors: monitors,
}, nil
})
client := &mock.FakeTWHelpClient{}
tribe := twhelp.Tribe{
ID: group.Monitors[0].TribeID,
Tag: "*TAG*",
}
client.ListTribesReturns([]twhelp.Tribe{tribe}, nil)
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
RemoveTribe(context.Background(), group.ID, group.ServerID, tribe.Tag)
assert.NoError(t, err)
assert.Len(t, g.Monitors, len(group.Monitors)-1)
assert.NotContains(t, g.Monitors, group.Monitors[0])
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
},
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
RemoveTribe(context.Background(), group.ID, tt.serverID, group.Monitors[0].ID)
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
t.Run("ERR: tribe not found", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
groupID := uuid.NewString()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: groupID,
ServerID: uuid.NewString(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: 1234,
},
{
ID: uuid.NewString(),
TribeID: 1237,
},
},
}
repo.GetReturns(group, nil)
repo.DeleteMonitorsCalls(func(ctx context.Context, id string, monitorIDs ...string) (domain.GroupWithMonitors, error) {
monitors := make([]domain.Monitor, 0, len(group.Monitors)-1)
for _, m := range group.Monitors {
var skip bool
for _, monitorID := range monitorIDs {
if m.ID == monitorID {
skip = true
break
}
}
if skip {
continue
}
monitors = append(monitors, m)
}
return domain.GroupWithMonitors{
Monitors: monitors,
}, nil
})
client := &mock.FakeTWHelpClient{}
tag := "*X*"
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
RemoveTribe(context.Background(), group.ID, group.ServerID, tag)
assert.ErrorIs(t, err, domain.TribeNotFoundError{
Tag: tag,
})
assert.Zero(t, g)
})
}
func TestGroup_SetLanguageTag(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
repo.UpdateCalls(func(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
return domain.GroupWithMonitors{
Group: domain.Group{
ID: id,
LanguageTag: params.LanguageTag.String,
},
}, nil
})
languageTag := uuid.NewString()
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetLanguageTag(context.Background(), group.ID, group.ServerID, languageTag)
assert.NoError(t, err)
assert.Equal(t, group.ID, g.ID)
assert.Equal(t, languageTag, g.LanguageTag)
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetLanguageTag(context.Background(), group.ID, tt.serverID, uuid.NewString())
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
}
func TestGroup_SetChannelGains(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
repo.UpdateCalls(func(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
return domain.GroupWithMonitors{
Group: domain.Group{
ID: id,
ChannelGains: params.ChannelGains.String,
},
}, nil
})
channelID := uuid.NewString()
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetChannelGains(context.Background(), group.ID, group.ServerID, channelID)
assert.NoError(t, err)
assert.Equal(t, group.ID, g.ID)
assert.Equal(t, channelID, g.ChannelGains)
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetChannelGains(context.Background(), group.ID, tt.serverID, uuid.NewString())
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
}
func TestGroup_SetChannelLosses(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
repo.UpdateCalls(func(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
return domain.GroupWithMonitors{
Group: domain.Group{
ID: id,
ChannelLosses: params.ChannelLosses.String,
},
}, nil
})
channelID := uuid.NewString()
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetChannelLosses(context.Background(), group.ID, group.ServerID, channelID)
assert.NoError(t, err)
assert.Equal(t, group.ID, g.ID)
assert.Equal(t, channelID, g.ChannelLosses)
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetChannelLosses(context.Background(), group.ID, tt.serverID, uuid.NewString())
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
}
func TestGroup_SetInternals(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
repo.UpdateCalls(func(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
return domain.GroupWithMonitors{
Group: domain.Group{
ID: id,
Internals: params.Internals.Bool,
},
}, nil
})
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetInternals(context.Background(), group.ID, group.ServerID, true)
assert.NoError(t, err)
assert.Equal(t, group.ID, g.ID)
assert.True(t, g.Internals)
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetInternals(context.Background(), group.ID, tt.serverID, true)
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
}
func TestGroup_SetBarbarians(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
repo.UpdateCalls(func(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
return domain.GroupWithMonitors{
Group: domain.Group{
ID: id,
Barbarians: params.Barbarians.Bool,
},
}, nil
})
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetBarbarians(context.Background(), group.ID, group.ServerID, true)
assert.NoError(t, err)
assert.Equal(t, group.ID, g.ID)
assert.True(t, g.Barbarians)
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetBarbarians(context.Background(), group.ID, tt.serverID, true)
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
}
func TestGroup_Get(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
Get(context.Background(), group.ID, group.ServerID)
assert.NoError(t, err)
assert.Equal(t, group, g)
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
Get(context.Background(), group.ID, tt.serverID)
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
}
func TestGroup_GetWithTribes(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
VersionCode: "pl",
ServerKey: "pl181",
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: 115,
},
{
ID: uuid.NewString(),
TribeID: 130,
},
{
ID: uuid.NewString(),
TribeID: 11,
},
},
}
repo.GetReturns(group, nil)
client := &mock.FakeTWHelpClient{}
client.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (twhelp.Tribe, error) {
return twhelp.Tribe{
ID: id,
Tag: fmt.Sprintf("*%d*", id),
Name: fmt.Sprintf("name %d", id),
ProfileURL: fmt.Sprintf("profile-%d", id),
}, nil
})
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
GetWithTribes(context.Background(), group.ID, group.ServerID)
assert.NoError(t, err)
assert.Equal(t, group.Group, g.Group)
assert.Len(t, g.Monitors, len(group.Monitors))
for _, m := range g.Monitors {
assert.NotZero(t, m.Tribe)
}
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
GetWithTribes(context.Background(), group.ID, tt.serverID)
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
t.Run("ERR: one of requests failed", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
VersionCode: "pl",
ServerKey: "pl181",
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: 115,
},
{
ID: uuid.NewString(),
TribeID: 130,
},
{
ID: uuid.NewString(),
TribeID: 11,
},
},
}
repo.GetReturns(group, nil)
client := &mock.FakeTWHelpClient{}
expectedErr := errors.New("err")
client.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (twhelp.Tribe, error) {
if id == 115 {
return twhelp.Tribe{}, expectedErr
}
<-ctx.Done()
return twhelp.Tribe{}, ctx.Err()
})
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
g, err := service.
NewGroup(repo, client, zap.NewNop(), 1, 1).
GetWithTribes(ctx, group.ID, group.ServerID)
assert.ErrorIs(t, err, expectedErr)
assert.Zero(t, g)
})
}
func TestGroup_Delete(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
assert.NoError(t, service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
Delete(context.Background(), group.ID, group.ServerID))
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
Delete(context.Background(), group.ID, tt.serverID)
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
})
}
})
}
func TestGroup_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
},
)
repo := &mock.FakeGroupRepository{}
groups := []domain.GroupWithMonitors{
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: "",
Barbarians: true,
ServerKey: "pl181",
VersionCode: "pl",
CreatedAt: time.Now(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl181"][0].ID,
CreatedAt: time.Now(),
},
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl181"][2].ID,
CreatedAt: time.Now(),
},
},
},
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: "",
ChannelLosses: uuid.NewString(),
ServerKey: "pl181",
VersionCode: "pl",
CreatedAt: time.Now(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl181"][1].ID,
CreatedAt: time.Now(),
},
},
},
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
ServerKey: "en130",
VersionCode: "en",
CreatedAt: time.Now(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: tribes["en:en130"][1].ID,
CreatedAt: time.Now(),
},
},
},
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Barbarians: true,
ServerKey: "pl180",
VersionCode: "pl",
CreatedAt: time.Now(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: tribes["pl:pl180"][0].ID,
CreatedAt: time.Now(),
},
},
},
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Barbarians: false,
Internals: true,
ServerKey: "de200",
VersionCode: "de",
CreatedAt: time.Now(),
},
Monitors: []domain.Monitor{
{
ID: uuid.NewString(),
TribeID: tribes["de:de200"][0].ID,
CreatedAt: time.Now(),
},
{
ID: uuid.NewString(),
TribeID: tribes["de:de200"][1].ID,
CreatedAt: time.Now(),
},
},
},
}
repo.ListReturns(groups, nil)
notifications, err := service.NewGroup(repo, client, zap.NewNop(), 1, 1).
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 TestGroup_CleanUp(t *testing.T) {
t.Parallel()
client := &mock.FakeTWHelpClient{}
versions := []twhelp.Version{
{
Code: "pl",
Host: "www.plemiona.pl",
Name: "Poland",
Timezone: "Europe/Warsaw",
},
{
Code: "uk",
Host: "www.tribalwars.co.uk",
Name: "United Kingdom",
Timezone: "Europe/London",
},
{
Code: "tr",
Host: "www.klanlar.org",
Name: "Turkey",
Timezone: "Europe/Istanbul",
},
}
client.ListVersionsReturns(versions, nil)
serversByVersion := map[string][]twhelp.Server{
versions[0].Code: { // pl
{
Key: "pl181",
URL: "https://pl181.plemiona.pl",
Open: false,
},
},
versions[1].Code: { // uk
{
Key: "uk51",
URL: "https://uk51.tribalwars.co.uk",
Open: false,
},
{
Key: "uk52",
URL: "https://uk52.tribalwars.co.uk",
Open: false,
},
},
}
client.ListServersCalls(func(ctx context.Context, version string, params twhelp.ListServersQueryParams) ([]twhelp.Server, error) {
return serversByVersion[version], nil
})
repo := &mock.FakeGroupRepository{}
groupsWithDisabledNotifications := []domain.GroupWithMonitors{
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: "",
ChannelLosses: "",
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: versions[0].Code,
CreatedAt: time.Now().Add(-48 * time.Hour),
},
},
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: "",
ChannelLosses: "",
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: versions[0].Code,
CreatedAt: time.Now().Add(-45 * time.Hour),
},
},
}
groupsPL := []domain.GroupWithMonitors{
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: "",
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: versions[0].Code,
CreatedAt: time.Now().Add(-120 * time.Hour),
},
},
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Internals: true,
Barbarians: true,
ServerKey: "pl181",
VersionCode: versions[0].Code,
CreatedAt: time.Now().Add(-11 * time.Hour),
},
},
}
groupsUK := []domain.GroupWithMonitors{
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Internals: true,
Barbarians: true,
ServerKey: "uk51",
VersionCode: versions[1].Code,
CreatedAt: time.Now().Add(-150 * time.Hour),
},
},
{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
ChannelGains: uuid.NewString(),
ChannelLosses: uuid.NewString(),
Internals: true,
Barbarians: true,
ServerKey: "uk52",
VersionCode: versions[1].Code,
CreatedAt: time.Now().Add(-200 * time.Hour),
},
},
}
repo.ListCalls(func(ctx context.Context, params domain.ListGroupsParams) ([]domain.GroupWithMonitors, error) {
if params.EnabledNotifications.Valid && !params.EnabledNotifications.Bool {
return groupsWithDisabledNotifications, nil
}
if params.VersionCode.String == versions[0].Code {
return groupsPL, nil
}
if params.VersionCode.String == versions[1].Code {
return groupsUK, nil
}
return nil, errors.New("unexpected call")
})
assert.NoError(t, service.NewGroup(repo, client, zap.NewNop(), 1, 1).CleanUp(context.Background()))
require.Equal(t, 3, repo.DeleteManyCallCount())
for _, tt := range []struct {
i int
groups []domain.GroupWithMonitors
}{
{
i: 0,
groups: groupsWithDisabledNotifications,
},
{
i: 1,
groups: groupsPL,
},
{
i: 2,
groups: groupsUK,
},
} {
_, ids := repo.DeleteManyArgsForCall(tt.i)
assert.Len(t, ids, len(tt.groups))
for i, id := range ids {
assert.Equal(t, tt.groups[i].ID, id)
}
}
}
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,
},
}
}