Go, Go Context! Added context transfer literally everywhere

This commit is contained in:
ErickSkrauch
2024-02-13 02:08:42 +01:00
parent fdafbc4f0e
commit f5bc474b4d
21 changed files with 209 additions and 246 deletions

1
go.mod
View File

@@ -14,6 +14,7 @@ require (
github.com/jellydator/ttlcache/v3 v3.1.1 github.com/jellydator/ttlcache/v3 v3.1.1
github.com/mediocregopher/radix/v4 v4.1.4 github.com/mediocregopher/radix/v4 v4.1.4
github.com/mono83/slf v0.0.0-20170919161409-79153e9636db github.com/mono83/slf v0.0.0-20170919161409-79153e9636db
github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.1 github.com/spf13/viper v1.18.1
github.com/valyala/fastjson v1.6.4 github.com/valyala/fastjson v1.6.4

View File

@@ -15,7 +15,6 @@ const userUuidToUsernameKey = "hash:uuid-to-username"
type Redis struct { type Redis struct {
client radix.Client client radix.Client
context context.Context
serializer db.ProfileSerializer serializer db.ProfileSerializer
} }
@@ -27,14 +26,13 @@ func New(ctx context.Context, profileSerializer db.ProfileSerializer, addr strin
return &Redis{ return &Redis{
client: client, client: client,
context: ctx,
serializer: profileSerializer, serializer: profileSerializer,
}, nil }, nil
} }
func (r *Redis) FindProfileByUsername(username string) (*db.Profile, error) { func (r *Redis) FindProfileByUsername(ctx context.Context, username string) (*db.Profile, error) {
var profile *db.Profile var profile *db.Profile
err := r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error { err := r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
var err error var err error
profile, err = r.findProfileByUsername(ctx, conn, username) profile, err = r.findProfileByUsername(ctx, conn, username)
@@ -58,38 +56,13 @@ func (r *Redis) findProfileByUsername(ctx context.Context, conn radix.Conn, user
return r.serializer.Deserialize(encodedResult) return r.serializer.Deserialize(encodedResult)
} }
func (r *Redis) FindProfileByUuid(uuid string) (*db.Profile, error) {
var skin *db.Profile
err := r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
var err error
skin, err = r.findProfileByUuid(ctx, conn, uuid)
return err
}))
return skin, err
}
func (r *Redis) findProfileByUuid(ctx context.Context, conn radix.Conn, uuid string) (*db.Profile, error) {
username, err := r.findUsernameHashKeyByUuid(ctx, conn, uuid)
if err != nil {
return nil, err
}
if username == "" {
return nil, nil
}
return r.findProfileByUsername(ctx, conn, username)
}
func (r *Redis) findUsernameHashKeyByUuid(ctx context.Context, conn radix.Conn, uuid string) (string, error) { func (r *Redis) findUsernameHashKeyByUuid(ctx context.Context, conn radix.Conn, uuid string) (string, error) {
var username string var username string
return username, conn.Do(ctx, radix.FlatCmd(&username, "HGET", userUuidToUsernameKey, normalizeUuid(uuid))) return username, conn.Do(ctx, radix.FlatCmd(&username, "HGET", userUuidToUsernameKey, normalizeUuid(uuid)))
} }
func (r *Redis) SaveProfile(profile *db.Profile) error { func (r *Redis) SaveProfile(ctx context.Context, profile *db.Profile) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error { return r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return r.saveProfile(ctx, conn, profile) return r.saveProfile(ctx, conn, profile)
})) }))
} }
@@ -137,8 +110,8 @@ func (r *Redis) saveProfile(ctx context.Context, conn radix.Conn, profile *db.Pr
return nil return nil
} }
func (r *Redis) RemoveProfileByUuid(uuid string) error { func (r *Redis) RemoveProfileByUuid(ctx context.Context, uuid string) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error { return r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return r.removeProfileByUuid(ctx, conn, uuid) return r.removeProfileByUuid(ctx, conn, uuid)
})) }))
} }
@@ -169,10 +142,10 @@ func (r *Redis) removeProfileByUuid(ctx context.Context, conn radix.Conn, uuid s
return conn.Do(ctx, radix.Cmd(nil, "EXEC")) return conn.Do(ctx, radix.Cmd(nil, "EXEC"))
} }
func (r *Redis) GetUuidForMojangUsername(username string) (string, string, error) { func (r *Redis) GetUuidForMojangUsername(ctx context.Context, username string) (string, string, error) {
var uuid string var uuid string
foundUsername := username foundUsername := username
err := r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error { err := r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
var err error var err error
uuid, foundUsername, err = findMojangUuidByUsername(ctx, conn, username) uuid, foundUsername, err = findMojangUuidByUsername(ctx, conn, username)
@@ -199,8 +172,8 @@ func findMojangUuidByUsername(ctx context.Context, conn radix.Conn, username str
return parts[1], parts[0], nil return parts[1], parts[0], nil
} }
func (r *Redis) StoreMojangUuid(username string, uuid string) error { func (r *Redis) StoreMojangUuid(ctx context.Context, username string, uuid string) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error { return r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return storeMojangUuid(ctx, conn, username, uuid) return storeMojangUuid(ctx, conn, username, uuid)
})) }))
} }

View File

@@ -115,19 +115,20 @@ func TestRedis(t *testing.T) {
} }
func (s *redisTestSuite) TestFindProfileByUsername() { func (s *redisTestSuite) TestFindProfileByUsername() {
ctx := context.Background()
s.Run("exists record", func() { s.Run("exists record", func() {
serializedData := []byte("mock.exists.profile") serializedData := []byte("mock.exists.profile")
expectedProfile := &db.Profile{} expectedProfile := &db.Profile{}
s.cmd("HSET", usernameToProfileKey, "mock", serializedData) s.cmd("HSET", usernameToProfileKey, "mock", serializedData)
s.Serializer.On("Deserialize", serializedData).Return(expectedProfile, nil) s.Serializer.On("Deserialize", serializedData).Return(expectedProfile, nil)
profile, err := s.Redis.FindProfileByUsername("Mock") profile, err := s.Redis.FindProfileByUsername(ctx, "Mock")
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Same(expectedProfile, profile) s.Require().Same(expectedProfile, profile)
}) })
s.Run("not exists record", func() { s.Run("not exists record", func() {
profile, err := s.Redis.FindProfileByUsername("Mock") profile, err := s.Redis.FindProfileByUsername(ctx, "Mock")
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Nil(profile) s.Require().Nil(profile)
}) })
@@ -137,40 +138,15 @@ func (s *redisTestSuite) TestFindProfileByUsername() {
s.cmd("HSET", usernameToProfileKey, "mock", "some-invalid-mock-data") s.cmd("HSET", usernameToProfileKey, "mock", "some-invalid-mock-data")
s.Serializer.On("Deserialize", mock.Anything).Return(nil, expectedError) s.Serializer.On("Deserialize", mock.Anything).Return(nil, expectedError)
profile, err := s.Redis.FindProfileByUsername("Mock") profile, err := s.Redis.FindProfileByUsername(ctx, "Mock")
s.Require().Nil(profile) s.Require().Nil(profile)
s.Require().ErrorIs(err, expectedError) s.Require().ErrorIs(err, expectedError)
}) })
} }
func (s *redisTestSuite) TestFindProfileByUuid() {
s.Run("exists record", func() {
serializedData := []byte("mock.exists.profile")
expectedProfile := &db.Profile{Username: "Mock"}
s.cmd("HSET", usernameToProfileKey, "mock", serializedData)
s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock")
s.Serializer.On("Deserialize", serializedData).Return(expectedProfile, nil)
profile, err := s.Redis.FindProfileByUuid("f57f36d5-4f50-4728-948a-42d5d80b18f3")
s.Require().NoError(err)
s.Require().Same(expectedProfile, profile)
})
s.Run("not exists record", func() {
profile, err := s.Redis.FindProfileByUuid("f57f36d5-4f50-4728-948a-42d5d80b18f3")
s.Require().NoError(err)
s.Require().Nil(profile)
})
s.Run("exists uuid record, but related profile not exists", func() {
s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock")
profile, err := s.Redis.FindProfileByUuid("f57f36d5-4f50-4728-948a-42d5d80b18f3")
s.Require().NoError(err)
s.Require().Nil(profile)
})
}
func (s *redisTestSuite) TestSaveProfile() { func (s *redisTestSuite) TestSaveProfile() {
ctx := context.Background()
s.Run("save new entity", func() { s.Run("save new entity", func() {
profile := &db.Profile{ profile := &db.Profile{
Uuid: "f57f36d5-4f50-4728-948a-42d5d80b18f3", Uuid: "f57f36d5-4f50-4728-948a-42d5d80b18f3",
@@ -182,7 +158,7 @@ func (s *redisTestSuite) TestSaveProfile() {
s.cmd("HSET", usernameToProfileKey, "mock", serializedProfile) s.cmd("HSET", usernameToProfileKey, "mock", serializedProfile)
s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock") s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock")
err := s.Redis.SaveProfile(profile) err := s.Redis.SaveProfile(ctx, profile)
s.Require().NoError(err) s.Require().NoError(err)
uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3") uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3")
@@ -203,7 +179,7 @@ func (s *redisTestSuite) TestSaveProfile() {
s.cmd("HSET", usernameToProfileKey, "mock", "serialized-old-profile") s.cmd("HSET", usernameToProfileKey, "mock", "serialized-old-profile")
s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock") s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock")
err := s.Redis.SaveProfile(newProfile) err := s.Redis.SaveProfile(ctx, newProfile)
s.Require().NoError(err) s.Require().NoError(err)
uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3") uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3")
@@ -218,11 +194,13 @@ func (s *redisTestSuite) TestSaveProfile() {
} }
func (s *redisTestSuite) TestRemoveProfileByUuid() { func (s *redisTestSuite) TestRemoveProfileByUuid() {
ctx := context.Background()
s.Run("exists record", func() { s.Run("exists record", func() {
s.cmd("HSET", usernameToProfileKey, "mock", "serialized-profile") s.cmd("HSET", usernameToProfileKey, "mock", "serialized-profile")
s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock") s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock")
err := s.Redis.RemoveProfileByUuid("f57f36d5-4f50-4728-948a-42d5d80b18f3") err := s.Redis.RemoveProfileByUuid(ctx, "f57f36d5-4f50-4728-948a-42d5d80b18f3")
s.Require().NoError(err) s.Require().NoError(err)
uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3") uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3")
@@ -235,7 +213,7 @@ func (s *redisTestSuite) TestRemoveProfileByUuid() {
s.Run("uuid exists, username is missing", func() { s.Run("uuid exists, username is missing", func() {
s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock") s.cmd("HSET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3", "mock")
err := s.Redis.RemoveProfileByUuid("f57f36d5-4f50-4728-948a-42d5d80b18f3") err := s.Redis.RemoveProfileByUuid(ctx, "f57f36d5-4f50-4728-948a-42d5d80b18f3")
s.Require().NoError(err) s.Require().NoError(err)
uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3") uuidResp := s.cmd("HGET", userUuidToUsernameKey, "f57f36d54f504728948a42d5d80b18f3")
@@ -243,16 +221,18 @@ func (s *redisTestSuite) TestRemoveProfileByUuid() {
}) })
s.Run("uuid not exists", func() { s.Run("uuid not exists", func() {
err := s.Redis.RemoveProfileByUuid("f57f36d5-4f50-4728-948a-42d5d80b18f3") err := s.Redis.RemoveProfileByUuid(ctx, "f57f36d5-4f50-4728-948a-42d5d80b18f3")
s.Require().NoError(err) s.Require().NoError(err)
}) })
} }
func (s *redisTestSuite) TestGetUuidForMojangUsername() { func (s *redisTestSuite) TestGetUuidForMojangUsername() {
ctx := context.Background()
s.Run("exists record", func() { s.Run("exists record", func() {
s.cmd("SET", "mojang:uuid:mock", "MoCk:d3ca513eb3e14946b58047f2bd3530fd") s.cmd("SET", "mojang:uuid:mock", "MoCk:d3ca513eb3e14946b58047f2bd3530fd")
uuid, username, err := s.Redis.GetUuidForMojangUsername("Mock") uuid, username, err := s.Redis.GetUuidForMojangUsername(ctx, "Mock")
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Equal("MoCk", username) s.Require().Equal("MoCk", username)
s.Require().Equal("d3ca513eb3e14946b58047f2bd3530fd", uuid) s.Require().Equal("d3ca513eb3e14946b58047f2bd3530fd", uuid)
@@ -261,14 +241,14 @@ func (s *redisTestSuite) TestGetUuidForMojangUsername() {
s.Run("exists record with empty uuid value", func() { s.Run("exists record with empty uuid value", func() {
s.cmd("SET", "mojang:uuid:mock", "MoCk:") s.cmd("SET", "mojang:uuid:mock", "MoCk:")
uuid, username, err := s.Redis.GetUuidForMojangUsername("Mock") uuid, username, err := s.Redis.GetUuidForMojangUsername(ctx, "Mock")
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Equal("MoCk", username) s.Require().Equal("MoCk", username)
s.Require().Empty(uuid) s.Require().Empty(uuid)
}) })
s.Run("not exists record", func() { s.Run("not exists record", func() {
uuid, username, err := s.Redis.GetUuidForMojangUsername("Mock") uuid, username, err := s.Redis.GetUuidForMojangUsername(ctx, "Mock")
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Empty(username) s.Require().Empty(username)
s.Require().Empty(uuid) s.Require().Empty(uuid)
@@ -276,8 +256,10 @@ func (s *redisTestSuite) TestGetUuidForMojangUsername() {
} }
func (s *redisTestSuite) TestStoreUuid() { func (s *redisTestSuite) TestStoreUuid() {
ctx := context.Background()
s.Run("store uuid", func() { s.Run("store uuid", func() {
err := s.Redis.StoreMojangUuid("MoCk", "d3ca513eb3e14946b58047f2bd3530fd") err := s.Redis.StoreMojangUuid(ctx, "MoCk", "d3ca513eb3e14946b58047f2bd3530fd")
s.Require().NoError(err) s.Require().NoError(err)
resp := s.cmd("GET", "mojang:uuid:mock") resp := s.cmd("GET", "mojang:uuid:mock")
@@ -285,7 +267,7 @@ func (s *redisTestSuite) TestStoreUuid() {
}) })
s.Run("store empty uuid", func() { s.Run("store empty uuid", func() {
err := s.Redis.StoreMojangUuid("MoCk", "") err := s.Redis.StoreMojangUuid(ctx, "MoCk", "")
s.Require().NoError(err) s.Require().NoError(err)
resp := s.cmd("GET", "mojang:uuid:mock") resp := s.cmd("GET", "mojang:uuid:mock")

View File

@@ -1,6 +1,7 @@
package http package http
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@@ -12,8 +13,8 @@ import (
) )
type ProfilesManager interface { type ProfilesManager interface {
PersistProfile(profile *db.Profile) error PersistProfile(ctx context.Context, profile *db.Profile) error
RemoveProfileByUuid(uuid string) error RemoveProfileByUuid(ctx context.Context, uuid string) error
} }
type Api struct { type Api struct {
@@ -47,7 +48,7 @@ func (ctx *Api) postProfileHandler(resp http.ResponseWriter, req *http.Request)
MojangSignature: req.Form.Get("mojangSignature"), MojangSignature: req.Form.Get("mojangSignature"),
} }
err = ctx.PersistProfile(profile) err = ctx.PersistProfile(req.Context(), profile)
if err != nil { if err != nil {
var v *profiles.ValidationError var v *profiles.ValidationError
if errors.As(err, &v) { if errors.As(err, &v) {
@@ -64,7 +65,7 @@ func (ctx *Api) postProfileHandler(resp http.ResponseWriter, req *http.Request)
func (ctx *Api) deleteProfileByUuidHandler(resp http.ResponseWriter, req *http.Request) { func (ctx *Api) deleteProfileByUuidHandler(resp http.ResponseWriter, req *http.Request) {
uuid := mux.Vars(req)["uuid"] uuid := mux.Vars(req)["uuid"]
err := ctx.ProfilesManager.RemoveProfileByUuid(uuid) err := ctx.ProfilesManager.RemoveProfileByUuid(req.Context(), uuid)
if err != nil { if err != nil {
apiServerError(resp, fmt.Errorf("unable to delete profile from db: %w", err)) apiServerError(resp, fmt.Errorf("unable to delete profile from db: %w", err))
return return

View File

@@ -2,6 +2,7 @@ package http
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"io" "io"
"net/http" "net/http"
@@ -21,12 +22,12 @@ type ProfilesManagerMock struct {
mock.Mock mock.Mock
} }
func (m *ProfilesManagerMock) PersistProfile(profile *db.Profile) error { func (m *ProfilesManagerMock) PersistProfile(ctx context.Context, profile *db.Profile) error {
return m.Called(profile).Error(0) return m.Called(ctx, profile).Error(0)
} }
func (m *ProfilesManagerMock) RemoveProfileByUuid(uuid string) error { func (m *ProfilesManagerMock) RemoveProfileByUuid(ctx context.Context, uuid string) error {
return m.Called(uuid).Error(0) return m.Called(ctx, uuid).Error(0)
} }
type ApiTestSuite struct { type ApiTestSuite struct {
@@ -50,7 +51,7 @@ func (t *ApiTestSuite) TearDownSubTest() {
func (t *ApiTestSuite) TestPostProfile() { func (t *ApiTestSuite) TestPostProfile() {
t.Run("successfully post profile", func() { t.Run("successfully post profile", func() {
t.ProfilesManager.On("PersistProfile", &db.Profile{ t.ProfilesManager.On("PersistProfile", mock.Anything, &db.Profile{
Uuid: "0f657aa8-bfbe-415d-b700-5750090d3af3", Uuid: "0f657aa8-bfbe-415d-b700-5750090d3af3",
Username: "mock_username", Username: "mock_username",
SkinUrl: "https://example.com/skin.png", SkinUrl: "https://example.com/skin.png",
@@ -100,7 +101,7 @@ func (t *ApiTestSuite) TestPostProfile() {
}) })
t.Run("receive validation errors", func() { t.Run("receive validation errors", func() {
t.ProfilesManager.On("PersistProfile", mock.Anything).Once().Return(&profiles.ValidationError{ t.ProfilesManager.On("PersistProfile", mock.Anything, mock.Anything).Once().Return(&profiles.ValidationError{
Errors: map[string][]string{ Errors: map[string][]string{
"mock": {"error1", "error2"}, "mock": {"error1", "error2"},
}, },
@@ -126,7 +127,7 @@ func (t *ApiTestSuite) TestPostProfile() {
}) })
t.Run("receive other error", func() { t.Run("receive other error", func() {
t.ProfilesManager.On("PersistProfile", mock.Anything).Once().Return(errors.New("mock error")) t.ProfilesManager.On("PersistProfile", mock.Anything, mock.Anything).Once().Return(errors.New("mock error"))
req := httptest.NewRequest("POST", "http://chrly/profiles", strings.NewReader("")) req := httptest.NewRequest("POST", "http://chrly/profiles", strings.NewReader(""))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
@@ -141,7 +142,7 @@ func (t *ApiTestSuite) TestPostProfile() {
func (t *ApiTestSuite) TestDeleteProfileByUuid() { func (t *ApiTestSuite) TestDeleteProfileByUuid() {
t.Run("successfully delete", func() { t.Run("successfully delete", func() {
t.ProfilesManager.On("RemoveProfileByUuid", "0f657aa8-bfbe-415d-b700-5750090d3af3").Once().Return(nil) t.ProfilesManager.On("RemoveProfileByUuid", mock.Anything, "0f657aa8-bfbe-415d-b700-5750090d3af3").Once().Return(nil)
req := httptest.NewRequest("DELETE", "http://chrly/profiles/0f657aa8-bfbe-415d-b700-5750090d3af3", nil) req := httptest.NewRequest("DELETE", "http://chrly/profiles/0f657aa8-bfbe-415d-b700-5750090d3af3", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
@@ -155,7 +156,7 @@ func (t *ApiTestSuite) TestDeleteProfileByUuid() {
}) })
t.Run("error from manager", func() { t.Run("error from manager", func() {
t.ProfilesManager.On("RemoveProfileByUuid", mock.Anything).Return(errors.New("mock error")) t.ProfilesManager.On("RemoveProfileByUuid", mock.Anything, mock.Anything).Return(errors.New("mock error"))
req := httptest.NewRequest("DELETE", "http://chrly/profiles/0f657aa8-bfbe-415d-b700-5750090d3af3", nil) req := httptest.NewRequest("DELETE", "http://chrly/profiles/0f657aa8-bfbe-415d-b700-5750090d3af3", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()

View File

@@ -25,9 +25,10 @@ type ProfilesProvider interface {
FindProfileByUsername(ctx context.Context, username string, allowProxy bool) (*db.Profile, error) FindProfileByUsername(ctx context.Context, username string, allowProxy bool) (*db.Profile, error)
} }
// TexturesSigner uses context because in the future we may separate this logic into a separate microservice
type TexturesSigner interface { type TexturesSigner interface {
SignTextures(textures string) (string, error) SignTextures(ctx context.Context, textures string) (string, error)
GetPublicKey() (*rsa.PublicKey, error) GetPublicKey(ctx context.Context) (*rsa.PublicKey, error)
} }
type Skinsystem struct { type Skinsystem struct {
@@ -202,7 +203,7 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
} }
if request.URL.Query().Has("unsigned") && !getToBool(request.URL.Query().Get("unsigned")) { if request.URL.Query().Has("unsigned") && !getToBool(request.URL.Query().Get("unsigned")) {
signature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value) signature, err := ctx.TexturesSigner.SignTextures(request.Context(), texturesProp.Value)
if err != nil { if err != nil {
apiServerError(response, fmt.Errorf("unable to sign textures: %w", err)) apiServerError(response, fmt.Errorf("unable to sign textures: %w", err))
return return
@@ -229,7 +230,7 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
} }
func (ctx *Skinsystem) signatureVerificationKeyHandler(response http.ResponseWriter, request *http.Request) { func (ctx *Skinsystem) signatureVerificationKeyHandler(response http.ResponseWriter, request *http.Request) {
publicKey, err := ctx.TexturesSigner.GetPublicKey() publicKey, err := ctx.TexturesSigner.GetPublicKey(request.Context())
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -38,13 +38,13 @@ type TexturesSignerMock struct {
mock.Mock mock.Mock
} }
func (m *TexturesSignerMock) SignTextures(textures string) (string, error) { func (m *TexturesSignerMock) SignTextures(ctx context.Context, textures string) (string, error) {
args := m.Called(textures) args := m.Called(ctx, textures)
return args.String(0), args.Error(1) return args.String(0), args.Error(1)
} }
func (m *TexturesSignerMock) GetPublicKey() (*rsa.PublicKey, error) { func (m *TexturesSignerMock) GetPublicKey(ctx context.Context) (*rsa.PublicKey, error) {
args := m.Called() args := m.Called(ctx)
var publicKey *rsa.PublicKey var publicKey *rsa.PublicKey
if casted, ok := args.Get(0).(*rsa.PublicKey); ok { if casted, ok := args.Get(0).(*rsa.PublicKey); ok {
publicKey = casted publicKey = casted
@@ -470,7 +470,7 @@ func (t *SkinsystemTestSuite) TestProfile() {
SkinUrl: "https://example.com/skin.png", SkinUrl: "https://example.com/skin.png",
SkinModel: "slim", SkinModel: "slim",
}, nil) }, nil)
t.TexturesSigner.On("SignTextures", "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6Im1vY2stdXVpZCIsInByb2ZpbGVOYW1lIjoibW9ja191c2VybmFtZSIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9za2luLnBuZyIsIm1ldGFkYXRhIjp7Im1vZGVsIjoic2xpbSJ9fX19").Return("mock signature", nil) t.TexturesSigner.On("SignTextures", mock.Anything, "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6Im1vY2stdXVpZCIsInByb2ZpbGVOYW1lIjoibW9ja191c2VybmFtZSIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9za2luLnBuZyIsIm1ldGFkYXRhIjp7Im1vZGVsIjoic2xpbSJ9fX19").Return("mock signature", nil)
t.App.Handler().ServeHTTP(w, req) t.App.Handler().ServeHTTP(w, req)
@@ -526,7 +526,7 @@ func (t *SkinsystemTestSuite) TestProfile() {
w := httptest.NewRecorder() w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{}, nil) t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{}, nil)
t.TexturesSigner.On("SignTextures", mock.Anything).Return("", errors.New("mock error")) t.TexturesSigner.On("SignTextures", mock.Anything, mock.Anything).Return("", errors.New("mock error"))
t.App.Handler().ServeHTTP(w, req) t.App.Handler().ServeHTTP(w, req)
@@ -551,7 +551,7 @@ var signingKeyTestsCases = []*signingKeyTestCase{
pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----")) pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----"))
publicKey, _ := x509.ParsePKIXPublicKey(pubPem.Bytes) publicKey, _ := x509.ParsePKIXPublicKey(pubPem.Bytes)
suite.TexturesSigner.On("GetPublicKey").Return(publicKey, nil) suite.TexturesSigner.On("GetPublicKey", mock.Anything).Return(publicKey, nil)
}, },
AfterTest: func(suite *SkinsystemTestSuite, response *http.Response) { AfterTest: func(suite *SkinsystemTestSuite, response *http.Response) {
suite.Equal(200, response.StatusCode) suite.Equal(200, response.StatusCode)
@@ -568,7 +568,7 @@ var signingKeyTestsCases = []*signingKeyTestCase{
pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----")) pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----"))
publicKey, _ := x509.ParsePKIXPublicKey(pubPem.Bytes) publicKey, _ := x509.ParsePKIXPublicKey(pubPem.Bytes)
suite.TexturesSigner.On("GetPublicKey").Return(publicKey, nil) suite.TexturesSigner.On("GetPublicKey", mock.Anything).Return(publicKey, nil)
}, },
AfterTest: func(suite *SkinsystemTestSuite, response *http.Response) { AfterTest: func(suite *SkinsystemTestSuite, response *http.Response) {
suite.Equal(200, response.StatusCode) suite.Equal(200, response.StatusCode)
@@ -582,7 +582,7 @@ var signingKeyTestsCases = []*signingKeyTestCase{
Name: "Error while obtaining public key", Name: "Error while obtaining public key",
KeyFormat: "DER", KeyFormat: "DER",
BeforeTest: func(suite *SkinsystemTestSuite) { BeforeTest: func(suite *SkinsystemTestSuite) {
suite.TexturesSigner.On("GetPublicKey").Return(nil, errors.New("textures signer error")) suite.TexturesSigner.On("GetPublicKey", mock.Anything).Return(nil, errors.New("textures signer error"))
}, },
PanicErr: "textures signer error", PanicErr: "textures signer error",
}, },

View File

@@ -6,14 +6,18 @@ import (
"sync" "sync"
"time" "time"
"github.com/SentimensRG/ctx/mergectx"
"ely.by/chrly/internal/utils" "ely.by/chrly/internal/utils"
) )
type UsernamesToUuidsEndpoint func(ctx context.Context, usernames []string) ([]*ProfileInfo, error)
type BatchUuidsProvider struct { type BatchUuidsProvider struct {
UsernamesToUuidsEndpoint func(usernames []string) ([]*ProfileInfo, error) UsernamesToUuidsEndpoint
batch int batch int
delay time.Duration delay time.Duration
fireOnFull bool fireOnFull bool
queue *utils.Queue[*job] queue *utils.Queue[*job]
fireChan chan any fireChan chan any
@@ -22,7 +26,7 @@ type BatchUuidsProvider struct {
} }
func NewBatchUuidsProvider( func NewBatchUuidsProvider(
endpoint func(usernames []string) ([]*ProfileInfo, error), endpoint UsernamesToUuidsEndpoint,
batchSize int, batchSize int,
awaitDelay time.Duration, awaitDelay time.Duration,
fireOnFull bool, fireOnFull bool,
@@ -115,12 +119,14 @@ func (p *BatchUuidsProvider) fireRequest() {
return return
} }
ctx := context.Background()
usernames := make([]string, len(jobs)) usernames := make([]string, len(jobs))
for i, job := range jobs { for i, job := range jobs {
usernames[i] = job.Username usernames[i] = job.Username
ctx = mergectx.Join(ctx, job.Ctx)
} }
profiles, err := p.UsernamesToUuidsEndpoint(usernames) profiles, err := p.UsernamesToUuidsEndpoint(ctx, usernames)
for _, job := range jobs { for _, job := range jobs {
response := &jobResult{} response := &jobResult{}
if err == nil { if err == nil {

View File

@@ -16,8 +16,8 @@ type mojangUsernamesToUuidsRequestMock struct {
mock.Mock mock.Mock
} }
func (o *mojangUsernamesToUuidsRequestMock) UsernamesToUuids(usernames []string) ([]*ProfileInfo, error) { func (o *mojangUsernamesToUuidsRequestMock) UsernamesToUuids(ctx context.Context, usernames []string) ([]*ProfileInfo, error) {
args := o.Called(usernames) args := o.Called(ctx, usernames)
var result []*ProfileInfo var result []*ProfileInfo
if casted, ok := args.Get(0).([]*ProfileInfo); ok { if casted, ok := args.Get(0).([]*ProfileInfo); ok {
result = casted result = casted
@@ -81,7 +81,7 @@ func (s *batchUuidsProviderTestSuite) TestGetUuidForFewUsernamesSuccessfully() {
expectedResult1 := &ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username1"} expectedResult1 := &ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username1"}
expectedResult2 := &ProfileInfo{Id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", Name: "username2"} expectedResult2 := &ProfileInfo{Id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", Name: "username2"}
s.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return([]*ProfileInfo{ s.MojangApi.On("UsernamesToUuids", mock.Anything, expectedUsernames).Once().Return([]*ProfileInfo{
expectedResult1, expectedResult1,
expectedResult2, expectedResult2,
}, nil) }, nil)
@@ -110,8 +110,8 @@ func (s *batchUuidsProviderTestSuite) TestGetUuidForFewUsernamesSuccessfully() {
func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesSplitByMultipleIterations() { func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesSplitByMultipleIterations() {
var emptyResponse []string var emptyResponse []string
s.MojangApi.On("UsernamesToUuids", []string{"username1", "username2", "username3"}).Once().Return(emptyResponse, nil) s.MojangApi.On("UsernamesToUuids", mock.Anything, []string{"username1", "username2", "username3"}).Once().Return(emptyResponse, nil)
s.MojangApi.On("UsernamesToUuids", []string{"username4"}).Once().Return(emptyResponse, nil) s.MojangApi.On("UsernamesToUuids", mock.Anything, []string{"username4"}).Once().Return(emptyResponse, nil)
resultChan1 := s.GetUuidAsync("username1") resultChan1 := s.GetUuidAsync("username1")
resultChan2 := s.GetUuidAsync("username2") resultChan2 := s.GetUuidAsync("username2")
@@ -133,7 +133,7 @@ func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesSplitByMultiple
func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesWhenOneOfContextIsDeadlined() { func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesWhenOneOfContextIsDeadlined() {
var emptyResponse []string var emptyResponse []string
s.MojangApi.On("UsernamesToUuids", []string{"username1", "username2", "username4"}).Once().Return(emptyResponse, nil) s.MojangApi.On("UsernamesToUuids", mock.Anything, []string{"username1", "username2", "username4"}).Once().Return(emptyResponse, nil)
ctx, cancelCtx := context.WithCancel(context.Background()) ctx, cancelCtx := context.WithCancel(context.Background())
@@ -167,8 +167,8 @@ func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesFireOnFull() {
var emptyResponse []string var emptyResponse []string
s.MojangApi.On("UsernamesToUuids", []string{"username1", "username2", "username3"}).Once().Return(emptyResponse, nil) s.MojangApi.On("UsernamesToUuids", mock.Anything, []string{"username1", "username2", "username3"}).Once().Return(emptyResponse, nil)
s.MojangApi.On("UsernamesToUuids", []string{"username4"}).Once().Return(emptyResponse, nil) s.MojangApi.On("UsernamesToUuids", mock.Anything, []string{"username4"}).Once().Return(emptyResponse, nil)
resultChan1 := s.GetUuidAsync("username1") resultChan1 := s.GetUuidAsync("username1")
resultChan2 := s.GetUuidAsync("username2") resultChan2 := s.GetUuidAsync("username2")
@@ -191,7 +191,7 @@ func (s *batchUuidsProviderTestSuite) TestGetUuidForFewUsernamesWithAnError() {
expectedUsernames := []string{"username1", "username2"} expectedUsernames := []string{"username1", "username2"}
expectedError := errors.New("mock error") expectedError := errors.New("mock error")
s.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return(nil, expectedError) s.MojangApi.On("UsernamesToUuids", mock.Anything, expectedUsernames).Once().Return(nil, expectedError)
resultChan1 := s.GetUuidAsync("username1") resultChan1 := s.GetUuidAsync("username1")
resultChan2 := s.GetUuidAsync("username2") resultChan2 := s.GetUuidAsync("username2")

View File

@@ -2,6 +2,7 @@ package mojang
import ( import (
"bytes" "bytes"
"context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -43,9 +44,9 @@ func NewMojangApi(
// Exchanges usernames array to array of uuids // Exchanges usernames array to array of uuids
// See https://wiki.vg/Mojang_API#Playernames_-.3E_UUIDs // See https://wiki.vg/Mojang_API#Playernames_-.3E_UUIDs
func (c *MojangApi) UsernamesToUuids(usernames []string) ([]*ProfileInfo, error) { func (c *MojangApi) UsernamesToUuids(ctx context.Context, usernames []string) ([]*ProfileInfo, error) {
requestBody, _ := json.Marshal(usernames) requestBody, _ := json.Marshal(usernames)
request, err := http.NewRequest("POST", c.batchUuidsUrl, bytes.NewBuffer(requestBody)) request, err := http.NewRequestWithContext(ctx, "POST", c.batchUuidsUrl, bytes.NewBuffer(requestBody))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -75,14 +76,14 @@ func (c *MojangApi) UsernamesToUuids(usernames []string) ([]*ProfileInfo, error)
// Obtains textures information for provided uuid // Obtains textures information for provided uuid
// See https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape // See https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape
func (c *MojangApi) UuidToTextures(uuid string, signed bool) (*ProfileResponse, error) { func (c *MojangApi) UuidToTextures(ctx context.Context, uuid string, signed bool) (*ProfileResponse, error) {
normalizedUuid := strings.ReplaceAll(uuid, "-", "") normalizedUuid := strings.ReplaceAll(uuid, "-", "")
url := c.profileUrl + normalizedUuid url := c.profileUrl + normalizedUuid
if signed { if signed {
url += "?unsigned=false" url += "?unsigned=false"
} }
request, err := http.NewRequest("GET", url, nil) request, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,6 +1,7 @@
package mojang package mojang
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
@@ -44,19 +45,18 @@ func (s *MojangApiSuite) TestUsernamesToUuidsSuccessfully() {
}, },
}) })
result, err := s.api.UsernamesToUuids([]string{"Thinkofdeath", "maksimkurb"}) result, err := s.api.UsernamesToUuids(context.Background(), []string{"Thinkofdeath", "maksimkurb"})
if s.Assert().NoError(err) { s.NoError(err)
s.Assert().Len(result, 2) s.Len(result, 2)
s.Assert().Equal("4566e69fc90748ee8d71d7ba5aa00d20", result[0].Id) s.Equal("4566e69fc90748ee8d71d7ba5aa00d20", result[0].Id)
s.Assert().Equal("Thinkofdeath", result[0].Name) s.Equal("Thinkofdeath", result[0].Name)
s.Assert().False(result[0].IsLegacy) s.False(result[0].IsLegacy)
s.Assert().True(result[0].IsDemo) s.True(result[0].IsDemo)
s.Assert().Equal("0d252b7218b648bfb86c2ae476954d32", result[1].Id) s.Equal("0d252b7218b648bfb86c2ae476954d32", result[1].Id)
s.Assert().Equal("maksimkurb", result[1].Name) s.Equal("maksimkurb", result[1].Name)
s.Assert().False(result[1].IsLegacy) s.False(result[1].IsLegacy)
s.Assert().False(result[1].IsDemo) s.False(result[1].IsDemo)
}
} }
func (s *MojangApiSuite) TestUsernamesToUuidsBadRequest() { func (s *MojangApiSuite) TestUsernamesToUuidsBadRequest() {
@@ -68,10 +68,10 @@ func (s *MojangApiSuite) TestUsernamesToUuidsBadRequest() {
"errorMessage": "profileName can not be null or empty.", "errorMessage": "profileName can not be null or empty.",
}) })
result, err := s.api.UsernamesToUuids([]string{""}) result, err := s.api.UsernamesToUuids(context.Background(), []string{""})
s.Assert().Nil(result) s.Nil(result)
s.Assert().IsType(&BadRequestError{}, err) s.IsType(&BadRequestError{}, err)
s.Assert().EqualError(err, "400 IllegalArgumentException: profileName can not be null or empty.") s.EqualError(err, "400 IllegalArgumentException: profileName can not be null or empty.")
} }
func (s *MojangApiSuite) TestUsernamesToUuidsForbidden() { func (s *MojangApiSuite) TestUsernamesToUuidsForbidden() {
@@ -80,10 +80,10 @@ func (s *MojangApiSuite) TestUsernamesToUuidsForbidden() {
Reply(403). Reply(403).
BodyString("just because") BodyString("just because")
result, err := s.api.UsernamesToUuids([]string{"Thinkofdeath", "maksimkurb"}) result, err := s.api.UsernamesToUuids(context.Background(), []string{"Thinkofdeath", "maksimkurb"})
s.Assert().Nil(result) s.Nil(result)
s.Assert().IsType(&ForbiddenError{}, err) s.IsType(&ForbiddenError{}, err)
s.Assert().EqualError(err, "403: Forbidden") s.EqualError(err, "403: Forbidden")
} }
func (s *MojangApiSuite) TestUsernamesToUuidsTooManyRequests() { func (s *MojangApiSuite) TestUsernamesToUuidsTooManyRequests() {
@@ -95,10 +95,10 @@ func (s *MojangApiSuite) TestUsernamesToUuidsTooManyRequests() {
"errorMessage": "The client has sent too many requests within a certain amount of time", "errorMessage": "The client has sent too many requests within a certain amount of time",
}) })
result, err := s.api.UsernamesToUuids([]string{"Thinkofdeath", "maksimkurb"}) result, err := s.api.UsernamesToUuids(context.Background(), []string{"Thinkofdeath", "maksimkurb"})
s.Assert().Nil(result) s.Nil(result)
s.Assert().IsType(&TooManyRequestsError{}, err) s.IsType(&TooManyRequestsError{}, err)
s.Assert().EqualError(err, "429: Too Many Requests") s.EqualError(err, "429: Too Many Requests")
} }
func (s *MojangApiSuite) TestUsernamesToUuidsServerError() { func (s *MojangApiSuite) TestUsernamesToUuidsServerError() {
@@ -107,11 +107,11 @@ func (s *MojangApiSuite) TestUsernamesToUuidsServerError() {
Reply(500). Reply(500).
BodyString("500 Internal Server Error") BodyString("500 Internal Server Error")
result, err := s.api.UsernamesToUuids([]string{"Thinkofdeath", "maksimkurb"}) result, err := s.api.UsernamesToUuids(context.Background(), []string{"Thinkofdeath", "maksimkurb"})
s.Assert().Nil(result) s.Nil(result)
s.Assert().IsType(&ServerError{}, err) s.IsType(&ServerError{}, err)
s.Assert().EqualError(err, "500: Server error") s.EqualError(err, "500: Server error")
s.Assert().Equal(500, err.(*ServerError).Status) s.Equal(500, err.(*ServerError).Status)
} }
func (s *MojangApiSuite) TestUuidToTexturesSuccessfulResponse() { func (s *MojangApiSuite) TestUuidToTexturesSuccessfulResponse() {
@@ -129,14 +129,14 @@ func (s *MojangApiSuite) TestUuidToTexturesSuccessfulResponse() {
}, },
}) })
result, err := s.api.UuidToTextures("4566e69fc90748ee8d71d7ba5aa00d20", false) result, err := s.api.UuidToTextures(context.Background(), "4566e69fc90748ee8d71d7ba5aa00d20", false)
s.Assert().NoError(err) s.NoError(err)
s.Assert().Equal("4566e69fc90748ee8d71d7ba5aa00d20", result.Id) s.Equal("4566e69fc90748ee8d71d7ba5aa00d20", result.Id)
s.Assert().Equal("Thinkofdeath", result.Name) s.Equal("Thinkofdeath", result.Name)
s.Assert().Equal(1, len(result.Props)) s.Equal(1, len(result.Props))
s.Assert().Equal("textures", result.Props[0].Name) s.Equal("textures", result.Props[0].Name)
s.Assert().Equal(476, len(result.Props[0].Value)) s.Equal(476, len(result.Props[0].Value))
s.Assert().Equal("", result.Props[0].Signature) s.Equal("", result.Props[0].Signature)
} }
func (s *MojangApiSuite) TestUuidToTexturesEmptyResponse() { func (s *MojangApiSuite) TestUuidToTexturesEmptyResponse() {
@@ -145,9 +145,9 @@ func (s *MojangApiSuite) TestUuidToTexturesEmptyResponse() {
Reply(204). Reply(204).
BodyString("") BodyString("")
result, err := s.api.UuidToTextures("4566e69fc90748ee8d71d7ba5aa00d20", false) result, err := s.api.UuidToTextures(context.Background(), "4566e69fc90748ee8d71d7ba5aa00d20", false)
s.Assert().Nil(result) s.NoError(err)
s.Assert().NoError(err) s.Nil(result)
} }
func (s *MojangApiSuite) TestUuidToTexturesTooManyRequests() { func (s *MojangApiSuite) TestUuidToTexturesTooManyRequests() {
@@ -159,10 +159,10 @@ func (s *MojangApiSuite) TestUuidToTexturesTooManyRequests() {
"errorMessage": "The client has sent too many requests within a certain amount of time", "errorMessage": "The client has sent too many requests within a certain amount of time",
}) })
result, err := s.api.UuidToTextures("4566e69fc90748ee8d71d7ba5aa00d20", false) result, err := s.api.UuidToTextures(context.Background(), "4566e69fc90748ee8d71d7ba5aa00d20", false)
s.Assert().Nil(result) s.Nil(result)
s.Assert().IsType(&TooManyRequestsError{}, err) s.IsType(&TooManyRequestsError{}, err)
s.Assert().EqualError(err, "429: Too Many Requests") s.EqualError(err, "429: Too Many Requests")
} }
func (s *MojangApiSuite) TestUuidToTexturesServerError() { func (s *MojangApiSuite) TestUuidToTexturesServerError() {
@@ -171,18 +171,18 @@ func (s *MojangApiSuite) TestUuidToTexturesServerError() {
Reply(500). Reply(500).
BodyString("500 Internal Server Error") BodyString("500 Internal Server Error")
result, err := s.api.UuidToTextures("4566e69fc90748ee8d71d7ba5aa00d20", false) result, err := s.api.UuidToTextures(context.Background(), "4566e69fc90748ee8d71d7ba5aa00d20", false)
s.Assert().Nil(result) s.Nil(result)
s.Assert().IsType(&ServerError{}, err) s.IsType(&ServerError{}, err)
s.Assert().EqualError(err, "500: Server error") s.EqualError(err, "500: Server error")
s.Assert().Equal(500, err.(*ServerError).Status) s.Equal(500, err.(*ServerError).Status)
} }
func TestMojangApi(t *testing.T) { func TestMojangApi(t *testing.T) {
suite.Run(t, new(MojangApiSuite)) suite.Run(t, new(MojangApiSuite))
} }
func TestSignedTexturesResponse(t *testing.T) { func TestProfileResponse(t *testing.T) {
t.Run("DecodeTextures", func(t *testing.T) { t.Run("DecodeTextures", func(t *testing.T) {
obj := &ProfileResponse{ obj := &ProfileResponse{
Id: "00000000000000000000000000000000", Id: "00000000000000000000000000000000",

View File

@@ -9,11 +9,11 @@ import (
) )
type MojangApiTexturesProvider struct { type MojangApiTexturesProvider struct {
MojangApiTexturesEndpoint func(uuid string, signed bool) (*ProfileResponse, error) MojangApiTexturesEndpoint func(ctx context.Context, uuid string, signed bool) (*ProfileResponse, error)
} }
func (p *MojangApiTexturesProvider) GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) { func (p *MojangApiTexturesProvider) GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) {
return p.MojangApiTexturesEndpoint(uuid, true) return p.MojangApiTexturesEndpoint(ctx, uuid, true)
} }
// Perfectly there should be an object with provider and cache implementation, // Perfectly there should be an object with provider and cache implementation,

View File

@@ -34,8 +34,8 @@ type MojangUuidToTexturesRequestMock struct {
mock.Mock mock.Mock
} }
func (m *MojangUuidToTexturesRequestMock) UuidToTextures(uuid string, signed bool) (*ProfileResponse, error) { func (m *MojangUuidToTexturesRequestMock) UuidToTextures(ctx context.Context, uuid string, signed bool) (*ProfileResponse, error) {
args := m.Called(uuid, signed) args := m.Called(ctx, uuid, signed)
var result *ProfileResponse var result *ProfileResponse
if casted, ok := args.Get(0).(*ProfileResponse); ok { if casted, ok := args.Get(0).(*ProfileResponse); ok {
result = casted result = casted
@@ -63,9 +63,11 @@ func (s *MojangApiTexturesProviderSuite) TearDownTest() {
} }
func (s *MojangApiTexturesProviderSuite) TestGetTextures() { func (s *MojangApiTexturesProviderSuite) TestGetTextures() {
s.MojangApi.On("UuidToTextures", "dead24f9a4fa4877b7b04c8c6c72bb46", true).Once().Return(signedTexturesResponse, nil) ctx := context.Background()
result, err := s.Provider.GetTextures(context.Background(), "dead24f9a4fa4877b7b04c8c6c72bb46") s.MojangApi.On("UuidToTextures", ctx, "dead24f9a4fa4877b7b04c8c6c72bb46", true).Once().Return(signedTexturesResponse, nil)
result, err := s.Provider.GetTextures(ctx, "dead24f9a4fa4877b7b04c8c6c72bb46")
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Equal(signedTexturesResponse, result) s.Require().Equal(signedTexturesResponse, result)
@@ -73,7 +75,7 @@ func (s *MojangApiTexturesProviderSuite) TestGetTextures() {
func (s *MojangApiTexturesProviderSuite) TestGetTexturesWithError() { func (s *MojangApiTexturesProviderSuite) TestGetTexturesWithError() {
expectedError := errors.New("mock error") expectedError := errors.New("mock error")
s.MojangApi.On("UuidToTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true).Once().Return(nil, expectedError) s.MojangApi.On("UuidToTextures", mock.Anything, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true).Once().Return(nil, expectedError)
result, err := s.Provider.GetTextures(context.Background(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") result, err := s.Provider.GetTextures(context.Background(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")

View File

@@ -5,9 +5,9 @@ import "context"
type MojangUuidsStorage interface { type MojangUuidsStorage interface {
// The second argument must be returned as a incoming username in case, // The second argument must be returned as a incoming username in case,
// when cached result indicates that there is no Mojang user with provided username // when cached result indicates that there is no Mojang user with provided username
GetUuidForMojangUsername(username string) (foundUuid string, foundUsername string, err error) GetUuidForMojangUsername(ctx context.Context, username string) (foundUuid string, foundUsername string, 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
StoreMojangUuid(username string, uuid string) error StoreMojangUuid(ctx context.Context, username string, uuid string) error
} }
type UuidsProviderWithCache struct { type UuidsProviderWithCache struct {
@@ -16,7 +16,7 @@ type UuidsProviderWithCache struct {
} }
func (p *UuidsProviderWithCache) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) { func (p *UuidsProviderWithCache) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) {
uuid, foundUsername, err := p.Storage.GetUuidForMojangUsername(username) uuid, foundUsername, err := p.Storage.GetUuidForMojangUsername(ctx, username)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -41,7 +41,7 @@ func (p *UuidsProviderWithCache) GetUuid(ctx context.Context, username string) (
wellCasedUsername = profile.Name wellCasedUsername = profile.Name
} }
_ = p.Storage.StoreMojangUuid(wellCasedUsername, freshUuid) _ = p.Storage.StoreMojangUuid(ctx, wellCasedUsername, freshUuid)
return profile, nil return profile, nil
} }

View File

@@ -29,13 +29,13 @@ type MojangUuidsStorageMock struct {
mock.Mock mock.Mock
} }
func (m *MojangUuidsStorageMock) GetUuidForMojangUsername(username string) (string, string, error) { func (m *MojangUuidsStorageMock) GetUuidForMojangUsername(ctx context.Context, username string) (string, string, error) {
args := m.Called(username) args := m.Called(ctx, username)
return args.String(0), args.String(1), args.Error(2) return args.String(0), args.String(1), args.Error(2)
} }
func (m *MojangUuidsStorageMock) StoreMojangUuid(username string, uuid string) error { func (m *MojangUuidsStorageMock) StoreMojangUuid(ctx context.Context, username string, uuid string) error {
m.Called(username, uuid) m.Called(ctx, username, uuid)
return nil return nil
} }
@@ -64,8 +64,8 @@ func (s *UuidsProviderWithCacheSuite) TearDownTest() {
func (s *UuidsProviderWithCacheSuite) TestUncachedSuccessfully() { func (s *UuidsProviderWithCacheSuite) TestUncachedSuccessfully() {
ctx := context.Background() ctx := context.Background()
s.Storage.On("GetUuidForMojangUsername", "username").Return("", "", nil) s.Storage.On("GetUuidForMojangUsername", ctx, "username").Return("", "", nil)
s.Storage.On("StoreMojangUuid", "UserName", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil) s.Storage.On("StoreMojangUuid", ctx, "UserName", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil)
s.Original.On("GetUuid", ctx, "username").Once().Return(mockProfile, nil) s.Original.On("GetUuid", ctx, "username").Once().Return(mockProfile, nil)
@@ -76,8 +76,8 @@ func (s *UuidsProviderWithCacheSuite) TestUncachedSuccessfully() {
} }
func (s *UuidsProviderWithCacheSuite) TestUncachedNotExistsMojangUsername() { func (s *UuidsProviderWithCacheSuite) TestUncachedNotExistsMojangUsername() {
s.Storage.On("GetUuidForMojangUsername", "username").Return("", "", nil) s.Storage.On("GetUuidForMojangUsername", mock.Anything, "username").Return("", "", nil)
s.Storage.On("StoreMojangUuid", "username", "").Once().Return(nil) s.Storage.On("StoreMojangUuid", mock.Anything, "username", "").Once().Return(nil)
s.Original.On("GetUuid", mock.Anything, "username").Once().Return(nil, nil) s.Original.On("GetUuid", mock.Anything, "username").Once().Return(nil, nil)
@@ -88,7 +88,7 @@ func (s *UuidsProviderWithCacheSuite) TestUncachedNotExistsMojangUsername() {
} }
func (s *UuidsProviderWithCacheSuite) TestKnownCachedUsername() { func (s *UuidsProviderWithCacheSuite) TestKnownCachedUsername() {
s.Storage.On("GetUuidForMojangUsername", "username").Return("mock-uuid", "UserName", nil) s.Storage.On("GetUuidForMojangUsername", mock.Anything, "username").Return("mock-uuid", "UserName", nil)
result, err := s.Provider.GetUuid(context.Background(), "username") result, err := s.Provider.GetUuid(context.Background(), "username")
@@ -99,7 +99,7 @@ func (s *UuidsProviderWithCacheSuite) TestKnownCachedUsername() {
} }
func (s *UuidsProviderWithCacheSuite) TestUnknownCachedUsername() { func (s *UuidsProviderWithCacheSuite) TestUnknownCachedUsername() {
s.Storage.On("GetUuidForMojangUsername", "username").Return("", "UserName", nil) s.Storage.On("GetUuidForMojangUsername", mock.Anything, "username").Return("", "UserName", nil)
result, err := s.Provider.GetUuid(context.Background(), "username") result, err := s.Provider.GetUuid(context.Background(), "username")
@@ -109,7 +109,7 @@ func (s *UuidsProviderWithCacheSuite) TestUnknownCachedUsername() {
func (s *UuidsProviderWithCacheSuite) TestErrorDuringCacheQuery() { func (s *UuidsProviderWithCacheSuite) TestErrorDuringCacheQuery() {
expectedError := errors.New("mock error") expectedError := errors.New("mock error")
s.Storage.On("GetUuidForMojangUsername", "username").Return("", "", expectedError) s.Storage.On("GetUuidForMojangUsername", mock.Anything, "username").Return("", "", expectedError)
result, err := s.Provider.GetUuid(context.Background(), "username") result, err := s.Provider.GetUuid(context.Background(), "username")
@@ -119,7 +119,7 @@ func (s *UuidsProviderWithCacheSuite) TestErrorDuringCacheQuery() {
func (s *UuidsProviderWithCacheSuite) TestErrorFromOriginalProvider() { func (s *UuidsProviderWithCacheSuite) TestErrorFromOriginalProvider() {
expectedError := errors.New("mock error") expectedError := errors.New("mock error")
s.Storage.On("GetUuidForMojangUsername", "username").Return("", "", nil) s.Storage.On("GetUuidForMojangUsername", mock.Anything, "username").Return("", "", nil)
s.Original.On("GetUuid", mock.Anything, "username").Once().Return(nil, expectedError) s.Original.On("GetUuid", mock.Anything, "username").Once().Return(nil, expectedError)

View File

@@ -1,6 +1,7 @@
package profiles package profiles
import ( import (
"context"
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
@@ -11,9 +12,8 @@ import (
) )
type ProfilesRepository interface { type ProfilesRepository interface {
FindProfileByUuid(uuid string) (*db.Profile, error) SaveProfile(ctx context.Context, profile *db.Profile) error
SaveProfile(profile *db.Profile) error RemoveProfileByUuid(ctx context.Context, uuid string) error
RemoveProfileByUuid(uuid string) error
} }
func NewManager(pr ProfilesRepository) *Manager { func NewManager(pr ProfilesRepository) *Manager {
@@ -28,7 +28,7 @@ type Manager struct {
profileValidator *validator.Validate profileValidator *validator.Validate
} }
func (m *Manager) PersistProfile(profile *db.Profile) error { func (m *Manager) PersistProfile(ctx context.Context, profile *db.Profile) error {
validationErrors := m.profileValidator.Struct(profile) validationErrors := m.profileValidator.Struct(profile)
if validationErrors != nil { if validationErrors != nil {
return mapValidationErrorsToCommonError(validationErrors.(validator.ValidationErrors)) return mapValidationErrorsToCommonError(validationErrors.(validator.ValidationErrors))
@@ -39,11 +39,11 @@ func (m *Manager) PersistProfile(profile *db.Profile) error {
profile.SkinModel = "" profile.SkinModel = ""
} }
return m.ProfilesRepository.SaveProfile(profile) return m.ProfilesRepository.SaveProfile(ctx, profile)
} }
func (m *Manager) RemoveProfileByUuid(uuid string) error { func (m *Manager) RemoveProfileByUuid(ctx context.Context, uuid string) error {
return m.ProfilesRepository.RemoveProfileByUuid(cleanupUuid(uuid)) return m.ProfilesRepository.RemoveProfileByUuid(ctx, cleanupUuid(uuid))
} }
type ValidationError struct { type ValidationError struct {

View File

@@ -1,6 +1,7 @@
package profiles package profiles
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
@@ -13,22 +14,12 @@ type ProfilesRepositoryMock struct {
mock.Mock mock.Mock
} }
func (m *ProfilesRepositoryMock) FindProfileByUuid(uuid string) (*db.Profile, error) { func (m *ProfilesRepositoryMock) SaveProfile(ctx context.Context, profile *db.Profile) error {
args := m.Called(uuid) return m.Called(ctx, profile).Error(0)
var result *db.Profile
if casted, ok := args.Get(0).(*db.Profile); ok {
result = casted
}
return result, args.Error(1)
} }
func (m *ProfilesRepositoryMock) SaveProfile(profile *db.Profile) error { func (m *ProfilesRepositoryMock) RemoveProfileByUuid(ctx context.Context, uuid string) error {
return m.Called(profile).Error(0) return m.Called(ctx, uuid).Error(0)
}
func (m *ProfilesRepositoryMock) RemoveProfileByUuid(uuid string) error {
return m.Called(uuid).Error(0)
} }
type ManagerTestSuite struct { type ManagerTestSuite struct {
@@ -50,6 +41,7 @@ func (t *ManagerTestSuite) TearDownSubTest() {
func (t *ManagerTestSuite) TestPersistProfile() { func (t *ManagerTestSuite) TestPersistProfile() {
t.Run("valid profile (full)", func() { t.Run("valid profile (full)", func() {
ctx := context.Background()
profile := &db.Profile{ profile := &db.Profile{
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51", Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
Username: "mock-username", Username: "mock-username",
@@ -59,9 +51,9 @@ func (t *ManagerTestSuite) TestPersistProfile() {
MojangTextures: "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0=", MojangTextures: "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0=",
MojangSignature: "QH+1rlQJYk8tW+8WlSJnzxZZUL5RIkeOO33dq84cgNoxwCkzL95Zy5pbPMFhoiMXXablqXeqyNRZDQa+OewgDBSZxm0BmkNmwdTLzCPHgnlNYhwbO4sirg3hKjCZ82ORZ2q7VP2NQIwNvc3befiCakhDlMWUuhjxe7p/HKNtmKA7a/JjzmzwW7BWMv8b88ZaQaMaAc7puFQcu2E54G2Zk2kyv3T1Bm7bV4m7ymbL8McOmQc6Ph7C95/EyqIK1a5gRBUHPEFIEj0I06YKTHsCRFU1U/hJpk98xXHzHuULJobpajqYXuVJ8QEVgF8k8dn9VkS8BMbXcjzfbb6JJ36v7YIV6Rlt75wwTk2wr3C3P0ij55y0iXth1HjwcEKsg54n83d9w8yQbkUCiTpMbOqxTEOOS7G2O0ZDBJDXAKQ4n5qCiCXKZ4febv4+dWVQtgfZHnpGJUD3KdduDKslMePnECOXMjGSAOQou//yze2EkL2rBpJtAAiOtvBlm/aWnDZpij5cQk+pWmeHWZIf0LSSlsYRUWRDk/VKBvUTEAO9fqOxWqmSgQRUY2Ea56u0ZsBb4vEa1UY6mlJj3+PNZaWu5aP2E9Unh0DIawV96eW8eFQgenlNXHMmXd4aOra4sz2eeOnY53JnJP+eVE4cB1hlq8RA2mnwTtcy3lahzZonOWc=", MojangSignature: "QH+1rlQJYk8tW+8WlSJnzxZZUL5RIkeOO33dq84cgNoxwCkzL95Zy5pbPMFhoiMXXablqXeqyNRZDQa+OewgDBSZxm0BmkNmwdTLzCPHgnlNYhwbO4sirg3hKjCZ82ORZ2q7VP2NQIwNvc3befiCakhDlMWUuhjxe7p/HKNtmKA7a/JjzmzwW7BWMv8b88ZaQaMaAc7puFQcu2E54G2Zk2kyv3T1Bm7bV4m7ymbL8McOmQc6Ph7C95/EyqIK1a5gRBUHPEFIEj0I06YKTHsCRFU1U/hJpk98xXHzHuULJobpajqYXuVJ8QEVgF8k8dn9VkS8BMbXcjzfbb6JJ36v7YIV6Rlt75wwTk2wr3C3P0ij55y0iXth1HjwcEKsg54n83d9w8yQbkUCiTpMbOqxTEOOS7G2O0ZDBJDXAKQ4n5qCiCXKZ4febv4+dWVQtgfZHnpGJUD3KdduDKslMePnECOXMjGSAOQou//yze2EkL2rBpJtAAiOtvBlm/aWnDZpij5cQk+pWmeHWZIf0LSSlsYRUWRDk/VKBvUTEAO9fqOxWqmSgQRUY2Ea56u0ZsBb4vEa1UY6mlJj3+PNZaWu5aP2E9Unh0DIawV96eW8eFQgenlNXHMmXd4aOra4sz2eeOnY53JnJP+eVE4cB1hlq8RA2mnwTtcy3lahzZonOWc=",
} }
t.ProfilesRepository.On("SaveProfile", profile).Once().Return(nil) t.ProfilesRepository.On("SaveProfile", ctx, profile).Once().Return(nil)
err := t.Manager.PersistProfile(profile) err := t.Manager.PersistProfile(ctx, profile)
t.NoError(err) t.NoError(err)
}) })
@@ -70,9 +62,9 @@ func (t *ManagerTestSuite) TestPersistProfile() {
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51", Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
Username: "mock-username", Username: "mock-username",
} }
t.ProfilesRepository.On("SaveProfile", profile).Once().Return(nil) t.ProfilesRepository.On("SaveProfile", mock.Anything, profile).Once().Return(nil)
err := t.Manager.PersistProfile(profile) err := t.Manager.PersistProfile(context.Background(), profile)
t.NoError(err) t.NoError(err)
}) })
@@ -86,9 +78,9 @@ func (t *ManagerTestSuite) TestPersistProfile() {
expectedProfile := *profile expectedProfile := *profile
expectedProfile.Uuid = "ba866a9cc8394268a30f7b26ae604c51" expectedProfile.Uuid = "ba866a9cc8394268a30f7b26ae604c51"
expectedProfile.SkinModel = "" expectedProfile.SkinModel = ""
t.ProfilesRepository.On("SaveProfile", &expectedProfile).Once().Return(nil) t.ProfilesRepository.On("SaveProfile", mock.Anything, &expectedProfile).Once().Return(nil)
err := t.Manager.PersistProfile(profile) err := t.Manager.PersistProfile(context.Background(), profile)
t.NoError(err) t.NoError(err)
}) })
@@ -99,7 +91,7 @@ func (t *ManagerTestSuite) TestPersistProfile() {
MojangTextures: "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0=", MojangTextures: "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0=",
} }
err := t.Manager.PersistProfile(profile) err := t.Manager.PersistProfile(context.Background(), profile)
t.Error(err) t.Error(err)
t.IsType(&ValidationError{}, err) t.IsType(&ValidationError{}, err)
castedErr := err.(*ValidationError) castedErr := err.(*ValidationError)
@@ -114,7 +106,7 @@ func (t *ManagerTestSuite) TestPersistProfile() {
Username: "invalid\"username", Username: "invalid\"username",
} }
err := t.Manager.PersistProfile(profile) err := t.Manager.PersistProfile(context.Background(), profile)
t.Error(err) t.Error(err)
t.IsType(&ValidationError{}, err) t.IsType(&ValidationError{}, err)
castedErr := err.(*ValidationError) castedErr := err.(*ValidationError)
@@ -126,7 +118,7 @@ func (t *ManagerTestSuite) TestPersistProfile() {
t.Run("empty profile", func() { t.Run("empty profile", func() {
profile := &db.Profile{} profile := &db.Profile{}
err := t.Manager.PersistProfile(profile) err := t.Manager.PersistProfile(context.Background(), profile)
t.Error(err) t.Error(err)
t.IsType(&ValidationError{}, err) t.IsType(&ValidationError{}, err)
// TODO: validate errors // TODO: validate errors

View File

@@ -9,7 +9,7 @@ import (
) )
type ProfilesFinder interface { type ProfilesFinder interface {
FindProfileByUsername(username string) (*db.Profile, error) FindProfileByUsername(ctx context.Context, username string) (*db.Profile, error)
} }
type MojangProfilesProvider interface { type MojangProfilesProvider interface {
@@ -22,7 +22,7 @@ type Provider struct {
} }
func (p *Provider) FindProfileByUsername(ctx context.Context, username string, allowProxy bool) (*db.Profile, error) { func (p *Provider) FindProfileByUsername(ctx context.Context, username string, allowProxy bool) (*db.Profile, error) {
profile, err := p.ProfilesFinder.FindProfileByUsername(username) profile, err := p.ProfilesFinder.FindProfileByUsername(ctx, username)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -18,8 +18,8 @@ type ProfilesFinderMock struct {
mock.Mock mock.Mock
} }
func (m *ProfilesFinderMock) FindProfileByUsername(username string) (*db.Profile, error) { func (m *ProfilesFinderMock) FindProfileByUsername(ctx context.Context, username string) (*db.Profile, error) {
args := m.Called(username) args := m.Called(ctx, username)
var result *db.Profile var result *db.Profile
if casted, ok := args.Get(0).(*db.Profile); ok { if casted, ok := args.Get(0).(*db.Profile); ok {
result = casted result = casted
@@ -67,14 +67,15 @@ func (t *CombinedProfilesProviderSuite) TearDownSubTest() {
func (t *CombinedProfilesProviderSuite) TestFindByUsername() { func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
t.Run("exists profile with a skin", func() { t.Run("exists profile with a skin", func() {
ctx := context.Background()
profile := &db.Profile{ profile := &db.Profile{
Uuid: "mock-uuid", Uuid: "mock-uuid",
Username: "Mock", Username: "Mock",
SkinUrl: "https://example.com/skin.png", SkinUrl: "https://example.com/skin.png",
} }
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(profile, nil) t.ProfilesFinder.On("FindProfileByUsername", ctx, "Mock").Return(profile, nil)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(ctx, "Mock", true)
t.NoError(err) t.NoError(err)
t.Same(profile, foundProfile) t.Same(profile, foundProfile)
}) })
@@ -85,7 +86,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Username: "Mock", Username: "Mock",
CapeUrl: "https://example.com/cape.png", CapeUrl: "https://example.com/cape.png",
} }
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(profile, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(profile, nil)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
t.NoError(err) t.NoError(err)
@@ -97,7 +98,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Uuid: "mock-uuid", Uuid: "mock-uuid",
Username: "Mock", Username: "Mock",
} }
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(profile, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(profile, nil)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", false) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", false)
t.NoError(err) t.NoError(err)
@@ -105,7 +106,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
}) })
t.Run("not exists profile (no proxy)", func() { t.Run("not exists profile (no proxy)", func() {
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(nil, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(nil, nil)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", false) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", false)
t.NoError(err) t.NoError(err)
@@ -114,7 +115,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
t.Run("handle error from profiles repository", func() { t.Run("handle error from profiles repository", func() {
expectedError := errors.New("mock error") expectedError := errors.New("mock error")
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(nil, expectedError) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(nil, expectedError)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", false) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", false)
t.Same(expectedError, err) t.Same(expectedError, err)
@@ -128,7 +129,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
} }
mojangProfile := createMojangProfile(true, true) mojangProfile := createMojangProfile(true, true)
ctx := context.Background() ctx := context.Background()
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(profile, nil) t.ProfilesFinder.On("FindProfileByUsername", ctx, "Mock").Return(profile, nil)
t.MojangProfilesProvider.On("GetForUsername", ctx, "Mock").Return(mojangProfile, nil) t.MojangProfilesProvider.On("GetForUsername", ctx, "Mock").Return(mojangProfile, nil)
foundProfile, err := t.Provider.FindProfileByUsername(ctx, "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(ctx, "Mock", true)
@@ -146,7 +147,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
t.Run("not exists profile (with proxy)", func() { t.Run("not exists profile (with proxy)", func() {
mojangProfile := createMojangProfile(true, true) mojangProfile := createMojangProfile(true, true)
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(nil, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(nil, nil)
t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(mojangProfile, nil) t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(mojangProfile, nil)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
@@ -167,7 +168,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Uuid: "mock-uuid", Uuid: "mock-uuid",
Username: "Mock", Username: "Mock",
} }
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(profile, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(profile, nil)
t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(nil, errors.New("mock error")) t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(nil, errors.New("mock error"))
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
@@ -176,7 +177,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
}) })
t.Run("should not return an error when passed the invalid username", func() { t.Run("should not return an error when passed the invalid username", func() {
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(nil, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(nil, nil)
t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(nil, mojang.InvalidUsername) t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(nil, mojang.InvalidUsername)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
@@ -186,7 +187,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
t.Run("should return an error from mojang provider", func() { t.Run("should return an error from mojang provider", func() {
expectedError := errors.New("mock error") expectedError := errors.New("mock error")
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(nil, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(nil, nil)
t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(nil, expectedError) t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(nil, expectedError)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
@@ -204,7 +205,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
}, },
}, },
} }
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(nil, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(nil, nil)
t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(mojangProfile, nil) t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(mojangProfile, nil)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
@@ -218,7 +219,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Name: "mOcK", Name: "mOcK",
Props: []*mojang.Property{}, Props: []*mojang.Property{},
} }
t.ProfilesFinder.On("FindProfileByUsername", "Mock").Return(nil, nil) t.ProfilesFinder.On("FindProfileByUsername", mock.Anything, "Mock").Return(nil, nil)
t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(mojangProfile, nil) t.MojangProfilesProvider.On("GetForUsername", mock.Anything, "Mock").Return(mojangProfile, nil)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true) foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)

View File

@@ -1,6 +1,7 @@
package security package security
import ( import (
"context"
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
@@ -18,7 +19,7 @@ type Signer struct {
Key *rsa.PrivateKey Key *rsa.PrivateKey
} }
func (s *Signer) SignTextures(textures string) (string, error) { func (s *Signer) SignTextures(ctx context.Context, textures string) (string, error) {
message := []byte(textures) message := []byte(textures)
messageHash := sha1.New() messageHash := sha1.New()
_, err := messageHash.Write(message) _, err := messageHash.Write(message)
@@ -35,6 +36,6 @@ func (s *Signer) SignTextures(textures string) (string, error) {
return base64.StdEncoding.EncodeToString(signature), nil return base64.StdEncoding.EncodeToString(signature), nil
} }
func (s *Signer) GetPublicKey() (*rsa.PublicKey, error) { func (s *Signer) GetPublicKey(ctx context.Context) (*rsa.PublicKey, error) {
return &s.Key.PublicKey, nil return &s.Key.PublicKey, nil
} }

View File

@@ -1,6 +1,7 @@
package security package security
import ( import (
"context"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
@@ -24,7 +25,7 @@ func TestSigner_SignTextures(t *testing.T) {
signer := NewSigner(key) signer := NewSigner(key)
signature, err := signer.SignTextures("eyJ0aW1lc3RhbXAiOjE2MTQzMDcxMzQsInByb2ZpbGVJZCI6ImZmYzhmZGM5NTgyNDUwOWU4YTU3Yzk5Yjk0MGZiOTk2IiwicHJvZmlsZU5hbWUiOiJFcmlja1NrcmF1Y2giLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly9lbHkuYnkvc3RvcmFnZS9za2lucy82OWM2NzQwZDI5OTNlNWQ2ZjZhN2ZjOTI0MjBlZmMyOS5wbmcifX0sImVseSI6dHJ1ZX0") signature, err := signer.SignTextures(context.Background(), "eyJ0aW1lc3RhbXAiOjE2MTQzMDcxMzQsInByb2ZpbGVJZCI6ImZmYzhmZGM5NTgyNDUwOWU4YTU3Yzk5Yjk0MGZiOTk2IiwicHJvZmlsZU5hbWUiOiJFcmlja1NrcmF1Y2giLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly9lbHkuYnkvc3RvcmFnZS9za2lucy82OWM2NzQwZDI5OTNlNWQ2ZjZhN2ZjOTI0MjBlZmMyOS5wbmcifX0sImVseSI6dHJ1ZX0")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "IyHCxTP5ITquEXTHcwCtLd08jWWy16JwlQeWg8naxhoAVQecHGRdzHRscuxtdq/446kmeox7h4EfRN2A2ZLL+A==", signature) require.Equal(t, "IyHCxTP5ITquEXTHcwCtLd08jWWy16JwlQeWg8naxhoAVQecHGRdzHRscuxtdq/446kmeox7h4EfRN2A2ZLL+A==", signature)
} }
@@ -37,7 +38,7 @@ func TestSigner_GetPublicKey(t *testing.T) {
signer := NewSigner(key) signer := NewSigner(key)
publicKey, err := signer.GetPublicKey() publicKey, err := signer.GetPublicKey(context.Background())
require.NoError(t, err) require.NoError(t, err)
require.IsType(t, &rsa.PublicKey{}, publicKey) require.IsType(t, &rsa.PublicKey{}, publicKey)
} }