add Printful client test coverage with Req.Test stubs
All checks were successful
deploy / deploy (push) Successful in 1m10s
All checks were successful
deploy / deploy (push) Successful in 1m10s
Wire up Req.Test plug for the Printful HTTP client so tests can stub responses. Adds HTTP-level tests for the client, provider integration tests, and mockup enricher tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f1b4e55cc7
commit
a45e85ef4c
@ -48,3 +48,7 @@ config :berrypod, Oban, testing: :inline
|
|||||||
|
|
||||||
# Isolate image cache so test cleanup doesn't wipe the dev cache
|
# Isolate image cache so test cleanup doesn't wipe the dev cache
|
||||||
config :berrypod, :image_cache_dir, Path.expand("../tmp/test_image_cache", __DIR__)
|
config :berrypod, :image_cache_dir, Path.expand("../tmp/test_image_cache", __DIR__)
|
||||||
|
|
||||||
|
# Route Printful HTTP client through Req.Test stubs
|
||||||
|
config :berrypod, Berrypod.Clients.Printful,
|
||||||
|
req_options: [plug: {Req.Test, Berrypod.Clients.Printful}, retry: false]
|
||||||
|
|||||||
@ -41,7 +41,7 @@ defmodule Berrypod.Clients.Printful do
|
|||||||
def get(path, _opts \\ []) do
|
def get(path, _opts \\ []) do
|
||||||
url = @base_url <> path
|
url = @base_url <> path
|
||||||
|
|
||||||
case Req.get(url, headers: auth_headers(), receive_timeout: 30_000) do
|
case Req.get(url, [headers: auth_headers(), receive_timeout: 30_000] ++ base_options()) do
|
||||||
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
||||||
{:ok, unwrap_response(path, body)}
|
{:ok, unwrap_response(path, body)}
|
||||||
|
|
||||||
@ -59,7 +59,10 @@ defmodule Berrypod.Clients.Printful do
|
|||||||
def post(path, body, _opts \\ []) do
|
def post(path, body, _opts \\ []) do
|
||||||
url = @base_url <> path
|
url = @base_url <> path
|
||||||
|
|
||||||
case Req.post(url, json: body, headers: auth_headers(), receive_timeout: 60_000) do
|
case Req.post(
|
||||||
|
url,
|
||||||
|
[json: body, headers: auth_headers(), receive_timeout: 60_000] ++ base_options()
|
||||||
|
) do
|
||||||
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
||||||
{:ok, unwrap_response(path, body)}
|
{:ok, unwrap_response(path, body)}
|
||||||
|
|
||||||
@ -77,13 +80,13 @@ defmodule Berrypod.Clients.Printful do
|
|||||||
def delete(path, _opts \\ []) do
|
def delete(path, _opts \\ []) do
|
||||||
url = @base_url <> path
|
url = @base_url <> path
|
||||||
|
|
||||||
case Req.delete(url, headers: auth_headers(), receive_timeout: 30_000) do
|
case Req.delete(url, [headers: auth_headers(), receive_timeout: 30_000] ++ base_options()) do
|
||||||
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
|
||||||
{:ok, unwrap_response(path, body)}
|
|
||||||
|
|
||||||
{:ok, %Req.Response{status: 204}} ->
|
{:ok, %Req.Response{status: 204}} ->
|
||||||
{:ok, nil}
|
{:ok, nil}
|
||||||
|
|
||||||
|
{:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
|
||||||
|
{:ok, unwrap_response(path, body)}
|
||||||
|
|
||||||
{:ok, %Req.Response{status: status, body: body}} ->
|
{:ok, %Req.Response{status: status, body: body}} ->
|
||||||
{:error, {status, body}}
|
{:error, {status, body}}
|
||||||
|
|
||||||
@ -362,4 +365,8 @@ defmodule Berrypod.Clients.Printful do
|
|||||||
id -> [{"X-PF-Store-Id", to_string(id)} | headers]
|
id -> [{"X-PF-Store-Id", to_string(id)} | headers]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp base_options do
|
||||||
|
Application.get_env(:berrypod, __MODULE__, [])[:req_options] || []
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
336
test/berrypod/clients/printful_http_test.exs
Normal file
336
test/berrypod/clients/printful_http_test.exs
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
defmodule Berrypod.Clients.PrintfulHttpTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests the Printful HTTP client against Req.Test stubs.
|
||||||
|
Exercises URL construction, auth headers, response unwrapping, and error handling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Berrypod.Clients.Printful
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Process.put(:printful_api_key, "test_token_abc")
|
||||||
|
Req.Test.stub(Printful, &route/1)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Response unwrapping
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "v1 response unwrapping" do
|
||||||
|
test "unwraps result key from v1 responses" do
|
||||||
|
assert {:ok, [%{"id" => 456}]} = Printful.list_sync_products()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_sync_product returns nested data" do
|
||||||
|
assert {:ok, data} = Printful.get_sync_product(456)
|
||||||
|
assert data["sync_product"]["name"] == "Test T-Shirt"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "v2 response unwrapping" do
|
||||||
|
test "unwraps data key from v2 responses" do
|
||||||
|
assert {:ok, stores} = Printful.get_stores()
|
||||||
|
assert [%{"id" => 123, "name" => "Test Store"}] = stores
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_order returns v2 data" do
|
||||||
|
assert {:ok, order} = Printful.get_order(12345)
|
||||||
|
assert order["status"] == "draft"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Error handling
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "error handling" do
|
||||||
|
test "returns error tuple on 4xx" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
Req.Test.json(conn |> Plug.Conn.put_status(404), %{"error" => "Not found"})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:error, {404, %{"error" => "Not found"}}} = Printful.get("/v2/orders/999")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error tuple on 5xx" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
Req.Test.json(conn |> Plug.Conn.put_status(500), %{"error" => "Internal error"})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:error, {500, _}} = Printful.get("/v2/stores")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "post returns error on 422" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
Req.Test.json(
|
||||||
|
conn |> Plug.Conn.put_status(422),
|
||||||
|
%{"error" => "Invalid address"}
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:error, {422, %{"error" => "Invalid address"}}} =
|
||||||
|
Printful.create_order(%{test: true})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Auth headers
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "auth headers" do
|
||||||
|
test "includes Bearer token" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
[auth] = Plug.Conn.get_req_header(conn, "authorization")
|
||||||
|
assert auth == "Bearer test_token_abc"
|
||||||
|
Req.Test.json(conn, %{"data" => []})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, _} = Printful.get_stores()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes X-PF-Store-Id when set" do
|
||||||
|
Process.put(:printful_store_id, 99999)
|
||||||
|
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
[store_id] = Plug.Conn.get_req_header(conn, "x-pf-store-id")
|
||||||
|
assert store_id == "99999"
|
||||||
|
Req.Test.json(conn, %{"code" => 200, "result" => []})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, _} = Printful.list_sync_products()
|
||||||
|
after
|
||||||
|
Process.delete(:printful_store_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "omits X-PF-Store-Id when not set" do
|
||||||
|
Process.delete(:printful_store_id)
|
||||||
|
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert Plug.Conn.get_req_header(conn, "x-pf-store-id") == []
|
||||||
|
Req.Test.json(conn, %{"data" => []})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, _} = Printful.get_stores()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Specific endpoints
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "get_stores/0" do
|
||||||
|
test "calls GET /v2/stores" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.method == "GET"
|
||||||
|
assert conn.request_path == "/v2/stores"
|
||||||
|
Req.Test.json(conn, %{"data" => [%{"id" => 1}]})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, [%{"id" => 1}]} = Printful.get_stores()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_store_id/0" do
|
||||||
|
test "returns first store id" do
|
||||||
|
assert {:ok, 123} = Printful.get_store_id()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when no stores" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
Req.Test.json(conn, %{"data" => []})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:error, :no_stores} = Printful.get_store_id()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "list_sync_products/1" do
|
||||||
|
test "passes offset and limit as query params" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.query_string == "offset=40&limit=20"
|
||||||
|
Req.Test.json(conn, %{"code" => 200, "result" => []})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, []} = Printful.list_sync_products(offset: 40)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "defaults to offset 0 and limit 20" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.query_string == "offset=0&limit=20"
|
||||||
|
Req.Test.json(conn, %{"code" => 200, "result" => []})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, []} = Printful.list_sync_products()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "calculate_shipping/2" do
|
||||||
|
test "sends recipient and items in POST body" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.method == "POST"
|
||||||
|
assert conn.request_path == "/v2/shipping-rates"
|
||||||
|
|
||||||
|
{:ok, body, _conn} = Plug.Conn.read_body(conn)
|
||||||
|
decoded = Jason.decode!(body)
|
||||||
|
assert decoded["recipient"]["country_code"] == "GB"
|
||||||
|
assert length(decoded["order_items"]) == 1
|
||||||
|
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"data" => [%{"shipping" => "STANDARD", "rate" => "4.99", "currency" => "USD"}]
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
recipient = %{country_code: "GB"}
|
||||||
|
items = [%{source: "catalog", catalog_variant_id: 474, quantity: 1}]
|
||||||
|
assert {:ok, [rate]} = Printful.calculate_shipping(recipient, items)
|
||||||
|
assert rate["rate"] == "4.99"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create_order/1 and confirm_order/1" do
|
||||||
|
test "create_order sends POST to /v2/orders" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.method == "POST"
|
||||||
|
assert conn.request_path == "/v2/orders"
|
||||||
|
Req.Test.json(conn, %{"data" => %{"id" => 12345, "status" => "draft"}})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, %{"id" => 12345}} = Printful.create_order(%{external_id: "SS-001"})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "confirm_order sends POST to /v2/orders/:id/confirmation" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.method == "POST"
|
||||||
|
assert conn.request_path == "/v2/orders/12345/confirmation"
|
||||||
|
Req.Test.json(conn, %{"data" => %{"id" => 12345, "status" => "pending"}})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, %{"status" => "pending"}} = Printful.confirm_order(12345)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_order_shipments/1" do
|
||||||
|
test "calls GET /v2/orders/:id/shipments" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.request_path == "/v2/orders/12345/shipments"
|
||||||
|
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"data" => [%{"tracking_number" => "1Z999", "tracking_url" => "https://ups.com/1Z999"}]
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, [shipment]} = Printful.get_order_shipments(12345)
|
||||||
|
assert shipment["tracking_number"] == "1Z999"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "setup_webhooks/2" do
|
||||||
|
test "sends POST to /v2/webhooks with url and events" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.method == "POST"
|
||||||
|
assert conn.request_path == "/v2/webhooks"
|
||||||
|
|
||||||
|
{:ok, body, _conn} = Plug.Conn.read_body(conn)
|
||||||
|
decoded = Jason.decode!(body)
|
||||||
|
assert decoded["url"] =~ "https://example.com"
|
||||||
|
assert is_list(decoded["events"])
|
||||||
|
|
||||||
|
Req.Test.json(conn, %{"data" => %{"url" => decoded["url"]}})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, _} =
|
||||||
|
Printful.setup_webhooks("https://example.com/webhooks", ["package_shipped"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "mockup generator" do
|
||||||
|
test "create_mockup_generator_task sends POST to /mockup-generator/create-task/:id" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.method == "POST"
|
||||||
|
assert conn.request_path == "/mockup-generator/create-task/71"
|
||||||
|
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => %{"task_key" => "gt-abc123", "status" => "pending"}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, %{"task_key" => "gt-abc123"}} =
|
||||||
|
Printful.create_mockup_generator_task(71, %{variant_ids: [4011]})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_mockup_generator_task polls by task key" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.query_string =~ "task_key=gt-abc123"
|
||||||
|
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => %{"status" => "completed", "mockups" => []}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, %{"status" => "completed"}} =
|
||||||
|
Printful.get_mockup_generator_task("gt-abc123")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete/1" do
|
||||||
|
test "delete_webhooks calls DELETE /v2/webhooks" do
|
||||||
|
Req.Test.stub(Printful, fn conn ->
|
||||||
|
assert conn.method == "DELETE"
|
||||||
|
assert conn.request_path == "/v2/webhooks"
|
||||||
|
Plug.Conn.send_resp(conn, 204, "")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, nil} = Printful.delete_webhooks()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Default stub router — handles all standard routes for basic tests
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
defp route(%Plug.Conn{method: "GET", request_path: "/v2/stores"} = conn) do
|
||||||
|
Req.Test.json(conn, %{"data" => [%{"id" => 123, "name" => "Test Store"}]})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route(%Plug.Conn{method: "GET", request_path: "/v2/orders/" <> rest} = conn) do
|
||||||
|
cond do
|
||||||
|
String.contains?(rest, "/shipments") ->
|
||||||
|
Req.Test.json(conn, %{"data" => []})
|
||||||
|
|
||||||
|
true ->
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"data" => %{"id" => 12345, "status" => "draft", "external_id" => "SS-001"}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route(%Plug.Conn{method: "GET", request_path: "/store/products/" <> _id} = conn) do
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => %{
|
||||||
|
"sync_product" => %{"id" => 456, "name" => "Test T-Shirt", "thumbnail_url" => nil},
|
||||||
|
"sync_variants" => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route(%Plug.Conn{method: "GET", request_path: "/store/products"} = conn) do
|
||||||
|
Req.Test.json(conn, %{"code" => 200, "result" => [%{"id" => 456}]})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route(%Plug.Conn{method: "GET"} = conn) do
|
||||||
|
Req.Test.json(conn, %{"data" => %{}})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route(%Plug.Conn{method: "POST"} = conn) do
|
||||||
|
Req.Test.json(conn, %{"data" => %{}})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route(%Plug.Conn{method: "DELETE"} = conn) do
|
||||||
|
Plug.Conn.send_resp(conn, 204, "")
|
||||||
|
end
|
||||||
|
end
|
||||||
429
test/berrypod/providers/printful_integration_test.exs
Normal file
429
test/berrypod/providers/printful_integration_test.exs
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
defmodule Berrypod.Providers.PrintfulIntegrationTest do
|
||||||
|
@moduledoc """
|
||||||
|
Happy-path integration tests for the Printful provider.
|
||||||
|
Uses Req.Test to stub HTTP responses and a real DB connection for fixtures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Berrypod.DataCase, async: false
|
||||||
|
|
||||||
|
alias Berrypod.Clients.Printful, as: Client
|
||||||
|
alias Berrypod.Providers.Printful
|
||||||
|
|
||||||
|
import Berrypod.ProductsFixtures
|
||||||
|
|
||||||
|
setup do
|
||||||
|
conn =
|
||||||
|
provider_connection_fixture(%{
|
||||||
|
provider_type: "printful",
|
||||||
|
config: %{"store_id" => "99999", "webhook_secret" => "pf_secret_123"}
|
||||||
|
})
|
||||||
|
|
||||||
|
%{provider_conn: conn}
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# test_connection/1
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "test_connection/1 happy path" do
|
||||||
|
test "returns store_id and store_name", %{provider_conn: conn} do
|
||||||
|
Req.Test.stub(Client, fn %{request_path: "/v2/stores"} = plug_conn ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => [%{"id" => 77777, "name" => "My Print Store"}]
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, result} = Printful.test_connection(conn)
|
||||||
|
assert result.store_id == 77777
|
||||||
|
assert result.store_name == "My Print Store"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# fetch_products/1
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "fetch_products/1 happy path" do
|
||||||
|
test "fetches and normalizes a single page of products", %{provider_conn: conn} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
route_fetch_products(plug_conn)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, products} = Printful.fetch_products(conn)
|
||||||
|
assert length(products) == 1
|
||||||
|
|
||||||
|
[product] = products
|
||||||
|
assert product.provider_product_id == "456789"
|
||||||
|
assert product.title == "PC Man T-Shirt"
|
||||||
|
assert product.category == "Apparel"
|
||||||
|
|
||||||
|
# Variants normalised
|
||||||
|
assert length(product.variants) == 2
|
||||||
|
[v1, v2] = product.variants
|
||||||
|
assert v1.title == "Black / S"
|
||||||
|
assert v1.price == 1350
|
||||||
|
assert v2.title == "Natural / S"
|
||||||
|
|
||||||
|
# Images extracted from preview files (one per unique src URL)
|
||||||
|
assert length(product.images) == 2
|
||||||
|
|
||||||
|
# Provider data populated
|
||||||
|
assert product.provider_data.catalog_product_id == 71
|
||||||
|
assert product.provider_data.blueprint_id == 71
|
||||||
|
assert is_list(product.provider_data.options)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates when first page returns 20 products", %{provider_conn: conn} do
|
||||||
|
call_count = :counters.new(1, [:atomics])
|
||||||
|
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
case plug_conn.request_path do
|
||||||
|
"/store/products" ->
|
||||||
|
:counters.add(call_count, 1, 1)
|
||||||
|
page = :counters.get(call_count, 1)
|
||||||
|
|
||||||
|
if page == 1 do
|
||||||
|
# First page: exactly 20 products to trigger pagination
|
||||||
|
products = for i <- 1..20, do: %{"id" => i, "name" => "Product #{i}"}
|
||||||
|
Req.Test.json(plug_conn, %{"code" => 200, "result" => products})
|
||||||
|
else
|
||||||
|
# Second page: fewer than 20 to stop
|
||||||
|
Req.Test.json(plug_conn, %{"code" => 200, "result" => [%{"id" => 21}]})
|
||||||
|
end
|
||||||
|
|
||||||
|
"/store/products/" <> _id ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => sync_product_detail_response()
|
||||||
|
})
|
||||||
|
|
||||||
|
"/v2/catalog-products/" <> _id ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => %{"colors" => [%{"name" => "Black", "value" => "#0b0b0b"}]}
|
||||||
|
})
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Req.Test.json(plug_conn, %{"data" => %{}})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, products} = Printful.fetch_products(conn)
|
||||||
|
# 20 from page 1 + 1 from page 2 = 21 products
|
||||||
|
assert length(products) == 21
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# submit_order/2
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "submit_order/2 happy path" do
|
||||||
|
test "creates and confirms an order", %{provider_conn: conn} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
case {plug_conn.method, plug_conn.request_path} do
|
||||||
|
{"POST", "/v2/orders"} ->
|
||||||
|
{:ok, body, _} = Plug.Conn.read_body(plug_conn)
|
||||||
|
decoded = Jason.decode!(body)
|
||||||
|
|
||||||
|
# Verify the order payload structure
|
||||||
|
assert decoded["external_id"] == "SS-001234"
|
||||||
|
assert decoded["shipping"] == "STANDARD"
|
||||||
|
assert decoded["recipient"]["email"] == "test@example.com"
|
||||||
|
assert length(decoded["items"]) == 1
|
||||||
|
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => %{"id" => 55555, "status" => "draft"}
|
||||||
|
})
|
||||||
|
|
||||||
|
{"POST", "/v2/orders/55555/confirmation"} ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => %{"id" => 55555, "status" => "pending"}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
order_data = %{
|
||||||
|
order_number: "SS-001234",
|
||||||
|
customer_email: "test@example.com",
|
||||||
|
shipping_address: %{
|
||||||
|
"name" => "Jane Doe",
|
||||||
|
"line1" => "1 High Street",
|
||||||
|
"city" => "London",
|
||||||
|
"country" => "GB",
|
||||||
|
"postal_code" => "SW1A 1AA"
|
||||||
|
},
|
||||||
|
line_items: [
|
||||||
|
%{
|
||||||
|
provider_variant_id: "5001",
|
||||||
|
quantity: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, %{provider_order_id: "55555"}} = Printful.submit_order(conn, order_data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# get_order_status/2
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "get_order_status/2 happy path" do
|
||||||
|
test "returns normalised status with tracking", %{provider_conn: conn} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
case plug_conn.request_path do
|
||||||
|
"/v2/orders/55555/shipments" ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => [
|
||||||
|
%{
|
||||||
|
"tracking_number" => "PF-TRACK-001",
|
||||||
|
"tracking_url" => "https://tracking.printful.com/PF-TRACK-001"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
"/v2/orders/55555" ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => %{"id" => 55555, "status" => "fulfilled"}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, status} = Printful.get_order_status(conn, "55555")
|
||||||
|
assert status.status == "shipped"
|
||||||
|
assert status.provider_status == "fulfilled"
|
||||||
|
assert status.tracking_number == "PF-TRACK-001"
|
||||||
|
assert status.tracking_url == "https://tracking.printful.com/PF-TRACK-001"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns status with nil tracking when no shipments", %{provider_conn: conn} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
case plug_conn.request_path do
|
||||||
|
"/v2/orders/55555/shipments" ->
|
||||||
|
Req.Test.json(plug_conn, %{"data" => []})
|
||||||
|
|
||||||
|
"/v2/orders/55555" ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => %{"id" => 55555, "status" => "pending"}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, status} = Printful.get_order_status(conn, "55555")
|
||||||
|
assert status.status == "submitted"
|
||||||
|
assert is_nil(status.tracking_number)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# fetch_shipping_rates/2
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "fetch_shipping_rates/2 happy path" do
|
||||||
|
test "returns rates for target countries", %{provider_conn: conn} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
assert plug_conn.request_path == "/v2/shipping-rates"
|
||||||
|
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => [
|
||||||
|
%{
|
||||||
|
"shipping" => "STANDARD",
|
||||||
|
"rate" => "4.99",
|
||||||
|
"currency" => "USD",
|
||||||
|
"max_delivery_days" => 7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
products = [
|
||||||
|
%{
|
||||||
|
provider_data: %{
|
||||||
|
catalog_product_id: 71,
|
||||||
|
catalog_variant_ids: [4011, 4012]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert {:ok, rates} = Printful.fetch_shipping_rates(conn, products)
|
||||||
|
# Should get one rate per target country (10 countries)
|
||||||
|
assert length(rates) == 10
|
||||||
|
|
||||||
|
[rate | _] = rates
|
||||||
|
assert rate.blueprint_id == 71
|
||||||
|
assert rate.first_item_cost == 499
|
||||||
|
assert rate.currency == "USD"
|
||||||
|
assert rate.handling_time_days == 7
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles partial failures gracefully", %{provider_conn: conn} do
|
||||||
|
call_count = :counters.new(1, [:atomics])
|
||||||
|
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
:counters.add(call_count, 1, 1)
|
||||||
|
count = :counters.get(call_count, 1)
|
||||||
|
|
||||||
|
# Fail every 3rd country
|
||||||
|
if rem(count, 3) == 0 do
|
||||||
|
Req.Test.json(plug_conn |> Plug.Conn.put_status(500), %{"error" => "rate limit"})
|
||||||
|
else
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"data" => [
|
||||||
|
%{
|
||||||
|
"shipping" => "STANDARD",
|
||||||
|
"rate" => "3.99",
|
||||||
|
"currency" => "USD",
|
||||||
|
"max_delivery_days" => 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
products = [
|
||||||
|
%{provider_data: %{catalog_product_id: 71, catalog_variant_ids: [4011]}}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert {:ok, rates} = Printful.fetch_shipping_rates(conn, products)
|
||||||
|
# Some countries fail, but we still get rates for the rest
|
||||||
|
assert length(rates) > 0
|
||||||
|
assert length(rates) < 10
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# register_webhooks/2
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
describe "register_webhooks/2" do
|
||||||
|
test "appends token to webhook URL", %{provider_conn: conn} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
{:ok, body, _} = Plug.Conn.read_body(plug_conn)
|
||||||
|
decoded = Jason.decode!(body)
|
||||||
|
|
||||||
|
# Verify the token is appended as a query param
|
||||||
|
assert decoded["url"] =~ "token=pf_secret_123"
|
||||||
|
assert decoded["url"] =~ "https://example.com/webhooks/printful"
|
||||||
|
assert is_list(decoded["events"])
|
||||||
|
assert "package_shipped" in decoded["events"]
|
||||||
|
|
||||||
|
Req.Test.json(plug_conn, %{"data" => %{"url" => decoded["url"]}})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, _} =
|
||||||
|
Printful.register_webhooks(conn, "https://example.com/webhooks/printful")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when no webhook_secret" do
|
||||||
|
conn =
|
||||||
|
provider_connection_fixture(%{
|
||||||
|
provider_type: "printful",
|
||||||
|
config: %{"store_id" => "99999"}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:error, :no_webhook_secret} =
|
||||||
|
Printful.register_webhooks(conn, "https://example.com/webhooks/printful")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when no store_id" do
|
||||||
|
conn =
|
||||||
|
provider_connection_fixture(%{
|
||||||
|
provider_type: "printful",
|
||||||
|
config: %{"webhook_secret" => "pf_secret_123"}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:error, :no_store_id} =
|
||||||
|
Printful.register_webhooks(conn, "https://example.com/webhooks/printful")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stub responses
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
defp route_fetch_products(%{request_path: "/store/products"} = conn) do
|
||||||
|
# Single page with one product (fewer than 20 — no pagination)
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => [%{"id" => 456_789, "name" => "PC Man T-Shirt"}]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route_fetch_products(%{request_path: "/store/products/" <> _id} = conn) do
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => sync_product_detail_response()
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route_fetch_products(%{request_path: "/v2/catalog-products/" <> _id} = conn) do
|
||||||
|
Req.Test.json(conn, %{
|
||||||
|
"data" => %{
|
||||||
|
"colors" => [
|
||||||
|
%{"name" => "Black", "value" => "#0b0b0b"},
|
||||||
|
%{"name" => "Natural", "value" => "#F5F5DC"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp route_fetch_products(conn) do
|
||||||
|
Req.Test.json(conn, %{"data" => %{}})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sync_product_detail_response do
|
||||||
|
%{
|
||||||
|
"sync_product" => %{
|
||||||
|
"id" => 456_789,
|
||||||
|
"name" => "PC Man T-Shirt",
|
||||||
|
"thumbnail_url" => "https://files.cdn.printful.com/thumb.png"
|
||||||
|
},
|
||||||
|
"sync_variants" => [
|
||||||
|
%{
|
||||||
|
"id" => 5001,
|
||||||
|
"color" => "Black",
|
||||||
|
"size" => "S",
|
||||||
|
"retail_price" => "13.50",
|
||||||
|
"sku" => "PCM-BK-S",
|
||||||
|
"synced" => true,
|
||||||
|
"availability_status" => "active",
|
||||||
|
"variant_id" => 4011,
|
||||||
|
"product" => %{
|
||||||
|
"product_id" => 71,
|
||||||
|
"name" => "Bella+Canvas 3001 Unisex Short Sleeve Jersey T-Shirt"
|
||||||
|
},
|
||||||
|
"files" => [
|
||||||
|
%{
|
||||||
|
"type" => "preview",
|
||||||
|
"preview_url" => "https://files.cdn.printful.com/preview-black.png"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"type" => "default",
|
||||||
|
"url" => "https://files.cdn.printful.com/artwork.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"id" => 5003,
|
||||||
|
"color" => "Natural",
|
||||||
|
"size" => "S",
|
||||||
|
"retail_price" => "13.50",
|
||||||
|
"sku" => "PCM-NT-S",
|
||||||
|
"synced" => true,
|
||||||
|
"availability_status" => "active",
|
||||||
|
"variant_id" => 4013,
|
||||||
|
"product" => %{
|
||||||
|
"product_id" => 71,
|
||||||
|
"name" => "Bella+Canvas 3001 Unisex Short Sleeve Jersey T-Shirt"
|
||||||
|
},
|
||||||
|
"files" => [
|
||||||
|
%{
|
||||||
|
"type" => "preview",
|
||||||
|
"preview_url" => "https://files.cdn.printful.com/preview-natural.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
241
test/berrypod/sync/mockup_enricher_test.exs
Normal file
241
test/berrypod/sync/mockup_enricher_test.exs
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
defmodule Berrypod.Sync.MockupEnricherTest do
|
||||||
|
use Berrypod.DataCase, async: false
|
||||||
|
|
||||||
|
alias Berrypod.Clients.Printful, as: Client
|
||||||
|
alias Berrypod.Products
|
||||||
|
alias Berrypod.Sync.MockupEnricher
|
||||||
|
|
||||||
|
import Berrypod.ProductsFixtures
|
||||||
|
|
||||||
|
@moduletag capture_log: true
|
||||||
|
|
||||||
|
setup do
|
||||||
|
conn =
|
||||||
|
provider_connection_fixture(%{
|
||||||
|
provider_type: "printful",
|
||||||
|
config: %{"store_id" => "99999"}
|
||||||
|
})
|
||||||
|
|
||||||
|
product =
|
||||||
|
product_fixture(%{
|
||||||
|
provider_connection: conn,
|
||||||
|
provider_data: %{
|
||||||
|
"catalog_product_id" => 71,
|
||||||
|
"artwork_url" => "https://files.cdn.printful.com/artwork.png",
|
||||||
|
"color_variant_map" => %{"Black" => 4011, "Natural" => 4013}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
%{provider_conn: conn, product: product}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "perform/1 happy path" do
|
||||||
|
test "creates product images from mockup results", %{
|
||||||
|
provider_conn: conn,
|
||||||
|
product: product
|
||||||
|
} do
|
||||||
|
stub_mockup_generator_success()
|
||||||
|
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => conn.id,
|
||||||
|
"product_id" => product.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert :ok = MockupEnricher.perform(job)
|
||||||
|
|
||||||
|
# Hero colour gets extra angles, other colours get front only
|
||||||
|
images = Products.list_product_images(product.id)
|
||||||
|
assert length(images) > 0
|
||||||
|
|
||||||
|
# Check images have color tags
|
||||||
|
assert Enum.any?(images, fn img -> img.color == "Black" end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "perform/1 skip conditions" do
|
||||||
|
test "skips when no artwork_url", %{provider_conn: conn} do
|
||||||
|
product =
|
||||||
|
product_fixture(%{
|
||||||
|
provider_connection: conn,
|
||||||
|
provider_data: %{
|
||||||
|
"catalog_product_id" => 71,
|
||||||
|
"color_variant_map" => %{"Black" => 4011}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => conn.id,
|
||||||
|
"product_id" => product.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert :ok = MockupEnricher.perform(job)
|
||||||
|
assert Products.list_product_images(product.id) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips when no catalog_product_id", %{provider_conn: conn} do
|
||||||
|
product =
|
||||||
|
product_fixture(%{
|
||||||
|
provider_connection: conn,
|
||||||
|
provider_data: %{
|
||||||
|
"artwork_url" => "https://example.com/art.png",
|
||||||
|
"color_variant_map" => %{"Black" => 4011}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => conn.id,
|
||||||
|
"product_id" => product.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert :ok = MockupEnricher.perform(job)
|
||||||
|
assert Products.list_product_images(product.id) == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips when already enriched", %{provider_conn: conn, product: product} do
|
||||||
|
# Create an existing color-tagged image to simulate prior enrichment
|
||||||
|
Products.create_product_image(%{
|
||||||
|
product_id: product.id,
|
||||||
|
src: "https://example.com/existing.jpg",
|
||||||
|
alt: "Front",
|
||||||
|
color: "Black",
|
||||||
|
position: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => conn.id,
|
||||||
|
"product_id" => product.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert :ok = MockupEnricher.perform(job)
|
||||||
|
|
||||||
|
# Should still only have the one existing image
|
||||||
|
images = Products.list_product_images(product.id)
|
||||||
|
assert length(images) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "perform/1 error handling" do
|
||||||
|
test "returns {:cancel, :not_found} when connection missing", %{product: product} do
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => Ecto.UUID.generate(),
|
||||||
|
"product_id" => product.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:cancel, :not_found} = MockupEnricher.perform(job)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns {:cancel, :not_found} when product missing", %{provider_conn: conn} do
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => conn.id,
|
||||||
|
"product_id" => Ecto.UUID.generate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:cancel, :not_found} = MockupEnricher.perform(job)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns {:snooze, 60} on 429 rate limit", %{
|
||||||
|
provider_conn: conn,
|
||||||
|
product: product
|
||||||
|
} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
Req.Test.json(plug_conn |> Plug.Conn.put_status(429), %{"error" => "rate limited"})
|
||||||
|
end)
|
||||||
|
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => conn.id,
|
||||||
|
"product_id" => product.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:snooze, 60} = MockupEnricher.perform(job)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns :ok on 400 unsupported product", %{
|
||||||
|
provider_conn: conn,
|
||||||
|
product: product
|
||||||
|
} do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
Req.Test.json(
|
||||||
|
plug_conn |> Plug.Conn.put_status(400),
|
||||||
|
%{"error" => "Product not supported"}
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
job = %Oban.Job{
|
||||||
|
args: %{
|
||||||
|
"provider_connection_id" => conn.id,
|
||||||
|
"product_id" => product.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert :ok = MockupEnricher.perform(job)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "enqueue/3" do
|
||||||
|
test "staggers jobs by delay_index", %{provider_conn: conn, product: product} do
|
||||||
|
stub_mockup_generator_success()
|
||||||
|
|
||||||
|
{:ok, job0} = MockupEnricher.enqueue(conn.id, product.id, 0)
|
||||||
|
{:ok, job2} = MockupEnricher.enqueue(conn.id, product.id, 2)
|
||||||
|
|
||||||
|
# Job 2 should be scheduled later than job 0
|
||||||
|
assert DateTime.compare(job2.scheduled_at, job0.scheduled_at) == :gt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stub helpers
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
defp stub_mockup_generator_success do
|
||||||
|
Req.Test.stub(Client, fn plug_conn ->
|
||||||
|
case {plug_conn.method, plug_conn.request_path} do
|
||||||
|
{"POST", "/mockup-generator/create-task/" <> _id} ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => %{"task_key" => "gt-test-123", "status" => "pending"}
|
||||||
|
})
|
||||||
|
|
||||||
|
{"GET", "/mockup-generator/task"} ->
|
||||||
|
Req.Test.json(plug_conn, %{
|
||||||
|
"code" => 200,
|
||||||
|
"result" => %{
|
||||||
|
"status" => "completed",
|
||||||
|
"mockups" => [
|
||||||
|
%{
|
||||||
|
"mockup_url" => "https://mockup.printful.com/front.jpg",
|
||||||
|
"extra" => [
|
||||||
|
%{
|
||||||
|
"url" => "https://mockup.printful.com/back.jpg",
|
||||||
|
"title" => "Back"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => "https://mockup.printful.com/left.jpg",
|
||||||
|
"title" => "Left"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Req.Test.json(plug_conn, %{"data" => %{}})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user