mirror of
				https://github.com/elyby/chrly.git
				synced 2025-05-31 14:11:51 +05:30 
			
		
		
		
	Make Mojang profiles provider cancellable
This commit is contained in:
		| @@ -1,6 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| @@ -39,6 +40,7 @@ func NewBatchUuidsProvider( | ||||
|  | ||||
| type job struct { | ||||
| 	Username   string | ||||
| 	Ctx        context.Context | ||||
| 	ResultChan chan<- *jobResult | ||||
| } | ||||
|  | ||||
| @@ -47,43 +49,68 @@ type jobResult struct { | ||||
| 	Error   error | ||||
| } | ||||
|  | ||||
| func (ctx *BatchUuidsProvider) GetUuid(username string) (*ProfileInfo, error) { | ||||
| func (p *BatchUuidsProvider) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) { | ||||
| 	resultChan := make(chan *jobResult) | ||||
| 	n := ctx.queue.Enqueue(&job{username, resultChan}) | ||||
| 	if ctx.fireOnFull && n%ctx.batch == 0 { | ||||
| 		ctx.fireChan <- struct{}{} | ||||
| 	n := p.queue.Enqueue(&job{username, ctx, resultChan}) | ||||
| 	if p.fireOnFull && n%p.batch == 0 { | ||||
| 		p.fireChan <- struct{}{} | ||||
| 	} | ||||
|  | ||||
| 	ctx.onFirstCall.Do(ctx.startQueue) | ||||
| 	p.onFirstCall.Do(p.startQueue) | ||||
|  | ||||
| 	result := <-resultChan | ||||
|  | ||||
| 	return result.Profile, result.Error | ||||
| 	select { | ||||
| 	case <-ctx.Done(): | ||||
| 		return nil, ctx.Err() | ||||
| 	case result := <-resultChan: | ||||
| 		return result.Profile, result.Error | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ctx *BatchUuidsProvider) StopQueue() { | ||||
| 	close(ctx.stopChan) | ||||
| func (p *BatchUuidsProvider) StopQueue() { | ||||
| 	close(p.stopChan) | ||||
| } | ||||
|  | ||||
| func (ctx *BatchUuidsProvider) startQueue() { | ||||
| func (p *BatchUuidsProvider) startQueue() { | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			t := time.NewTimer(ctx.delay) | ||||
| 			t := time.NewTimer(p.delay) | ||||
| 			select { | ||||
| 			case <-ctx.stopChan: | ||||
| 			case <-p.stopChan: | ||||
| 				return | ||||
| 			case <-t.C: | ||||
| 				go ctx.fireRequest() | ||||
| 			case <-ctx.fireChan: | ||||
| 				go p.fireRequest() | ||||
| 			case <-p.fireChan: | ||||
| 				t.Stop() | ||||
| 				go ctx.fireRequest() | ||||
| 				go p.fireRequest() | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| func (ctx *BatchUuidsProvider) fireRequest() { | ||||
| 	jobs, _ := ctx.queue.Dequeue(ctx.batch) | ||||
| func (p *BatchUuidsProvider) fireRequest() { | ||||
| 	jobs := make([]*job, 0, p.batch) | ||||
| 	n := p.batch | ||||
| 	for { | ||||
| 		foundJobs, left := p.queue.Dequeue(n) | ||||
| 		for i := range foundJobs { | ||||
| 			if foundJobs[i].Ctx.Err() != nil { | ||||
| 				// If the job context has already ended, its result will be returned in the GetUuid method | ||||
| 				close(foundJobs[i].ResultChan) | ||||
|  | ||||
| 				foundJobs[i] = foundJobs[len(foundJobs)-1] | ||||
| 				foundJobs = foundJobs[:len(foundJobs)-1] | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		jobs = append(jobs, foundJobs...) | ||||
| 		if len(jobs) != p.batch && left != 0 { | ||||
| 			n = p.batch - len(jobs) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	if len(jobs) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| @@ -93,7 +120,7 @@ func (ctx *BatchUuidsProvider) fireRequest() { | ||||
| 		usernames[i] = job.Username | ||||
| 	} | ||||
|  | ||||
| 	profiles, err := ctx.UsernamesToUuidsEndpoint(usernames) | ||||
| 	profiles, err := p.UsernamesToUuidsEndpoint(usernames) | ||||
| 	for _, job := range jobs { | ||||
| 		response := &jobResult{} | ||||
| 		if err == nil { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @@ -54,11 +55,15 @@ func (s *batchUuidsProviderTestSuite) TearDownTest() { | ||||
| } | ||||
|  | ||||
| func (s *batchUuidsProviderTestSuite) GetUuidAsync(username string) <-chan *batchUuidsProviderGetUuidResult { | ||||
| 	return s.GetUuidAsyncWithCtx(context.Background(), username) | ||||
| } | ||||
|  | ||||
| func (s *batchUuidsProviderTestSuite) GetUuidAsyncWithCtx(ctx context.Context, username string) <-chan *batchUuidsProviderGetUuidResult { | ||||
| 	startedChan := make(chan any) | ||||
| 	c := make(chan *batchUuidsProviderGetUuidResult, 1) | ||||
| 	go func() { | ||||
| 		close(startedChan) | ||||
| 		profile, err := s.Provider.GetUuid(username) | ||||
| 		profile, err := s.Provider.GetUuid(ctx, username) | ||||
| 		c <- &batchUuidsProviderGetUuidResult{ | ||||
| 			Result: profile, | ||||
| 			Error:  err, | ||||
| @@ -125,6 +130,38 @@ func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesSplitByMultiple | ||||
| 	s.Require().NotEmpty(resultChan4) | ||||
| } | ||||
|  | ||||
| func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesWhenOneOfContextIsDeadlined() { | ||||
| 	var emptyResponse []string | ||||
|  | ||||
| 	s.MojangApi.On("UsernamesToUuids", []string{"username1", "username2", "username4"}).Once().Return(emptyResponse, nil) | ||||
|  | ||||
| 	ctx, cancelCtx := context.WithCancel(context.Background()) | ||||
|  | ||||
| 	resultChan1 := s.GetUuidAsync("username1") | ||||
| 	resultChan2 := s.GetUuidAsync("username2") | ||||
| 	resultChan3 := s.GetUuidAsyncWithCtx(ctx, "username3") | ||||
| 	resultChan4 := s.GetUuidAsync("username4") | ||||
|  | ||||
| 	cancelCtx() | ||||
|  | ||||
| 	time.Sleep(time.Duration(float64(awaitDelay) * 0.5)) | ||||
|  | ||||
| 	s.Empty(resultChan1) | ||||
| 	s.Empty(resultChan2) | ||||
| 	s.NotEmpty(resultChan3, "canceled context must immediately release the job") | ||||
| 	s.Empty(resultChan4) | ||||
|  | ||||
| 	result3 := <-resultChan3 | ||||
| 	s.Nil(result3.Result) | ||||
| 	s.ErrorIs(result3.Error, context.Canceled) | ||||
|  | ||||
| 	time.Sleep(awaitDelay) | ||||
|  | ||||
| 	s.Require().NotEmpty(resultChan1) | ||||
| 	s.Require().NotEmpty(resultChan2) | ||||
| 	s.Require().NotEmpty(resultChan4) | ||||
| } | ||||
|  | ||||
| func (s *batchUuidsProviderTestSuite) TestGetUuidForManyUsernamesFireOnFull() { | ||||
| 	s.Provider.fireOnFull = true | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| @@ -14,11 +15,11 @@ var InvalidUsername = errors.New("the username passed doesn't meet Mojang's requ | ||||
| var allowedUsernamesRegex = regexp.MustCompile(`(?i)^[0-9a-z_]{3,16}$`) | ||||
|  | ||||
| type UuidsProvider interface { | ||||
| 	GetUuid(username string) (*ProfileInfo, error) | ||||
| 	GetUuid(ctx context.Context, username string) (*ProfileInfo, error) | ||||
| } | ||||
|  | ||||
| type TexturesProvider interface { | ||||
| 	GetTextures(uuid string) (*ProfileResponse, error) | ||||
| 	GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) | ||||
| } | ||||
|  | ||||
| type MojangTexturesProvider struct { | ||||
| @@ -28,7 +29,7 @@ type MojangTexturesProvider struct { | ||||
| 	group singleflight.Group[string, *ProfileResponse] | ||||
| } | ||||
|  | ||||
| func (p *MojangTexturesProvider) GetForUsername(username string) (*ProfileResponse, error) { | ||||
| func (p *MojangTexturesProvider) GetForUsername(ctx context.Context, username string) (*ProfileResponse, error) { | ||||
| 	if !allowedUsernamesRegex.MatchString(username) { | ||||
| 		return nil, InvalidUsername | ||||
| 	} | ||||
| @@ -36,7 +37,7 @@ func (p *MojangTexturesProvider) GetForUsername(username string) (*ProfileRespon | ||||
| 	username = strings.ToLower(username) | ||||
|  | ||||
| 	result, err, _ := p.group.Do(username, func() (*ProfileResponse, error) { | ||||
| 		profile, err := p.UuidsProvider.GetUuid(username) | ||||
| 		profile, err := p.UuidsProvider.GetUuid(ctx, username) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @@ -45,7 +46,7 @@ func (p *MojangTexturesProvider) GetForUsername(username string) (*ProfileRespon | ||||
| 			return nil, nil | ||||
| 		} | ||||
|  | ||||
| 		return p.TexturesProvider.GetTextures(profile.Id) | ||||
| 		return p.TexturesProvider.GetTextures(ctx, profile.Id) | ||||
| 	}) | ||||
|  | ||||
| 	return result, err | ||||
| @@ -54,6 +55,6 @@ func (p *MojangTexturesProvider) GetForUsername(username string) (*ProfileRespon | ||||
| type NilProvider struct { | ||||
| } | ||||
|  | ||||
| func (*NilProvider) GetForUsername(username string) (*ProfileResponse, error) { | ||||
| func (*NilProvider) GetForUsername(ctx context.Context, username string) (*ProfileResponse, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| @@ -15,8 +16,8 @@ type mockUuidsProvider struct { | ||||
| 	mock.Mock | ||||
| } | ||||
|  | ||||
| func (m *mockUuidsProvider) GetUuid(username string) (*ProfileInfo, error) { | ||||
| 	args := m.Called(username) | ||||
| func (m *mockUuidsProvider) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) { | ||||
| 	args := m.Called(ctx, username) | ||||
| 	var result *ProfileInfo | ||||
| 	if casted, ok := args.Get(0).(*ProfileInfo); ok { | ||||
| 		result = casted | ||||
| @@ -29,8 +30,8 @@ type TexturesProviderMock struct { | ||||
| 	mock.Mock | ||||
| } | ||||
|  | ||||
| func (m *TexturesProviderMock) GetTextures(uuid string) (*ProfileResponse, error) { | ||||
| 	args := m.Called(uuid) | ||||
| func (m *TexturesProviderMock) GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) { | ||||
| 	args := m.Called(ctx, uuid) | ||||
| 	var result *ProfileResponse | ||||
| 	if casted, ok := args.Get(0).(*ProfileResponse); ok { | ||||
| 		result = casted | ||||
| @@ -46,64 +47,64 @@ type providerTestSuite struct { | ||||
| 	TexturesProvider *TexturesProviderMock | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) SetupTest() { | ||||
| 	suite.UuidsProvider = &mockUuidsProvider{} | ||||
| 	suite.TexturesProvider = &TexturesProviderMock{} | ||||
| func (s *providerTestSuite) SetupTest() { | ||||
| 	s.UuidsProvider = &mockUuidsProvider{} | ||||
| 	s.TexturesProvider = &TexturesProviderMock{} | ||||
|  | ||||
| 	suite.Provider = &MojangTexturesProvider{ | ||||
| 		UuidsProvider:    suite.UuidsProvider, | ||||
| 		TexturesProvider: suite.TexturesProvider, | ||||
| 	s.Provider = &MojangTexturesProvider{ | ||||
| 		UuidsProvider:    s.UuidsProvider, | ||||
| 		TexturesProvider: s.TexturesProvider, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TearDownTest() { | ||||
| 	suite.UuidsProvider.AssertExpectations(suite.T()) | ||||
| 	suite.TexturesProvider.AssertExpectations(suite.T()) | ||||
| func (s *providerTestSuite) TearDownTest() { | ||||
| 	s.UuidsProvider.AssertExpectations(s.T()) | ||||
| 	s.TexturesProvider.AssertExpectations(s.T()) | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TestGetForValidUsernameSuccessfully() { | ||||
| func (s *providerTestSuite) TestGetForValidUsernameSuccessfully() { | ||||
| 	expectedProfile := &ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
| 	expectedResult := &ProfileResponse{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	suite.UuidsProvider.On("GetUuid", "username").Once().Return(expectedProfile, nil) | ||||
| 	suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(expectedResult, nil) | ||||
| 	s.UuidsProvider.On("GetUuid", ctx, "username").Once().Return(expectedProfile, nil) | ||||
| 	s.TexturesProvider.On("GetTextures", ctx, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(expectedResult, nil) | ||||
|  | ||||
| 	result, err := suite.Provider.GetForUsername("username") | ||||
| 	result, err := s.Provider.GetForUsername(ctx, "username") | ||||
|  | ||||
| 	suite.Assert().NoError(err) | ||||
| 	suite.Assert().Equal(expectedResult, result) | ||||
| 	s.NoError(err) | ||||
| 	s.Same(expectedResult, result) | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TestGetForUsernameWhichHasNoMojangAccount() { | ||||
| 	suite.UuidsProvider.On("GetUuid", "username").Once().Return(nil, nil) | ||||
| func (s *providerTestSuite) TestGetForUsernameWhichHasNoMojangAccount() { | ||||
| 	s.UuidsProvider.On("GetUuid", mock.Anything, "username").Once().Return(nil, nil) | ||||
|  | ||||
| 	result, err := suite.Provider.GetForUsername("username") | ||||
| 	result, err := s.Provider.GetForUsername(context.Background(), "username") | ||||
|  | ||||
| 	suite.Assert().NoError(err) | ||||
| 	suite.Assert().Nil(result) | ||||
| 	s.NoError(err) | ||||
| 	s.Nil(result) | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TestGetForUsernameWhichHasMojangAccountButHasNoMojangSkin() { | ||||
| func (s *providerTestSuite) TestGetForUsernameWhichHasMojangAccountButHasNoMojangSkin() { | ||||
| 	expectedProfile := &ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
|  | ||||
| 	suite.UuidsProvider.On("GetUuid", "username").Once().Return(expectedProfile, nil) | ||||
| 	suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, nil) | ||||
| 	s.UuidsProvider.On("GetUuid", mock.Anything, "username").Once().Return(expectedProfile, nil) | ||||
| 	s.TexturesProvider.On("GetTextures", mock.Anything, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, nil) | ||||
|  | ||||
| 	result, err := suite.Provider.GetForUsername("username") | ||||
| 	result, err := s.Provider.GetForUsername(context.Background(), "username") | ||||
|  | ||||
| 	suite.Assert().NoError(err) | ||||
| 	suite.Assert().Nil(result) | ||||
| 	s.NoError(err) | ||||
| 	s.Nil(result) | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TestGetForTheSameUsername() { | ||||
| func (s *providerTestSuite) TestGetForTheSameUsernameInRow() { | ||||
| 	expectedProfile := &ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
| 	expectedResult := &ProfileResponse{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
|  | ||||
| 	awaitChan := make(chan time.Time) | ||||
|  | ||||
| 	// If possible, then remove this .After call | ||||
| 	suite.UuidsProvider.On("GetUuid", "username").Once().WaitUntil(awaitChan).Return(expectedProfile, nil) | ||||
| 	suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(expectedResult, nil) | ||||
| 	s.UuidsProvider.On("GetUuid", mock.Anything, "username").Once().WaitUntil(awaitChan).Return(expectedProfile, nil) | ||||
| 	s.TexturesProvider.On("GetTextures", mock.Anything, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(expectedResult, nil) | ||||
|  | ||||
| 	results := make([]*ProfileResponse, 2) | ||||
| 	var wgStarted sync.WaitGroup | ||||
| @@ -113,7 +114,7 @@ func (suite *providerTestSuite) TestGetForTheSameUsername() { | ||||
| 		wgDone.Add(1) | ||||
| 		go func(i int) { | ||||
| 			wgStarted.Done() | ||||
| 			textures, _ := suite.Provider.GetForUsername("username") | ||||
| 			textures, _ := s.Provider.GetForUsername(context.Background(), "username") | ||||
| 			results[i] = textures | ||||
| 			wgDone.Done() | ||||
| 		}(i) | ||||
| @@ -123,35 +124,48 @@ func (suite *providerTestSuite) TestGetForTheSameUsername() { | ||||
| 	close(awaitChan) | ||||
| 	wgDone.Wait() | ||||
|  | ||||
| 	suite.Assert().Equal(expectedResult, results[0]) | ||||
| 	suite.Assert().Equal(expectedResult, results[1]) | ||||
| 	s.Same(expectedResult, results[0]) | ||||
| 	s.Same(expectedResult, results[1]) | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TestGetForNotAllowedMojangUsername() { | ||||
| 	result, err := suite.Provider.GetForUsername("Not allowed") | ||||
| 	suite.Assert().ErrorIs(err, InvalidUsername) | ||||
| 	suite.Assert().Nil(result) | ||||
| func (s *providerTestSuite) TestGetForTheSameUsernameOneAfterAnother() { | ||||
| 	expectedProfile := &ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
| 	expectedResult := &ProfileResponse{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
|  | ||||
| 	s.UuidsProvider.On("GetUuid", mock.Anything, "username").Times(2).Return(expectedProfile, nil) | ||||
| 	s.TexturesProvider.On("GetTextures", mock.Anything, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Times(2).Return(expectedResult, nil) | ||||
|  | ||||
| 	// Just ensure that providers will be called twice | ||||
| 	_, _ = s.Provider.GetForUsername(context.Background(), "username") | ||||
| 	time.Sleep(time.Millisecond * 20) | ||||
| 	_, _ = s.Provider.GetForUsername(context.Background(), "username") | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TestGetErrorFromUuidsProvider() { | ||||
| func (s *providerTestSuite) TestGetForNotAllowedMojangUsername() { | ||||
| 	result, err := s.Provider.GetForUsername(context.Background(), "Not allowed") | ||||
| 	s.ErrorIs(err, InvalidUsername) | ||||
| 	s.Nil(result) | ||||
| } | ||||
|  | ||||
| func (s *providerTestSuite) TestGetErrorFromUuidsProvider() { | ||||
| 	err := errors.New("mock error") | ||||
| 	suite.UuidsProvider.On("GetUuid", "username").Once().Return(nil, err) | ||||
| 	s.UuidsProvider.On("GetUuid", mock.Anything, "username").Once().Return(nil, err) | ||||
|  | ||||
| 	result, resErr := suite.Provider.GetForUsername("username") | ||||
| 	suite.Assert().Nil(result) | ||||
| 	suite.Assert().Equal(err, resErr) | ||||
| 	result, resErr := s.Provider.GetForUsername(context.Background(), "username") | ||||
| 	s.Nil(result) | ||||
| 	s.Equal(err, resErr) | ||||
| } | ||||
|  | ||||
| func (suite *providerTestSuite) TestGetErrorFromTexturesProvider() { | ||||
| func (s *providerTestSuite) TestGetErrorFromTexturesProvider() { | ||||
| 	expectedProfile := &ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"} | ||||
| 	err := errors.New("mock error") | ||||
|  | ||||
| 	suite.UuidsProvider.On("GetUuid", "username").Once().Return(expectedProfile, nil) | ||||
| 	suite.TexturesProvider.On("GetTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, err) | ||||
| 	s.UuidsProvider.On("GetUuid", mock.Anything, "username").Once().Return(expectedProfile, nil) | ||||
| 	s.TexturesProvider.On("GetTextures", mock.Anything, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").Once().Return(nil, err) | ||||
|  | ||||
| 	result, resErr := suite.Provider.GetForUsername("username") | ||||
| 	suite.Assert().Nil(result) | ||||
| 	suite.Assert().Equal(err, resErr) | ||||
| 	result, resErr := s.Provider.GetForUsername(context.Background(), "username") | ||||
| 	s.Nil(result) | ||||
| 	s.Same(err, resErr) | ||||
| } | ||||
|  | ||||
| func TestProvider(t *testing.T) { | ||||
| @@ -160,7 +174,7 @@ func TestProvider(t *testing.T) { | ||||
|  | ||||
| func TestNilProvider_GetForUsername(t *testing.T) { | ||||
| 	provider := &NilProvider{} | ||||
| 	result, err := provider.GetForUsername("username") | ||||
| 	result, err := provider.GetForUsername(context.Background(), "username") | ||||
| 	require.Nil(t, result) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| @@ -11,8 +12,8 @@ type MojangApiTexturesProvider struct { | ||||
| 	MojangApiTexturesEndpoint func(uuid string, signed bool) (*ProfileResponse, error) | ||||
| } | ||||
|  | ||||
| func (ctx *MojangApiTexturesProvider) GetTextures(uuid string) (*ProfileResponse, error) { | ||||
| 	return ctx.MojangApiTexturesEndpoint(uuid, true) | ||||
| func (p *MojangApiTexturesProvider) GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) { | ||||
| 	return p.MojangApiTexturesEndpoint(uuid, true) | ||||
| } | ||||
|  | ||||
| // Perfectly there should be an object with provider and cache implementation, | ||||
| @@ -35,14 +36,14 @@ func NewTexturesProviderWithInMemoryCache(provider TexturesProvider) *TexturesPr | ||||
| 	return storage | ||||
| } | ||||
|  | ||||
| func (s *TexturesProviderWithInMemoryCache) GetTextures(uuid string) (*ProfileResponse, error) { | ||||
| func (s *TexturesProviderWithInMemoryCache) GetTextures(ctx context.Context, uuid string) (*ProfileResponse, error) { | ||||
| 	item := s.cache.Get(uuid) | ||||
| 	// Don't check item.IsExpired() since Get function is already did this check | ||||
| 	if item != nil { | ||||
| 		return item.Value(), nil | ||||
| 	} | ||||
|  | ||||
| 	result, err := s.provider.GetTextures(uuid) | ||||
| 	result, err := s.provider.GetTextures(ctx, uuid) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @@ -64,7 +65,7 @@ func (s *MojangApiTexturesProviderSuite) TearDownTest() { | ||||
| func (s *MojangApiTexturesProviderSuite) TestGetTextures() { | ||||
| 	s.MojangApi.On("UuidToTextures", "dead24f9a4fa4877b7b04c8c6c72bb46", true).Once().Return(signedTexturesResponse, nil) | ||||
|  | ||||
| 	result, err := s.Provider.GetTextures("dead24f9a4fa4877b7b04c8c6c72bb46") | ||||
| 	result, err := s.Provider.GetTextures(context.Background(), "dead24f9a4fa4877b7b04c8c6c72bb46") | ||||
|  | ||||
| 	s.Require().NoError(err) | ||||
| 	s.Require().Equal(signedTexturesResponse, result) | ||||
| @@ -74,7 +75,7 @@ func (s *MojangApiTexturesProviderSuite) TestGetTexturesWithError() { | ||||
| 	expectedError := errors.New("mock error") | ||||
| 	s.MojangApi.On("UuidToTextures", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true).Once().Return(nil, expectedError) | ||||
|  | ||||
| 	result, err := s.Provider.GetTextures("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") | ||||
| 	result, err := s.Provider.GetTextures(context.Background(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") | ||||
|  | ||||
| 	s.Require().Nil(result) | ||||
| 	s.Require().Equal(expectedError, err) | ||||
| @@ -101,10 +102,11 @@ func (s *TexturesProviderWithInMemoryCacheSuite) TearDownTest() { | ||||
| } | ||||
|  | ||||
| func (s *TexturesProviderWithInMemoryCacheSuite) TestGetTexturesWithSuccessfulOriginalProviderResponse() { | ||||
| 	s.Original.On("GetTextures", "uuid").Once().Return(signedTexturesResponse, nil) | ||||
| 	ctx := context.Background() | ||||
| 	s.Original.On("GetTextures", ctx, "uuid").Once().Return(signedTexturesResponse, nil) | ||||
| 	// Do the call multiple times to ensure, that there will be only one call to the Original provider | ||||
| 	for i := 0; i < 5; i++ { | ||||
| 		result, err := s.Provider.GetTextures("uuid") | ||||
| 		result, err := s.Provider.GetTextures(ctx, "uuid") | ||||
|  | ||||
| 		s.Require().NoError(err) | ||||
| 		s.Require().Same(signedTexturesResponse, result) | ||||
| @@ -112,10 +114,10 @@ func (s *TexturesProviderWithInMemoryCacheSuite) TestGetTexturesWithSuccessfulOr | ||||
| } | ||||
|  | ||||
| func (s *TexturesProviderWithInMemoryCacheSuite) TestGetTexturesWithEmptyOriginalProviderResponse() { | ||||
| 	s.Original.On("GetTextures", "uuid").Once().Return(nil, nil) | ||||
| 	s.Original.On("GetTextures", mock.Anything, "uuid").Once().Return(nil, nil) | ||||
| 	// Do the call multiple times to ensure, that there will be only one call to the original provider | ||||
| 	for i := 0; i < 5; i++ { | ||||
| 		result, err := s.Provider.GetTextures("uuid") | ||||
| 		result, err := s.Provider.GetTextures(context.Background(), "uuid") | ||||
|  | ||||
| 		s.Require().NoError(err) | ||||
| 		s.Require().Nil(result) | ||||
| @@ -124,10 +126,10 @@ func (s *TexturesProviderWithInMemoryCacheSuite) TestGetTexturesWithEmptyOrigina | ||||
|  | ||||
| func (s *TexturesProviderWithInMemoryCacheSuite) TestGetTexturesWithErrorFromOriginalProvider() { | ||||
| 	expectedErr := errors.New("mock error") | ||||
| 	s.Original.On("GetTextures", "uuid").Times(5).Return(nil, expectedErr) | ||||
| 	s.Original.On("GetTextures", mock.Anything, "uuid").Times(5).Return(nil, expectedErr) | ||||
| 	// Do the call multiple times to ensure, that the error will not be cached and there will be a request on each call | ||||
| 	for i := 0; i < 5; i++ { | ||||
| 		result, err := s.Provider.GetTextures("uuid") | ||||
| 		result, err := s.Provider.GetTextures(context.Background(), "uuid") | ||||
|  | ||||
| 		s.Require().Same(expectedErr, err) | ||||
| 		s.Require().Nil(result) | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| 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 | ||||
| @@ -13,7 +15,7 @@ type UuidsProviderWithCache struct { | ||||
| 	Storage  MojangUuidsStorage | ||||
| } | ||||
|  | ||||
| func (p *UuidsProviderWithCache) GetUuid(username string) (*ProfileInfo, error) { | ||||
| func (p *UuidsProviderWithCache) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) { | ||||
| 	uuid, foundUsername, err := p.Storage.GetUuidForMojangUsername(username) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -27,7 +29,7 @@ func (p *UuidsProviderWithCache) GetUuid(username string) (*ProfileInfo, error) | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	profile, err := p.Provider.GetUuid(username) | ||||
| 	profile, err := p.Provider.GetUuid(ctx, username) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package mojang | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"testing" | ||||
|  | ||||
| @@ -14,8 +15,8 @@ type UuidsProviderMock struct { | ||||
| 	mock.Mock | ||||
| } | ||||
|  | ||||
| func (m *UuidsProviderMock) GetUuid(username string) (*ProfileInfo, error) { | ||||
| 	args := m.Called(username) | ||||
| func (m *UuidsProviderMock) GetUuid(ctx context.Context, username string) (*ProfileInfo, error) { | ||||
| 	args := m.Called(ctx, username) | ||||
| 	var result *ProfileInfo | ||||
| 	if casted, ok := args.Get(0).(*ProfileInfo); ok { | ||||
| 		result = casted | ||||
| @@ -61,12 +62,14 @@ 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.Original.On("GetUuid", "username").Once().Return(mockProfile, nil) | ||||
| 	s.Original.On("GetUuid", ctx, "username").Once().Return(mockProfile, nil) | ||||
|  | ||||
| 	result, err := s.Provider.GetUuid("username") | ||||
| 	result, err := s.Provider.GetUuid(ctx, "username") | ||||
|  | ||||
| 	s.Require().NoError(err) | ||||
| 	s.Require().Equal(mockProfile, result) | ||||
| @@ -76,9 +79,9 @@ func (s *UuidsProviderWithCacheSuite) TestUncachedNotExistsMojangUsername() { | ||||
| 	s.Storage.On("GetUuidForMojangUsername", "username").Return("", "", nil) | ||||
| 	s.Storage.On("StoreMojangUuid", "username", "").Once().Return(nil) | ||||
|  | ||||
| 	s.Original.On("GetUuid", "username").Once().Return(nil, nil) | ||||
| 	s.Original.On("GetUuid", mock.Anything, "username").Once().Return(nil, nil) | ||||
|  | ||||
| 	result, err := s.Provider.GetUuid("username") | ||||
| 	result, err := s.Provider.GetUuid(context.Background(), "username") | ||||
|  | ||||
| 	s.Require().NoError(err) | ||||
| 	s.Require().Nil(result) | ||||
| @@ -87,7 +90,7 @@ func (s *UuidsProviderWithCacheSuite) TestUncachedNotExistsMojangUsername() { | ||||
| func (s *UuidsProviderWithCacheSuite) TestKnownCachedUsername() { | ||||
| 	s.Storage.On("GetUuidForMojangUsername", "username").Return("mock-uuid", "UserName", nil) | ||||
|  | ||||
| 	result, err := s.Provider.GetUuid("username") | ||||
| 	result, err := s.Provider.GetUuid(context.Background(), "username") | ||||
|  | ||||
| 	s.Require().NoError(err) | ||||
| 	s.Require().NotNil(result) | ||||
| @@ -98,7 +101,7 @@ func (s *UuidsProviderWithCacheSuite) TestKnownCachedUsername() { | ||||
| func (s *UuidsProviderWithCacheSuite) TestUnknownCachedUsername() { | ||||
| 	s.Storage.On("GetUuidForMojangUsername", "username").Return("", "UserName", nil) | ||||
|  | ||||
| 	result, err := s.Provider.GetUuid("username") | ||||
| 	result, err := s.Provider.GetUuid(context.Background(), "username") | ||||
|  | ||||
| 	s.Require().NoError(err) | ||||
| 	s.Require().Nil(result) | ||||
| @@ -108,7 +111,7 @@ func (s *UuidsProviderWithCacheSuite) TestErrorDuringCacheQuery() { | ||||
| 	expectedError := errors.New("mock error") | ||||
| 	s.Storage.On("GetUuidForMojangUsername", "username").Return("", "", expectedError) | ||||
|  | ||||
| 	result, err := s.Provider.GetUuid("username") | ||||
| 	result, err := s.Provider.GetUuid(context.Background(), "username") | ||||
|  | ||||
| 	s.Require().Same(expectedError, err) | ||||
| 	s.Require().Nil(result) | ||||
| @@ -118,9 +121,9 @@ func (s *UuidsProviderWithCacheSuite) TestErrorFromOriginalProvider() { | ||||
| 	expectedError := errors.New("mock error") | ||||
| 	s.Storage.On("GetUuidForMojangUsername", "username").Return("", "", nil) | ||||
|  | ||||
| 	s.Original.On("GetUuid", "username").Once().Return(nil, expectedError) | ||||
| 	s.Original.On("GetUuid", mock.Anything, "username").Once().Return(nil, expectedError) | ||||
|  | ||||
| 	result, err := s.Provider.GetUuid("username") | ||||
| 	result, err := s.Provider.GetUuid(context.Background(), "username") | ||||
|  | ||||
| 	s.Require().Same(expectedError, err) | ||||
| 	s.Require().Nil(result) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user