mirror of
https://github.com/elyby/chrly.git
synced 2024-11-30 02:32:19 +05:30
275 lines
8.4 KiB
Go
275 lines
8.4 KiB
Go
package queue
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/elyby/chrly/api/mojang"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/suite"
|
|
"testing"
|
|
)
|
|
|
|
type MojangApiMocks struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (o *MojangApiMocks) UsernameToUuids(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)
|
|
}
|
|
|
|
func (o *MojangApiMocks) UuidToTextures(uuid string, signed bool) (*mojang.SignedTexturesResponse, error) {
|
|
args := o.Called(uuid, signed)
|
|
var result *mojang.SignedTexturesResponse
|
|
if casted, ok := args.Get(0).(*mojang.SignedTexturesResponse); ok {
|
|
result = casted
|
|
}
|
|
|
|
return result, args.Error(1)
|
|
}
|
|
|
|
type MockStorage struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockStorage) Get(username string) *mojang.SignedTexturesResponse {
|
|
args := m.Called(username)
|
|
var result *mojang.SignedTexturesResponse
|
|
if casted, ok := args.Get(0).(*mojang.SignedTexturesResponse); ok {
|
|
result = casted
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (m *MockStorage) Set(textures *mojang.SignedTexturesResponse) {
|
|
m.Called(textures)
|
|
}
|
|
|
|
type QueueTestSuite struct {
|
|
suite.Suite
|
|
Queue *JobsQueue
|
|
Storage *MockStorage
|
|
MojangApi *MojangApiMocks
|
|
Iterate func()
|
|
|
|
iterateChan chan bool
|
|
done func()
|
|
}
|
|
|
|
func (suite *QueueTestSuite) SetupSuite() {
|
|
delay = 0
|
|
}
|
|
|
|
func (suite *QueueTestSuite) SetupTest() {
|
|
suite.Storage = &MockStorage{}
|
|
|
|
suite.Queue = &JobsQueue{Storage: suite.Storage}
|
|
|
|
suite.iterateChan = make(chan bool)
|
|
forever = func() bool {
|
|
return <-suite.iterateChan
|
|
}
|
|
|
|
suite.Iterate = func() {
|
|
suite.iterateChan <- true
|
|
}
|
|
|
|
suite.done = func() {
|
|
suite.iterateChan <- false
|
|
}
|
|
|
|
suite.MojangApi = new(MojangApiMocks)
|
|
usernamesToUuids = suite.MojangApi.UsernameToUuids
|
|
uuidToTextures = suite.MojangApi.UuidToTextures
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TearDownTest() {
|
|
suite.done()
|
|
suite.MojangApi.AssertExpectations(suite.T())
|
|
suite.Storage.AssertExpectations(suite.T())
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestReceiveTexturesForOneUsername() {
|
|
expectedResult := &mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}
|
|
|
|
suite.Storage.On("Get", mock.Anything).Return(nil)
|
|
suite.Storage.On("Set", expectedResult).Once()
|
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
|
|
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
|
}, nil)
|
|
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(expectedResult, nil)
|
|
|
|
resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
|
|
suite.Iterate()
|
|
|
|
result := <-resultChan
|
|
suite.Assert().Equal(expectedResult, result)
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestReceiveTexturesForFewUsernames() {
|
|
expectedResult1 := &mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}
|
|
expectedResult2 := &mojang.SignedTexturesResponse{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"}
|
|
|
|
suite.Storage.On("Get", mock.Anything).Return(nil)
|
|
suite.Storage.On("Set", expectedResult1).Once()
|
|
suite.Storage.On("Set", expectedResult2).Once()
|
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb", "Thinkofdeath"}).Once().Return([]*mojang.ProfileInfo{
|
|
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
|
{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"},
|
|
}, nil)
|
|
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(expectedResult1, nil)
|
|
suite.MojangApi.On("UuidToTextures", "4566e69fc90748ee8d71d7ba5aa00d20", true).Once().Return(expectedResult2, nil)
|
|
|
|
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
resultChan2 := suite.Queue.GetTexturesForUsername("Thinkofdeath")
|
|
|
|
suite.Iterate()
|
|
|
|
suite.Assert().Equal(expectedResult1, <-resultChan1)
|
|
suite.Assert().Equal(expectedResult2, <-resultChan2)
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
|
|
usernames := make([]string, 120, 120)
|
|
for i := 0; i < 120; i++ {
|
|
usernames[i] = randStr(8)
|
|
}
|
|
|
|
suite.Storage.On("Get", mock.Anything).Times(120).Return(nil)
|
|
// Storage.Set shouldn't be called
|
|
suite.MojangApi.On("UsernameToUuids", usernames[0:100]).Once().Return([]*mojang.ProfileInfo{}, nil)
|
|
suite.MojangApi.On("UsernameToUuids", usernames[100:120]).Once().Return([]*mojang.ProfileInfo{}, nil)
|
|
|
|
for _, username := range usernames {
|
|
suite.Queue.GetTexturesForUsername(username)
|
|
}
|
|
|
|
suite.Iterate()
|
|
suite.Iterate()
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestReceiveTexturesForTheSameUsernames() {
|
|
expectedResult := &mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}
|
|
|
|
suite.Storage.On("Get", mock.Anything).Twice().Return(nil)
|
|
suite.Storage.On("Set", expectedResult).Once()
|
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
|
|
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
|
}, nil)
|
|
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(expectedResult, nil)
|
|
|
|
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
|
|
suite.Iterate()
|
|
|
|
suite.Assert().Equal(expectedResult, <-resultChan1)
|
|
suite.Assert().Equal(expectedResult, <-resultChan2)
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestReceiveTexturesForUsernameThatAlreadyProcessing() {
|
|
expectedResult := &mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}
|
|
|
|
suite.Storage.On("Get", mock.Anything).Return(nil)
|
|
suite.Storage.On("Set", expectedResult).Once()
|
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
|
|
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
|
}, nil)
|
|
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).
|
|
Once().
|
|
After(10*time.Millisecond). // Simulate long round trip
|
|
Return(expectedResult, nil)
|
|
|
|
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
|
|
// Note that for entire test there is only one iteration
|
|
suite.Iterate()
|
|
|
|
// Let it meet delayed UuidToTextures request
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
|
|
suite.Assert().Equal(expectedResult, <-resultChan1)
|
|
suite.Assert().Equal(expectedResult, <-resultChan2)
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() {
|
|
suite.Storage.On("Get", mock.Anything).Return(nil)
|
|
// Storage.Set shouldn't be called
|
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{}, nil)
|
|
|
|
// Perform first iteration and await it finish
|
|
resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
|
|
suite.Iterate()
|
|
|
|
suite.Assert().Nil(<-resultChan)
|
|
|
|
// Let it to perform a few more iterations to ensure, that there is no calls to external APIs
|
|
suite.Iterate()
|
|
suite.Iterate()
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestHandle429ResponseWhenExchangingUsernamesToUuids() {
|
|
suite.Storage.On("Get", mock.Anything).Return(nil)
|
|
// Storage.Set shouldn't be called
|
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return(nil, &mojang.TooManyRequestsError{})
|
|
|
|
resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
|
|
suite.Iterate()
|
|
|
|
suite.Assert().Nil(<-resultChan)
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestHandle429ResponseWhenRequestingUsersTextures() {
|
|
suite.Storage.On("Get", mock.Anything).Return(nil)
|
|
// Storage.Set shouldn't be called
|
|
suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
|
|
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
|
|
}, nil)
|
|
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(
|
|
nil,
|
|
&mojang.TooManyRequestsError{},
|
|
)
|
|
|
|
resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
|
|
|
|
suite.Iterate()
|
|
|
|
suite.Assert().Nil(<-resultChan)
|
|
}
|
|
|
|
func (suite *QueueTestSuite) TestReceiveTexturesForNotAllowedMojangUsername() {
|
|
resultChan := suite.Queue.GetTexturesForUsername("Not allowed")
|
|
suite.Assert().Nil(<-resultChan)
|
|
}
|
|
|
|
func TestJobsQueueSuite(t *testing.T) {
|
|
suite.Run(t, new(QueueTestSuite))
|
|
}
|
|
|
|
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]
|
|
}
|