Resolves #26. Rework UUIDs storage interface to simplify results handling

This commit is contained in:
ErickSkrauch 2020-04-28 17:57:51 +03:00
parent be30c23823
commit f997fdf9b0
12 changed files with 141 additions and 125 deletions

View File

@ -14,7 +14,6 @@ import (
"github.com/mediocregopher/radix.v2/util" "github.com/mediocregopher/radix.v2/util"
"github.com/elyby/chrly/model" "github.com/elyby/chrly/model"
"github.com/elyby/chrly/mojangtextures"
) )
var now = time.Now var now = time.Now
@ -186,20 +185,21 @@ func removeByUsername(username string, conn util.Cmder) error {
return nil return nil
} }
func (db *Redis) GetUuid(username string) (string, error) { func (db *Redis) GetUuid(username string) (string, bool, error) {
conn, err := db.pool.Get() conn, err := db.pool.Get()
if err != nil { if err != nil {
return "", err return "", false, err
} }
defer db.pool.Put(conn) defer db.pool.Put(conn)
return findMojangUuidByUsername(username, conn) return findMojangUuidByUsername(username, conn)
} }
func findMojangUuidByUsername(username string, conn util.Cmder) (string, error) { func findMojangUuidByUsername(username string, conn util.Cmder) (string, bool, error) {
response := conn.Cmd("HGET", mojangUsernameToUuidKey, strings.ToLower(username)) key := strings.ToLower(username)
response := conn.Cmd("HGET", mojangUsernameToUuidKey, key)
if response.IsType(redis.Nil) { if response.IsType(redis.Nil) {
return "", &mojangtextures.ValueNotFound{} return "", false, nil
} }
data, _ := response.Str() data, _ := response.Str()
@ -207,10 +207,11 @@ func findMojangUuidByUsername(username string, conn util.Cmder) (string, error)
timestamp, _ := strconv.ParseInt(parts[1], 10, 64) timestamp, _ := strconv.ParseInt(parts[1], 10, 64)
storedAt := time.Unix(timestamp, 0) storedAt := time.Unix(timestamp, 0)
if storedAt.Add(time.Hour * 24 * 30).Before(now()) { if storedAt.Add(time.Hour * 24 * 30).Before(now()) {
return "", &mojangtextures.ValueNotFound{} conn.Cmd("HDEL", mojangUsernameToUuidKey, key)
return "", false, nil
} }
return parts[0], nil return parts[0], true, nil
} }
func (db *Redis) StoreUuid(username string, uuid string) error { func (db *Redis) StoreUuid(username string, uuid string) error {

View File

@ -13,7 +13,6 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/elyby/chrly/model" "github.com/elyby/chrly/model"
"github.com/elyby/chrly/mojangtextures"
) )
const redisAddr = "localhost:6379" const redisAddr = "localhost:6379"
@ -317,15 +316,30 @@ func (suite *redisTestSuite) TestGetUuid() {
fmt.Sprintf("%s:%d", "d3ca513eb3e14946b58047f2bd3530fd", time.Now().Unix()), fmt.Sprintf("%s:%d", "d3ca513eb3e14946b58047f2bd3530fd", time.Now().Unix()),
) )
uuid, err := suite.Redis.GetUuid("Mock") uuid, found, err := suite.Redis.GetUuid("Mock")
suite.Require().Nil(err) suite.Require().Nil(err)
suite.Require().True(found)
suite.Require().Equal("d3ca513eb3e14946b58047f2bd3530fd", uuid) suite.Require().Equal("d3ca513eb3e14946b58047f2bd3530fd", uuid)
}) })
suite.RunSubTest("exists record with empty uuid value", func() {
suite.cmd("HSET",
"hash:mojang-username-to-uuid",
"mock",
fmt.Sprintf(":%d", time.Now().Unix()),
)
uuid, found, err := suite.Redis.GetUuid("Mock")
suite.Require().Nil(err)
suite.Require().True(found)
suite.Require().Empty("", uuid)
})
suite.RunSubTest("not exists record", func() { suite.RunSubTest("not exists record", func() {
uuid, err := suite.Redis.GetUuid("Mock") uuid, found, err := suite.Redis.GetUuid("Mock")
suite.Require().Nil(err)
suite.Require().False(found)
suite.Require().Empty(uuid) suite.Require().Empty(uuid)
suite.Require().IsType(new(mojangtextures.ValueNotFound), err)
}) })
suite.RunSubTest("exists, but expired record", func() { suite.RunSubTest("exists, but expired record", func() {
@ -335,9 +349,10 @@ func (suite *redisTestSuite) TestGetUuid() {
fmt.Sprintf("%s:%d", "d3ca513eb3e14946b58047f2bd3530fd", time.Now().Add(-1*time.Hour*24*31).Unix()), fmt.Sprintf("%s:%d", "d3ca513eb3e14946b58047f2bd3530fd", time.Now().Add(-1*time.Hour*24*31).Unix()),
) )
uuid, err := suite.Redis.GetUuid("Mock") uuid, found, err := suite.Redis.GetUuid("Mock")
suite.Require().Empty(uuid) suite.Require().Empty(uuid)
suite.Require().IsType(new(mojangtextures.ValueNotFound), err) suite.Require().False(found)
suite.Require().Nil(err)
}) })
} }

View File

@ -22,7 +22,7 @@ import (
var db = di.Options( var db = di.Options(
di.Provide(newRedis, di.Provide(newRedis,
di.As(new(http.SkinsRepository)), di.As(new(http.SkinsRepository)),
di.As(new(mojangtextures.UuidsStorage)), di.As(new(mojangtextures.UUIDsStorage)),
), ),
di.Provide(newFSFactory, di.Provide(newFSFactory,
di.As(new(http.CapesRepository)), di.As(new(http.CapesRepository)),

View File

@ -210,11 +210,11 @@ func newMojangSignedTexturesProvider(emitter mojangtextures.Emitter) mojangtextu
} }
func newMojangTexturesStorageFactory( func newMojangTexturesStorageFactory(
uuidsStorage mojangtextures.UuidsStorage, uuidsStorage mojangtextures.UUIDsStorage,
texturesStorage mojangtextures.TexturesStorage, texturesStorage mojangtextures.TexturesStorage,
) mojangtextures.Storage { ) mojangtextures.Storage {
return &mojangtextures.SeparatedStorage{ return &mojangtextures.SeparatedStorage{
UuidsStorage: uuidsStorage, UUIDsStorage: uuidsStorage,
TexturesStorage: texturesStorage, TexturesStorage: texturesStorage,
} }
} }

View File

@ -34,8 +34,8 @@ func (s *StatsReporter) ConfigureWithDispatcher(d Subscriber) {
// Mojang signed textures source events // Mojang signed textures source events
d.Subscribe("mojang_textures:call", s.incCounterHandler("mojang_textures.request")) d.Subscribe("mojang_textures:call", s.incCounterHandler("mojang_textures.request"))
d.Subscribe("mojang_textures:usernames:after_cache", func(username string, uuid string, err error) { d.Subscribe("mojang_textures:usernames:after_cache", func(username string, uuid string, found bool, err error) {
if err != nil { if err != nil || !found {
return return
} }

View File

@ -213,13 +213,19 @@ var statsReporterTestCases = []*StatsReporterTestCase{
}, },
{ {
Events: [][]interface{}{ Events: [][]interface{}{
{"mojang_textures:usernames:after_cache", "username", "", errors.New("error")}, {"mojang_textures:usernames:after_cache", "username", "", false, errors.New("error")},
}, },
ExpectedCalls: [][]interface{}{}, ExpectedCalls: [][]interface{}{},
}, },
{ {
Events: [][]interface{}{ Events: [][]interface{}{
{"mojang_textures:usernames:after_cache", "username", "", nil}, {"mojang_textures:usernames:after_cache", "username", "", false, nil},
},
ExpectedCalls: [][]interface{}{},
},
{
Events: [][]interface{}{
{"mojang_textures:usernames:after_cache", "username", "", true, nil},
}, },
ExpectedCalls: [][]interface{}{ ExpectedCalls: [][]interface{}{
{"IncCounter", "mojang_textures:usernames:cache_hit_nil", int64(1)}, {"IncCounter", "mojang_textures:usernames:cache_hit_nil", int64(1)},
@ -227,7 +233,7 @@ var statsReporterTestCases = []*StatsReporterTestCase{
}, },
{ {
Events: [][]interface{}{ Events: [][]interface{}{
{"mojang_textures:usernames:after_cache", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil}, {"mojang_textures:usernames:after_cache", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true, nil},
}, },
ExpectedCalls: [][]interface{}{ ExpectedCalls: [][]interface{}{
{"IncCounter", "mojang_textures:usernames:cache_hit", int64(1)}, {"IncCounter", "mojang_textures:usernames:cache_hit", int64(1)},

View File

@ -66,7 +66,7 @@ func (s *InMemoryTexturesStorage) GetTextures(uuid string) (*mojang.SignedTextur
item, exists := s.data[uuid] item, exists := s.data[uuid]
validRange := s.getMinimalNotExpiredTimestamp() validRange := s.getMinimalNotExpiredTimestamp()
if !exists || validRange > item.timestamp { if !exists || validRange > item.timestamp {
return nil, &ValueNotFound{} return nil, nil
} }
return item.textures, nil return item.textures, nil

View File

@ -1,12 +1,12 @@
package mojangtextures package mojangtextures
import ( import (
"testing"
"time" "time"
"github.com/elyby/chrly/api/mojang" assert "github.com/stretchr/testify/require"
testify "github.com/stretchr/testify/assert" "github.com/elyby/chrly/api/mojang"
"testing"
) )
var texturesWithSkin = &mojang.SignedTexturesResponse{ var texturesWithSkin = &mojang.SignedTexturesResponse{
@ -45,30 +45,24 @@ var texturesWithoutSkin = &mojang.SignedTexturesResponse{
} }
func TestInMemoryTexturesStorage_GetTextures(t *testing.T) { func TestInMemoryTexturesStorage_GetTextures(t *testing.T) {
t.Run("get error when uuid is not exists", func(t *testing.T) { t.Run("should return nil, nil when textures are unavailable", func(t *testing.T) {
assert := testify.New(t)
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
result, err := storage.GetTextures("b5d58475007d4f9e9ddd1403e2497579") result, err := storage.GetTextures("b5d58475007d4f9e9ddd1403e2497579")
assert.Nil(result) assert.Nil(t, result)
assert.Error(err, "value not found in the storage") assert.Nil(t, err)
}) })
t.Run("get textures object, when uuid is stored in the storage", func(t *testing.T) { t.Run("get textures object, when uuid is stored in the storage", func(t *testing.T) {
assert := testify.New(t)
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin) storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin)
result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46")
assert.Equal(texturesWithSkin, result) assert.Equal(t, texturesWithSkin, result)
assert.Nil(err) assert.Nil(t, err)
}) })
t.Run("get error when uuid is exists, but textures are expired", func(t *testing.T) { t.Run("should return nil, nil when textures are exists, but cache duration is expired", func(t *testing.T) {
assert := testify.New(t)
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin) storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin)
@ -78,8 +72,8 @@ func TestInMemoryTexturesStorage_GetTextures(t *testing.T) {
result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46")
assert.Nil(result) assert.Nil(t, result)
assert.Error(err, "value not found in the storage") assert.Nil(t, err)
now = time.Now now = time.Now
}) })
@ -87,50 +81,42 @@ func TestInMemoryTexturesStorage_GetTextures(t *testing.T) {
func TestInMemoryTexturesStorage_StoreTextures(t *testing.T) { func TestInMemoryTexturesStorage_StoreTextures(t *testing.T) {
t.Run("store textures for previously not existed uuid", func(t *testing.T) { t.Run("store textures for previously not existed uuid", func(t *testing.T) {
assert := testify.New(t)
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin) storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin)
result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46")
assert.Equal(texturesWithSkin, result) assert.Equal(t, texturesWithSkin, result)
assert.Nil(err) assert.Nil(t, err)
}) })
t.Run("override already existed textures for uuid", func(t *testing.T) { t.Run("override already existed textures for uuid", func(t *testing.T) {
assert := testify.New(t)
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithoutSkin) storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithoutSkin)
storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin) storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", texturesWithSkin)
result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46")
assert.NotEqual(texturesWithoutSkin, result) assert.NotEqual(t, texturesWithoutSkin, result)
assert.Equal(texturesWithSkin, result) assert.Equal(t, texturesWithSkin, result)
assert.Nil(err) assert.Nil(t, err)
}) })
t.Run("store nil textures", func(t *testing.T) { t.Run("store nil textures", func(t *testing.T) {
assert := testify.New(t)
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", nil) storage.StoreTextures("dead24f9a4fa4877b7b04c8c6c72bb46", nil)
result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") result, err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46")
assert.Nil(result) assert.Nil(t, result)
assert.Nil(err) assert.Nil(t, err)
}) })
t.Run("should panic if textures prop is not decoded", func(t *testing.T) { t.Run("should panic if textures prop is not decoded", func(t *testing.T) {
assert := testify.New(t)
toStore := &mojang.SignedTexturesResponse{ toStore := &mojang.SignedTexturesResponse{
Id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", Id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
Name: "mock", Name: "mock",
Props: []*mojang.Property{}, Props: []*mojang.Property{},
} }
assert.PanicsWithValue("unable to decode textures", func() { assert.PanicsWithValue(t, "unable to decode textures", func() {
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
storage.StoreTextures("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", toStore) storage.StoreTextures("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", toStore)
}) })
@ -138,8 +124,6 @@ func TestInMemoryTexturesStorage_StoreTextures(t *testing.T) {
} }
func TestInMemoryTexturesStorage_GarbageCollection(t *testing.T) { func TestInMemoryTexturesStorage_GarbageCollection(t *testing.T) {
assert := testify.New(t)
storage := NewInMemoryTexturesStorage() storage := NewInMemoryTexturesStorage()
storage.GCPeriod = 10 * time.Millisecond storage.GCPeriod = 10 * time.Millisecond
storage.Duration = 10 * time.Millisecond storage.Duration = 10 * time.Millisecond
@ -179,22 +163,25 @@ func TestInMemoryTexturesStorage_GarbageCollection(t *testing.T) {
storage.StoreTextures("b5d58475007d4f9e9ddd1403e2497579", textures2) storage.StoreTextures("b5d58475007d4f9e9ddd1403e2497579", textures2)
storage.Start() storage.Start()
defer storage.Stop()
time.Sleep(storage.GCPeriod + time.Millisecond) // Let it start first iteration time.Sleep(storage.GCPeriod + time.Millisecond) // Let it start first iteration
_, textures1Err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") texturesFromStorage1, textures1Err := storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46")
_, textures2Err := storage.GetTextures("b5d58475007d4f9e9ddd1403e2497579") texturesFromStorage2, textures2Err := storage.GetTextures("b5d58475007d4f9e9ddd1403e2497579")
assert.Nil(textures1Err) assert.NotNil(t, texturesFromStorage1)
assert.Error(textures2Err) assert.Nil(t, textures1Err)
assert.Nil(t, texturesFromStorage2)
assert.Nil(t, textures2Err)
time.Sleep(storage.GCPeriod + time.Millisecond) // Let another iteration happen time.Sleep(storage.GCPeriod + time.Millisecond) // Let another iteration happen
_, textures1Err = storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") texturesFromStorage1, textures1Err = storage.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46")
_, textures2Err = storage.GetTextures("b5d58475007d4f9e9ddd1403e2497579") texturesFromStorage2, textures2Err = storage.GetTextures("b5d58475007d4f9e9ddd1403e2497579")
assert.Error(textures1Err) assert.Nil(t, texturesFromStorage1)
assert.Error(textures2Err) assert.Nil(t, textures1Err)
assert.Nil(t, texturesFromStorage2)
storage.Stop() assert.Nil(t, textures2Err)
} }

View File

@ -98,14 +98,18 @@ func (ctx *Provider) GetForUsername(username string) (*mojang.SignedTexturesResp
username = strings.ToLower(username) username = strings.ToLower(username)
ctx.Emit("mojang_textures:call", username) ctx.Emit("mojang_textures:call", username)
uuid, err := ctx.getUuidFromCache(username) uuid, found, err := ctx.getUuidFromCache(username)
if err == nil && uuid == "" { if err != nil {
return nil, err
}
if found && uuid == "" {
return nil, nil return nil, nil
} }
if uuid != "" { if uuid != "" {
textures, err := ctx.getTexturesFromCache(uuid) textures, err := ctx.getTexturesFromCache(uuid)
if err == nil { if err == nil && textures != nil {
return textures, nil return textures, nil
} }
} }
@ -162,12 +166,12 @@ func (ctx *Provider) getResult(username string, uuid string) *broadcastResult {
return &broadcastResult{textures, nil} return &broadcastResult{textures, nil}
} }
func (ctx *Provider) getUuidFromCache(username string) (string, error) { func (ctx *Provider) getUuidFromCache(username string) (string, bool, error) {
ctx.Emit("mojang_textures:usernames:before_cache", username) ctx.Emit("mojang_textures:usernames:before_cache", username)
uuid, err := ctx.Storage.GetUuid(username) uuid, found, err := ctx.Storage.GetUuid(username)
ctx.Emit("mojang_textures:usernames:after_cache", username, uuid, err) ctx.Emit("mojang_textures:usernames:after_cache", username, uuid, found, err)
return uuid, err return uuid, found, err
} }
func (ctx *Provider) getTexturesFromCache(uuid string) (*mojang.SignedTexturesResponse, error) { func (ctx *Provider) getTexturesFromCache(uuid string) (*mojang.SignedTexturesResponse, error) {

View File

@ -122,9 +122,9 @@ type mockStorage struct {
mock.Mock mock.Mock
} }
func (m *mockStorage) GetUuid(username string) (string, error) { func (m *mockStorage) GetUuid(username string) (string, bool, error) {
args := m.Called(username) args := m.Called(username)
return args.String(0), args.Error(1) return args.String(0), args.Bool(1), args.Error(2)
} }
func (m *mockStorage) StoreUuid(username string, uuid string) error { func (m *mockStorage) StoreUuid(username string, uuid string) error {
@ -186,7 +186,7 @@ func (suite *providerTestSuite) TestGetForUsernameWithoutAnyCache() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", &ValueNotFound{}).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", false, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once() suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once()
@ -194,7 +194,7 @@ func (suite *providerTestSuite) TestGetForUsernameWithoutAnyCache() {
suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once()
suite.Storage.On("GetUuid", "username").Once().Return("", &ValueNotFound{}) suite.Storage.On("GetUuid", "username").Once().Return("", false, nil)
suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil) suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil)
suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once() suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once()
@ -213,16 +213,16 @@ func (suite *providerTestSuite) TestGetForUsernameWithCachedUuid() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:textures:before_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once() suite.Emitter.On("Emit", "mojang_textures:textures:before_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once()
suite.Emitter.On("Emit", "mojang_textures:textures:after_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedCachedTextures, &ValueNotFound{}).Once() suite.Emitter.On("Emit", "mojang_textures:textures:after_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedCachedTextures, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once() suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once()
suite.Emitter.On("Emit", "mojang_textures:textures:before_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once() suite.Emitter.On("Emit", "mojang_textures:textures:before_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once()
suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once()
suite.Storage.On("GetUuid", "username").Once().Return("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil) suite.Storage.On("GetUuid", "username").Once().Return("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true, nil)
suite.Storage.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, &ValueNotFound{}) suite.Storage.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, nil)
suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once() suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once()
suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Return(expectedResult, nil) suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Return(expectedResult, nil)
@ -238,11 +238,11 @@ func (suite *providerTestSuite) TestGetForUsernameWithFullyCachedResult() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:textures:before_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once() suite.Emitter.On("Emit", "mojang_textures:textures:before_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once()
suite.Emitter.On("Emit", "mojang_textures:textures:after_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:textures:after_cache", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once()
suite.Storage.On("GetUuid", "username").Once().Return("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil) suite.Storage.On("GetUuid", "username").Once().Return("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true, nil)
suite.Storage.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(expectedResult, nil) suite.Storage.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(expectedResult, nil)
result, err := suite.Provider.GetForUsername("username") result, err := suite.Provider.GetForUsername("username")
@ -254,9 +254,9 @@ func (suite *providerTestSuite) TestGetForUsernameWithFullyCachedResult() {
func (suite *providerTestSuite) TestGetForUsernameWithCachedUnknownUuid() { func (suite *providerTestSuite) TestGetForUsernameWithCachedUnknownUuid() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", nil).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", true, nil).Once()
suite.Storage.On("GetUuid", "username").Once().Return("", nil) suite.Storage.On("GetUuid", "username").Once().Return("", true, nil)
result, err := suite.Provider.GetForUsername("username") result, err := suite.Provider.GetForUsername("username")
@ -270,13 +270,13 @@ func (suite *providerTestSuite) TestGetForUsernameWhichHasNoMojangAccount() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", &ValueNotFound{}).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", false, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once() suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once()
suite.Storage.On("GetUuid", "username").Once().Return("", &ValueNotFound{}) suite.Storage.On("GetUuid", "username").Once().Return("", false, nil)
suite.Storage.On("StoreUuid", "username", "").Once().Return(nil) suite.Storage.On("StoreUuid", "username", "").Once().Return(nil)
suite.UuidsProvider.On("GetUuid", "username").Once().Return(nil, nil) suite.UuidsProvider.On("GetUuid", "username").Once().Return(nil, nil)
@ -293,7 +293,7 @@ func (suite *providerTestSuite) TestGetForUsernameWhichHasMojangAccountButHasNoM
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", &ValueNotFound{}).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", false, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once() suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once()
@ -301,7 +301,7 @@ func (suite *providerTestSuite) TestGetForUsernameWhichHasMojangAccountButHasNoM
suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once()
suite.Storage.On("GetUuid", "username").Once().Return("", &ValueNotFound{}) suite.Storage.On("GetUuid", "username").Once().Return("", false, nil)
suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil) suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil)
suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once() suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once()
@ -320,7 +320,7 @@ func (suite *providerTestSuite) TestGetForTheSameUsernames() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Twice() suite.Emitter.On("Emit", "mojang_textures:call", "username").Twice()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Twice() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Twice()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", &ValueNotFound{}).Twice() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", false, nil).Twice()
suite.Emitter.On("Emit", "mojang_textures:already_processing", "username").Once() suite.Emitter.On("Emit", "mojang_textures:already_processing", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once() suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once()
@ -329,7 +329,7 @@ func (suite *providerTestSuite) TestGetForTheSameUsernames() {
suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once() suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, nil).Once()
suite.Storage.On("GetUuid", "username").Twice().Return("", &ValueNotFound{}) suite.Storage.On("GetUuid", "username").Twice().Return("", false, nil)
suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil) suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil)
suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once() suite.Storage.On("StoreTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult).Once()
@ -359,6 +359,21 @@ func (suite *providerTestSuite) TestGetForNotAllowedMojangUsername() {
suite.Assert().Nil(result) suite.Assert().Nil(result)
} }
func (suite *providerTestSuite) TestGetErrorFromUUIDsStorage() {
expectedErr := errors.New("mock error")
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", false, expectedErr).Once()
suite.Storage.On("GetUuid", "username").Once().Return("", false, expectedErr)
result, err := suite.Provider.GetForUsername("username")
suite.Assert().Nil(result)
suite.Assert().Equal(expectedErr, err)
}
func (suite *providerTestSuite) TestGetErrorFromUuidsProvider() { func (suite *providerTestSuite) TestGetErrorFromUuidsProvider() {
var expectedProfile *mojang.ProfileInfo var expectedProfile *mojang.ProfileInfo
var expectedResult *mojang.SignedTexturesResponse var expectedResult *mojang.SignedTexturesResponse
@ -366,13 +381,13 @@ func (suite *providerTestSuite) TestGetErrorFromUuidsProvider() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", &ValueNotFound{}).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", false, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once() suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, err).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, err).Once()
suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, err).Once() suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, err).Once()
suite.Storage.On("GetUuid", "username").Once().Return("", &ValueNotFound{}) suite.Storage.On("GetUuid", "username").Once().Return("", false, nil)
suite.UuidsProvider.On("GetUuid", "username").Once().Return(nil, err) suite.UuidsProvider.On("GetUuid", "username").Once().Return(nil, err)
result, resErr := suite.Provider.GetForUsername("username") result, resErr := suite.Provider.GetForUsername("username")
@ -387,7 +402,7 @@ func (suite *providerTestSuite) TestGetErrorFromTexturesProvider() {
suite.Emitter.On("Emit", "mojang_textures:call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_cache", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", &ValueNotFound{}).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_cache", "username", "", false, nil).Once()
suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once() suite.Emitter.On("Emit", "mojang_textures:before_result", "username", "").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once() suite.Emitter.On("Emit", "mojang_textures:usernames:before_call", "username").Once()
suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once() suite.Emitter.On("Emit", "mojang_textures:usernames:after_call", "username", expectedProfile, nil).Once()
@ -395,7 +410,7 @@ func (suite *providerTestSuite) TestGetErrorFromTexturesProvider() {
suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, err).Once() suite.Emitter.On("Emit", "mojang_textures:textures:after_call", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expectedResult, err).Once()
suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, err).Once() suite.Emitter.On("Emit", "mojang_textures:after_result", "username", expectedResult, err).Once()
suite.Storage.On("GetUuid", "username").Return("", &ValueNotFound{}) suite.Storage.On("GetUuid", "username").Return("", false, nil)
suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Return(nil) suite.Storage.On("StoreUuid", "username", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Return(nil)
suite.UuidsProvider.On("GetUuid", "username").Once().Return(expectedProfile, nil) suite.UuidsProvider.On("GetUuid", "username").Once().Return(expectedProfile, nil)
suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, err) suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, err)

View File

@ -4,12 +4,13 @@ import (
"github.com/elyby/chrly/api/mojang" "github.com/elyby/chrly/api/mojang"
) )
// UuidsStorage is a key-value storage of Mojang usernames pairs to its UUIDs, // UUIDsStorage is a key-value storage of Mojang usernames pairs to its UUIDs,
// used to reduce the load on the account information queue // used to reduce the load on the account information queue
type UuidsStorage interface { type UUIDsStorage interface {
// Since only primitive types are used in this method, you should return a special error ValueNotFound // The second argument indicates whether a record was found in the storage,
// to return the information that no error has occurred and username does not have uuid // since depending on it, the empty value must be interpreted as "no cached record"
GetUuid(username string) (string, error) // or "value cached and has an empty value"
GetUuid(username string) (uuid string, found bool, err error)
// An empty uuid value can be passed if the corresponding account has not been found // An empty uuid value can be passed if the corresponding account has not been found
StoreUuid(username string, uuid string) error StoreUuid(username string, uuid string) error
} }
@ -24,23 +25,23 @@ type TexturesStorage interface {
} }
type Storage interface { type Storage interface {
UuidsStorage UUIDsStorage
TexturesStorage TexturesStorage
} }
// SeparatedStorage allows you to use separate storage engines to satisfy // SeparatedStorage allows you to use separate storage engines to satisfy
// the Storage interface // the Storage interface
type SeparatedStorage struct { type SeparatedStorage struct {
UuidsStorage UUIDsStorage
TexturesStorage TexturesStorage
} }
func (s *SeparatedStorage) GetUuid(username string) (string, error) { func (s *SeparatedStorage) GetUuid(username string) (string, bool, error) {
return s.UuidsStorage.GetUuid(username) return s.UUIDsStorage.GetUuid(username)
} }
func (s *SeparatedStorage) StoreUuid(username string, uuid string) error { func (s *SeparatedStorage) StoreUuid(username string, uuid string) error {
return s.UuidsStorage.StoreUuid(username, uuid) return s.UUIDsStorage.StoreUuid(username, uuid)
} }
func (s *SeparatedStorage) GetTextures(uuid string) (*mojang.SignedTexturesResponse, error) { func (s *SeparatedStorage) GetTextures(uuid string) (*mojang.SignedTexturesResponse, error) {
@ -50,12 +51,3 @@ func (s *SeparatedStorage) GetTextures(uuid string) (*mojang.SignedTexturesRespo
func (s *SeparatedStorage) StoreTextures(uuid string, textures *mojang.SignedTexturesResponse) { func (s *SeparatedStorage) StoreTextures(uuid string, textures *mojang.SignedTexturesResponse) {
s.TexturesStorage.StoreTextures(uuid, textures) s.TexturesStorage.StoreTextures(uuid, textures)
} }
// This error can be used to indicate, that requested
// value doesn't exists in the storage
type ValueNotFound struct {
}
func (*ValueNotFound) Error() string {
return "value not found in the storage"
}

View File

@ -12,9 +12,9 @@ type uuidsStorageMock struct {
mock.Mock mock.Mock
} }
func (m *uuidsStorageMock) GetUuid(username string) (string, error) { func (m *uuidsStorageMock) GetUuid(username string) (string, bool, error) {
args := m.Called(username) args := m.Called(username)
return args.String(0), args.Error(1) return args.String(0), args.Bool(1), args.Error(2)
} }
func (m *uuidsStorageMock) StoreUuid(username string, uuid string) error { func (m *uuidsStorageMock) StoreUuid(username string, uuid string) error {
@ -50,9 +50,10 @@ func TestSplittedStorage(t *testing.T) {
t.Run("GetUuid", func(t *testing.T) { t.Run("GetUuid", func(t *testing.T) {
storage, uuidsMock, _ := createMockedStorage() storage, uuidsMock, _ := createMockedStorage()
uuidsMock.On("GetUuid", "username").Once().Return("find me", nil) uuidsMock.On("GetUuid", "username").Once().Return("find me", true, nil)
result, err := storage.GetUuid("username") result, found, err := storage.GetUuid("username")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, found)
assert.Equal(t, "find me", result) assert.Equal(t, "find me", result)
uuidsMock.AssertExpectations(t) uuidsMock.AssertExpectations(t)
}) })
@ -82,8 +83,3 @@ func TestSplittedStorage(t *testing.T) {
texturesMock.AssertExpectations(t) texturesMock.AssertExpectations(t)
}) })
} }
func TestValueNotFound_Error(t *testing.T) {
err := &ValueNotFound{}
assert.Equal(t, "value not found in the storage", err.Error())
}