From c6ae711c810d3c30326ec913f13d93f8eaa4c332 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Sun, 20 Sep 2026 23:09:23 +0330 Subject: [PATCH] feat(service): add card conversion service --- backend/internal/service/card_service.go | 38 +++++ backend/internal/service/card_service_test.go | 135 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 backend/internal/service/card_service.go create mode 100644 backend/internal/service/card_service_test.go diff --git a/backend/internal/service/card_service.go b/backend/internal/service/card_service.go new file mode 100644 index 0000000..fc44131 --- /dev/null +++ b/backend/internal/service/card_service.go @@ -0,0 +1,38 @@ +package service + +import ( + "context" + "fmt" + + "card-to-iban/internal/card" +) + +type CardToIBANClient interface { + Convert(ctx context.Context, cardNumber string) (string, error) +} + +type CardService struct { + client CardToIBANClient +} + +func NewCardService(client CardToIBANClient) *CardService { + return &CardService{ + client: client, + } +} + +func (s *CardService) ConvertCardToIBAN( + ctx context.Context, + cardNumber string, +) (string, error) { + if err := card.Validate(cardNumber); err != nil { + return "", err + } + + iban, err := s.client.Convert(ctx, cardNumber) + if err != nil { + return "", fmt.Errorf("convert card to iban: %w", err) + } + + return iban, nil +} diff --git a/backend/internal/service/card_service_test.go b/backend/internal/service/card_service_test.go new file mode 100644 index 0000000..f3993be --- /dev/null +++ b/backend/internal/service/card_service_test.go @@ -0,0 +1,135 @@ +package service + +import ( + "context" + "errors" + "testing" +) + +type fakeCardToIBANClient struct { + iban string + err error + called bool + receivedCard string + receivedContext context.Context +} + +func (f *fakeCardToIBANClient) Convert( + ctx context.Context, + cardNumber string, +) (string, error) { + f.called = true + f.receivedCard = cardNumber + f.receivedContext = ctx + + return f.iban, f.err +} + +func TestCardServiceConvertCardToIBANSuccess(t *testing.T) { + client := &fakeCardToIBANClient{ + iban: "IR123456789012345678901234", + } + + service := NewCardService(client) + + cardNumber := "6037991234567890" + + iban, err := service.ConvertCardToIBAN( + context.Background(), + cardNumber, + ) + + if err != nil { + t.Fatalf("ConvertCardToIBAN() unexpected error: %v", err) + } + + if iban != client.iban { + t.Errorf( + "ConvertCardToIBAN() iban = %q, want %q", + iban, + client.iban, + ) + } + + if !client.called { + t.Error("expected client to be called") + } + + if client.receivedCard != cardNumber { + t.Errorf( + "client received card = %q, want %q", + client.receivedCard, + cardNumber, + ) + } +} + +func TestCardServiceRejectsInvalidCardBeforeClientCall(t *testing.T) { + client := &fakeCardToIBANClient{} + + service := NewCardService(client) + + _, err := service.ConvertCardToIBAN( + context.Background(), + "123", + ) + + if err == nil { + t.Fatal("expected validation error") + } + + if client.called { + t.Error("client should not be called for an invalid card") + } +} + +func TestCardServiceReturnsClientError(t *testing.T) { + clientErr := errors.New("upstream unavailable") + + client := &fakeCardToIBANClient{ + err: clientErr, + } + + service := NewCardService(client) + + _, err := service.ConvertCardToIBAN( + context.Background(), + "6037991234567890", + ) + + if err == nil { + t.Fatal("expected client error") + } + + if !errors.Is(err, clientErr) { + t.Errorf( + "ConvertCardToIBAN() error = %v, want wrapped %v", + err, + clientErr, + ) + } +} + +func TestCardServicePassesContextToClient(t *testing.T) { + client := &fakeCardToIBANClient{ + iban: "IR123456789012345678901234", + } + + service := NewCardService(client) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, err := service.ConvertCardToIBAN( + ctx, + "6037991234567890", + ) + + if err != nil { + t.Fatalf("ConvertCardToIBAN() unexpected error: %v", err) + } + + if client.receivedContext != ctx { + t.Error("service did not pass the request context to the client") + } +}