feat(gl): expose ledger application and grpc operations
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user