2019-11-21 04:03:13 +05:30
|
|
|
package mojangtextures
|
|
|
|
|
|
|
|
import (
|
2020-04-24 15:50:03 +05:30
|
|
|
"context"
|
2019-11-21 04:03:13 +05:30
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/mock"
|
2020-04-24 15:50:03 +05:30
|
|
|
"github.com/stretchr/testify/require"
|
2019-11-21 04:03:13 +05:30
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
|
|
|
|
"github.com/elyby/chrly/api/mojang"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestJobsQueue(t *testing.T) {
|
|
|
|
t.Run("Enqueue", func(t *testing.T) {
|
2020-04-24 15:50:03 +05:30
|
|
|
s := newJobsQueue()
|
|
|
|
require.Equal(t, 1, s.Enqueue(&job{Username: "username1"}))
|
|
|
|
require.Equal(t, 2, s.Enqueue(&job{Username: "username2"}))
|
|
|
|
require.Equal(t, 3, s.Enqueue(&job{Username: "username3"}))
|
2019-11-21 04:03:13 +05:30
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Dequeue", func(t *testing.T) {
|
2020-04-24 15:50:03 +05:30
|
|
|
s := newJobsQueue()
|
|
|
|
s.Enqueue(&job{Username: "username1"})
|
|
|
|
s.Enqueue(&job{Username: "username2"})
|
|
|
|
s.Enqueue(&job{Username: "username3"})
|
|
|
|
s.Enqueue(&job{Username: "username4"})
|
|
|
|
s.Enqueue(&job{Username: "username5"})
|
|
|
|
|
|
|
|
items, queueLen := s.Dequeue(2)
|
|
|
|
require.Len(t, items, 2)
|
|
|
|
require.Equal(t, 3, queueLen)
|
|
|
|
require.Equal(t, "username1", items[0].Username)
|
|
|
|
require.Equal(t, "username2", items[1].Username)
|
|
|
|
|
|
|
|
items, queueLen = s.Dequeue(40)
|
|
|
|
require.Len(t, items, 3)
|
|
|
|
require.Equal(t, 0, queueLen)
|
|
|
|
require.Equal(t, "username3", items[0].Username)
|
|
|
|
require.Equal(t, "username4", items[1].Username)
|
|
|
|
require.Equal(t, "username5", items[2].Username)
|
2019-11-21 04:03:13 +05:30
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type mojangUsernamesToUuidsRequestMock struct {
|
|
|
|
mock.Mock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o *mojangUsernamesToUuidsRequestMock) UsernamesToUuids(usernames []string) ([]*mojang.ProfileInfo, error) {
|
|
|
|
args := o.Called(usernames)
|
|
|
|
var result []*mojang.ProfileInfo
|
|
|
|
if casted, ok := args.Get(0).([]*mojang.ProfileInfo); ok {
|
|
|
|
result = casted
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, args.Error(1)
|
|
|
|
}
|
|
|
|
|
2020-04-24 15:50:03 +05:30
|
|
|
type queueStrategyMock struct {
|
|
|
|
mock.Mock
|
|
|
|
ch chan *JobsIteration
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *queueStrategyMock) Queue(job *job) {
|
|
|
|
m.Called(job)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *queueStrategyMock) GetJobs(abort context.Context) <-chan *JobsIteration {
|
|
|
|
m.Called(abort)
|
|
|
|
return m.ch
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *queueStrategyMock) PushIteration(iteration *JobsIteration) {
|
|
|
|
m.ch <- iteration
|
|
|
|
}
|
|
|
|
|
2019-11-21 04:03:13 +05:30
|
|
|
type batchUuidsProviderGetUuidResult struct {
|
|
|
|
Result *mojang.ProfileInfo
|
|
|
|
Error error
|
|
|
|
}
|
|
|
|
|
|
|
|
type batchUuidsProviderTestSuite struct {
|
|
|
|
suite.Suite
|
|
|
|
|
|
|
|
Provider *BatchUuidsProvider
|
|
|
|
GetUuidAsync func(username string) chan *batchUuidsProviderGetUuidResult
|
|
|
|
|
2020-02-08 15:58:10 +05:30
|
|
|
Emitter *mockEmitter
|
2019-11-21 04:03:13 +05:30
|
|
|
MojangApi *mojangUsernamesToUuidsRequestMock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *batchUuidsProviderTestSuite) SetupTest() {
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter = &mockEmitter{}
|
2019-11-21 04:03:13 +05:30
|
|
|
|
|
|
|
suite.Provider = &BatchUuidsProvider{
|
2020-04-24 15:50:03 +05:30
|
|
|
// Emitter: suite.Emitter,
|
|
|
|
// IterationDelay: 0,
|
|
|
|
// IterationSize: 10,
|
2019-11-21 04:03:13 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
suite.iterateChan = make(chan bool)
|
2020-04-24 15:50:03 +05:30
|
|
|
// forever = func() bool {
|
|
|
|
// return <-suite.iterateChan
|
|
|
|
// }
|
2019-11-21 04:03:13 +05:30
|
|
|
|
|
|
|
suite.Iterate = func() {
|
|
|
|
suite.iterateChan <- true
|
|
|
|
}
|
|
|
|
|
|
|
|
suite.done = func() {
|
|
|
|
suite.iterateChan <- false
|
|
|
|
}
|
|
|
|
|
|
|
|
suite.GetUuidAsync = func(username string) chan *batchUuidsProviderGetUuidResult {
|
2020-01-06 01:55:17 +05:30
|
|
|
s := make(chan bool)
|
|
|
|
// This dirty hack ensures, that the username will be queued before we return control to the caller.
|
|
|
|
// It's needed to keep expected calls order and prevent cases when iteration happens before all usernames
|
|
|
|
// will be queued.
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit",
|
|
|
|
"mojang_textures:batch_uuids_provider:queued",
|
|
|
|
username,
|
|
|
|
).Once().Run(func(args mock.Arguments) {
|
2020-01-06 01:55:17 +05:30
|
|
|
s <- true
|
|
|
|
})
|
2020-01-03 04:11:51 +05:30
|
|
|
|
2019-11-21 04:03:13 +05:30
|
|
|
c := make(chan *batchUuidsProviderGetUuidResult)
|
|
|
|
go func() {
|
|
|
|
profile, err := suite.Provider.GetUuid(username)
|
|
|
|
c <- &batchUuidsProviderGetUuidResult{
|
|
|
|
Result: profile,
|
|
|
|
Error: err,
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-11-21 22:03:05 +05:30
|
|
|
<-s
|
|
|
|
|
2019-11-21 04:03:13 +05:30
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
suite.MojangApi = &mojangUsernamesToUuidsRequestMock{}
|
|
|
|
usernamesToUuids = suite.MojangApi.UsernamesToUuids
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *batchUuidsProviderTestSuite) TearDownTest() {
|
|
|
|
suite.done()
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.AssertExpectations(suite.T())
|
2019-11-21 04:03:13 +05:30
|
|
|
suite.MojangApi.AssertExpectations(suite.T())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBatchUuidsProvider(t *testing.T) {
|
|
|
|
suite.Run(t, new(batchUuidsProviderTestSuite))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *batchUuidsProviderTestSuite) TestGetUuidForOneUsername() {
|
2020-04-10 05:17:53 +05:30
|
|
|
expectedUsernames := []string{"username"}
|
2019-11-21 04:03:13 +05:30
|
|
|
expectedResult := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"}
|
2020-04-10 05:17:53 +05:30
|
|
|
expectedResponse := []*mojang.ProfileInfo{expectedResult}
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
|
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, expectedResponse, nil).Once()
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return([]*mojang.ProfileInfo{expectedResult}, nil)
|
2019-11-21 04:03:13 +05:30
|
|
|
|
|
|
|
resultChan := suite.GetUuidAsync("username")
|
|
|
|
|
|
|
|
suite.Iterate()
|
|
|
|
|
|
|
|
result := <-resultChan
|
|
|
|
suite.Assert().Equal(expectedResult, result.Result)
|
|
|
|
suite.Assert().Nil(result.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernames() {
|
2020-04-10 05:17:53 +05:30
|
|
|
expectedUsernames := []string{"username1", "username2"}
|
2019-11-21 04:03:13 +05:30
|
|
|
expectedResult1 := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username1"}
|
|
|
|
expectedResult2 := &mojang.ProfileInfo{Id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", Name: "username2"}
|
2020-04-10 05:17:53 +05:30
|
|
|
expectedResponse := []*mojang.ProfileInfo{expectedResult1, expectedResult2}
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
|
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, expectedResponse, nil).Once()
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return([]*mojang.ProfileInfo{
|
2019-11-21 04:03:13 +05:30
|
|
|
expectedResult1,
|
|
|
|
expectedResult2,
|
|
|
|
}, nil)
|
|
|
|
|
|
|
|
resultChan1 := suite.GetUuidAsync("username1")
|
|
|
|
resultChan2 := suite.GetUuidAsync("username2")
|
|
|
|
|
|
|
|
suite.Iterate()
|
|
|
|
|
|
|
|
result1 := <-resultChan1
|
|
|
|
suite.Assert().Equal(expectedResult1, result1.Result)
|
|
|
|
suite.Assert().Nil(result1.Error)
|
|
|
|
|
|
|
|
result2 := <-resultChan2
|
|
|
|
suite.Assert().Equal(expectedResult2, result2.Result)
|
|
|
|
suite.Assert().Nil(result2.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *batchUuidsProviderTestSuite) TestGetUuidForMoreThan10Usernames() {
|
|
|
|
usernames := make([]string, 12)
|
|
|
|
for i := 0; i < cap(usernames); i++ {
|
|
|
|
usernames[i] = randStr(8)
|
|
|
|
}
|
|
|
|
|
2020-04-10 05:17:53 +05:30
|
|
|
// In this test we're not testing response, so always return an empty resultset
|
|
|
|
expectedResponse := []*mojang.ProfileInfo{}
|
|
|
|
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Twice()
|
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[0:10], 2).Once()
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", usernames[0:10], expectedResponse, nil).Once()
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[10:12], 0).Once()
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", usernames[10:12], expectedResponse, nil).Once()
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Twice()
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.MojangApi.On("UsernamesToUuids", usernames[0:10]).Once().Return(expectedResponse, nil)
|
|
|
|
suite.MojangApi.On("UsernamesToUuids", usernames[10:12]).Once().Return(expectedResponse, nil)
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-01-03 04:11:51 +05:30
|
|
|
channels := make([]chan *batchUuidsProviderGetUuidResult, len(usernames))
|
2019-11-21 04:03:13 +05:30
|
|
|
for i, username := range usernames {
|
|
|
|
channels[i] = suite.GetUuidAsync(username)
|
|
|
|
}
|
|
|
|
|
|
|
|
suite.Iterate()
|
|
|
|
suite.Iterate()
|
|
|
|
|
|
|
|
for _, channel := range channels {
|
|
|
|
<-channel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *batchUuidsProviderTestSuite) TestDoNothingWhenNoTasks() {
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Times(3)
|
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username"}, 0).Once()
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", []string{"username"}, mock.Anything, nil).Once()
|
2020-02-08 15:58:10 +05:30
|
|
|
var nilStringSlice []string
|
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", nilStringSlice, 0).Twice()
|
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Times(3)
|
2019-11-21 04:03:13 +05:30
|
|
|
|
|
|
|
suite.MojangApi.On("UsernamesToUuids", []string{"username"}).Once().Return([]*mojang.ProfileInfo{}, nil)
|
|
|
|
|
2020-01-06 01:55:17 +05:30
|
|
|
// Perform first iteration and await it finishes
|
2019-11-21 04:03:13 +05:30
|
|
|
resultChan := suite.GetUuidAsync("username")
|
|
|
|
|
|
|
|
suite.Iterate()
|
|
|
|
|
|
|
|
result := <-resultChan
|
|
|
|
suite.Assert().Nil(result.Result)
|
|
|
|
suite.Assert().Nil(result.Error)
|
|
|
|
|
2020-01-06 01:55:17 +05:30
|
|
|
// Let it to perform a few more iterations to ensure, that there are no calls to external APIs
|
2019-11-21 04:03:13 +05:30
|
|
|
suite.Iterate()
|
|
|
|
suite.Iterate()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernamesWithAnError() {
|
2020-04-10 05:17:53 +05:30
|
|
|
expectedUsernames := []string{"username1", "username2"}
|
2019-11-21 04:03:13 +05:30
|
|
|
expectedError := &mojang.TooManyRequestsError{}
|
2020-04-10 05:17:53 +05:30
|
|
|
var nilProfilesResponse []*mojang.ProfileInfo
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
|
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, nilProfilesResponse, expectedError).Once()
|
2020-02-08 15:58:10 +05:30
|
|
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
2019-11-21 04:03:13 +05:30
|
|
|
|
2020-04-10 05:17:53 +05:30
|
|
|
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return(nil, expectedError)
|
2019-11-21 04:03:13 +05:30
|
|
|
|
|
|
|
resultChan1 := suite.GetUuidAsync("username1")
|
|
|
|
resultChan2 := suite.GetUuidAsync("username2")
|
|
|
|
|
|
|
|
suite.Iterate()
|
|
|
|
|
|
|
|
result1 := <-resultChan1
|
|
|
|
suite.Assert().Nil(result1.Result)
|
|
|
|
suite.Assert().Equal(expectedError, result1.Error)
|
|
|
|
|
|
|
|
result2 := <-resultChan2
|
|
|
|
suite.Assert().Nil(result2.Result)
|
|
|
|
suite.Assert().Equal(expectedError, result2.Error)
|
|
|
|
}
|
|
|
|
|
|
|
|
var replacer = strings.NewReplacer("-", "_", "=", "")
|
|
|
|
|
|
|
|
// https://stackoverflow.com/a/50581165
|
|
|
|
func randStr(len int) string {
|
|
|
|
buff := make([]byte, len)
|
|
|
|
_, _ = rand.Read(buff)
|
|
|
|
str := replacer.Replace(base64.URLEncoding.EncodeToString(buff))
|
|
|
|
|
|
|
|
// Base 64 can be longer than len
|
|
|
|
return str[:len]
|
|
|
|
}
|