From 2b63c179b6e99e56ee4b960c1b66a5d628a958e9 Mon Sep 17 00:00:00 2001 From: Mike Cifelli <26522946+macifell@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:00:37 -0400 Subject: [PATCH 1/3] Redirect bc to home page (#152) --- lib/recognizer/bigcommerce.ex | 1 + .../controllers/accounts/user_registration_controller_test.exs | 2 +- .../controllers/accounts/user_session_controller_test.exs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/recognizer/bigcommerce.ex b/lib/recognizer/bigcommerce.ex index 8dc6365c..37f661ef 100644 --- a/lib/recognizer/bigcommerce.ex +++ b/lib/recognizer/bigcommerce.ex @@ -61,6 +61,7 @@ defmodule Recognizer.BigCommerce do user |> Recognizer.Repo.preload(:bigcommerce_user) |> jwt_claims() + |> Map.put("redirect_to", "/") |> Token.generate_and_sign(jwt_signer()) token 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 ff946a06..7190800d 100644 --- a/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs +++ b/test/recognizer_web/controllers/accounts/user_registration_controller_test.exs @@ -41,7 +41,7 @@ defmodule RecognizerWeb.Accounts.UserRegistrationControllerTest do |> Jason.decode!(keys: :atoms) assert redirect =~ "http://localhost/login/" - refute :redirect_to in Map.keys(jwt_payload) + assert %{redirect_to: "/"} = jwt_payload end test "redirects to bigcommerce checkout if already logged in", %{conn: conn} do diff --git a/test/recognizer_web/controllers/accounts/user_session_controller_test.exs b/test/recognizer_web/controllers/accounts/user_session_controller_test.exs index 6c0d426c..11dd6a97 100644 --- a/test/recognizer_web/controllers/accounts/user_session_controller_test.exs +++ b/test/recognizer_web/controllers/accounts/user_session_controller_test.exs @@ -40,7 +40,7 @@ defmodule RecognizerWeb.Accounts.UserSessionControllerTest do |> Jason.decode!(keys: :atoms) assert redirect =~ "http://localhost/login/" - refute :redirect_to in Map.keys(jwt_payload) + assert %{redirect_to: "/"} = jwt_payload end test "redirects to bigcommerce checkout if already logged in", %{conn: conn, user: user} do From 285a7ef5bc62ef60e771a766e8e13973fc757fc4 Mon Sep 17 00:00:00 2001 From: Andrew Hebert <108428778+ah-s76@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:12:49 -0500 Subject: [PATCH 2/3] enable bc redirect (#153) --- config/releases.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/releases.exs b/config/releases.exs index 1a268563..f9743f90 100644 --- a/config/releases.exs +++ b/config/releases.exs @@ -78,7 +78,7 @@ config :recognizer, Recognizer.BigCommerce, login_path: recognizer_config["BIGCOMMERCE_LOGIN_PATH"], logout_path: recognizer_config["BIGCOMMERCE_LOGOUT_PATH"], http_client: HTTPoison, - enabled?: false + enabled?: true config :recognizer, Recognizer.Accounts, cache_expiry: recognizer_config["ACCOUNT_CACHE_EXPIRY_SECONDS"] From fdad9ffc5ca204c39bcec955ec17c3e6876b9c5c Mon Sep 17 00:00:00 2001 From: Andrew Hebert Date: Tue, 12 Nov 2024 15:07:29 -0500 Subject: [PATCH 3/3] link existing customer --- lib/recognizer/accounts.ex | 2 +- lib/recognizer/bigcommerce.ex | 18 +++++++ lib/recognizer/bigcommerce/client.ex | 47 +++++++++++++++++++ test/recognizer/accounts_test.exs | 5 ++ .../api/user_registration_controller_test.exs | 5 ++ .../user_registration_controller_test.exs | 16 +++++++ test/support/bigcommerce_test_helpers.ex | 6 +++ 7 files changed, 98 insertions(+), 1 deletion(-) 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}]})