Files

124 lines
3.8 KiB
Go

package grpcadapter
import (
"context"
"errors"
"testing"
"time"
applicationledger "gl/application/ledger"
domain "gl/domain/ledger"
ledgerv1 "gl/gen/ledger/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
type ledgerRepositoryStub struct {
journal domain.Journal
}
func (r *ledgerRepositoryStub) Append(_ context.Context, journal domain.Journal) (domain.AppendResult, error) {
journal.RecordedAt = time.Unix(2, 0).UTC()
r.journal = journal
return domain.AppendResult{JournalID: journal.ID}, nil
}
func (r *ledgerRepositoryStub) AppendEvent(_ context.Context, event domain.TransactionEvent) (domain.TransactionEvent, bool, error) {
event.RecordedAt = time.Unix(2, 0).UTC()
return event, false, nil
}
func (r *ledgerRepositoryStub) GetByID(context.Context, string) (domain.Journal, error) {
return r.journal, nil
}
func (r *ledgerRepositoryStub) GetByIdempotencyKey(context.Context, string) (domain.Journal, error) {
return r.journal, nil
}
func (r *ledgerRepositoryStub) List(context.Context, domain.JournalFilter) ([]domain.Journal, error) {
return []domain.Journal{r.journal}, nil
}
func (r *ledgerRepositoryStub) Balance(context.Context, domain.AccountReference, time.Time) (domain.Amount, error) {
return domain.ParseAmount("12.5")
}
func TestAppendJournalMapsProtoToExactDomainAndBack(t *testing.T) {
repository := &ledgerRepositoryStub{}
service := applicationledger.NewService(repository, func() (string, error) {
return "11111111-1111-4111-8111-111111111111", nil
})
handler := NewHandler(nil, service)
response, err := handler.AppendJournal(context.Background(), &ledgerv1.AppendJournalRequest{
SourceService: "wallet",
IdempotencyKey: "wallet:1:internal-transfer:v1",
SourceTransactionId: "1",
EffectKind: "internal-transfer",
EventVersion: 1,
OccurredAt: timestamppb.New(time.Unix(1, 0).UTC()),
Entries: []*ledgerv1.JournalEntry{
{
LineNumber: 1,
Account: &ledgerv1.AccountReference{
AccountClass: ledgerv1.AccountClass_ACCOUNT_CLASS_USER_AVAILABLE,
OwnerType: "user",
OwnerId: "1",
AssetId: 5,
},
Amount: "-1.2500",
},
{
LineNumber: 2,
Account: &ledgerv1.AccountReference{
AccountClass: ledgerv1.AccountClass_ACCOUNT_CLASS_USER_AVAILABLE,
OwnerType: "user",
OwnerId: "2",
AssetId: 5,
},
Amount: "1.25",
},
},
})
if err != nil {
t.Fatal(err)
}
if got := response.GetJournal().GetEntries()[0].GetAmount(); got != "-1.25" {
t.Fatalf("unexpected canonical amount: %q", got)
}
if response.GetJournal().GetPayloadHash() == "" {
t.Fatal("payload hash is missing")
}
}
func TestAppendJournalRejectsMissingTimestamp(t *testing.T) {
handler := NewHandler(nil, nil)
_, err := handler.AppendJournal(context.Background(), &ledgerv1.AppendJournalRequest{})
if status.Code(err) != codes.InvalidArgument {
t.Fatalf("expected InvalidArgument, got %v", err)
}
}
func TestRPCErrorMapping(t *testing.T) {
for _, testCase := range []struct {
err error
code codes.Code
}{
{err: applicationledger.ErrInvalidArgument, code: codes.InvalidArgument},
{err: domain.ErrNotFound, code: codes.NotFound},
{err: domain.ErrIdempotencyConflict, code: codes.AlreadyExists},
{err: domain.ErrIncompleteJournal, code: codes.FailedPrecondition},
{err: domain.ErrAlreadyReversed, code: codes.FailedPrecondition},
{err: context.Canceled, code: codes.Canceled},
{err: context.DeadlineExceeded, code: codes.DeadlineExceeded},
{err: errors.New("database details must not escape"), code: codes.Internal},
} {
if got := status.Code(rpcError(testCase.err)); got != testCase.code {
t.Fatalf("rpcError(%v) = %v, want %v", testCase.err, got, testCase.code)
}
}
}