mirror of
https://github.com/elyby/chrly.git
synced 2024-08-24 22:00:01 +05:30
Merge branch 'worker_health_status'. Resolves #16
This commit is contained in:
commit
b0ba94751a
9
Gopkg.lock
generated
9
Gopkg.lock
generated
@ -39,6 +39,14 @@
|
|||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:2e7c296138d042515eb2995fe58026eaef2c08f660a5f36584faecf34eea3cf0"
|
||||||
|
name = "github.com/etherlabsio/healthcheck"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "dd3d2fd8c3f620a32b7f3cd9b4f0d2f7d0875ab1"
|
||||||
|
version = "2.0.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:9f1e571696860f2b4f8a241b43ce91c6085e7aaed849ccca53f590a4dc7b95bd"
|
digest = "1:9f1e571696860f2b4f8a241b43ce91c6085e7aaed849ccca53f590a4dc7b95bd"
|
||||||
name = "github.com/fsnotify/fsnotify"
|
name = "github.com/fsnotify/fsnotify"
|
||||||
@ -311,6 +319,7 @@
|
|||||||
"github.com/SermoDigital/jose/crypto",
|
"github.com/SermoDigital/jose/crypto",
|
||||||
"github.com/SermoDigital/jose/jws",
|
"github.com/SermoDigital/jose/jws",
|
||||||
"github.com/asaskevich/EventBus",
|
"github.com/asaskevich/EventBus",
|
||||||
|
"github.com/etherlabsio/healthcheck",
|
||||||
"github.com/getsentry/raven-go",
|
"github.com/getsentry/raven-go",
|
||||||
"github.com/gorilla/mux",
|
"github.com/gorilla/mux",
|
||||||
"github.com/h2non/gock",
|
"github.com/h2non/gock",
|
||||||
|
@ -41,6 +41,10 @@ ignored = ["github.com/elyby/chrly"]
|
|||||||
source = "https://github.com/erickskrauch/EventBus.git"
|
source = "https://github.com/erickskrauch/EventBus.git"
|
||||||
branch = "publish_nil_values"
|
branch = "publish_nil_values"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/etherlabsio/healthcheck"
|
||||||
|
version = "2.0.3"
|
||||||
|
|
||||||
# Testing dependencies
|
# Testing dependencies
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
|
@ -4,7 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/etherlabsio/healthcheck"
|
||||||
"github.com/mono83/slf/wd"
|
"github.com/mono83/slf/wd"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -51,6 +53,22 @@ var workerCmd = &cobra.Command{
|
|||||||
Emitter: dispatcher,
|
Emitter: dispatcher,
|
||||||
UUIDsProvider: uuidsProvider,
|
UUIDsProvider: uuidsProvider,
|
||||||
}).CreateHandler()
|
}).CreateHandler()
|
||||||
|
handler.Handle("/healthcheck", healthcheck.Handler(
|
||||||
|
healthcheck.WithChecker(
|
||||||
|
"mojang-batch-uuids-provider-response",
|
||||||
|
eventsubscribers.MojangBatchUuidsProviderResponseChecker(
|
||||||
|
dispatcher,
|
||||||
|
viper.GetDuration("healthcheck.mojang_batch_uuids_provider_cool_down_duration"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
healthcheck.WithChecker(
|
||||||
|
"mojang-batch-uuids-provider-queue-length",
|
||||||
|
eventsubscribers.MojangBatchUuidsProviderQueueLengthChecker(
|
||||||
|
dispatcher,
|
||||||
|
viper.GetInt("healthcheck.mojang_batch_uuids_provider_queue_length_limit"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)).Methods("GET")
|
||||||
|
|
||||||
finishChan := make(chan bool)
|
finishChan := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
@ -73,4 +91,6 @@ var workerCmd = &cobra.Command{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(workerCmd)
|
RootCmd.AddCommand(workerCmd)
|
||||||
|
viper.SetDefault("healthcheck.mojang_batch_uuids_provider_cool_down_duration", time.Minute)
|
||||||
|
viper.SetDefault("healthcheck.mojang_batch_uuids_provider_queue_length_limit", 50)
|
||||||
}
|
}
|
||||||
|
64
eventsubscribers/health_checkers.go
Normal file
64
eventsubscribers/health_checkers.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package eventsubscribers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/etherlabsio/healthcheck"
|
||||||
|
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MojangBatchUuidsProviderResponseChecker(dispatcher Subscriber, resetDuration time.Duration) healthcheck.CheckerFunc {
|
||||||
|
var mutex sync.Mutex
|
||||||
|
var lastCallErr error
|
||||||
|
var expireTimer *time.Timer
|
||||||
|
dispatcher.Subscribe(
|
||||||
|
"mojang_textures:batch_uuids_provider:result",
|
||||||
|
func(usernames []string, profiles []*mojang.ProfileInfo, err error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
lastCallErr = err
|
||||||
|
if expireTimer != nil {
|
||||||
|
expireTimer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
expireTimer = time.AfterFunc(resetDuration, func() {
|
||||||
|
mutex.Lock()
|
||||||
|
lastCallErr = nil
|
||||||
|
mutex.Unlock()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
return lastCallErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MojangBatchUuidsProviderQueueLengthChecker(dispatcher Subscriber, maxLength int) healthcheck.CheckerFunc {
|
||||||
|
var mutex sync.Mutex
|
||||||
|
queueLength := 0
|
||||||
|
dispatcher.Subscribe("mojang_textures:batch_uuids_provider:round", func(usernames []string, tasksInQueue int) {
|
||||||
|
mutex.Lock()
|
||||||
|
queueLength = tasksInQueue
|
||||||
|
mutex.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
return func(ctx context.Context) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
if queueLength < maxLength {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("the maximum number of tasks in the queue has been exceeded")
|
||||||
|
}
|
||||||
|
}
|
71
eventsubscribers/health_checkers_test.go
Normal file
71
eventsubscribers/health_checkers_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package eventsubscribers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
|
"github.com/elyby/chrly/bootstrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMojangBatchUuidsProviderChecker(t *testing.T) {
|
||||||
|
t.Run("empty state", func(t *testing.T) {
|
||||||
|
dispatcher := bootstrap.CreateEventDispatcher()
|
||||||
|
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, time.Millisecond)
|
||||||
|
assert.Nil(t, checker(context.Background()))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when no error occurred", func(t *testing.T) {
|
||||||
|
dispatcher := bootstrap.CreateEventDispatcher()
|
||||||
|
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, time.Millisecond)
|
||||||
|
dispatcher.Emit("mojang_textures:batch_uuids_provider:result", []string{"username"}, []*mojang.ProfileInfo{}, nil)
|
||||||
|
assert.Nil(t, checker(context.Background()))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when error occurred", func(t *testing.T) {
|
||||||
|
dispatcher := bootstrap.CreateEventDispatcher()
|
||||||
|
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, time.Millisecond)
|
||||||
|
err := errors.New("some error occurred")
|
||||||
|
dispatcher.Emit("mojang_textures:batch_uuids_provider:result", []string{"username"}, nil, err)
|
||||||
|
assert.Equal(t, err, checker(context.Background()))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should reset value after passed duration", func(t *testing.T) {
|
||||||
|
dispatcher := bootstrap.CreateEventDispatcher()
|
||||||
|
checker := MojangBatchUuidsProviderResponseChecker(dispatcher, 20*time.Millisecond)
|
||||||
|
err := errors.New("some error occurred")
|
||||||
|
dispatcher.Emit("mojang_textures:batch_uuids_provider:result", []string{"username"}, nil, err)
|
||||||
|
assert.Equal(t, err, checker(context.Background()))
|
||||||
|
time.Sleep(40 * time.Millisecond)
|
||||||
|
assert.Nil(t, checker(context.Background()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMojangBatchUuidsProviderQueueLengthChecker(t *testing.T) {
|
||||||
|
t.Run("empty state", func(t *testing.T) {
|
||||||
|
dispatcher := bootstrap.CreateEventDispatcher()
|
||||||
|
checker := MojangBatchUuidsProviderQueueLengthChecker(dispatcher, 10)
|
||||||
|
assert.Nil(t, checker(context.Background()))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("less than allowed limit", func(t *testing.T) {
|
||||||
|
dispatcher := bootstrap.CreateEventDispatcher()
|
||||||
|
checker := MojangBatchUuidsProviderQueueLengthChecker(dispatcher, 10)
|
||||||
|
dispatcher.Emit("mojang_textures:batch_uuids_provider:round", []string{"username"}, 9)
|
||||||
|
assert.Nil(t, checker(context.Background()))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("greater than allowed limit", func(t *testing.T) {
|
||||||
|
dispatcher := bootstrap.CreateEventDispatcher()
|
||||||
|
checker := MojangBatchUuidsProviderQueueLengthChecker(dispatcher, 10)
|
||||||
|
dispatcher.Emit("mojang_textures:batch_uuids_provider:round", []string{"username"}, 10)
|
||||||
|
checkResult := checker(context.Background())
|
||||||
|
if assert.Error(t, checkResult) {
|
||||||
|
assert.Equal(t, "the maximum number of tasks in the queue has been exceeded", checkResult.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -111,12 +111,13 @@ func (ctx *BatchUuidsProvider) queueRound() {
|
|||||||
usernames = append(usernames, job.username)
|
usernames = append(usernames, job.username)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Emit("mojang_textures:batch_uuids_provider:round", usernames, queueSize - len(jobs))
|
ctx.Emit("mojang_textures:batch_uuids_provider:round", usernames, queueSize-len(jobs))
|
||||||
if len(usernames) == 0 {
|
if len(usernames) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles, err := usernamesToUuids(usernames)
|
profiles, err := usernamesToUuids(usernames)
|
||||||
|
ctx.Emit("mojang_textures:batch_uuids_provider:result", usernames, profiles, err)
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
go func(job *jobItem) {
|
go func(job *jobItem) {
|
||||||
response := &jobResult{}
|
response := &jobResult{}
|
||||||
|
@ -155,13 +155,16 @@ func TestBatchUuidsProvider(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *batchUuidsProviderTestSuite) TestGetUuidForOneUsername() {
|
func (suite *batchUuidsProviderTestSuite) TestGetUuidForOneUsername() {
|
||||||
|
expectedUsernames := []string{"username"}
|
||||||
expectedResult := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"}
|
expectedResult := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username"}
|
||||||
|
expectedResponse := []*mojang.ProfileInfo{expectedResult}
|
||||||
|
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username"}, 0).Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
|
||||||
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, expectedResponse, nil).Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
||||||
|
|
||||||
suite.MojangApi.On("UsernamesToUuids", []string{"username"}).Once().Return([]*mojang.ProfileInfo{expectedResult}, nil)
|
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return([]*mojang.ProfileInfo{expectedResult}, nil)
|
||||||
|
|
||||||
resultChan := suite.GetUuidAsync("username")
|
resultChan := suite.GetUuidAsync("username")
|
||||||
|
|
||||||
@ -173,14 +176,17 @@ func (suite *batchUuidsProviderTestSuite) TestGetUuidForOneUsername() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernames() {
|
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernames() {
|
||||||
|
expectedUsernames := []string{"username1", "username2"}
|
||||||
expectedResult1 := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username1"}
|
expectedResult1 := &mojang.ProfileInfo{Id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Name: "username1"}
|
||||||
expectedResult2 := &mojang.ProfileInfo{Id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", Name: "username2"}
|
expectedResult2 := &mojang.ProfileInfo{Id: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", Name: "username2"}
|
||||||
|
expectedResponse := []*mojang.ProfileInfo{expectedResult1, expectedResult2}
|
||||||
|
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username1", "username2"}, 0).Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
|
||||||
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, expectedResponse, nil).Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
||||||
|
|
||||||
suite.MojangApi.On("UsernamesToUuids", []string{"username1", "username2"}).Once().Return([]*mojang.ProfileInfo{
|
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return([]*mojang.ProfileInfo{
|
||||||
expectedResult1,
|
expectedResult1,
|
||||||
expectedResult2,
|
expectedResult2,
|
||||||
}, nil)
|
}, nil)
|
||||||
@ -205,13 +211,18 @@ func (suite *batchUuidsProviderTestSuite) TestGetUuidForMoreThan10Usernames() {
|
|||||||
usernames[i] = randStr(8)
|
usernames[i] = randStr(8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In this test we're not testing response, so always return an empty resultset
|
||||||
|
expectedResponse := []*mojang.ProfileInfo{}
|
||||||
|
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Twice()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Twice()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[0:10], 2).Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[0:10], 2).Once()
|
||||||
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", usernames[0:10], expectedResponse, nil).Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[10:12], 0).Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", usernames[10:12], 0).Once()
|
||||||
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", usernames[10:12], expectedResponse, nil).Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Twice()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Twice()
|
||||||
|
|
||||||
suite.MojangApi.On("UsernamesToUuids", usernames[0:10]).Once().Return([]*mojang.ProfileInfo{}, nil)
|
suite.MojangApi.On("UsernamesToUuids", usernames[0:10]).Once().Return(expectedResponse, nil)
|
||||||
suite.MojangApi.On("UsernamesToUuids", usernames[10:12]).Once().Return([]*mojang.ProfileInfo{}, nil)
|
suite.MojangApi.On("UsernamesToUuids", usernames[10:12]).Once().Return(expectedResponse, nil)
|
||||||
|
|
||||||
channels := make([]chan *batchUuidsProviderGetUuidResult, len(usernames))
|
channels := make([]chan *batchUuidsProviderGetUuidResult, len(usernames))
|
||||||
for i, username := range usernames {
|
for i, username := range usernames {
|
||||||
@ -229,6 +240,7 @@ func (suite *batchUuidsProviderTestSuite) TestGetUuidForMoreThan10Usernames() {
|
|||||||
func (suite *batchUuidsProviderTestSuite) TestDoNothingWhenNoTasks() {
|
func (suite *batchUuidsProviderTestSuite) TestDoNothingWhenNoTasks() {
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Times(3)
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Times(3)
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username"}, 0).Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username"}, 0).Once()
|
||||||
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", []string{"username"}, mock.Anything, nil).Once()
|
||||||
var nilStringSlice []string
|
var nilStringSlice []string
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", nilStringSlice, 0).Twice()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", nilStringSlice, 0).Twice()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Times(3)
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Times(3)
|
||||||
@ -250,13 +262,16 @@ func (suite *batchUuidsProviderTestSuite) TestDoNothingWhenNoTasks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernamesWithAnError() {
|
func (suite *batchUuidsProviderTestSuite) TestGetUuidForTwoUsernamesWithAnError() {
|
||||||
|
expectedUsernames := []string{"username1", "username2"}
|
||||||
expectedError := &mojang.TooManyRequestsError{}
|
expectedError := &mojang.TooManyRequestsError{}
|
||||||
|
var nilProfilesResponse []*mojang.ProfileInfo
|
||||||
|
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:before_round").Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", []string{"username1", "username2"}, 0).Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:round", expectedUsernames, 0).Once()
|
||||||
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:result", expectedUsernames, nilProfilesResponse, expectedError).Once()
|
||||||
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
suite.Emitter.On("Emit", "mojang_textures:batch_uuids_provider:after_round").Once()
|
||||||
|
|
||||||
suite.MojangApi.On("UsernamesToUuids", []string{"username1", "username2"}).Once().Return(nil, expectedError)
|
suite.MojangApi.On("UsernamesToUuids", expectedUsernames).Once().Return(nil, expectedError)
|
||||||
|
|
||||||
resultChan1 := suite.GetUuidAsync("username1")
|
resultChan1 := suite.GetUuidAsync("username1")
|
||||||
resultChan2 := suite.GetUuidAsync("username2")
|
resultChan2 := suite.GetUuidAsync("username2")
|
||||||
|
Loading…
Reference in New Issue
Block a user