mirror of
				https://github.com/elyby/chrly.git
				synced 2025-05-31 14:11:51 +05:30 
			
		
		
		
	#1: Fix race conditions errors and rewrite tests
This commit is contained in:
		
							
								
								
									
										17
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										17
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							@@ -226,10 +226,23 @@
 | 
				
			|||||||
  revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
 | 
					  revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
 | 
				
			||||||
  version = "v1.0.0"
 | 
					  version = "v1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[projects]]
 | 
				
			||||||
 | 
					  digest = "1:711eebe744c0151a9d09af2315f0bb729b2ec7637ef4c410fa90a18ef74b65b6"
 | 
				
			||||||
 | 
					  name = "github.com/stretchr/objx"
 | 
				
			||||||
 | 
					  packages = ["."]
 | 
				
			||||||
 | 
					  pruneopts = ""
 | 
				
			||||||
 | 
					  revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c"
 | 
				
			||||||
 | 
					  version = "v0.1.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[projects]]
 | 
					[[projects]]
 | 
				
			||||||
  digest = "1:381bcbeb112a51493d9d998bbba207a529c73dbb49b3fd789e48c63fac1f192c"
 | 
					  digest = "1:381bcbeb112a51493d9d998bbba207a529c73dbb49b3fd789e48c63fac1f192c"
 | 
				
			||||||
  name = "github.com/stretchr/testify"
 | 
					  name = "github.com/stretchr/testify"
 | 
				
			||||||
  packages = ["assert"]
 | 
					  packages = [
 | 
				
			||||||
 | 
					    "assert",
 | 
				
			||||||
 | 
					    "mock",
 | 
				
			||||||
 | 
					    "require",
 | 
				
			||||||
 | 
					    "suite",
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
  pruneopts = ""
 | 
					  pruneopts = ""
 | 
				
			||||||
  revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
 | 
					  revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
 | 
				
			||||||
  version = "v1.3.0"
 | 
					  version = "v1.3.0"
 | 
				
			||||||
@@ -303,6 +316,8 @@
 | 
				
			|||||||
    "github.com/spf13/cobra",
 | 
					    "github.com/spf13/cobra",
 | 
				
			||||||
    "github.com/spf13/viper",
 | 
					    "github.com/spf13/viper",
 | 
				
			||||||
    "github.com/stretchr/testify/assert",
 | 
					    "github.com/stretchr/testify/assert",
 | 
				
			||||||
 | 
					    "github.com/stretchr/testify/mock",
 | 
				
			||||||
 | 
					    "github.com/stretchr/testify/suite",
 | 
				
			||||||
    "github.com/thedevsaddam/govalidator",
 | 
					    "github.com/thedevsaddam/govalidator",
 | 
				
			||||||
    "gopkg.in/h2non/gock.v1",
 | 
					    "gopkg.in/h2non/gock.v1",
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,24 +25,29 @@ func (s *jobsQueue) New() *jobsQueue {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (s *jobsQueue) Enqueue(t *jobItem) {
 | 
					func (s *jobsQueue) Enqueue(t *jobItem) {
 | 
				
			||||||
	s.lock.Lock()
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s.items = append(s.items, t)
 | 
						s.items = append(s.items, t)
 | 
				
			||||||
	s.lock.Unlock()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *jobsQueue) Dequeue(n int) []*jobItem {
 | 
					func (s *jobsQueue) Dequeue(n int) []*jobItem {
 | 
				
			||||||
	s.lock.Lock()
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if n > s.Size() {
 | 
						if n > s.Size() {
 | 
				
			||||||
		n = s.Size()
 | 
							n = s.Size()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	items := s.items[0:n]
 | 
						items := s.items[0:n]
 | 
				
			||||||
	s.items = s.items[n:len(s.items)]
 | 
						s.items = s.items[n:len(s.items)]
 | 
				
			||||||
	s.lock.Unlock()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return items
 | 
						return items
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *jobsQueue) IsEmpty() bool {
 | 
					func (s *jobsQueue) IsEmpty() bool {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return len(s.items) == 0
 | 
						return len(s.items) == 0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,9 @@ import (
 | 
				
			|||||||
var usernamesToUuids = mojang.UsernamesToUuids
 | 
					var usernamesToUuids = mojang.UsernamesToUuids
 | 
				
			||||||
var uuidToTextures = mojang.UuidToTextures
 | 
					var uuidToTextures = mojang.UuidToTextures
 | 
				
			||||||
var delay = time.Second
 | 
					var delay = time.Second
 | 
				
			||||||
 | 
					var forever = func() bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type JobsQueue struct {
 | 
					type JobsQueue struct {
 | 
				
			||||||
	Storage Storage
 | 
						Storage Storage
 | 
				
			||||||
@@ -19,7 +22,7 @@ type JobsQueue struct {
 | 
				
			|||||||
	queue       jobsQueue
 | 
						queue       jobsQueue
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ctx *JobsQueue) GetTexturesForUsername(username string) *mojang.SignedTexturesResponse {
 | 
					func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.SignedTexturesResponse {
 | 
				
			||||||
	ctx.onFirstCall.Do(func() {
 | 
						ctx.onFirstCall.Do(func() {
 | 
				
			||||||
		ctx.queue.New()
 | 
							ctx.queue.New()
 | 
				
			||||||
		ctx.startQueue()
 | 
							ctx.startQueue()
 | 
				
			||||||
@@ -28,14 +31,15 @@ func (ctx *JobsQueue) GetTexturesForUsername(username string) *mojang.SignedText
 | 
				
			|||||||
	resultChan := make(chan *mojang.SignedTexturesResponse)
 | 
						resultChan := make(chan *mojang.SignedTexturesResponse)
 | 
				
			||||||
	// TODO: prevent of adding the same username more than once
 | 
						// TODO: prevent of adding the same username more than once
 | 
				
			||||||
	ctx.queue.Enqueue(&jobItem{username, resultChan})
 | 
						ctx.queue.Enqueue(&jobItem{username, resultChan})
 | 
				
			||||||
 | 
						// TODO: return nil if processing takes more than 5 seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return <-resultChan
 | 
						return resultChan
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ctx *JobsQueue) startQueue() {
 | 
					func (ctx *JobsQueue) startQueue() {
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		time.Sleep(delay)
 | 
							time.Sleep(delay)
 | 
				
			||||||
		for true {
 | 
							for forever() {
 | 
				
			||||||
			start := time.Now()
 | 
								start := time.Now()
 | 
				
			||||||
			ctx.queueRound()
 | 
								ctx.queueRound()
 | 
				
			||||||
			time.Sleep(delay - time.Since(start))
 | 
								time.Sleep(delay - time.Since(start))
 | 
				
			||||||
@@ -81,6 +85,7 @@ func (ctx *JobsQueue) queueRound() {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if uuid != "" {
 | 
								if uuid != "" {
 | 
				
			||||||
 | 
									var err error
 | 
				
			||||||
				result, err = uuidToTextures(uuid, true)
 | 
									result, err = uuidToTextures(uuid, true)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					if _, ok := err.(*mojang.TooManyRequestsError); !ok {
 | 
										if _, ok := err.(*mojang.TooManyRequestsError); !ok {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,211 +3,187 @@ package queue
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/rand"
 | 
						"crypto/rand"
 | 
				
			||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/elyby/chrly/api/mojang"
 | 
						"github.com/elyby/chrly/api/mojang"
 | 
				
			||||||
	testify "github.com/stretchr/testify/assert"
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/mock"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/suite"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestJobsQueue_GetTexturesForUsername(t *testing.T) {
 | 
					type MojangApiMocks struct {
 | 
				
			||||||
	delay = 50 * time.Millisecond
 | 
						mock.Mock
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("receive textures for one username", func(t *testing.T) {
 | 
					func (o *MojangApiMocks) UsernameToUuids(usernames []string) ([]*mojang.ProfileInfo, error) {
 | 
				
			||||||
		assert := testify.New(t)
 | 
						args := o.Called(usernames)
 | 
				
			||||||
 | 
						var result []*mojang.ProfileInfo
 | 
				
			||||||
 | 
						if casted, ok := args.Get(0).([]*mojang.ProfileInfo); ok {
 | 
				
			||||||
 | 
							result = casted
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		usernamesToUuids = createUsernameToUuidsMock(
 | 
						return result, args.Error(1)
 | 
				
			||||||
			assert,
 | 
					}
 | 
				
			||||||
			[]string{"maksimkurb"},
 | 
					
 | 
				
			||||||
			[]*mojang.ProfileInfo{
 | 
					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 QueueTestSuite struct {
 | 
				
			||||||
 | 
						suite.Suite
 | 
				
			||||||
 | 
						Queue     *JobsQueue
 | 
				
			||||||
 | 
						MojangApi *MojangApiMocks
 | 
				
			||||||
 | 
						Iterate   func()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						iterateChan chan bool
 | 
				
			||||||
 | 
						done        func()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *QueueTestSuite) SetupSuite() {
 | 
				
			||||||
 | 
						delay = 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *QueueTestSuite) SetupTest() {
 | 
				
			||||||
 | 
						suite.Queue = &JobsQueue{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *QueueTestSuite) TestReceiveTexturesForOneUsername() {
 | 
				
			||||||
 | 
						suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
 | 
				
			||||||
		{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
							{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
				
			||||||
			},
 | 
						}, nil)
 | 
				
			||||||
 | 
						suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(
 | 
				
			||||||
 | 
							&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
				
			||||||
		nil,
 | 
							nil,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
		uuidToTextures = createUuidToTextures([]*createUuidToTexturesResult{
 | 
					 | 
				
			||||||
			createTexturesResult("0d252b7218b648bfb86c2ae476954d32", "maksimkurb"),
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		queue := &JobsQueue{Storage: &NilStorage{}}
 | 
						resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
 | 
				
			||||||
		result := queue.GetTexturesForUsername("maksimkurb")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if assert.NotNil(result) {
 | 
						suite.Iterate()
 | 
				
			||||||
			assert.Equal("0d252b7218b648bfb86c2ae476954d32", result.Id)
 | 
					
 | 
				
			||||||
			assert.Equal("maksimkurb", result.Name)
 | 
						result := <-resultChan
 | 
				
			||||||
 | 
						if suite.Assert().NotNil(result) {
 | 
				
			||||||
 | 
							suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result.Id)
 | 
				
			||||||
 | 
							suite.Assert().Equal("maksimkurb", result.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("receive textures for few usernames", func(t *testing.T) {
 | 
					func (suite *QueueTestSuite) TestReceiveTexturesForFewUsernames() {
 | 
				
			||||||
		assert := testify.New(t)
 | 
						suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb", "Thinkofdeath"}).Once().Return([]*mojang.ProfileInfo{
 | 
				
			||||||
 | 
					 | 
				
			||||||
		usernamesToUuids = createUsernameToUuidsMock(
 | 
					 | 
				
			||||||
			assert,
 | 
					 | 
				
			||||||
			[]string{"maksimkurb", "Thinkofdeath"},
 | 
					 | 
				
			||||||
			[]*mojang.ProfileInfo{
 | 
					 | 
				
			||||||
		{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
							{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
				
			||||||
		{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"},
 | 
							{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"},
 | 
				
			||||||
			},
 | 
						}, nil)
 | 
				
			||||||
 | 
						suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(
 | 
				
			||||||
 | 
							&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
				
			||||||
 | 
							nil,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						suite.MojangApi.On("UuidToTextures", "4566e69fc90748ee8d71d7ba5aa00d20", true).Once().Return(
 | 
				
			||||||
 | 
							&mojang.SignedTexturesResponse{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"},
 | 
				
			||||||
		nil,
 | 
							nil,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
		uuidToTextures = createUuidToTextures([]*createUuidToTexturesResult{
 | 
					 | 
				
			||||||
			createTexturesResult("0d252b7218b648bfb86c2ae476954d32", "maksimkurb"),
 | 
					 | 
				
			||||||
			createTexturesResult("4566e69fc90748ee8d71d7ba5aa00d20", "Thinkofdeath"),
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		queue := &JobsQueue{Storage: &NilStorage{}}
 | 
						resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
 | 
				
			||||||
		resultChan1 := make(chan *mojang.SignedTexturesResponse)
 | 
						resultChan2 := suite.Queue.GetTexturesForUsername("Thinkofdeath")
 | 
				
			||||||
		resultChan2 := make(chan *mojang.SignedTexturesResponse)
 | 
					 | 
				
			||||||
		go func() {
 | 
					 | 
				
			||||||
			resultChan1 <- queue.GetTexturesForUsername("maksimkurb")
 | 
					 | 
				
			||||||
		}()
 | 
					 | 
				
			||||||
		go func() {
 | 
					 | 
				
			||||||
			resultChan2 <- queue.GetTexturesForUsername("Thinkofdeath")
 | 
					 | 
				
			||||||
		}()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert.NotNil(<-resultChan1)
 | 
						suite.Iterate()
 | 
				
			||||||
		assert.NotNil(<-resultChan2)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("query no more than 100 usernames and all left on the next iteration", func(t *testing.T) {
 | 
						suite.Assert().NotNil(<-resultChan1)
 | 
				
			||||||
		assert := testify.New(t)
 | 
						suite.Assert().NotNil(<-resultChan2)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
 | 
				
			||||||
	usernames := make([]string, 120, 120)
 | 
						usernames := make([]string, 120, 120)
 | 
				
			||||||
	for i := 0; i < 120; i++ {
 | 
						for i := 0; i < 120; i++ {
 | 
				
			||||||
		usernames[i] = randStr(8)
 | 
							usernames[i] = randStr(8)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		usernamesToUuids = createUsernameToUuidsMock(assert, usernames[0:100], []*mojang.ProfileInfo{}, nil)
 | 
						suite.MojangApi.On("UsernameToUuids", usernames[0:100]).Once().Return([]*mojang.ProfileInfo{}, nil)
 | 
				
			||||||
 | 
						suite.MojangApi.On("UsernameToUuids", usernames[100:120]).Once().Return([]*mojang.ProfileInfo{}, nil)
 | 
				
			||||||
		queue := &JobsQueue{Storage: &NilStorage{}}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		scheduleUsername := func(username string) {
 | 
					 | 
				
			||||||
			queue.GetTexturesForUsername(username)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, username := range usernames {
 | 
						for _, username := range usernames {
 | 
				
			||||||
			go scheduleUsername(username)
 | 
							suite.Queue.GetTexturesForUsername(username)
 | 
				
			||||||
			time.Sleep(50 * time.Microsecond) // Add delay to have consistent order
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Let it begin first iteration
 | 
						suite.Iterate()
 | 
				
			||||||
		time.Sleep(delay + delay/2)
 | 
						suite.Iterate()
 | 
				
			||||||
 | 
					 | 
				
			||||||
		usernamesToUuids = createUsernameToUuidsMock(
 | 
					 | 
				
			||||||
			assert,
 | 
					 | 
				
			||||||
			usernames[100:120],
 | 
					 | 
				
			||||||
			[]*mojang.ProfileInfo{},
 | 
					 | 
				
			||||||
			nil,
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		time.Sleep(delay)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("should do nothing if queue is empty", func(t *testing.T) {
 | 
					 | 
				
			||||||
		assert := testify.New(t)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		usernamesToUuids = createUsernameToUuidsMock(assert, []string{"maksimkurb"}, []*mojang.ProfileInfo{}, nil)
 | 
					 | 
				
			||||||
		uuidToTextures = func(uuid string, signed bool) (*mojang.SignedTexturesResponse, error) {
 | 
					 | 
				
			||||||
			t.Error("this method shouldn't be called")
 | 
					 | 
				
			||||||
			return nil, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() {
 | 
				
			||||||
 | 
						suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{}, nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Perform first iteration and await it finish
 | 
						// Perform first iteration and await it finish
 | 
				
			||||||
		queue := &JobsQueue{Storage: &NilStorage{}}
 | 
						resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
 | 
				
			||||||
		result := queue.GetTexturesForUsername("maksimkurb")
 | 
					 | 
				
			||||||
		assert.Nil(result)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Override external API call that indicates, that queue is still trying to obtain somethid
 | 
						suite.Iterate()
 | 
				
			||||||
		usernamesToUuids = func(usernames []string) ([]*mojang.ProfileInfo, error) {
 | 
					
 | 
				
			||||||
			t.Error("this method shouldn't be called")
 | 
						suite.Assert().Nil(<-resultChan)
 | 
				
			||||||
			return nil, nil
 | 
					
 | 
				
			||||||
 | 
						// Let it to perform a few more iterations to ensure, that there is no calls to external APIs
 | 
				
			||||||
 | 
						suite.Iterate()
 | 
				
			||||||
 | 
						suite.Iterate()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Let it to iterate few times
 | 
					func (suite *QueueTestSuite) TestHandle429ResponseWhenExchangingUsernamesToUuids() {
 | 
				
			||||||
		time.Sleep(delay * 2)
 | 
						suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return(nil, &mojang.TooManyRequestsError{})
 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("handle 429 error when exchanging usernames to uuids", func(t *testing.T) {
 | 
						resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
 | 
				
			||||||
		assert := testify.New(t)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		usernamesToUuids = createUsernameToUuidsMock(assert, []string{"maksimkurb"}, nil, &mojang.TooManyRequestsError{})
 | 
						suite.Iterate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		queue := &JobsQueue{Storage: &NilStorage{}}
 | 
						suite.Assert().Nil(<-resultChan)
 | 
				
			||||||
		result := queue.GetTexturesForUsername("maksimkurb")
 | 
					}
 | 
				
			||||||
		assert.Nil(result)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t.Run("handle 429 error when requesting user's textures", func(t *testing.T) {
 | 
					func (suite *QueueTestSuite) TestHandle429ResponseWhenRequestingUsersTextures() {
 | 
				
			||||||
		assert := testify.New(t)
 | 
						suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
 | 
				
			||||||
 | 
					 | 
				
			||||||
		usernamesToUuids = createUsernameToUuidsMock(
 | 
					 | 
				
			||||||
			assert,
 | 
					 | 
				
			||||||
			[]string{"maksimkurb"},
 | 
					 | 
				
			||||||
			[]*mojang.ProfileInfo{
 | 
					 | 
				
			||||||
		{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
							{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
 | 
				
			||||||
			},
 | 
						}, nil)
 | 
				
			||||||
 | 
						suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(
 | 
				
			||||||
		nil,
 | 
							nil,
 | 
				
			||||||
 | 
							&mojang.TooManyRequestsError{},
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
		uuidToTextures = createUuidToTextures([]*createUuidToTexturesResult{
 | 
					 | 
				
			||||||
			createTexturesResult("0d252b7218b648bfb86c2ae476954d32", &mojang.TooManyRequestsError{}),
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		queue := &JobsQueue{Storage: &NilStorage{}}
 | 
						resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
 | 
				
			||||||
		result := queue.GetTexturesForUsername("maksimkurb")
 | 
					
 | 
				
			||||||
		assert.Nil(result)
 | 
						suite.Iterate()
 | 
				
			||||||
	})
 | 
					
 | 
				
			||||||
 | 
						suite.Assert().Nil(<-resultChan)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func createUsernameToUuidsMock(
 | 
					func TestJobsQueueSuite(t *testing.T) {
 | 
				
			||||||
	assert *testify.Assertions,
 | 
						suite.Run(t, new(QueueTestSuite))
 | 
				
			||||||
	expectedUsernames []string,
 | 
					 | 
				
			||||||
	result []*mojang.ProfileInfo,
 | 
					 | 
				
			||||||
	err error,
 | 
					 | 
				
			||||||
) func(usernames []string) ([]*mojang.ProfileInfo, error) {
 | 
					 | 
				
			||||||
	return func(usernames []string) ([]*mojang.ProfileInfo, error) {
 | 
					 | 
				
			||||||
		assert.ElementsMatch(expectedUsernames, usernames)
 | 
					 | 
				
			||||||
		return result, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type createUuidToTexturesResult struct {
 | 
					 | 
				
			||||||
	uuid   string
 | 
					 | 
				
			||||||
	result *mojang.SignedTexturesResponse
 | 
					 | 
				
			||||||
	err    error
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func createTexturesResult(uuid string, result interface{}) *createUuidToTexturesResult {
 | 
					 | 
				
			||||||
	output := &createUuidToTexturesResult{uuid: uuid}
 | 
					 | 
				
			||||||
	if username, ok := result.(string); ok {
 | 
					 | 
				
			||||||
		output.result = &mojang.SignedTexturesResponse{Id: uuid, Name: username}
 | 
					 | 
				
			||||||
	} else if err, ok := result.(error); ok {
 | 
					 | 
				
			||||||
		output.err = err
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		log.Fatal("invalid result type passed")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return output
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func createUuidToTextures(
 | 
					 | 
				
			||||||
	results []*createUuidToTexturesResult,
 | 
					 | 
				
			||||||
) func(uuid string, signed bool) (*mojang.SignedTexturesResponse, error) {
 | 
					 | 
				
			||||||
	return func(uuid string, signed bool) (*mojang.SignedTexturesResponse, error) {
 | 
					 | 
				
			||||||
		for _, result := range results {
 | 
					 | 
				
			||||||
			if result.uuid == uuid {
 | 
					 | 
				
			||||||
				return result.result, result.err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil, errors.New("cannot find corresponding result")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://stackoverflow.com/a/50581165
 | 
					// https://stackoverflow.com/a/50581165
 | 
				
			||||||
func randStr(len int) string {
 | 
					func randStr(len int) string {
 | 
				
			||||||
	buff := make([]byte, len)
 | 
						buff := make([]byte, len)
 | 
				
			||||||
	rand.Read(buff)
 | 
						_, _ = rand.Read(buff)
 | 
				
			||||||
	str := base64.StdEncoding.EncodeToString(buff)
 | 
						str := base64.StdEncoding.EncodeToString(buff)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Base 64 can be longer than len
 | 
						// Base 64 can be longer than len
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user