136 lines
2.7 KiB
Go
136 lines
2.7 KiB
Go
|
package ledger_test
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
"gitlab.com/slon/shad-go/ledger"
|
||
|
"gitlab.com/slon/shad-go/pgfixture"
|
||
|
)
|
||
|
|
||
|
func TestLedger(t *testing.T) {
|
||
|
dsn := pgfixture.Start(t)
|
||
|
|
||
|
ctx := context.Background()
|
||
|
|
||
|
l0, err := ledger.New(ctx, dsn)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
t.Run("SimpleCommands", func(t *testing.T) {
|
||
|
checkBalance := func(account ledger.ID, amount ledger.Money) {
|
||
|
b, err := l0.GetBalance(ctx, account)
|
||
|
require.NoError(t, err)
|
||
|
require.Equal(t, b, amount)
|
||
|
}
|
||
|
|
||
|
require.NoError(t, l0.CreateAccount(ctx, "a0"))
|
||
|
checkBalance("a0", 0)
|
||
|
|
||
|
require.Error(t, l0.CreateAccount(ctx, "a0"))
|
||
|
|
||
|
require.NoError(t, l0.Deposit(ctx, "a0", ledger.Money(100)))
|
||
|
checkBalance("a0", 100)
|
||
|
|
||
|
require.NoError(t, l0.Withdraw(ctx, "a0", ledger.Money(50)))
|
||
|
checkBalance("a0", 50)
|
||
|
|
||
|
require.ErrorIs(t, l0.Withdraw(ctx, "a0", ledger.Money(100)), ledger.ErrNoMoney)
|
||
|
|
||
|
require.NoError(t, l0.CreateAccount(ctx, "a1"))
|
||
|
|
||
|
require.NoError(t, l0.Transfer(ctx, "a0", "a1", ledger.Money(40)))
|
||
|
checkBalance("a0", 10)
|
||
|
checkBalance("a1", 40)
|
||
|
|
||
|
require.ErrorIs(t, l0.Transfer(ctx, "a0", "a1", ledger.Money(50)), ledger.ErrNoMoney)
|
||
|
})
|
||
|
|
||
|
t.Run("Transactions", func(t *testing.T) {
|
||
|
const nAccounts = 10
|
||
|
const initialBalance = 5
|
||
|
|
||
|
var accounts []ledger.ID
|
||
|
for i := 0; i < nAccounts; i++ {
|
||
|
id := ledger.ID(fmt.Sprint(i))
|
||
|
accounts = append(accounts, id)
|
||
|
|
||
|
require.NoError(t, l0.CreateAccount(ctx, id))
|
||
|
require.NoError(t, l0.Deposit(ctx, id, initialBalance))
|
||
|
}
|
||
|
|
||
|
var wg sync.WaitGroup
|
||
|
done := make(chan struct{})
|
||
|
|
||
|
spawn := func(action func() error) {
|
||
|
wg.Add(1)
|
||
|
|
||
|
go func() {
|
||
|
defer wg.Done()
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case <-done:
|
||
|
return
|
||
|
|
||
|
default:
|
||
|
if err := action(); err != nil {
|
||
|
if !errors.Is(err, ledger.ErrNoMoney) {
|
||
|
t.Errorf("operation failed: %v", err)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
for i := 0; i < nAccounts; i++ {
|
||
|
i := i
|
||
|
|
||
|
account := accounts[i]
|
||
|
next := accounts[(i+1)%len(accounts)]
|
||
|
prev := accounts[(i+len(accounts)-1)%len(accounts)]
|
||
|
|
||
|
spawn(func() error {
|
||
|
balance, err := l0.GetBalance(ctx, account)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if balance < 0 {
|
||
|
return fmt.Errorf("%q balance is negative", account)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
})
|
||
|
|
||
|
spawn(func() error {
|
||
|
return l0.Transfer(ctx, account, next, 1)
|
||
|
})
|
||
|
|
||
|
spawn(func() error {
|
||
|
return l0.Transfer(ctx, account, prev, 1)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
time.Sleep(time.Second * 10)
|
||
|
close(done)
|
||
|
wg.Wait()
|
||
|
|
||
|
var total ledger.Money
|
||
|
for i := 0; i < nAccounts; i++ {
|
||
|
amount, err := l0.GetBalance(ctx, accounts[i])
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
total += amount
|
||
|
}
|
||
|
|
||
|
require.Equal(t, total, ledger.Money(initialBalance*nAccounts))
|
||
|
})
|
||
|
}
|