mirror of
https://github.com/elyby/chrly.git
synced 2025-05-31 14:11:51 +05:30
[BREAKING]
Introduce universal profile entity Remove fs-based capes serving Rework management API Rework Redis storage schema Reducing amount of the bus emitter usage
This commit is contained in:
122
internal/profiles/manager.go
Normal file
122
internal/profiles/manager.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package profiles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
||||
"github.com/elyby/chrly/db"
|
||||
)
|
||||
|
||||
type ProfilesRepository interface {
|
||||
FindProfileByUuid(uuid string) (*db.Profile, error)
|
||||
SaveProfile(profile *db.Profile) error
|
||||
RemoveProfileByUuid(uuid string) error
|
||||
}
|
||||
|
||||
func NewManager(pr ProfilesRepository) *Manager {
|
||||
return &Manager{
|
||||
ProfilesRepository: pr,
|
||||
profileValidator: createProfileValidator(),
|
||||
}
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
ProfilesRepository
|
||||
profileValidator *validator.Validate
|
||||
}
|
||||
|
||||
func (m *Manager) PersistProfile(profile *db.Profile) error {
|
||||
validationErrors := m.profileValidator.Struct(profile)
|
||||
if validationErrors != nil {
|
||||
return mapValidationErrorsToCommonError(validationErrors.(validator.ValidationErrors))
|
||||
}
|
||||
|
||||
profile.Uuid = cleanupUuid(profile.Uuid)
|
||||
if profile.SkinUrl == "" || isClassicModel(profile.SkinModel) {
|
||||
profile.SkinModel = ""
|
||||
}
|
||||
|
||||
return m.ProfilesRepository.SaveProfile(profile)
|
||||
}
|
||||
|
||||
func (m *Manager) RemoveProfileByUuid(uuid string) error {
|
||||
return m.ProfilesRepository.RemoveProfileByUuid(cleanupUuid(uuid))
|
||||
}
|
||||
|
||||
type ValidationError struct {
|
||||
Errors map[string][]string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return "The profile is invalid and cannot be persisted"
|
||||
}
|
||||
|
||||
func cleanupUuid(uuid string) string {
|
||||
return strings.ReplaceAll(strings.ToLower(uuid), "-", "")
|
||||
}
|
||||
|
||||
func createProfileValidator() *validator.Validate {
|
||||
validate := validator.New()
|
||||
|
||||
regexUuidAny := regexp.MustCompile("(?i)^[a-f0-9]{8}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{12}$")
|
||||
_ = validate.RegisterValidation("uuid_any", func(fl validator.FieldLevel) bool {
|
||||
return regexUuidAny.MatchString(fl.Field().String())
|
||||
})
|
||||
|
||||
regexUsername := regexp.MustCompile(`^[-\w.!$%^&*()\[\]:;]+$`)
|
||||
_ = validate.RegisterValidation("username", func(fl validator.FieldLevel) bool {
|
||||
return regexUsername.MatchString(fl.Field().String())
|
||||
})
|
||||
|
||||
validate.RegisterStructValidationMapRules(map[string]string{
|
||||
"Username": "required,username,max=21",
|
||||
"Uuid": "required,uuid_any",
|
||||
"SkinUrl": "omitempty,url",
|
||||
"SkinModel": "omitempty,max=20",
|
||||
"CapeUrl": "omitempty,url",
|
||||
"MojangTextures": "omitempty,base64",
|
||||
"MojangSignature": "required_with=MojangTextures,omitempty,base64",
|
||||
}, db.Profile{})
|
||||
|
||||
return validate
|
||||
}
|
||||
|
||||
func mapValidationErrorsToCommonError(err validator.ValidationErrors) *ValidationError {
|
||||
resultErr := &ValidationError{make(map[string][]string)}
|
||||
for _, e := range err {
|
||||
// Manager can return multiple errors per field, but the current validation implementation
|
||||
// returns only one error per field
|
||||
resultErr.Errors[e.Field()] = []string{formatValidationErr(e)}
|
||||
}
|
||||
|
||||
return resultErr
|
||||
}
|
||||
|
||||
// The go-playground/validator lib already contains tools for translated errors output.
|
||||
// However, the implementation is very heavy and becomes even more so when you need to add messages for custom validators.
|
||||
// So for simplicity, I've extracted validation error formatting into this simple implementation
|
||||
func formatValidationErr(err validator.FieldError) string {
|
||||
switch err.Tag() {
|
||||
case "required", "required_with":
|
||||
return fmt.Sprintf("%s is a required field", err.Field())
|
||||
case "username":
|
||||
return fmt.Sprintf("%s must be a valid username", err.Field())
|
||||
case "max":
|
||||
return fmt.Sprintf("%s must be a maximum of %s in length", err.Field(), err.Param())
|
||||
case "uuid_any":
|
||||
return fmt.Sprintf("%s must be a valid UUID", err.Field())
|
||||
case "url":
|
||||
return fmt.Sprintf("%s must be a valid URL", err.Field())
|
||||
case "base64":
|
||||
return fmt.Sprintf("%s must be a valid Base64 string", err.Field())
|
||||
default:
|
||||
return fmt.Sprintf(`Field validation for "%s" failed on the "%s" tag`, err.Field(), err.Tag())
|
||||
}
|
||||
}
|
||||
|
||||
func isClassicModel(model string) bool {
|
||||
return model == "" || model == "classic" || model == "default" || model == "steve"
|
||||
}
|
||||
138
internal/profiles/manager_test.go
Normal file
138
internal/profiles/manager_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package profiles
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/elyby/chrly/db"
|
||||
)
|
||||
|
||||
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(profile *db.Profile) error {
|
||||
return m.Called(profile).Error(0)
|
||||
}
|
||||
|
||||
func (m *ProfilesRepositoryMock) RemoveProfileByUuid(uuid string) error {
|
||||
return m.Called(uuid).Error(0)
|
||||
}
|
||||
|
||||
type ManagerTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
Manager *Manager
|
||||
|
||||
ProfilesRepository *ProfilesRepositoryMock
|
||||
}
|
||||
|
||||
func (t *ManagerTestSuite) SetupSubTest() {
|
||||
t.ProfilesRepository = &ProfilesRepositoryMock{}
|
||||
t.Manager = NewManager(t.ProfilesRepository)
|
||||
}
|
||||
|
||||
func (t *ManagerTestSuite) TearDownSubTest() {
|
||||
t.ProfilesRepository.AssertExpectations(t.T())
|
||||
}
|
||||
|
||||
func (t *ManagerTestSuite) TestPersistProfile() {
|
||||
t.Run("valid profile (full)", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
|
||||
Username: "mock-username",
|
||||
SkinUrl: "https://example.com/skin.png",
|
||||
SkinModel: "slim",
|
||||
CapeUrl: "https://example.com/cape.png",
|
||||
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)
|
||||
|
||||
err := t.Manager.PersistProfile(profile)
|
||||
t.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("valid profile (minimal)", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
|
||||
Username: "mock-username",
|
||||
}
|
||||
t.ProfilesRepository.On("SaveProfile", profile).Once().Return(nil)
|
||||
|
||||
err := t.Manager.PersistProfile(profile)
|
||||
t.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("normalize uuid and skin model", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "BA866A9C-C839-4268-A30F-7B26AE604C51",
|
||||
Username: "mock-username",
|
||||
SkinUrl: "https://example.com/skin.png",
|
||||
SkinModel: "default",
|
||||
}
|
||||
expectedProfile := *profile
|
||||
expectedProfile.Uuid = "ba866a9cc8394268a30f7b26ae604c51"
|
||||
expectedProfile.SkinModel = ""
|
||||
t.ProfilesRepository.On("SaveProfile", &expectedProfile).Once().Return(nil)
|
||||
|
||||
err := t.Manager.PersistProfile(profile)
|
||||
t.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("require mojangSignature when mojangTexturesProvided", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
|
||||
Username: "mock-username",
|
||||
MojangTextures: "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0=",
|
||||
}
|
||||
|
||||
err := t.Manager.PersistProfile(profile)
|
||||
t.Error(err)
|
||||
t.IsType(&ValidationError{}, err)
|
||||
castedErr := err.(*ValidationError)
|
||||
mojangSignatureErr, mojangSignatureErrExists := castedErr.Errors["MojangSignature"]
|
||||
t.True(mojangSignatureErrExists)
|
||||
t.Contains(mojangSignatureErr[0], "required")
|
||||
})
|
||||
|
||||
t.Run("validate username", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "ba866a9c-c839-4268-a30f-7b26ae604c51",
|
||||
Username: "invalid\"username",
|
||||
}
|
||||
|
||||
err := t.Manager.PersistProfile(profile)
|
||||
t.Error(err)
|
||||
t.IsType(&ValidationError{}, err)
|
||||
castedErr := err.(*ValidationError)
|
||||
usernameErrs, usernameErrExists := castedErr.Errors["Username"]
|
||||
t.True(usernameErrExists)
|
||||
t.Contains(usernameErrs[0], "valid")
|
||||
})
|
||||
|
||||
t.Run("empty profile", func() {
|
||||
profile := &db.Profile{}
|
||||
|
||||
err := t.Manager.PersistProfile(profile)
|
||||
t.Error(err)
|
||||
t.IsType(&ValidationError{}, err)
|
||||
// TODO: validate errors
|
||||
})
|
||||
}
|
||||
|
||||
func TestManager(t *testing.T) {
|
||||
suite.Run(t, new(ManagerTestSuite))
|
||||
}
|
||||
88
internal/profiles/provider.go
Normal file
88
internal/profiles/provider.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package profiles
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/elyby/chrly/db"
|
||||
"github.com/elyby/chrly/mojang"
|
||||
)
|
||||
|
||||
type ProfilesFinder interface {
|
||||
FindProfileByUsername(username string) (*db.Profile, error)
|
||||
}
|
||||
|
||||
type MojangProfilesProvider interface {
|
||||
GetForUsername(username string) (*mojang.ProfileResponse, error)
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
ProfilesFinder
|
||||
MojangProfilesProvider
|
||||
}
|
||||
|
||||
func (p *Provider) FindProfileByUsername(username string, allowProxy bool) (*db.Profile, error) {
|
||||
profile, err := p.ProfilesFinder.FindProfileByUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if profile != nil && (profile.SkinUrl != "" || profile.CapeUrl != "") {
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
if allowProxy {
|
||||
mojangProfile, err := p.MojangProfilesProvider.GetForUsername(username)
|
||||
// If we at least know something about the user,
|
||||
// then we can ignore an error and return profile without textures
|
||||
if err != nil && profile != nil {
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
if err != nil || mojangProfile == nil {
|
||||
if errors.Is(err, mojang.InvalidUsername) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodedTextures, err := mojangProfile.DecodeTextures()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profile = &db.Profile{
|
||||
Uuid: mojangProfile.Id,
|
||||
Username: mojangProfile.Name,
|
||||
}
|
||||
|
||||
// There might be no textures property
|
||||
if decodedTextures != nil {
|
||||
if decodedTextures.Textures.Skin != nil {
|
||||
profile.SkinUrl = decodedTextures.Textures.Skin.Url
|
||||
if decodedTextures.Textures.Skin.Metadata != nil {
|
||||
profile.SkinModel = decodedTextures.Textures.Skin.Metadata.Model
|
||||
}
|
||||
}
|
||||
|
||||
if decodedTextures.Textures.Cape != nil {
|
||||
profile.CapeUrl = decodedTextures.Textures.Cape.Url
|
||||
}
|
||||
}
|
||||
|
||||
var texturesProp *mojang.Property
|
||||
for _, prop := range mojangProfile.Props {
|
||||
if prop.Name == "textures" {
|
||||
texturesProp = prop
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if texturesProp != nil {
|
||||
profile.MojangTextures = texturesProp.Value
|
||||
profile.MojangSignature = texturesProp.Signature
|
||||
}
|
||||
}
|
||||
|
||||
return profile, nil
|
||||
}
|
||||
272
internal/profiles/provider_test.go
Normal file
272
internal/profiles/provider_test.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package profiles
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/elyby/chrly/db"
|
||||
"github.com/elyby/chrly/mojang"
|
||||
"github.com/elyby/chrly/utils"
|
||||
)
|
||||
|
||||
type ProfilesFinderMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *ProfilesFinderMock) FindProfileByUsername(username string) (*db.Profile, error) {
|
||||
args := m.Called(username)
|
||||
var result *db.Profile
|
||||
if casted, ok := args.Get(0).(*db.Profile); ok {
|
||||
result = casted
|
||||
}
|
||||
|
||||
return result, args.Error(1)
|
||||
}
|
||||
|
||||
type MojangProfilesProviderMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MojangProfilesProviderMock) GetForUsername(username string) (*mojang.ProfileResponse, error) {
|
||||
args := m.Called(username)
|
||||
var result *mojang.ProfileResponse
|
||||
if casted, ok := args.Get(0).(*mojang.ProfileResponse); ok {
|
||||
result = casted
|
||||
}
|
||||
|
||||
return result, args.Error(1)
|
||||
}
|
||||
|
||||
type CombinedProfilesProviderSuite struct {
|
||||
suite.Suite
|
||||
|
||||
Provider *Provider
|
||||
|
||||
ProfilesRepository *ProfilesFinderMock
|
||||
MojangProfilesProvider *MojangProfilesProviderMock
|
||||
}
|
||||
|
||||
func (t *CombinedProfilesProviderSuite) SetupSubTest() {
|
||||
t.ProfilesRepository = &ProfilesFinderMock{}
|
||||
t.MojangProfilesProvider = &MojangProfilesProviderMock{}
|
||||
t.Provider = &Provider{
|
||||
ProfilesFinder: t.ProfilesRepository,
|
||||
MojangProfilesProvider: t.MojangProfilesProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CombinedProfilesProviderSuite) TearDownSubTest() {
|
||||
t.ProfilesRepository.AssertExpectations(t.T())
|
||||
t.MojangProfilesProvider.AssertExpectations(t.T())
|
||||
}
|
||||
|
||||
func (t *CombinedProfilesProviderSuite) TestFindByUsername() {
|
||||
t.Run("exists profile with a skin", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "mock-uuid",
|
||||
Username: "Mock",
|
||||
SkinUrl: "https://example.com/skin.png",
|
||||
}
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(profile, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.NoError(err)
|
||||
t.Same(profile, foundProfile)
|
||||
})
|
||||
|
||||
t.Run("exists profile with a cape", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "mock-uuid",
|
||||
Username: "Mock",
|
||||
CapeUrl: "https://example.com/cape.png",
|
||||
}
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(profile, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.NoError(err)
|
||||
t.Same(profile, foundProfile)
|
||||
})
|
||||
|
||||
t.Run("exists profile without textures (no proxy)", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "mock-uuid",
|
||||
Username: "Mock",
|
||||
}
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(profile, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", false)
|
||||
t.NoError(err)
|
||||
t.Same(profile, foundProfile)
|
||||
})
|
||||
|
||||
t.Run("not exists profile (no proxy)", func() {
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(nil, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", false)
|
||||
t.NoError(err)
|
||||
t.Nil(foundProfile)
|
||||
})
|
||||
|
||||
t.Run("handle error from profiles repository", func() {
|
||||
expectedError := errors.New("mock error")
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(nil, expectedError)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", false)
|
||||
t.Same(expectedError, err)
|
||||
t.Nil(foundProfile)
|
||||
})
|
||||
|
||||
t.Run("exists profile without textures (with proxy)", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "mock-uuid",
|
||||
Username: "Mock",
|
||||
}
|
||||
mojangProfile := createMojangProfile(true, true)
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(profile, nil)
|
||||
t.MojangProfilesProvider.On("GetForUsername", "Mock").Return(mojangProfile, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.NoError(err)
|
||||
t.Equal(&db.Profile{
|
||||
Uuid: "mock-mojang-uuid",
|
||||
Username: "mOcK",
|
||||
SkinUrl: "https://mojang/skin.png",
|
||||
SkinModel: "slim",
|
||||
CapeUrl: "https://mojang/cape.png",
|
||||
MojangTextures: mojangProfile.Props[0].Value,
|
||||
MojangSignature: mojangProfile.Props[0].Signature,
|
||||
}, foundProfile)
|
||||
})
|
||||
|
||||
t.Run("not exists profile (with proxy)", func() {
|
||||
mojangProfile := createMojangProfile(true, true)
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(nil, nil)
|
||||
t.MojangProfilesProvider.On("GetForUsername", "Mock").Return(mojangProfile, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.NoError(err)
|
||||
t.Equal(&db.Profile{
|
||||
Uuid: "mock-mojang-uuid",
|
||||
Username: "mOcK",
|
||||
SkinUrl: "https://mojang/skin.png",
|
||||
SkinModel: "slim",
|
||||
CapeUrl: "https://mojang/cape.png",
|
||||
MojangTextures: mojangProfile.Props[0].Value,
|
||||
MojangSignature: mojangProfile.Props[0].Signature,
|
||||
}, foundProfile)
|
||||
})
|
||||
|
||||
t.Run("should return known profile without textures when received an error from the mojang", func() {
|
||||
profile := &db.Profile{
|
||||
Uuid: "mock-uuid",
|
||||
Username: "Mock",
|
||||
}
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(profile, nil)
|
||||
t.MojangProfilesProvider.On("GetForUsername", "Mock").Return(nil, errors.New("mock error"))
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.NoError(err)
|
||||
t.Same(profile, foundProfile)
|
||||
})
|
||||
|
||||
t.Run("should not return an error when passed the invalid username", func() {
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(nil, nil)
|
||||
t.MojangProfilesProvider.On("GetForUsername", "Mock").Return(nil, mojang.InvalidUsername)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.NoError(err)
|
||||
t.Nil(foundProfile)
|
||||
})
|
||||
|
||||
t.Run("should return an error from mojang provider", func() {
|
||||
expectedError := errors.New("mock error")
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(nil, nil)
|
||||
t.MojangProfilesProvider.On("GetForUsername", "Mock").Return(nil, expectedError)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.Same(expectedError, err)
|
||||
t.Nil(foundProfile)
|
||||
})
|
||||
|
||||
t.Run("should correctly handle invalid textures from mojang", func() {
|
||||
mojangProfile := &mojang.ProfileResponse{
|
||||
Props: []*mojang.Property{
|
||||
{
|
||||
Name: "textures",
|
||||
Value: "this is invalid base64",
|
||||
Signature: "mojang signature",
|
||||
},
|
||||
},
|
||||
}
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(nil, nil)
|
||||
t.MojangProfilesProvider.On("GetForUsername", "Mock").Return(mojangProfile, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.ErrorContains(err, "illegal base64 data")
|
||||
t.Nil(foundProfile)
|
||||
})
|
||||
|
||||
t.Run("should correctly handle missing textures property from Mojang", func() {
|
||||
mojangProfile := &mojang.ProfileResponse{
|
||||
Id: "mock-mojang-uuid",
|
||||
Name: "mOcK",
|
||||
Props: []*mojang.Property{},
|
||||
}
|
||||
t.ProfilesRepository.On("FindProfileByUsername", "Mock").Return(nil, nil)
|
||||
t.MojangProfilesProvider.On("GetForUsername", "Mock").Return(mojangProfile, nil)
|
||||
|
||||
foundProfile, err := t.Provider.FindProfileByUsername("Mock", true)
|
||||
t.NoError(err)
|
||||
t.Equal(&db.Profile{
|
||||
Uuid: "mock-mojang-uuid",
|
||||
Username: "mOcK",
|
||||
}, foundProfile)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
suite.Run(t, new(CombinedProfilesProviderSuite))
|
||||
}
|
||||
|
||||
func createMojangProfile(withSkin bool, withCape bool) *mojang.ProfileResponse {
|
||||
timeZone, _ := time.LoadLocation("Europe/Warsaw")
|
||||
textures := &mojang.TexturesProp{
|
||||
Timestamp: utils.UnixMillisecond(time.Date(2024, 1, 29, 13, 34, 12, 0, timeZone)),
|
||||
ProfileID: "mock-mojang-uuid",
|
||||
ProfileName: "mOcK",
|
||||
Textures: &mojang.TexturesResponse{},
|
||||
}
|
||||
|
||||
if withSkin {
|
||||
textures.Textures.Skin = &mojang.SkinTexturesResponse{
|
||||
Url: "https://mojang/skin.png",
|
||||
Metadata: &mojang.SkinTexturesMetadata{
|
||||
Model: "slim",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if withCape {
|
||||
textures.Textures.Cape = &mojang.CapeTexturesResponse{
|
||||
Url: "https://mojang/cape.png",
|
||||
}
|
||||
}
|
||||
|
||||
response := &mojang.ProfileResponse{
|
||||
Id: textures.ProfileID,
|
||||
Name: textures.ProfileName,
|
||||
Props: []*mojang.Property{
|
||||
{
|
||||
Name: "textures",
|
||||
Value: mojang.EncodeTextures(textures),
|
||||
Signature: "mojang signature",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
Reference in New Issue
Block a user