All checks were successful
deploy / deploy (push) Successful in 4m22s
Single activity_log table powering two views: chronological timeline on each order detail page (replacing the old fulfilment card) and a global feed at /admin/activity with tabs, category filters, search, and pagination. Real-time via PubSub — new entries appear instantly, nav badge updates across all admin pages. Instrumented across all event points: Stripe webhooks, order notifier, submission worker, fulfilment status worker, product sync worker, and Oban exhausted-job telemetry. Contextual action buttons (retry submission, retry sync, dismiss) with Oban unique constraints to prevent double-enqueue. 90-day pruning via cron. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
234 lines
6.5 KiB
Elixir
234 lines
6.5 KiB
Elixir
defmodule BerrypodWeb.Admin.OrdersTest do
|
|
use BerrypodWeb.ConnCase, async: false
|
|
|
|
import Phoenix.LiveViewTest
|
|
import Berrypod.AccountsFixtures
|
|
import Berrypod.OrdersFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
%{user: user}
|
|
end
|
|
|
|
describe "unauthenticated" do
|
|
test "redirects to login", %{conn: conn} do
|
|
{:error, redirect} = live(conn, ~p"/admin/orders")
|
|
assert {:redirect, %{to: path}} = redirect
|
|
assert path == ~p"/users/log-in"
|
|
end
|
|
end
|
|
|
|
describe "order list" do
|
|
setup %{conn: conn, user: user} do
|
|
conn = log_in_user(conn, user)
|
|
%{conn: conn}
|
|
end
|
|
|
|
test "renders empty state when no orders", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders")
|
|
|
|
assert html =~ "No orders yet"
|
|
assert html =~ "Orders"
|
|
end
|
|
|
|
test "renders orders table", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid", customer_email: "test@shop.com")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders")
|
|
|
|
assert html =~ order.order_number
|
|
assert html =~ "test@shop.com"
|
|
assert html =~ "paid"
|
|
end
|
|
|
|
test "filters by status", %{conn: conn} do
|
|
paid = order_fixture(payment_status: "paid")
|
|
_pending = order_fixture()
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/orders")
|
|
|
|
html = render_click(view, "filter", %{"status" => "paid"})
|
|
|
|
assert html =~ paid.order_number
|
|
end
|
|
|
|
test "navigates to order detail", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders")
|
|
|
|
assert html =~ ~p"/admin/orders/#{order}"
|
|
end
|
|
end
|
|
|
|
describe "order detail" do
|
|
setup %{conn: conn, user: user} do
|
|
conn = log_in_user(conn, user)
|
|
%{conn: conn}
|
|
end
|
|
|
|
test "renders order details", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid", customer_email: "buyer@example.com")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ order.order_number
|
|
assert html =~ "buyer@example.com"
|
|
assert html =~ "paid"
|
|
assert html =~ "Order details"
|
|
end
|
|
|
|
test "shows line items", %{conn: conn} do
|
|
order = order_fixture(product_name: "Cool T-shirt", variant_title: "Blue / XL")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "Cool T-shirt"
|
|
assert html =~ "Blue / XL"
|
|
end
|
|
|
|
test "shows shipping address", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
{:ok, updated_order} =
|
|
Berrypod.Orders.update_order(order, %{
|
|
shipping_address: %{
|
|
"name" => "Jane Doe",
|
|
"line1" => "42 Test Street",
|
|
"city" => "London",
|
|
"postal_code" => "SW1A 1AA",
|
|
"country" => "GB"
|
|
}
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{updated_order}")
|
|
|
|
assert html =~ "Jane Doe"
|
|
assert html =~ "42 Test Street"
|
|
assert html =~ "London"
|
|
assert html =~ "SW1A 1AA"
|
|
end
|
|
|
|
test "redirects when order not found", %{conn: conn} do
|
|
fake_id = Ecto.UUID.generate()
|
|
|
|
{:error, {:live_redirect, %{to: "/admin/orders"}}} =
|
|
live(conn, ~p"/admin/orders/#{fake_id}")
|
|
end
|
|
|
|
test "shows timeline card", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "Timeline"
|
|
assert html =~ "unfulfilled"
|
|
end
|
|
|
|
test "shows submit button for paid unfulfilled orders", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "Submit to provider"
|
|
end
|
|
|
|
test "shows retry button for failed fulfilment", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
{:ok, order} =
|
|
Berrypod.Orders.update_fulfilment(order, %{fulfilment_status: "failed"})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "Retry submission"
|
|
end
|
|
|
|
test "shows refresh button for submitted orders", %{conn: conn} do
|
|
{order, _v, _p, _c} =
|
|
Berrypod.OrdersFixtures.submitted_order_fixture()
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "Refresh status"
|
|
end
|
|
|
|
test "shows tracking info when available", %{conn: conn} do
|
|
{order, _v, _p, _c} =
|
|
Berrypod.OrdersFixtures.submitted_order_fixture()
|
|
|
|
{:ok, order} =
|
|
Berrypod.Orders.update_fulfilment(order, %{
|
|
fulfilment_status: "shipped",
|
|
tracking_number: "TRACK123",
|
|
tracking_url: "https://track.example.com/TRACK123",
|
|
shipped_at: DateTime.utc_now() |> DateTime.truncate(:second)
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "TRACK123"
|
|
assert html =~ "https://track.example.com/TRACK123"
|
|
end
|
|
end
|
|
|
|
describe "order timeline" do
|
|
setup %{conn: conn, user: user} do
|
|
conn = log_in_user(conn, user)
|
|
%{conn: conn}
|
|
end
|
|
|
|
test "renders timeline entries for an order", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
Berrypod.ActivityLog.log_event("order.created", "Order placed", order_id: order.id)
|
|
|
|
Berrypod.ActivityLog.log_event("order.submitted", "Submitted to provider",
|
|
order_id: order.id
|
|
)
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "Order placed"
|
|
assert html =~ "Submitted to provider"
|
|
end
|
|
|
|
test "shows empty state when no timeline entries", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
assert html =~ "No activity recorded yet"
|
|
end
|
|
|
|
test "updates timeline via PubSub", %{conn: conn} do
|
|
order = order_fixture(payment_status: "paid")
|
|
|
|
{:ok, view, _html} = live(conn, ~p"/admin/orders/#{order}")
|
|
|
|
Berrypod.ActivityLog.log_event("order.submitted", "Submitted to provider",
|
|
order_id: order.id
|
|
)
|
|
|
|
html = render(view)
|
|
assert html =~ "Submitted to provider"
|
|
end
|
|
end
|
|
|
|
describe "order list fulfilment column" do
|
|
setup %{conn: conn, user: user} do
|
|
conn = log_in_user(conn, user)
|
|
%{conn: conn}
|
|
end
|
|
|
|
test "shows fulfilment badge in table", %{conn: conn} do
|
|
order_fixture(payment_status: "paid")
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/orders")
|
|
|
|
assert html =~ "Fulfilment"
|
|
assert html =~ "unfulfilled"
|
|
end
|
|
end
|
|
end
|