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
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
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/mediocregopher/radix/v4 v4.1.4
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/viper v1.18.1
github.com/valyala/fastjson v1.6.4

View File

@ -15,7 +15,6 @@ const userUuidToUsernameKey = "hash:uuid-to-username"
type Redis struct {
client radix.Client
context context.Context
serializer db.ProfileSerializer
}
@ -27,14 +26,13 @@ func New(ctx context.Context, profileSerializer db.ProfileSerializer, addr strin
return &Redis{
client: client,
context: ctx,
serializer: profileSerializer,
}, 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
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
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)
}
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) {
var username string
return username, conn.Do(ctx, radix.FlatCmd(&username, "HGET", userUuidToUsernameKey, normalizeUuid(uuid)))
}
func (r *Redis) SaveProfile(profile *db.Profile) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
func (r *Redis) SaveProfile(ctx context.Context, profile *db.Profile) error {
return r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
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
}
func (r *Redis) RemoveProfileByUuid(uuid string) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
func (r *Redis) RemoveProfileByUuid(ctx context.Context, uuid string) error {
return r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
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"))
}
func (r *Redis) GetUuidForMojangUsername(username string) (string, string, error) {
func (r *Redis) GetUuidForMojangUsername(ctx context.Context, username string) (string, string, error) {
var uuid string
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
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
}
func (r *Redis) StoreMojangUuid(username string, uuid string) error {
return r.client.Do(r.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
func (r *Redis) StoreMojangUuid(ctx context.Context, username string, uuid string) error {
return r.client.Do(ctx, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
return storeMojangUuid(ctx, conn, username, uuid)
}))
}

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package http
import (
"bytes"
"context"
"errors"
"io"
"net/http"
@ -21,12 +22,12 @@ type ProfilesManagerMock struct {
mock.Mock
}
func (m *ProfilesManagerMock) PersistProfile(profile *db.Profile) error {
return m.Called(profile).Error(0)
func (m *ProfilesManagerMock) PersistProfile(ctx context.Context, profile *db.Profile) error {
return m.Called(ctx, profile).Error(0)
}
func (m *ProfilesManagerMock) RemoveProfileByUuid(uuid string) error {
return m.Called(uuid).Error(0)
func (m *ProfilesManagerMock) RemoveProfileByUuid(ctx context.Context, uuid string) error {
return m.Called(ctx, uuid).Error(0)
}
type ApiTestSuite struct {
@ -50,7 +51,7 @@ func (t *ApiTestSuite) TearDownSubTest() {
func (t *ApiTestSuite) TestPostProfile() {
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",
Username: "mock_username",
SkinUrl: "https://example.com/skin.png",
@ -100,7 +101,7 @@ func (t *ApiTestSuite) TestPostProfile() {
})
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{
"mock": {"error1", "error2"},
},
@ -126,7 +127,7 @@ func (t *ApiTestSuite) TestPostProfile() {
})
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.Header.Add("Content-Type", "application/x-www-form-urlencoded")
@ -141,7 +142,7 @@ func (t *ApiTestSuite) TestPostProfile() {
func (t *ApiTestSuite) TestDeleteProfileByUuid() {
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)
w := httptest.NewRecorder()
@ -155,7 +156,7 @@ func (t *ApiTestSuite) TestDeleteProfileByUuid() {
})
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)
w := httptest.NewRecorder()

View File

@ -25,9 +25,10 @@ type ProfilesProvider interface {
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 {
SignTextures(textures string) (string, error)
GetPublicKey() (*rsa.PublicKey, error)
SignTextures(ctx context.Context, textures string) (string, error)
GetPublicKey(ctx context.Context) (*rsa.PublicKey, error)
}
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")) {
signature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value)
signature, err := ctx.TexturesSigner.SignTextures(request.Context(), texturesProp.Value)
if err != nil {
apiServerError(response, fmt.Errorf("unable to sign textures: %w", err))
return
@ -229,7 +230,7 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
}
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 {
panic(err)
}

View File

@ -38,13 +38,13 @@ type TexturesSignerMock struct {
mock.Mock
}
func (m *TexturesSignerMock) SignTextures(textures string) (string, error) {
args := m.Called(textures)
func (m *TexturesSignerMock) SignTextures(ctx context.Context, textures string) (string, error) {
args := m.Called(ctx, textures)
return args.String(0), args.Error(1)
}
func (m *TexturesSignerMock) GetPublicKey() (*rsa.PublicKey, error) {
args := m.Called()
func (m *TexturesSignerMock) GetPublicKey(ctx context.Context) (*rsa.PublicKey, error) {
args := m.Called(ctx)
var publicKey *rsa.PublicKey
if casted, ok := args.Get(0).(*rsa.PublicKey); ok {
publicKey = casted
@ -470,7 +470,7 @@ func (t *SkinsystemTestSuite) TestProfile() {
SkinUrl: "https://example.com/skin.png",
SkinModel: "slim",
}, 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)
@ -526,7 +526,7 @@ func (t *SkinsystemTestSuite) TestProfile() {
w := httptest.NewRecorder()
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)
@ -551,7 +551,7 @@ var signingKeyTestsCases = []*signingKeyTestCase{
pubPem, _ := pem.Decode([]byte("-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnm\nUDlzHBQH3DpYef5WCO32TDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQ==\n-----END PUBLIC KEY-----"))
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) {
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-----"))
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) {
suite.Equal(200, response.StatusCode)
@ -582,7 +582,7 @@ var signingKeyTestsCases = []*signingKeyTestCase{
Name: "Error while obtaining public key",
KeyFormat: "DER",
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",
},

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package mojang
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
@ -43,9 +44,9 @@ func NewMojangApi(
// Exchanges usernames array to array of 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)
request, err := http.NewRequest("POST", c.batchUuidsUrl, bytes.NewBuffer(requestBody))
request, err := http.NewRequestWithContext(ctx, "POST", c.batchUuidsUrl, bytes.NewBuffer(requestBody))
if err != nil {
return nil, err
}
@ -75,14 +76,14 @@ func (c *MojangApi) UsernamesToUuids(usernames []string) ([]*ProfileInfo, error)
// Obtains textures information for provided uuid
// 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, "-", "")
url := c.profileUrl + normalizedUuid
if signed {
url += "?unsigned=false"
}
request, err := http.NewRequest("GET", url, nil)
request, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}

View File

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

View File

@ -9,11 +9,11 @@ import (
)
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) {
return p.MojangApiTexturesEndpoint(uuid, true)
return p.MojangApiTexturesEndpoint(ctx, uuid, true)
}
// Perfectly there should be an object with provider and cache implementation,

View File

@ -34,8 +34,8 @@ type MojangUuidToTexturesRequestMock struct {
mock.Mock
}
func (m *MojangUuidToTexturesRequestMock) UuidToTextures(uuid string, signed bool) (*ProfileResponse, error) {
args := m.Called(uuid, signed)
func (m *MojangUuidToTexturesRequestMock) UuidToTextures(ctx context.Context, uuid string, signed bool) (*ProfileResponse, error) {
args := m.Called(ctx, uuid, signed)
var result *ProfileResponse
if casted, ok := args.Get(0).(*ProfileResponse); ok {
result = casted
@ -63,9 +63,11 @@ func (s *MojangApiTexturesProviderSuite) TearDownTest() {
}
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().Equal(signedTexturesResponse, result)
@ -73,7 +75,7 @@ func (s *MojangApiTexturesProviderSuite) TestGetTextures() {
func (s *MojangApiTexturesProviderSuite) TestGetTexturesWithError() {
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")

View File

@ -5,9 +5,9 @@ import "context"
type MojangUuidsStorage interface {
// 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
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
StoreMojangUuid(username string, uuid string) error
StoreMojangUuid(ctx context.Context, username string, uuid string) error
}
type UuidsProviderWithCache struct {
@ -16,7 +16,7 @@ type UuidsProviderWithCache struct {
}
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 {
return nil, err
}
@ -41,7 +41,7 @@ func (p *UuidsProviderWithCache) GetUuid(ctx context.Context, username string) (
wellCasedUsername = profile.Name
}
_ = p.Storage.StoreMojangUuid(wellCasedUsername, freshUuid)
_ = p.Storage.StoreMojangUuid(ctx, wellCasedUsername, freshUuid)
return profile, nil
}

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package profiles
import (
"context"
"testing"
"github.com/stretchr/testify/mock"
@ -13,22 +14,12 @@ type ProfilesRepositoryMock struct {
mock.Mock
}
func (m *ProfilesRepositoryMock) FindProfileByUuid(uuid string) (*db.Profile, error) {
args := m.Called(uuid)
var result *db.Profile
if casted, ok := args.Get(0).(*db.Profile); ok {
result = casted
}
return result, args.Error(1)
func (m *ProfilesRepositoryMock) SaveProfile(ctx context.Context, profile *db.Profile) error {
return m.Called(ctx, profile).Error(0)
}
func (m *ProfilesRepositoryMock) SaveProfile(profile *db.Profile) error {
return m.Called(profile).Error(0)
}
func (m *ProfilesRepositoryMock) RemoveProfileByUuid(uuid string) error {
return m.Called(uuid).Error(0)
func (m *ProfilesRepositoryMock) RemoveProfileByUuid(ctx context.Context, uuid string) error {
return m.Called(ctx, uuid).Error(0)
}
type ManagerTestSuite struct {
@ -50,6 +41,7 @@ func (t *ManagerTestSuite) TearDownSubTest() {
func (t *ManagerTestSuite) TestPersistProfile() {
t.Run("valid profile (full)", func() {
ctx := context.Background()
profile := &db.Profile{
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
Username: "mock-username",
@ -59,9 +51,9 @@ func (t *ManagerTestSuite) TestPersistProfile() {
MojangTextures: "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0=",
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)
})
@ -70,9 +62,9 @@ func (t *ManagerTestSuite) TestPersistProfile() {
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
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)
})
@ -86,9 +78,9 @@ func (t *ManagerTestSuite) TestPersistProfile() {
expectedProfile := *profile
expectedProfile.Uuid = "ba866a9cc8394268a30f7b26ae604c51"
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)
})
@ -99,7 +91,7 @@ func (t *ManagerTestSuite) TestPersistProfile() {
MojangTextures: "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0=",
}
err := t.Manager.PersistProfile(profile)
err := t.Manager.PersistProfile(context.Background(), profile)
t.Error(err)
t.IsType(&ValidationError{}, err)
castedErr := err.(*ValidationError)
@ -114,7 +106,7 @@ func (t *ManagerTestSuite) TestPersistProfile() {
Username: "invalid\"username",
}
err := t.Manager.PersistProfile(profile)
err := t.Manager.PersistProfile(context.Background(), profile)
t.Error(err)
t.IsType(&ValidationError{}, err)
castedErr := err.(*ValidationError)
@ -126,7 +118,7 @@ func (t *ManagerTestSuite) TestPersistProfile() {
t.Run("empty profile", func() {
profile := &db.Profile{}
err := t.Manager.PersistProfile(profile)
err := t.Manager.PersistProfile(context.Background(), profile)
t.Error(err)
t.IsType(&ValidationError{}, err)
// TODO: validate errors

View File

@ -9,7 +9,7 @@ import (
)
type ProfilesFinder interface {
FindProfileByUsername(username string) (*db.Profile, error)
FindProfileByUsername(ctx context.Context, username string) (*db.Profile, error)
}
type MojangProfilesProvider interface {
@ -22,7 +22,7 @@ type Provider struct {
}
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 {
return nil, err
}

View File

@ -18,8 +18,8 @@ type ProfilesFinderMock struct {
mock.Mock
}
func (m *ProfilesFinderMock) FindProfileByUsername(username string) (*db.Profile, error) {
args := m.Called(username)
func (m *ProfilesFinderMock) FindProfileByUsername(ctx context.Context, username string) (*db.Profile, error) {
args := m.Called(ctx, username)
var result *db.Profile
if casted, ok := args.Get(0).(*db.Profile); ok {
result = casted
@ -67,14 +67,15 @@ func (t *CombinedProfilesProviderSuite) TearDownSubTest() {
func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
t.Run("exists profile with a skin", func() {
ctx := context.Background()
profile := &db.Profile{
Uuid: "mock-uuid",
Username: "Mock",
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.Same(profile, foundProfile)
})
@ -85,7 +86,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Username: "Mock",
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)
t.NoError(err)
@ -97,7 +98,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Uuid: "mock-uuid",
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)
t.NoError(err)
@ -105,7 +106,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
})
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)
t.NoError(err)
@ -114,7 +115,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
t.Run("handle error from profiles repository", func() {
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)
t.Same(expectedError, err)
@ -128,7 +129,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
}
mojangProfile := createMojangProfile(true, true)
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)
foundProfile, err := t.Provider.FindProfileByUsername(ctx, "Mock", true)
@ -146,7 +147,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
t.Run("not exists profile (with proxy)", func() {
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)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
@ -167,7 +168,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Uuid: "mock-uuid",
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"))
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.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)
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() {
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)
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)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)
@ -218,7 +219,7 @@ func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
Name: "mOcK",
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)
foundProfile, err := t.Provider.FindProfileByUsername(context.Background(), "Mock", true)

View File

@ -1,6 +1,7 @@
package security
import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
@ -18,7 +19,7 @@ type Signer struct {
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)
messageHash := sha1.New()
_, err := messageHash.Write(message)
@ -35,6 +36,6 @@ func (s *Signer) SignTextures(textures string) (string, error) {
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
}

View File

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