S SmartDocs
Série: Go go 170 linhas · Atualizado 2026-04-18

payment_test.go

Go/payment-gateway-challenge/internal/handlers/payment_test.go

package handlers

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
	"time"

	"payment-gateway-challenge/internal/bank"
	"payment-gateway-challenge/internal/config"
	"payment-gateway-challenge/internal/models"

	"github.com/google/uuid"
	"github.com/labstack/echo/v4"
	"gorm.io/gorm"
)

// memRepo is an in-memory PaymentWriterReader for fast handler tests.
type memRepo struct {
	mu   sync.Mutex
	rows map[uuid.UUID]*models.Payment
}

func newMemRepo() *memRepo {
	return &memRepo{rows: make(map[uuid.UUID]*models.Payment)}
}

func (m *memRepo) Create(_ context.Context, p *models.Payment) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	cp := *p
	m.rows[p.ID] = &cp
	return nil
}

func (m *memRepo) GetByID(_ context.Context, id uuid.UUID) (*models.Payment, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	p, ok := m.rows[id]
	if !ok {
		return nil, gorm.ErrRecordNotFound
	}
	return p, nil
}

func TestProcessPayment_Authorized(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/payments", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(bank.Response{
			Authorized:        true,
			AuthorizationCode: "test-auth-code",
		})
	})
	srv := httptest.NewServer(mux)
	t.Cleanup(srv.Close)

	cfg := &config.Config{
		BankSimulatorURL:  srv.URL + "/payments",
		AllowedCurrencies: []string{"GBP"},
		HTTPClientTimeout: 5 * time.Second,
	}
	bc := bank.NewClient(cfg.BankSimulatorURL, srv.Client())
	repo := newMemRepo()
	api := NewPaymentAPI(cfg, repo, bc)
	api.clockFn = func() time.Time { return time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) }

	body := `{
		"card_number":"2222405343248877",
		"expiry_month":4,
		"expiry_year":2028,
		"currency":"GBP",
		"amount":100,
		"cvv":"123"
	}`

	e := echo.New()
	req := httptest.NewRequest(http.MethodPost, "/api/payments", strings.NewReader(body))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	if err := api.ProcessPayment(c); err != nil {
		t.Fatal(err)
	}
	if rec.Code != http.StatusCreated {
		t.Fatalf("status = %d, body=%s", rec.Code, rec.Body.String())
	}
	var got ProcessPaymentResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
		t.Fatal(err)
	}
	if got.Status != "Authorized" || got.LastFourCardDigits != "8877" {
		t.Fatalf("unexpected response: %+v", got)
	}
	if _, err := uuid.Parse(got.ID); err != nil {
		t.Fatalf("id not uuid: %v", err)
	}
}

func TestProcessPayment_RejectedValidation(t *testing.T) {
	// Bank should never be called — use invalid URL that would fail if hit.
	cfg := &config.Config{
		BankSimulatorURL:  "http://127.0.0.1:1/payments",
		AllowedCurrencies: []string{"USD"},
		HTTPClientTimeout: time.Millisecond,
	}
	bc := bank.NewClient(cfg.BankSimulatorURL, http.DefaultClient)
	repo := newMemRepo()
	api := NewPaymentAPI(cfg, repo, bc)
	api.clockFn = func() time.Time { return time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) }

	body := `{"card_number":"1","expiry_month":13,"expiry_year":2020,"currency":"XXX","amount":0,"cvv":"ab"}`

	e := echo.New()
	req := httptest.NewRequest(http.MethodPost, "/api/payments", strings.NewReader(body))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)

	if err := api.ProcessPayment(c); err != nil {
		t.Fatal(err)
	}
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d: %s", rec.Code, rec.Body.String())
	}
}

func TestGetPayment_OK(t *testing.T) {
	id := uuid.New()
	repo := newMemRepo()
	_ = repo.Create(context.Background(), &models.Payment{
		ID:               id,
		Status:           "Authorized",
		CardLast4:        "4242",
		CardNumberLength: 16,
		ExpiryMonth:      12,
		ExpiryYear:       2030,
		Currency:         "USD",
		Amount:           50,
	})

	cfg := &config.Config{AllowedCurrencies: []string{"USD"}}
	api := NewPaymentAPI(cfg, repo, bank.NewClient("http://unused", http.DefaultClient))

	e := echo.New()
	req := httptest.NewRequest(http.MethodGet, "/api/payments/"+id.String(), nil)
	rec := httptest.NewRecorder()
	c := e.NewContext(req, rec)
	c.SetParamNames("id")
	c.SetParamValues(id.String())

	if err := api.GetPayment(c); err != nil {
		t.Fatal(err)
	}
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
	var got PaymentDetailResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
		t.Fatal(err)
	}
	if got.MaskedCardNumber != "************4242" {
		t.Fatalf("mask = %q", got.MaskedCardNumber)
	}
}

Artigos relacionados