berrypod/test/berrypod/orders_test.exs
jamey 01ff8decd5
All checks were successful
deploy / deploy (push) Successful in 1m17s
add order status lookup for customers
Magic link flow on contact page: customer enters email, gets a
time-limited signed link, clicks through to /orders showing all their
paid orders and full detail pages with thumbnails and product links.

- OrderLookupController generates/verifies Phoenix.Token signed links
- Contact LiveView handles lookup_orders + reset_tracking events
- Orders and OrderDetail LiveViews gated by session email
- Order detail shows thumbnails, links to products still available
- .themed-button gets base padding/font-weight so all usages are consistent
- order-summary-card sticky scoped to .cart-grid (was leaking to orders list)
- 27 new tests (1095 total)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 08:40:08 +00:00

279 lines
8.2 KiB
Elixir

defmodule Berrypod.OrdersTest do
use Berrypod.DataCase, async: false
import Ecto.Query
import Mox
import Berrypod.OrdersFixtures
alias Berrypod.Orders
alias Berrypod.Providers.MockProvider
setup :verify_on_exit!
describe "list_orders/1" do
test "returns all orders" do
order1 = order_fixture()
order2 = order_fixture()
orders = Orders.list_orders()
order_ids = Enum.map(orders, & &1.id)
assert order1.id in order_ids
assert order2.id in order_ids
assert length(orders) == 2
end
test "filters by payment status" do
_pending = order_fixture()
paid = order_fixture(payment_status: "paid")
_failed = order_fixture(payment_status: "failed")
orders = Orders.list_orders(status: "paid")
assert length(orders) == 1
assert hd(orders).id == paid.id
end
test "returns all when status is 'all'" do
order_fixture()
order_fixture(payment_status: "paid")
orders = Orders.list_orders(status: "all")
assert length(orders) == 2
end
test "preloads items" do
order_fixture()
[order] = Orders.list_orders()
assert Ecto.assoc_loaded?(order.items)
assert length(order.items) == 1
end
end
describe "count_orders_by_status/0" do
test "returns empty map when no orders" do
assert Orders.count_orders_by_status() == %{}
end
test "counts orders by status" do
order_fixture()
order_fixture()
order_fixture(payment_status: "paid")
order_fixture(payment_status: "failed")
counts = Orders.count_orders_by_status()
assert counts["pending"] == 2
assert counts["paid"] == 1
assert counts["failed"] == 1
end
end
describe "submit_to_provider/1" do
setup do
Application.put_env(:berrypod, :provider_modules, %{
"printify" => MockProvider
})
on_exit(fn -> Application.delete_env(:berrypod, :provider_modules) end)
:ok
end
test "submits order and sets fulfilment status" do
{order, _variant, _product, _conn} = paid_order_with_products_fixture()
expect(MockProvider, :submit_order, fn _conn, order_data ->
assert order_data.order_number == order.order_number
assert is_list(order_data.line_items)
assert hd(order_data.line_items).quantity == 1
{:ok, %{provider_order_id: "pfy_123"}}
end)
assert {:ok, updated} = Orders.submit_to_provider(order)
assert updated.fulfilment_status == "submitted"
assert updated.provider_order_id == "pfy_123"
assert updated.submitted_at != nil
assert updated.fulfilment_error == nil
end
test "is idempotent when already submitted" do
{order, _variant, _product, _conn} = submitted_order_fixture()
# No mock expectations — provider should not be called
assert {:ok, ^order} = Orders.submit_to_provider(order)
end
test "sets failed status on provider error" do
{order, _variant, _product, _conn} = paid_order_with_products_fixture()
expect(MockProvider, :submit_order, fn _conn, _data ->
{:error, {500, %{"message" => "Server error"}}}
end)
assert {:error, {500, _}} = Orders.submit_to_provider(order)
updated = Orders.get_order(order.id)
assert updated.fulfilment_status == "failed"
assert updated.fulfilment_error =~ "Provider API error (500)"
end
end
describe "refresh_fulfilment_status/1" do
setup do
Application.put_env(:berrypod, :provider_modules, %{
"printify" => MockProvider
})
on_exit(fn -> Application.delete_env(:berrypod, :provider_modules) end)
:ok
end
test "updates tracking info from provider" do
{order, _variant, _product, _conn} = submitted_order_fixture()
expect(MockProvider, :get_order_status, fn _conn, pid ->
assert pid == order.provider_order_id
{:ok,
%{
status: "shipped",
provider_status: "shipped",
tracking_number: "TRACK123",
tracking_url: "https://track.example.com/TRACK123"
}}
end)
assert {:ok, updated} = Orders.refresh_fulfilment_status(order)
assert updated.fulfilment_status == "shipped"
assert updated.tracking_number == "TRACK123"
assert updated.tracking_url == "https://track.example.com/TRACK123"
assert updated.shipped_at != nil
end
test "no-op when no provider_order_id" do
order = order_fixture(payment_status: "paid")
assert is_nil(order.provider_order_id)
assert {:ok, ^order} = Orders.refresh_fulfilment_status(order)
end
end
describe "update_fulfilment/2" do
test "updates fulfilment fields" do
order = order_fixture()
assert {:ok, updated} =
Orders.update_fulfilment(order, %{
fulfilment_status: "submitted",
provider_order_id: "test_123"
})
assert updated.fulfilment_status == "submitted"
assert updated.provider_order_id == "test_123"
end
test "validates fulfilment status inclusion" do
order = order_fixture()
assert {:error, changeset} =
Orders.update_fulfilment(order, %{fulfilment_status: "bogus"})
assert errors_on(changeset).fulfilment_status != []
end
end
describe "list_submitted_orders/0" do
test "returns orders with submitted or processing status" do
{submitted, _v, _p, _c} = submitted_order_fixture()
{processing, _v2, _p2, _c2} = submitted_order_fixture()
{:ok, processing} =
Orders.update_fulfilment(processing, %{fulfilment_status: "processing"})
_unfulfilled = order_fixture(payment_status: "paid")
orders = Orders.list_submitted_orders()
ids = Enum.map(orders, & &1.id)
assert submitted.id in ids
assert processing.id in ids
assert length(orders) == 2
end
test "returns empty list when no submitted orders" do
order_fixture()
assert Orders.list_submitted_orders() == []
end
end
describe "get_order_by_number/1" do
test "finds order by order number" do
order = order_fixture()
found = Orders.get_order_by_number(order.order_number)
assert found.id == order.id
end
test "returns nil for unknown order number" do
assert is_nil(Orders.get_order_by_number("SS-000000-XXXX"))
end
end
describe "list_orders_by_email/1" do
test "returns paid orders for the given email" do
order = order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})
results = Orders.list_orders_by_email("buyer@example.com")
ids = Enum.map(results, & &1.id)
assert order.id in ids
end
test "excludes pending and failed orders" do
order_fixture(%{customer_email: "buyer@example.com"})
order_fixture(%{customer_email: "buyer@example.com", payment_status: "failed"})
assert Orders.list_orders_by_email("buyer@example.com") == []
end
test "excludes other customers' orders" do
order_fixture(%{customer_email: "other@example.com", payment_status: "paid"})
assert Orders.list_orders_by_email("buyer@example.com") == []
end
test "is case-insensitive" do
order = order_fixture(%{customer_email: "Buyer@Example.COM", payment_status: "paid"})
results = Orders.list_orders_by_email("buyer@example.com")
ids = Enum.map(results, & &1.id)
assert order.id in ids
end
test "preloads items" do
order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})
[order] = Orders.list_orders_by_email("buyer@example.com")
assert Ecto.assoc_loaded?(order.items)
end
test "returns newest first" do
old = order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})
new = order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})
# Force different inserted_at by updating order records
now = DateTime.utc_now() |> DateTime.truncate(:second)
earlier = DateTime.add(now, -60, :second)
Berrypod.Repo.update_all(
from(o in Berrypod.Orders.Order, where: o.id == ^old.id),
set: [inserted_at: earlier]
)
[first, second] = Orders.list_orders_by_email("buyer@example.com")
assert first.id == new.id
assert second.id == old.id
end
end
end