diff --git a/lib/recognizer/accounts.ex b/lib/recognizer/accounts.ex index c39146c2..75656b4f 100644 --- a/lib/recognizer/accounts.ex +++ b/lib/recognizer/accounts.ex @@ -220,7 +220,7 @@ defmodule Recognizer.Accounts do def maybe_create_big_commerce_customer({:ok, user}) do if BigCommerce.enabled?() do - BigCommerce.create_customer(user) + BigCommerce.get_or_create_customer(user) else {:ok, user} end diff --git a/lib/recognizer/bigcommerce.ex b/lib/recognizer/bigcommerce.ex index 37f661ef..bee0fd92 100644 --- a/lib/recognizer/bigcommerce.ex +++ b/lib/recognizer/bigcommerce.ex @@ -26,6 +26,24 @@ defmodule Recognizer.BigCommerce do end end + def get_or_create_customer(%{email: email, id: id} = user) do + case Client.get_customers(emails: [email]) do + {:ok, []} -> create_customer(user) + {:ok, [customer_id]} -> + Logger.info("found existing customer for account create: #{inspect(email)}") + Repo.insert(%Customer{user_id: id, bc_id: customer_id}) + {:ok, user} + e -> + Logger.error("error while getting or creating customer: #{inspect(e)}") + {:error, e} + end + end + + def get_or_create_customer(e) do + Logger.error("unexpected customer #{e}") + {:error, "unexpected customer"} + end + def update_customer(user) do case Client.update_customer(Repo.preload(user, :bigcommerce_user)) do {:ok, _} -> diff --git a/lib/recognizer/bigcommerce/client.ex b/lib/recognizer/bigcommerce/client.ex index a1cfd85e..56991f95 100644 --- a/lib/recognizer/bigcommerce/client.ex +++ b/lib/recognizer/bigcommerce/client.ex @@ -77,6 +77,53 @@ defmodule Recognizer.BigCommerce.Client do end end + def get_customers(queries \\ []) do + with params <- customer_queries_as_params(queries), + full_uri <- customers_uri(), + headers <- default_headers(), + :ok <- Logger.debug("GET bigcommerce customers by params: #{inspect(params)}"), + {:ok, %Response{body: response, status_code: 200}} <- + http_client().get(full_uri, headers, params: params), + {:ok, %{"data" => customers}} <- Jason.decode(response), + customer_ids <- Enum.map(customers, fn %{"id" => id} -> id end) do + {:ok, customer_ids} + else + {:ok, %Response{status_code: 429, headers: headers}} -> + sleep_for_rate_limit(headers) + get_customers(queries) + + {:ok, %Response{status_code: 503}} -> + sleep_for_rate_limit(@default_retry_ms) + get_customers(queries) + + {_, %Response{body: body, status_code: code}} -> + Logger.error("Unexpected http response #{inspect(code)}: #{inspect(body)}") + {:error, code} + + e -> + Logger.error("cannot get customers with error: #{inspect(e)}") + {:error, e} + end + end + + defp customer_queries_as_params(queries) do + [] + |> Keyword.merge( + case Keyword.get(queries, :emails) do + nil -> [] + [] -> [] + emails -> [{"email:in", Enum.join(emails, ",")}] + end + ) + |> Keyword.merge( + case Keyword.get(queries, :ids) do + nil -> [] + [] -> [] + ids -> [{"id:in", Enum.join(ids, ",")}] + end + ) + end + defp get_id(response) do case Jason.decode(response) do {:ok, %{"data" => [%{"id" => id}]}} -> {:ok, id} diff --git a/test/recognizer/accounts_test.exs b/test/recognizer/accounts_test.exs index 29bdc127..86ff3881 100644 --- a/test/recognizer/accounts_test.exs +++ b/test/recognizer/accounts_test.exs @@ -61,6 +61,11 @@ defmodule Recognizer.AccountsTest do end describe "register_user/1" do + setup do + stub(HTTPoisonMock, :get, fn _, _, _ -> empty_bigcommerce_response() end) + :ok + end + test "requires email and password to be set" do {:error, changeset} = Accounts.register_user(%{}) diff --git a/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs b/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs index 2e78e413..a566dc6e 100644 --- a/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs +++ b/test/recognizer_web/controllers/accounts/api/user_registration_controller_test.exs @@ -13,6 +13,11 @@ defmodule RecognizerWeb.Api.UserRegistrationControllerTest do setup :verify_on_exit! setup :register_and_log_in_admin + setup do + stub(HTTPoisonMock, :get, fn _, _, _ -> empty_bigcommerce_response() end) + :ok + end + describe "POST /api/create-account" do test "POST /api/create-account is limited to staff only", %{conn: conn} do user = %{ diff --git a/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs b/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs index 7190800d..f50ba633 100644 --- a/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs +++ b/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs @@ -68,6 +68,7 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do describe "POST /users/register" do @tag :capture_log test "creates account and prompts for verification", %{conn: conn} do + expect(HTTPoisonMock, :get, 1, fn _, _, _ -> empty_bigcommerce_response() end) expect(HTTPoisonMock, :post, 1, fn _, _, _ -> ok_bigcommerce_response() end) conn = @@ -80,6 +81,19 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do assert Repo.get_by(BCCustomerUser, bc_id: 1001) end + test "creates account linked to existing bigcommerce account", %{conn: conn} do + expect(HTTPoisonMock, :get, 1, fn _, _, _ -> ok_bigcommerce_response() end) + + conn = + post(conn, Routes.user_registration_path(conn, :create), %{ + "user" => params_for(:user) + }) + + refute Recognizer.Guardian.Plug.current_resource(conn) + assert redirected_to(conn) =~ "/prompt/verification" + assert Repo.get_by(BCCustomerUser, bc_id: 1001) + end + test "render errors for invalid data", %{conn: conn} do conn = post(conn, Routes.user_registration_path(conn, :create), %{ @@ -95,6 +109,7 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do @tag :capture_log test "renders an error page for a bigcommerce failure", %{conn: conn} do + expect(HTTPoisonMock, :get, 1, fn _, _, _ -> empty_bigcommerce_response() end) expect(HTTPoisonMock, :post, 1, fn _, _, _ -> bad_bigcommerce_response() end) conn = @@ -109,6 +124,7 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do end test "rate limits account creation", %{conn: conn} do + stub(HTTPoisonMock, :get, fn _, _, _ -> empty_bigcommerce_response() end) stub(HTTPoisonMock, :post, fn _, _, _ -> ok_bigcommerce_response() end) Enum.each(0..20, fn _ -> diff --git a/test/support/bigcommerce_test_helpers.ex b/test/support/bigcommerce_test_helpers.ex index 38ed5cf0..73472e29 100644 --- a/test/support/bigcommerce_test_helpers.ex +++ b/test/support/bigcommerce_test_helpers.ex @@ -7,6 +7,12 @@ defmodule Recognizer.BigCommerceTestHelpers do {:ok, %HTTPoison.Response{body: body, status_code: 200}} end + def empty_bigcommerce_response() do + body = Jason.encode!(%{data: []}) + + {:ok, %HTTPoison.Response{body: body, status_code: 200}} + end + def bad_bigcommerce_response() do body = Jason.encode!(%{errors: [%{failure: 1}]})