Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package shopify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestShopifySKUSearchQueryOR(t *testing.T) {
|
||||
got := shopifySKUSearchQueryOR([]string{"A", "B\"C", ""})
|
||||
want := `sku:"A" OR sku:"B\"C"`
|
||||
if got != want {
|
||||
t.Fatalf("got %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindProductsBySKUsBatchesOneGraphQLCall(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/admin/api/2024-10/graphql.json" {
|
||||
t.Fatalf("unexpected path %s", r.URL.Path)
|
||||
}
|
||||
calls.Add(1)
|
||||
var body struct {
|
||||
Variables struct {
|
||||
Q string `json:"q"`
|
||||
N int `json:"n"`
|
||||
} `json:"variables"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Variables.N != 3 {
|
||||
t.Fatalf("expected n=3 got %d", body.Variables.N)
|
||||
}
|
||||
if !strings.Contains(body.Variables.Q, `sku:"SKU-1"`) || !strings.Contains(body.Variables.Q, `sku:"SKU-2"`) {
|
||||
t.Fatalf("query missing SKUs: %q", body.Variables.Q)
|
||||
}
|
||||
_, _ = io.WriteString(w, `{
|
||||
"data": {
|
||||
"productVariants": {
|
||||
"edges": [
|
||||
{"node":{"sku":"SKU-1","product":{"id":"gid://shopify/Product/101","title":"One"}}},
|
||||
{"node":{"sku":"SKU-2","product":{"id":"gid://shopify/Product/102","title":"Two"}}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient("demo.myshopify.com", "tok", "2024-10", srv.Client())
|
||||
c.HTTP = rewriteShopifyHost(srv, c.HTTP)
|
||||
|
||||
found, err := c.FindProductsBySKUs(t.Context(), []string{"SKU-1", "SKU-2", "SKU-MISSING"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls.Load() != 1 {
|
||||
t.Fatalf("expected 1 GraphQL call, got %d", calls.Load())
|
||||
}
|
||||
if found["SKU-1"].ID != 101 || found["SKU-2"].ID != 102 {
|
||||
t.Fatalf("unexpected map: %+v", found)
|
||||
}
|
||||
if _, ok := found["SKU-MISSING"]; ok {
|
||||
t.Fatal("missing SKU should be absent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShopifyRESTRetries429(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
n := calls.Add(1)
|
||||
if n == 1 {
|
||||
w.Header().Set("Retry-After", "0")
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
_, _ = io.WriteString(w, `{"errors":"throttle"}`)
|
||||
return
|
||||
}
|
||||
_, _ = io.WriteString(w, `{"shop":{"id":1,"name":"Demo","domain":"demo.myshopify.com","currency":"USD"}}`)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient("demo.myshopify.com", "tok", "2024-10", srv.Client())
|
||||
c.HTTP = rewriteShopifyHost(srv, c.HTTP)
|
||||
|
||||
start := time.Now()
|
||||
shop, err := c.TestConnection(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if shop == nil || shop.Name != "Demo" {
|
||||
t.Fatalf("unexpected shop %+v", shop)
|
||||
}
|
||||
if calls.Load() != 2 {
|
||||
t.Fatalf("expected 2 attempts, got %d", calls.Load())
|
||||
}
|
||||
if time.Since(start) > 3*time.Second {
|
||||
t.Fatal("retry waited too long")
|
||||
}
|
||||
}
|
||||
|
||||
// rewriteShopifyHost routes myshopify Admin API calls to the test server.
|
||||
func rewriteShopifyHost(srv *httptest.Server, base *http.Client) *http.Client {
|
||||
rt := base.Transport
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
u := *req.URL
|
||||
su, _ := http.NewRequest(req.Method, srv.URL+u.Path, req.Body)
|
||||
su.URL.RawQuery = u.RawQuery
|
||||
su.Header = req.Header.Clone()
|
||||
su = su.WithContext(req.Context())
|
||||
return rt.RoundTrip(su)
|
||||
}),
|
||||
Timeout: base.Timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
||||
Reference in New Issue
Block a user