package main import ( "context" "testing" "time" ledgerv1 "gl/gen/ledger/v1" "google.golang.org/grpc" ) type clientStub struct { journal *ledgerv1.AppendJournalRequest events []*ledgerv1.AppendTransactionEventRequest balance *ledgerv1.GetBalanceRequest lookup *ledgerv1.GetJournalRequest } func (s *clientStub) AppendJournal(_ context.Context, request *ledgerv1.AppendJournalRequest, _ ...grpc.CallOption) (*ledgerv1.AppendJournalResponse, error) { s.journal = request return &ledgerv1.AppendJournalResponse{Journal: &ledgerv1.Journal{JournalId: "journal-1"}}, nil } func (s *clientStub) AppendTransactionEvent(_ context.Context, request *ledgerv1.AppendTransactionEventRequest, _ ...grpc.CallOption) (*ledgerv1.AppendTransactionEventResponse, error) { s.events = append(s.events, request) return &ledgerv1.AppendTransactionEventResponse{}, nil } func (s *clientStub) GetJournal(_ context.Context, request *ledgerv1.GetJournalRequest, _ ...grpc.CallOption) (*ledgerv1.Journal, error) { s.lookup = request return &ledgerv1.Journal{JournalId: "journal-1"}, nil } func (s *clientStub) GetBalance(_ context.Context, request *ledgerv1.GetBalanceRequest, _ ...grpc.CallOption) (*ledgerv1.GetBalanceResponse, error) { s.balance = request return &ledgerv1.GetBalanceResponse{Balance: "1"}, nil } func TestRunTransactionCreatesWalletAccountsAndFullLifecycle(t *testing.T) { ledger := &clientStub{} samples := runTransaction(context.Background(), ledger, config{ RunID: "test-run", Wallets: 10, AssetID: 7, Amount: "2.5", RequestTimeout: time.Second, }, 3) if len(samples) != 5 { t.Fatalf("expected five RPC measurements, got %d", len(samples)) } if ledger.journal == nil || len(ledger.journal.Entries) != 2 { t.Fatal("balanced journal was not appended") } debit, credit := ledger.journal.Entries[0], ledger.journal.Entries[1] if debit.Amount != "-2.5" || credit.Amount != "2.5" { t.Fatalf("unexpected journal amounts: %q and %q", debit.Amount, credit.Amount) } if debit.Account.OwnerId == credit.Account.OwnerId || debit.Account.AssetId != 7 || credit.Account.AssetId != 7 { t.Fatalf("unexpected simulated wallets: debit=%+v credit=%+v", debit.Account, credit.Account) } if len(ledger.events) != 2 || ledger.events[0].State != ledgerv1.TransactionState_TRANSACTION_STATE_CREATED || ledger.events[1].State != ledgerv1.TransactionState_TRANSACTION_STATE_SUCCESSFUL { t.Fatalf("unexpected transaction lifecycle: %+v", ledger.events) } if ledger.lookup.GetJournalId() != "journal-1" || ledger.balance.Account.OwnerId != credit.Account.OwnerId { t.Fatal("journal and balance reads did not follow the write") } } func TestTransactionHashIsStableAndUnique(t *testing.T) { first := transactionHash("run-1") if len(first) != 64 || first != transactionHash("run-1") || first == transactionHash("run-2") { t.Fatalf("unexpected transaction hashes: %q", first) } } func TestPercentileUsesNearestRank(t *testing.T) { values := []time.Duration{time.Millisecond, 4 * time.Millisecond, 2 * time.Millisecond, 3 * time.Millisecond} if got := percentile(values, 95); got != 4*time.Millisecond { t.Fatalf("unexpected p95: %s", got) } }