Files
treasury/pkg/provider/square/square_test.go
zeekay a0d73577c7 feat(provider/square): forward CreatePaymentRequest.IdempotencyKey
CreatePayment now sets the JSON body field idempotency_key (Square's
required shape — per developer.squareup.com/docs/working-with-apis/
idempotency, NOT a header) from req.IdempotencyKey.

Drops the previous wall-clock-derived "lux_<UnixNano>" value, which
was worse than nothing because retries got fresh keys and Square
would happily create duplicate payments. Empty IdempotencyKey now
falls back to provider.DeriveIdempotencyKey, computed over the body
BEFORE the idempotency_key field is added to avoid self-feedback.

Hermetic httptest covers caller-supplied (body-field) and
deterministic-fallback paths.
2026-05-24 19:14:59 -07:00

65 lines
2.0 KiB
Go

package square
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/luxfi/treasury/pkg/types"
)
func newTestProvider(t *testing.T, h http.HandlerFunc) *Provider {
t.Helper()
srv := httptest.NewServer(h)
t.Cleanup(srv.Close)
return &Provider{cfg: Config{BaseURL: srv.URL, AccessToken: "k"}, client: srv.Client()}
}
// Square uses a JSON body field, not a header.
func TestCreatePayment_CallerIdempotencyKey_BodyField(t *testing.T) {
var seen string
p := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
var got map[string]interface{}
_ = json.Unmarshal(b, &got)
seen, _ = got["idempotency_key"].(string)
_, _ = w.Write([]byte(`{"payment":{"id":"sq_1","status":"COMPLETED","amount_money":{"amount":100,"currency":"USD"}}}`))
})
req := &types.CreatePaymentRequest{
Amount: "100", Currency: "USD", SourceAccountID: "src",
IdempotencyKey: "caller-sq",
}
if _, err := p.CreatePayment(context.Background(), req); err != nil {
t.Fatalf("CreatePayment: %v", err)
}
if seen != "caller-sq" {
t.Fatalf("idempotency_key body field: %q", seen)
}
}
func TestCreatePayment_DeterministicFallback_StableAcrossRetries(t *testing.T) {
var seen []string
p := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
var got map[string]interface{}
_ = json.Unmarshal(b, &got)
k, _ := got["idempotency_key"].(string)
seen = append(seen, k)
_, _ = w.Write([]byte(`{"payment":{"id":"sq_1","status":"COMPLETED","amount_money":{"amount":100,"currency":"USD"}}}`))
})
req := &types.CreatePaymentRequest{
Amount: "100", Currency: "USD", SourceAccountID: "src", OrgID: "org-A",
}
for i := 0; i < 3; i++ {
if _, err := p.CreatePayment(context.Background(), req); err != nil {
t.Fatalf("CreatePayment: %v", err)
}
}
if seen[0] == "" || seen[0] != seen[1] || seen[1] != seen[2] {
t.Fatalf("expected identical deterministic body-field keys, got %v", seen)
}
}