From 7fd2e6680627a1680dd6acf2499edc614f5e34f5 Mon Sep 17 00:00:00 2001 From: Igor Fernandes Date: Mon, 13 Jan 2025 14:26:16 -0300 Subject: [PATCH] Add EntitlementOverride upsert_or_delete support (#143) * Add EntitlementOverride upsert_or_delete support * finish module doc --- lib/chargebeex/builder.ex | 3 + .../entitlement_override.ex | 99 +++++++++++++++++++ mix.exs | 1 + .../builder/entitlement_override_test.exs | 37 +++++++ test/chargebeex/entitlement_override_test.exs | 84 ++++++++++++++++ test/support/fixtures/entitlement_override.ex | 34 +++++++ 6 files changed, 258 insertions(+) create mode 100644 lib/chargebeex/entitlement_override/entitlement_override.ex create mode 100644 test/chargebeex/builder/entitlement_override_test.exs create mode 100644 test/chargebeex/entitlement_override_test.exs create mode 100644 test/support/fixtures/entitlement_override.ex diff --git a/lib/chargebeex/builder.ex b/lib/chargebeex/builder.ex index dba23b7..00c8ad7 100644 --- a/lib/chargebeex/builder.ex +++ b/lib/chargebeex/builder.ex @@ -8,6 +8,7 @@ defmodule Chargebeex.Builder do Customer, CustomerEntitlement, Event, + EntitlementOverride, Invoice, HostedPage, PaymentSource, @@ -40,6 +41,7 @@ defmodule Chargebeex.Builder do def build_resource(%{"customer_entitlement" => params}), do: CustomerEntitlement.build(params) def build_resource(%{"portal_session" => params}), do: PortalSession.build(params) def build_resource(%{"event" => params}), do: Event.build(params) + def build_resource(%{"entitlement_override" => params}), do: EntitlementOverride.build(params) def build_resource(%{"card" => params}), do: Card.build(params) def build_resource(%{"billing_address" => params}), do: BillingAddress.build(params) def build_resource(%{"payment_source" => params}), do: PaymentSource.build(params) @@ -61,6 +63,7 @@ defmodule Chargebeex.Builder do def build_resource("customer_entitlement", params), do: CustomerEntitlement.build(params) def build_resource("portal_session", params), do: PortalSession.build(params) def build_resource("event", params), do: Event.build(params) + def build_resource("entitlement_override", params), do: EntitlementOverride.build(params) def build_resource("card", params), do: Card.build(params) def build_resource("billing_address", params), do: BillingAddress.build(params) def build_resource("payment_source", params), do: PaymentSource.build(params) diff --git a/lib/chargebeex/entitlement_override/entitlement_override.ex b/lib/chargebeex/entitlement_override/entitlement_override.ex new file mode 100644 index 0000000..2a1fd08 --- /dev/null +++ b/lib/chargebeex/entitlement_override/entitlement_override.ex @@ -0,0 +1,99 @@ +defmodule Chargebeex.EntitlementOverride do + use TypedStruct + + @resource "entitlement_override" + use Chargebeex.Resource, resource: @resource, only: [] + + @moduledoc """ + Struct that represent a Chargebee's API entitlement override resource. + """ + + @typedoc """ + "subscription" + """ + @type entity_type :: String.t() + + typedstruct do + field :entity_id, String.t() + field :entity_type, entity_type() + field :feature_id, String.t() + field :feature_name, String.t() + field :id, String.t() + field :name, String.t() + field :object, String.t() + field :value, String.t() + end + + use ExConstructor, :build + + @typedoc """ + A single entitlement override map. + """ + @type entitlement_override :: %{ + required(:feature_id) => String.t(), + optional(:value) => String.t(), + optional(:expires_at) => non_neg_integer(), + optional(:effective_from) => non_neg_integer() + } + + @typedoc """ + Parameters for the `upsert_or_remove` function. + The `action` field must be either `upsert` or `remove`. + """ + @type upsert_or_remove_params :: %{ + required(:action) => :upsert | :remove, + optional(:entitlement_overrides) => [entitlement_override()], + optional(any()) => any() + } + + @doc """ + Upserts or removes a set of entitlement_overrides for a subscription depending on the action specified + + More info: https://apidocs.eu.chargebee.com/docs/api/entitlement_overrides?lang=curl#upsert/remove_entitlement_overrides_for_a_subscription + + ## Examples + + iex(1)> subscription_id = "BTLybZUInBGCXDMY" + "BTLybZUInBGCXDMY" + iex(2)> params = %{ + ...(2)> "action" => :upsert, + ...(2)> "entitlement_overrides" => [ + ...(2)> %{ + ...(2)> "feature_id" => "foo", + ...(2)> "value" => "false" + ...(2)> } + ...(2)> ] + ...(2)> } + %{ + "action" => :upsert, + "entitlement_overrides" => [ + %{"feature_id" => "foo", "value" => "false"} + ] + } + iex(3)> Chargebeex.EntitlementOverride.upsert_or_remove(subscription_id, params) + {:ok, + [ + %Chargebeex.EntitlementOverride{...} + ], %{"next_offset" => nil}} + """ + @spec upsert_or_remove(String.t(), upsert_or_remove_params(), keyword()) :: any() + def upsert_or_remove(subscription_id, params, opts \\ []) + + def upsert_or_remove(_subscription_id, %{"action" => action} = _params, _opts) + when action not in [:upsert, :remove] do + raise ArgumentError, """ + Invalid action provided to `upsert_or_remove`. Ensure that `params[:action]` is either `:upsert` or `:remove`. + """ + end + + def upsert_or_remove(subscription_id, params, opts) do + nested_generic_action_without_id( + :post, + [subscription: subscription_id], + @resource, + "entitlement_overrides", + params, + opts + ) + end +end diff --git a/mix.exs b/mix.exs index b91cb7d..0efc647 100644 --- a/mix.exs +++ b/mix.exs @@ -76,6 +76,7 @@ defmodule Chargebeex.MixProject do Chargebeex.Customer, Chargebeex.CustomerEntitlement, Chargebeex.Event, + Chargebeex.EntitlementOverride, Chargebeex.Invoice, Chargebeex.PortalSession, Chargebeex.Subscription, diff --git a/test/chargebeex/builder/entitlement_override_test.exs b/test/chargebeex/builder/entitlement_override_test.exs new file mode 100644 index 0000000..c7de236 --- /dev/null +++ b/test/chargebeex/builder/entitlement_override_test.exs @@ -0,0 +1,37 @@ +defmodule Chargebeex.Builder.EntitlementOverrideTest do + use ExUnit.Case, async: true + + alias Chargebeex.Builder + alias Chargebeex.Fixtures.EntitlementOverride, as: EntitlementOverrideFixture + alias Chargebeex.EntitlementOverride + + describe "build/1" do + test "should build an entitlement override" do + builded = + EntitlementOverrideFixture.retrieve() + |> Jason.decode!() + |> Builder.build() + + assert %{"entitlement_override" => %EntitlementOverride{}} = builded + end + + test "should have the entitlement_override params" do + entitlement_override = + EntitlementOverrideFixture.retrieve() + |> Jason.decode!() + |> Builder.build() + |> Map.get("entitlement_override") + + params = EntitlementOverrideFixture.entitlement_override_params() |> Jason.decode!() + + assert entitlement_override.entity_id == Map.get(params, "entity_id") + assert entitlement_override.entity_type == Map.get(params, "entity_type") + assert entitlement_override.feature_id == Map.get(params, "feature_id") + assert entitlement_override.feature_name == Map.get(params, "feature_name") + assert entitlement_override.id == Map.get(params, "id") + assert entitlement_override.name == Map.get(params, "name") + assert entitlement_override.object == Map.get(params, "object") + assert entitlement_override.value == Map.get(params, "value") + end + end +end diff --git a/test/chargebeex/entitlement_override_test.exs b/test/chargebeex/entitlement_override_test.exs new file mode 100644 index 0000000..4be925c --- /dev/null +++ b/test/chargebeex/entitlement_override_test.exs @@ -0,0 +1,84 @@ +defmodule Chargebeex.EntitlementOverrideTest do + use ExUnit.Case, async: true + + import Hammox + + alias Chargebeex.Fixtures.Common + alias Chargebeex.EntitlementOverride + alias Chargebeex.Fixtures.EntitlementOverride, as: EntitlementOverrideFixture + + setup :verify_on_exit! + + describe "upsert_or_remove" do + test "with bad authentication should fail" do + unauthorized = Common.unauthorized() + + expect( + Chargebeex.HTTPClientMock, + :post, + fn url, body, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/subscriptions/subscription_id/entitlement_overrides" + + assert headers == [ + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + + assert body == "" + + {:ok, 401, [], Jason.encode!(unauthorized)} + end + ) + + assert {:error, 401, [], ^unauthorized} = + EntitlementOverride.upsert_or_remove("subscription_id", %{}) + end + + test "with valid data should succeed" do + expect( + Chargebeex.HTTPClientMock, + :post, + fn url, data, headers -> + assert url == + "https://test-namespace.chargebee.com/api/v2/subscriptions/subscription_id/entitlement_overrides" + + assert headers == [ + {"Authorization", "Basic dGVzdF9jaGFyZ2VlYmVlX2FwaV9rZXk6"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ] + + assert data == + "action=upsert&entitlement_overrides[feature_id][0]=override-1935f8d6-b791-4181-9fa2-65fd8bfbd7ae&entitlement_overrides[value][0]=true" + + {:ok, 200, [], EntitlementOverrideFixture.list()} + end + ) + + subscription_id = "subscription_id" + + params = %{ + "action" => :upsert, + "entitlement_overrides" => [ + %{ + "feature_id" => "override-1935f8d6-b791-4181-9fa2-65fd8bfbd7ae", + "value" => "true" + } + ] + } + + assert {:ok, [%EntitlementOverride{}], %{"next_offset" => nil}} = + Chargebeex.EntitlementOverride.upsert_or_remove(subscription_id, params) + end + + test "raises an error when action is invalid" do + subscription_id = "sub_123" + invalid_params = %{"action" => :invalid_action} + opts = [] + + assert_raise ArgumentError, ~r/Invalid action provided to `upsert_or_remove`/, fn -> + EntitlementOverride.upsert_or_remove(subscription_id, invalid_params, opts) + end + end + end +end diff --git a/test/support/fixtures/entitlement_override.ex b/test/support/fixtures/entitlement_override.ex new file mode 100644 index 0000000..23a8ff6 --- /dev/null +++ b/test/support/fixtures/entitlement_override.ex @@ -0,0 +1,34 @@ +defmodule Chargebeex.Fixtures.EntitlementOverride do + def entitlement_override_params() do + """ + { + "id": "override-1935f8d6-b791-4181-9fa2-65fd8bfbd7ae", + "entity_id": "Jdf63vklssSDFdb", + "entity_type": "subscription", + "feature_id": "fea-be1a9281-d8df-48ce-82e2-294667eb4d94", + "feature_name": "Quickbooks Integration_123", + "name": "Available", + "object": "entitlement_override", + "value": "true" + } + """ + end + + def retrieve() do + """ + { + "entitlement_override": #{entitlement_override_params()} + } + """ + end + + def list() do + """ + { + "list": [ + #{retrieve()} + ] + } + """ + end +end