111 lines
3.9 KiB
Go
111 lines
3.9 KiB
Go
package repository
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"sync"
|
||
|
|
"sync/atomic"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/google/uuid"
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
|
||
|
|
"apskel-pos-be/internal/constants"
|
||
|
|
)
|
||
|
|
|
||
|
|
// The Economy Guard counters against Postgres (docs/rfc-enakgame.md §9). Needs
|
||
|
|
// TEST_DATABASE_URL; see wallet_repository_test.go.
|
||
|
|
func TestGameRewardCounterRepository_ConcurrentConsumeNeverPassesTheLimit(t *testing.T) {
|
||
|
|
db := walletTestDB(t)
|
||
|
|
org, game := uuid.New(), uuid.New()
|
||
|
|
t.Cleanup(func() { db.Exec(`DELETE FROM game_reward_counters WHERE organization_id = ?`, org) })
|
||
|
|
repo := NewGameRewardCounterRepository(db)
|
||
|
|
txm := NewTxManager(db)
|
||
|
|
day := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC)
|
||
|
|
|
||
|
|
for _, round := range []struct {
|
||
|
|
each int64
|
||
|
|
wantTotal int64
|
||
|
|
}{{10, 100}, {7, 100}} {
|
||
|
|
var granted atomic.Int64
|
||
|
|
var wg sync.WaitGroup
|
||
|
|
begin := make(chan struct{})
|
||
|
|
for i := 0; i < 20; i++ {
|
||
|
|
wg.Add(1)
|
||
|
|
go func() {
|
||
|
|
defer wg.Done()
|
||
|
|
<-begin
|
||
|
|
require.NoError(t, txm.WithTransaction(context.Background(), func(ctx context.Context) error {
|
||
|
|
got, err := repo.Consume(ctx, org, constants.GameRewardScopeGame, game, day, round.each, 100)
|
||
|
|
granted.Add(got)
|
||
|
|
return err
|
||
|
|
}))
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
close(begin)
|
||
|
|
wg.Wait()
|
||
|
|
total, err := repo.Get(context.Background(), org, constants.GameRewardScopeGame, game, day)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.EqualValues(t, round.wantTotal, total)
|
||
|
|
if round.each == 10 {
|
||
|
|
assert.EqualValues(t, 100, granted.Load(), "exactly what fits")
|
||
|
|
} else {
|
||
|
|
assert.Zero(t, granted.Load(), "nothing is left")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGameRewardCounterRepository(t *testing.T) {
|
||
|
|
db := walletTestDB(t)
|
||
|
|
org, customer := uuid.New(), uuid.New()
|
||
|
|
t.Cleanup(func() { db.Exec(`DELETE FROM game_reward_counters WHERE organization_id = ?`, org) })
|
||
|
|
repo := NewGameRewardCounterRepository(db)
|
||
|
|
txm := NewTxManager(db)
|
||
|
|
ctx := context.Background()
|
||
|
|
today, tomorrow := time.Date(2026, 10, 7, 23, 0, 0, 0, time.FixedZone("WIB", 7*3600)), time.Date(2026, 10, 8, 0, 0, 0, 0, time.UTC)
|
||
|
|
consume := func(day time.Time, amount, limit int64) int64 {
|
||
|
|
t.Helper()
|
||
|
|
var got int64
|
||
|
|
require.NoError(t, txm.WithTransaction(ctx, func(ctx context.Context) error {
|
||
|
|
var err error
|
||
|
|
got, err = repo.Consume(ctx, org, constants.GameRewardScopeUser, customer, day, amount, limit)
|
||
|
|
return err
|
||
|
|
}))
|
||
|
|
return got
|
||
|
|
}
|
||
|
|
|
||
|
|
assert.EqualValues(t, 30, consume(today, 30, 40))
|
||
|
|
assert.EqualValues(t, 10, consume(today, 30, 40), "cut to what is left")
|
||
|
|
assert.EqualValues(t, 0, consume(today, 5, 40))
|
||
|
|
assert.EqualValues(t, 5, consume(today, 5, 0), "no limit, still counted")
|
||
|
|
assert.EqualValues(t, 30, consume(tomorrow, 30, 40), "a new day starts over")
|
||
|
|
got, err := repo.Get(ctx, org, constants.GameRewardScopeUser, customer, today)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.EqualValues(t, 45, got)
|
||
|
|
|
||
|
|
require.NoError(t, txm.WithTransaction(ctx, func(ctx context.Context) error {
|
||
|
|
return repo.Release(ctx, org, constants.GameRewardScopeUser, customer, today, 15)
|
||
|
|
}))
|
||
|
|
got, _ = repo.Get(ctx, org, constants.GameRewardScopeUser, customer, today)
|
||
|
|
assert.EqualValues(t, 30, got)
|
||
|
|
assert.Error(t, txm.WithTransaction(ctx, func(ctx context.Context) error {
|
||
|
|
return repo.Release(ctx, org, constants.GameRewardScopeUser, customer, today, 31)
|
||
|
|
}), "cannot release more than counted")
|
||
|
|
|
||
|
|
// A rolled back reward leaves the counter as it was.
|
||
|
|
_ = txm.WithTransaction(ctx, func(ctx context.Context) error {
|
||
|
|
_, err := repo.Consume(ctx, org, constants.GameRewardScopeUser, customer, today, 10, 0)
|
||
|
|
require.NoError(t, err)
|
||
|
|
return assert.AnError
|
||
|
|
})
|
||
|
|
got, _ = repo.Get(ctx, org, constants.GameRewardScopeUser, customer, today)
|
||
|
|
assert.EqualValues(t, 30, got)
|
||
|
|
|
||
|
|
_, err = repo.Consume(ctx, org, constants.GameRewardScopeUser, customer, today, 1, 0)
|
||
|
|
assert.ErrorIs(t, err, ErrGameRewardCounterTxRequired)
|
||
|
|
got, err = repo.Get(ctx, org, constants.GameRewardScopeGlobal, org, today)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.Zero(t, got)
|
||
|
|
}
|