defmodule BerrypodWeb.WebhookControllerTest do use BerrypodWeb.ConnCase import Berrypod.ProductsFixtures @webhook_secret "test_webhook_secret_123" setup do _conn = provider_connection_fixture(%{ provider_type: "printify", config: %{"shop_id" => "12345", "webhook_secret" => @webhook_secret} }) :ok end describe "POST /webhooks/printify" do test "returns 401 without signature", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") |> post(~p"/webhooks/printify", %{type: "product:updated"}) assert json_response(conn, 401)["error"] == "Invalid signature" end test "returns 401 with invalid signature", %{conn: conn} do body = Jason.encode!(%{type: "product:updated", resource: %{id: "123"}}) conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pfy-signature", "sha256=invalid") |> post(~p"/webhooks/printify", body) assert json_response(conn, 401)["error"] == "Invalid signature" end test "accepts valid signature and returns 200", %{conn: conn} do body = Jason.encode!(%{type: "product:updated", resource: %{id: "123", shop_id: "12345"}}) signature = compute_signature(body, @webhook_secret) conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pfy-signature", "sha256=#{signature}") |> post(~p"/webhooks/printify", body) assert json_response(conn, 200)["status"] == "ok" end test "handles product:updated event", %{conn: conn} do body = Jason.encode!(%{type: "product:updated", resource: %{id: "123", shop_id: "12345"}}) signature = compute_signature(body, @webhook_secret) conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pfy-signature", "sha256=#{signature}") |> post(~p"/webhooks/printify", body) # Should return 200 even if job processing fails (inline mode tries to execute) assert json_response(conn, 200)["status"] == "ok" end test "handles product:deleted event", %{conn: conn} do body = Jason.encode!(%{type: "product:deleted", resource: %{id: "456"}}) signature = compute_signature(body, @webhook_secret) conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pfy-signature", "sha256=#{signature}") |> post(~p"/webhooks/printify", body) assert json_response(conn, 200)["status"] == "ok" end test "returns 401 when no webhook secret configured", %{conn: conn} do # Remove the provider connection to simulate no secret Berrypod.Repo.delete_all(Berrypod.Products.ProviderConnection) body = Jason.encode!(%{type: "product:updated", resource: %{id: "123"}}) signature = compute_signature(body, @webhook_secret) conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pfy-signature", "sha256=#{signature}") |> post(~p"/webhooks/printify", body) assert json_response(conn, 401)["error"] == "Invalid signature" end end defp compute_signature(body, secret) do :crypto.mac(:hmac, :sha256, secret, body) |> Base.encode16(case: :lower) end describe "POST /webhooks/printful" do @printful_secret "printful_test_secret_456" setup do _conn = provider_connection_fixture(%{ provider_type: "printful", config: %{"store_id" => "99999", "webhook_secret" => @printful_secret} }) :ok end test "returns 401 without token", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") |> post(~p"/webhooks/printful", %{type: "product_updated"}) assert json_response(conn, 401)["error"] == "Invalid token" end test "returns 401 with wrong token", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pf-webhook-token", "wrong_token") |> post(~p"/webhooks/printful", %{type: "product_updated"}) assert json_response(conn, 401)["error"] == "Invalid token" end test "accepts valid token via header", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pf-webhook-token", @printful_secret) |> post(~p"/webhooks/printful", %{type: "product_updated", data: %{}}) assert json_response(conn, 200)["status"] == "ok" end test "accepts valid token via query param", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") |> post(~p"/webhooks/printful?token=#{@printful_secret}", %{ type: "product_updated", data: %{} }) assert json_response(conn, 200)["status"] == "ok" end test "handles unknown event gracefully", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pf-webhook-token", @printful_secret) |> post(~p"/webhooks/printful", %{type: "unknown_event", data: %{}}) assert json_response(conn, 200)["status"] == "ok" end test "returns 401 when no webhook secret configured", %{conn: conn} do Berrypod.Repo.delete_all(Berrypod.Products.ProviderConnection) conn = conn |> put_req_header("content-type", "application/json") |> put_req_header("x-pf-webhook-token", @printful_secret) |> post(~p"/webhooks/printful", %{type: "product_updated"}) assert json_response(conn, 401)["error"] == "Invalid token" end end end