year normalization
This commit is contained in:
@@ -4,11 +4,14 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -82,6 +85,51 @@ func TestSendCardPayout_SignsPayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendCardPayout_NormalizesTwoDigitYearBeforeSend(t *testing.T) {
|
||||
var captured CardPayoutRequest
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &captured); err != nil {
|
||||
t.Fatalf("failed to decode request: %v", err)
|
||||
}
|
||||
payload, _ := json.Marshal(APIResponse{})
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewReader(payload)),
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
BaseURL: "https://monetix.test",
|
||||
SecretKey: "secret",
|
||||
}
|
||||
client := NewClient(cfg, httpClient, zap.NewNop())
|
||||
|
||||
req := CardPayoutRequest{
|
||||
General: General{ProjectID: 1, PaymentID: "payout-1"},
|
||||
Customer: Customer{
|
||||
ID: "cust-1",
|
||||
FirstName: "Jane",
|
||||
LastName: "Doe",
|
||||
IP: "203.0.113.10",
|
||||
},
|
||||
Payment: Payment{Amount: 1000, Currency: "RUB"},
|
||||
Card: Card{PAN: "4111111111111111", Year: 30, Month: 12, CardHolder: "JANE DOE"},
|
||||
}
|
||||
|
||||
_, err := client.CreateCardPayout(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if captured.Card.Year != 2030 {
|
||||
t.Fatalf("expected normalized year 2030, got %d", captured.Card.Year)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendCardPayout_HTTPError(t *testing.T) {
|
||||
httpClient := &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
@@ -126,3 +174,288 @@ func TestSendCardPayout_HTTPError(t *testing.T) {
|
||||
t.Fatalf("expected error message denied, got %q", result.ErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
type errorReadCloser struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e errorReadCloser) Read(_ []byte) (int, error) {
|
||||
return 0, e.err
|
||||
}
|
||||
|
||||
func (e errorReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type countingReadCloser struct {
|
||||
data []byte
|
||||
offset int
|
||||
readBytes int
|
||||
chunkSize int
|
||||
}
|
||||
|
||||
func (c *countingReadCloser) Read(p []byte) (int, error) {
|
||||
if c.offset >= len(c.data) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := c.chunkSize
|
||||
if n <= 0 || n > len(p) {
|
||||
n = len(p)
|
||||
}
|
||||
remaining := len(c.data) - c.offset
|
||||
if n > remaining {
|
||||
n = remaining
|
||||
}
|
||||
copy(p[:n], c.data[c.offset:c.offset+n])
|
||||
c.offset += n
|
||||
c.readBytes += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *countingReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func counterValue(t *testing.T, counter prometheus.Counter) float64 {
|
||||
t.Helper()
|
||||
metric := &dto.Metric{}
|
||||
if err := counter.Write(metric); err != nil {
|
||||
t.Fatalf("failed to read counter value: %v", err)
|
||||
}
|
||||
return metric.GetCounter().GetValue()
|
||||
}
|
||||
|
||||
func TestSendCardPayout_ReadBodyError(t *testing.T) {
|
||||
httpClient := &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: errorReadCloser{err: errors.New("read failed")},
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
BaseURL: "https://monetix.test",
|
||||
SecretKey: "secret",
|
||||
}
|
||||
client := NewClient(cfg, httpClient, zap.NewNop())
|
||||
|
||||
req := CardPayoutRequest{
|
||||
General: General{ProjectID: 1, PaymentID: "payout-1"},
|
||||
Customer: Customer{
|
||||
ID: "cust-1",
|
||||
FirstName: "Jane",
|
||||
LastName: "Doe",
|
||||
IP: "203.0.113.10",
|
||||
},
|
||||
Payment: Payment{Amount: 1000, Currency: "RUB"},
|
||||
Card: Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
|
||||
}
|
||||
|
||||
_, err := client.CreateCardPayout(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatalf("expected read error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to read monetix response") {
|
||||
t.Fatalf("expected wrapped read error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendCardPayout_DecodeErrorTrackedAsHTTPError(t *testing.T) {
|
||||
initMetrics()
|
||||
beforeAccepted := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeAccepted))
|
||||
beforeHTTPError := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeHTTPError))
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(strings.NewReader("{")),
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
BaseURL: "https://monetix.test",
|
||||
SecretKey: "secret",
|
||||
}
|
||||
client := NewClient(cfg, httpClient, zap.NewNop())
|
||||
|
||||
req := CardPayoutRequest{
|
||||
General: General{ProjectID: 1, PaymentID: "payout-1"},
|
||||
Customer: Customer{
|
||||
ID: "cust-1",
|
||||
FirstName: "Jane",
|
||||
LastName: "Doe",
|
||||
IP: "203.0.113.10",
|
||||
},
|
||||
Payment: Payment{Amount: 1000, Currency: "RUB"},
|
||||
Card: Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
|
||||
}
|
||||
|
||||
_, err := client.CreateCardPayout(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatalf("expected decode error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to decode monetix response") {
|
||||
t.Fatalf("expected wrapped decode error, got %v", err)
|
||||
}
|
||||
|
||||
afterAccepted := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeAccepted))
|
||||
afterHTTPError := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeHTTPError))
|
||||
|
||||
if afterAccepted != beforeAccepted {
|
||||
t.Fatalf("accepted counter changed unexpectedly: before=%v after=%v", beforeAccepted, afterAccepted)
|
||||
}
|
||||
if afterHTTPError != beforeHTTPError+1 {
|
||||
t.Fatalf("http_error counter not incremented: before=%v after=%v", beforeHTTPError, afterHTTPError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendCardPayout_ReadsFullMonetixRegistrationBody(t *testing.T) {
|
||||
rawBody := `{"status":"waiting","request_id":"req-123","project_id":10,"payment_id":"pay-1"}`
|
||||
body := &countingReadCloser{
|
||||
data: []byte(rawBody),
|
||||
chunkSize: 7,
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: body,
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
BaseURL: "https://monetix.test",
|
||||
SecretKey: "secret",
|
||||
}
|
||||
client := NewClient(cfg, httpClient, zap.NewNop())
|
||||
|
||||
req := CardPayoutRequest{
|
||||
General: General{ProjectID: 1, PaymentID: "payout-1"},
|
||||
Customer: Customer{
|
||||
ID: "cust-1",
|
||||
FirstName: "Jane",
|
||||
LastName: "Doe",
|
||||
IP: "203.0.113.10",
|
||||
},
|
||||
Payment: Payment{Amount: 1000, Currency: "RUB"},
|
||||
Card: Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
|
||||
}
|
||||
|
||||
result, err := client.CreateCardPayout(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if !result.Accepted {
|
||||
t.Fatalf("expected accepted response")
|
||||
}
|
||||
if result.ProviderRequestID != "req-123" {
|
||||
t.Fatalf("expected provider request id req-123, got %q", result.ProviderRequestID)
|
||||
}
|
||||
if result.ProviderStatus != "waiting" {
|
||||
t.Fatalf("expected provider status waiting, got %q", result.ProviderStatus)
|
||||
}
|
||||
if body.readBytes != len(rawBody) {
|
||||
t.Fatalf("expected full body read (%d), got %d", len(rawBody), body.readBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendCardPayout_ProviderStatusFallsBackToOperationStatus(t *testing.T) {
|
||||
httpClient := &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
body := `{"operation":{"request_id":"req-1","status":"processing"}}`
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(strings.NewReader(body)),
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
BaseURL: "https://monetix.test",
|
||||
SecretKey: "secret",
|
||||
}
|
||||
client := NewClient(cfg, httpClient, zap.NewNop())
|
||||
|
||||
req := CardPayoutRequest{
|
||||
General: General{ProjectID: 1, PaymentID: "payout-1"},
|
||||
Customer: Customer{
|
||||
ID: "cust-1",
|
||||
FirstName: "Jane",
|
||||
LastName: "Doe",
|
||||
IP: "203.0.113.10",
|
||||
},
|
||||
Payment: Payment{Amount: 1000, Currency: "RUB"},
|
||||
Card: Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
|
||||
}
|
||||
|
||||
result, err := client.CreateCardPayout(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if result.ProviderStatus != "processing" {
|
||||
t.Fatalf("expected provider status processing, got %q", result.ProviderStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendCardTokenization_NormalizesTwoDigitYearBeforeSend(t *testing.T) {
|
||||
var captured CardTokenizeRequest
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
if r.URL.Path != "/v1/tokenize" {
|
||||
t.Fatalf("expected tokenization path, got %q", r.URL.Path)
|
||||
}
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(body, &captured); err != nil {
|
||||
t.Fatalf("failed to decode request: %v", err)
|
||||
}
|
||||
payload, _ := json.Marshal(APIResponse{})
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewReader(payload)),
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
BaseURL: "https://monetix.test",
|
||||
SecretKey: "secret",
|
||||
}
|
||||
client := NewClient(cfg, httpClient, zap.NewNop())
|
||||
|
||||
req := CardTokenizeRequest{
|
||||
General: General{ProjectID: 1, PaymentID: "tokenize-1"},
|
||||
Customer: Customer{
|
||||
ID: "cust-1",
|
||||
FirstName: "Jane",
|
||||
LastName: "Doe",
|
||||
IP: "203.0.113.10",
|
||||
},
|
||||
Card: CardTokenize{
|
||||
PAN: "4111111111111111",
|
||||
Year: 30,
|
||||
Month: 12,
|
||||
CardHolder: "JANE DOE",
|
||||
CVV: "123",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.CreateCardTokenization(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if captured.Card.Year != 2030 {
|
||||
t.Fatalf("expected normalized year 2030, got %d", captured.Card.Year)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user