Skip to content

Commit

Permalink
#1: Add storage integration
Browse files Browse the repository at this point in the history
  • Loading branch information
erickskrauch committed Apr 20, 2019
1 parent abea94a commit b1e18d0
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 53 deletions.
8 changes: 4 additions & 4 deletions api/mojang/queue/jobs_structure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func TestDequeueN(t *testing.T) {
assert.True(s.IsEmpty())
}

func createQueue() jobsQueue {
s := jobsQueue{}
s.New()
func createQueue() *jobsQueue {
queue := &jobsQueue{}
queue.New()

return s
return queue
}
20 changes: 16 additions & 4 deletions api/mojang/queue/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.Signe
})

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)
if isFirstListener {
resultChan := make(chan *mojang.SignedTexturesResponse)
Expand All @@ -39,6 +50,7 @@ func (ctx *JobsQueue) GetTexturesForUsername(username string) chan *mojang.Signe

go func() {
result := <-resultChan
close(resultChan)
ctx.broadcast.BroadcastAndRemove(username, result)
}()
}
Expand Down Expand Up @@ -108,11 +120,11 @@ func (ctx *JobsQueue) queueRound() {

wg.Done()

job.RespondTo <- result

if shouldCache {
// TODO: store result to cache
if shouldCache && result != nil {
ctx.Storage.Set(result)
}

job.RespondTo <- result
}(job)
}

Expand Down
106 changes: 61 additions & 45 deletions api/mojang/queue/queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,28 @@ func (o *MojangApiMocks) UuidToTextures(uuid string, signed bool) (*mojang.Signe
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()

Expand All @@ -51,7 +70,9 @@ func (suite *QueueTestSuite) SetupSuite() {
}

func (suite *QueueTestSuite) SetupTest() {
suite.Queue = &JobsQueue{}
suite.Storage = &MockStorage{}

suite.Queue = &JobsQueue{Storage: suite.Storage}

suite.iterateChan = make(chan bool)
forever = func() bool {
Expand All @@ -74,49 +95,48 @@ func (suite *QueueTestSuite) SetupTest() {
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(
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
nil,
)
suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return(expectedResult, nil)

resultChan := suite.Queue.GetTexturesForUsername("maksimkurb")

suite.Iterate()

result := <-resultChan
if suite.Assert().NotNil(result) {
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result.Id)
suite.Assert().Equal("maksimkurb", result.Name)
}
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(
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
nil,
)
suite.MojangApi.On("UuidToTextures", "4566e69fc90748ee8d71d7ba5aa00d20", true).Once().Return(
&mojang.SignedTexturesResponse{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().NotNil(<-resultChan1)
suite.Assert().NotNil(<-resultChan2)
suite.Assert().Equal(expectedResult1, <-resultChan1)
suite.Assert().Equal(expectedResult2, <-resultChan2)
}

func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
Expand All @@ -125,6 +145,8 @@ func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
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)

Expand All @@ -137,41 +159,36 @@ func (suite *QueueTestSuite) TestReceiveTexturesForMoreThan100Usernames() {
}

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(
&mojang.SignedTexturesResponse{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()

result1 := <-resultChan1
result2 := <-resultChan2

if suite.Assert().NotNil(result1) {
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result1.Id)
suite.Assert().Equal("maksimkurb", result1.Name)

suite.Assert().Equal(result1, result2)
}
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(
&mojang.SignedTexturesResponse{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
nil,
)
Return(expectedResult, nil)

resultChan1 := suite.Queue.GetTexturesForUsername("maksimkurb")

Expand All @@ -183,18 +200,13 @@ func (suite *QueueTestSuite) TestReceiveTexturesForUsernameThatAlreadyProcessing

resultChan2 := suite.Queue.GetTexturesForUsername("maksimkurb")

result1 := <-resultChan1
result2 := <-resultChan2

if suite.Assert().NotNil(result1) {
suite.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result1.Id)
suite.Assert().Equal("maksimkurb", result1.Name)

suite.Assert().Equal(result1, result2)
}
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
Expand All @@ -210,6 +222,8 @@ func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() {
}

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")
Expand All @@ -220,6 +234,8 @@ func (suite *QueueTestSuite) TestHandle429ResponseWhenExchangingUsernamesToUuids
}

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)
Expand Down

0 comments on commit b1e18d0

Please sign in to comment.