mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-09 02:45:29 +08:00
180 lines
4.9 KiB
Go
180 lines
4.9 KiB
Go
package account
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
|
|
"ds2api/internal/config"
|
|
)
|
|
|
|
func newPoolForTest(t *testing.T, maxInflight string) *Pool {
|
|
t.Helper()
|
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", maxInflight)
|
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
|
"keys":["k1"],
|
|
"accounts":[
|
|
{"email":"acc1@example.com","token":"token1"},
|
|
{"email":"acc2@example.com","token":"token2"}
|
|
]
|
|
}`)
|
|
store := config.LoadStore()
|
|
return NewPool(store)
|
|
}
|
|
|
|
func TestPoolRoundRobinWithConcurrentSlots(t *testing.T) {
|
|
pool := newPoolForTest(t, "2")
|
|
|
|
order := make([]string, 0, 4)
|
|
for i := 0; i < 4; i++ {
|
|
acc, ok := pool.Acquire("", nil)
|
|
if !ok {
|
|
t.Fatalf("expected acquire success at step %d", i+1)
|
|
}
|
|
order = append(order, acc.Identifier())
|
|
}
|
|
want := []string{"acc1@example.com", "acc2@example.com", "acc1@example.com", "acc2@example.com"}
|
|
for i := range want {
|
|
if order[i] != want[i] {
|
|
t.Fatalf("unexpected order at %d: got %q want %q (full=%v)", i, order[i], want[i], order)
|
|
}
|
|
}
|
|
|
|
if _, ok := pool.Acquire("", nil); ok {
|
|
t.Fatalf("expected acquire to fail when all inflight slots are occupied")
|
|
}
|
|
|
|
pool.Release("acc1@example.com")
|
|
acc, ok := pool.Acquire("", nil)
|
|
if !ok || acc.Identifier() != "acc1@example.com" {
|
|
t.Fatalf("expected reacquire acc1 after releasing one slot, got ok=%v id=%q", ok, acc.Identifier())
|
|
}
|
|
}
|
|
|
|
func TestPoolTargetAccountInflightLimit(t *testing.T) {
|
|
pool := newPoolForTest(t, "2")
|
|
|
|
for i := 0; i < 2; i++ {
|
|
if _, ok := pool.Acquire("acc1@example.com", nil); !ok {
|
|
t.Fatalf("expected target acquire success at step %d", i+1)
|
|
}
|
|
}
|
|
if _, ok := pool.Acquire("acc1@example.com", nil); ok {
|
|
t.Fatalf("expected third acquire on same target to fail due to inflight limit")
|
|
}
|
|
}
|
|
|
|
func TestPoolConcurrentAcquireDistribution(t *testing.T) {
|
|
pool := newPoolForTest(t, "2")
|
|
|
|
start := make(chan struct{})
|
|
results := make(chan string, 6)
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 6; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
<-start
|
|
acc, ok := pool.Acquire("", nil)
|
|
if !ok {
|
|
results <- "FAIL"
|
|
return
|
|
}
|
|
results <- acc.Identifier()
|
|
}()
|
|
}
|
|
|
|
close(start)
|
|
wg.Wait()
|
|
close(results)
|
|
|
|
success := 0
|
|
fail := 0
|
|
perAccount := map[string]int{}
|
|
for id := range results {
|
|
if id == "FAIL" {
|
|
fail++
|
|
continue
|
|
}
|
|
success++
|
|
perAccount[id]++
|
|
}
|
|
if success != 4 || fail != 2 {
|
|
t.Fatalf("unexpected concurrent acquire result: success=%d fail=%d perAccount=%v", success, fail, perAccount)
|
|
}
|
|
for id, n := range perAccount {
|
|
if n > 2 {
|
|
t.Fatalf("account %s exceeded inflight limit: %d", id, n)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPoolStatusRecommendedConcurrencyDefault(t *testing.T) {
|
|
pool := newPoolForTest(t, "")
|
|
status := pool.Status()
|
|
|
|
if got, ok := status["max_inflight_per_account"].(int); !ok || got != 2 {
|
|
t.Fatalf("unexpected max_inflight_per_account: %#v", status["max_inflight_per_account"])
|
|
}
|
|
if got, ok := status["recommended_concurrency"].(int); !ok || got != 4 {
|
|
t.Fatalf("unexpected recommended_concurrency: %#v", status["recommended_concurrency"])
|
|
}
|
|
}
|
|
|
|
func TestPoolStatusRecommendedConcurrencyRespectsOverride(t *testing.T) {
|
|
pool := newPoolForTest(t, "3")
|
|
status := pool.Status()
|
|
|
|
if got, ok := status["max_inflight_per_account"].(int); !ok || got != 3 {
|
|
t.Fatalf("unexpected max_inflight_per_account: %#v", status["max_inflight_per_account"])
|
|
}
|
|
if got, ok := status["recommended_concurrency"].(int); !ok || got != 6 {
|
|
t.Fatalf("unexpected recommended_concurrency: %#v", status["recommended_concurrency"])
|
|
}
|
|
}
|
|
|
|
func TestPoolAccountConcurrencyAliasEnv(t *testing.T) {
|
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "")
|
|
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "4")
|
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
|
"keys":["k1"],
|
|
"accounts":[
|
|
{"email":"acc1@example.com","token":"token1"},
|
|
{"email":"acc2@example.com","token":"token2"}
|
|
]
|
|
}`)
|
|
|
|
pool := NewPool(config.LoadStore())
|
|
status := pool.Status()
|
|
if got, ok := status["max_inflight_per_account"].(int); !ok || got != 4 {
|
|
t.Fatalf("unexpected max_inflight_per_account: %#v", status["max_inflight_per_account"])
|
|
}
|
|
if got, ok := status["recommended_concurrency"].(int); !ok || got != 8 {
|
|
t.Fatalf("unexpected recommended_concurrency: %#v", status["recommended_concurrency"])
|
|
}
|
|
}
|
|
|
|
func TestPoolSupportsTokenOnlyAccount(t *testing.T) {
|
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "1")
|
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
|
"keys":["k1"],
|
|
"accounts":[{"token":"token-only-account"}]
|
|
}`)
|
|
|
|
pool := NewPool(config.LoadStore())
|
|
status := pool.Status()
|
|
if got, ok := status["total"].(int); !ok || got != 1 {
|
|
t.Fatalf("unexpected total in pool status: %#v", status["total"])
|
|
}
|
|
if got, ok := status["available"].(int); !ok || got != 1 {
|
|
t.Fatalf("unexpected available in pool status: %#v", status["available"])
|
|
}
|
|
|
|
acc, ok := pool.Acquire("", nil)
|
|
if !ok {
|
|
t.Fatalf("expected acquire success for token-only account")
|
|
}
|
|
if acc.Token != "token-only-account" {
|
|
t.Fatalf("unexpected token on acquired account: %q", acc.Token)
|
|
}
|
|
}
|