#1: Add storage integration

This commit is contained in:
ErickSkrauch 2019-04-20 19:35:37 +03:00
parent abea94a41f
commit b1e18d0d01
3 changed files with 81 additions and 53 deletions

View File

@ -39,9 +39,9 @@ func TestDequeueN(t *testing.T) {
assert.True(s.IsEmpty()) assert.True(s.IsEmpty())
} }
func createQueue() jobsQueue { func createQueue() *jobsQueue {
s := jobsQueue{} queue := &jobsQueue{}
s.New() queue.New()
return s return queue
} }

View File

@ -31,6 +31,17 @@ func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.Signe
}) })
responseChan := make(chan *mojang.SignedTexturesResponse) responseChan := make(chan *mojang.SignedTexturesResponse)
cachedResult := ctx.Storage.Get(username)
if cachedResult != nil {
go func() {
responseChan <- cachedResult
close(responseChan)
}()
return responseChan
}
isFirstListener := ctx.broadcast.AddListener(username, responseChan) isFirstListener := ctx.broadcast.AddListener(username, responseChan)
if isFirstListener { if isFirstListener {
resultChan := make(chan *mojang.SignedTexturesResponse) resultChan := make(chan *mojang.SignedTexturesResponse)
@ -39,6 +50,7 @@ func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.Signe
go func() { go func() {
result := <-resultChan result := <-resultChan
close(resultChan)
ctx.broadcast.BroadcastAndRemove(username, result) ctx.broadcast.BroadcastAndRemove(username, result)
}() }()
} }
@ -108,11 +120,11 @@ func (ctx *JobsQueue) queueRound() {
wg.Done() wg.Done()
job.RespondTo <- result if shouldCache && result != nil {
ctx.Storage.Set(result)
if shouldCache {
// TODO: store result to cache
} }
job.RespondTo <- result
}(job) }(job)
} }

View File

@ -36,9 +36,28 @@ func (o *MojangApiMocks) UuidToTextures(uuid string, signed bool) (*mojang.Signe
return result, args.Error(1) 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 { type QueueTestSuite struct {
suite.Suite suite.Suite
Queue *JobsQueue Queue *JobsQueue
Storage *MockStorage
MojangApi *MojangApiMocks MojangApi *MojangApiMocks
Iterate func() Iterate func()
@ -51,7 +70,9 @@ func (suite *QueueTestSuite) SetupSuite() {
} }
func (suite *QueueTestSuite) SetupTest() { func (suite *QueueTestSuite) SetupTest() {
suite.Queue = &JobsQueue{} suite.Storage = &MockStorage{}
suite.Queue = &JobsQueue{Storage: suite.Storage}
suite.iterateChan = make(chan bool) suite.iterateChan = make(chan bool)
forever = func() bool { forever = func() bool {
@ -74,49 +95,48 @@ func (suite *QueueTestSuite) SetupTest() {
func (suite *QueueTestSuite) TearDownTest() { func (suite *QueueTestSuite) TearDownTest() {
suite.done() suite.done()
suite.MojangApi.AssertExpectations(suite.T()) suite.MojangApi.AssertExpectations(suite.T())
suite.Storage.AssertExpectations(suite.T())
} }
func (suite *QueueTestSuite) TestReceiveTexturesForOneUsername() { 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{ suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}, {Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
}, nil) }, nil)
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return( suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(expectedResult, nil)
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
nil,
)
resultChan := suite.Queue.GetTexturesForUsername("maksimkurb") resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
suite.Iterate() suite.Iterate()
result := <-resultChan result := <-resultChan
if suite.Assert().NotNil(result) { suite.Assert().Equal(expectedResult, result)
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result.Id)
suite.Assert().Equal("maksimkurb", result.Name)
}
} }
func (suite *QueueTestSuite) TestReceiveTexturesForFewUsernames() { 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{ suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb", "Thinkofdeath"}).Once().Return([]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}, {Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"}, {Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"},
}, nil) }, nil)
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return( suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(expectedResult1, nil)
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}, suite.MojangApi.On("UuidToTextures", "4566e69fc90748ee8d71d7ba5aa00d20", true).Once().Return(expectedResult2, nil)
nil,
)
suite.MojangApi.On("UuidToTextures", "4566e69fc90748ee8d71d7ba5aa00d20", true).Once().Return(
&mojang.SignedTexturesResponse{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"},
nil,
)
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb") resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
resultChan2 := suite.Queue.GetTexturesForUsername("Thinkofdeath") resultChan2 := suite.Queue.GetTexturesForUsername("Thinkofdeath")
suite.Iterate() suite.Iterate()
suite.Assert().NotNil(<-resultChan1) suite.Assert().Equal(expectedResult1, <-resultChan1)
suite.Assert().NotNil(<-resultChan2) suite.Assert().Equal(expectedResult2, <-resultChan2)
} }
func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() { func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
@ -125,6 +145,8 @@ func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
usernames[i] = randStr(8) 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[0:100]).Once().Return([]*mojang.ProfileInfo{}, nil)
suite.MojangApi.On("UsernameToUuids", usernames[100:120]).Once().Return([]*mojang.ProfileInfo{}, nil) suite.MojangApi.On("UsernameToUuids", usernames[100:120]).Once().Return([]*mojang.ProfileInfo{}, nil)
@ -137,41 +159,36 @@ func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
} }
func (suite *QueueTestSuite) TestReceiveTexturesForTheSameUsernames() { 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{ suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}, {Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
}, nil) }, nil)
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return( suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(expectedResult, nil)
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
nil,
)
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb") resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb") resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb")
suite.Iterate() suite.Iterate()
result1 := <-resultChan1 suite.Assert().Equal(expectedResult, <-resultChan1)
result2 := <-resultChan2 suite.Assert().Equal(expectedResult, <-resultChan2)
if suite.Assert().NotNil(result1) {
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result1.Id)
suite.Assert().Equal("maksimkurb", result1.Name)
suite.Assert().Equal(result1, result2)
}
} }
func (suite *QueueTestSuite) TestReceiveTexturesForUsernameThatAlreadyProcessing() { 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{ suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}, {Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
}, nil) }, nil)
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true). suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).
Once(). Once().
After(10*time.Millisecond). // Simulate long round trip After(10*time.Millisecond). // Simulate long round trip
Return( Return(expectedResult, nil)
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
nil,
)
resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb") resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")
@ -183,18 +200,13 @@ func (suite *QueueTestSuite) TestReceiveTexturesForUsernameThatAlreadyProcessing
resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb") resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb")
result1 := <-resultChan1 suite.Assert().Equal(expectedResult, <-resultChan1)
result2 := <-resultChan2 suite.Assert().Equal(expectedResult, <-resultChan2)
if suite.Assert().NotNil(result1) {
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result1.Id)
suite.Assert().Equal("maksimkurb", result1.Name)
suite.Assert().Equal(result1, result2)
}
} }
func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() { 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) 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
@ -210,6 +222,8 @@ func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() {
} }
func (suite *QueueTestSuite) TestHandle429ResponseWhenExchangingUsernamesToUuids() { 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{}) suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return(nil, &mojang.TooManyRequestsError{})
resultChan := suite.Queue.GetTexturesForUsername("maksimkurb") resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")
@ -220,6 +234,8 @@ func (suite *QueueTestSuite) TestHandle429ResponseWhenExchangingUsernamesToUuids
} }
func (suite *QueueTestSuite) TestHandle429ResponseWhenRequestingUsersTextures() { 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{ suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}, {Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
}, nil) }, nil)