Skip to content

Commit

Permalink
Tests passing with custom IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
nkezhaya committed Oct 18, 2019
1 parent 1ddf9cb commit 9c117dd
Show file tree
Hide file tree
Showing 25 changed files with 137 additions and 105 deletions.
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ config :stripe_mock, StripeMockWeb.Endpoint,
render_errors: [view: StripeMockWeb.ErrorView, accepts: ~w(json)]

config :stripe_mock, ecto_repos: [StripeMock.Repo]
config :stripe_mock, StripeMock.Repo, migration_primary_key: [type: :binary_id]

# Configures Elixir's Logger
config :logger, :console,
Expand Down
4 changes: 3 additions & 1 deletion lib/stripe_mock/api/card.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule StripeMock.API.Card do
schema "cards" do
field :brand, :string
field :deleted, :boolean
field :metadata, :map, default: %{}
field :last4, :string
field :source, :string

Expand All @@ -15,6 +14,7 @@ defmodule StripeMock.API.Card do

belongs_to :customer, API.Customer

common_fields()
timestamps()
end

Expand All @@ -23,6 +23,7 @@ defmodule StripeMock.API.Card do
card
|> cast(attrs, [:source, :metadata])
|> validate_required([:source, :metadata])
|> put_common_fields()
end

@doc false
Expand All @@ -39,6 +40,7 @@ defmodule StripeMock.API.Card do
|> validate_required([:number, :exp_month, :exp_year, :cvc])
|> set_brand()
|> set_last4()
|> put_common_fields()
end

defp set_brand(changeset) do
Expand Down
1 change: 1 addition & 0 deletions lib/stripe_mock/api/charge.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ defmodule StripeMock.API.Charge do
def capture_changeset(charge, payment_intent) do
charge
|> change(%{payment_intent_id: payment_intent.id})
|> put_common_fields()
end

defp set_customer_and_source(changeset) do
Expand Down
4 changes: 2 additions & 2 deletions lib/stripe_mock/api/customer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ defmodule StripeMock.API.Customer do
schema "customers" do
field :currency, :string, default: "usd"
field :deleted, :boolean, default: false
field :description, :string
field :email, :string
field :metadata, :map, default: %{}
field :name, :string
field :phone, :string

common_fields()
timestamps()
end

Expand All @@ -18,6 +17,7 @@ defmodule StripeMock.API.Customer do
customer
|> cast(attrs, [:created, :currency, :description, :email, :name, :phone])
|> validate_email()
|> put_common_fields()
end

defp validate_email(changeset) do
Expand Down
3 changes: 2 additions & 1 deletion lib/stripe_mock/api/refund.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ defmodule StripeMock.API.Refund do

schema "refunds" do
field :amount, :integer
field :metadata, :map, default: %{}
field :reason, :string

belongs_to :charge, API.Charge

common_fields()
timestamps()
end

Expand All @@ -21,6 +21,7 @@ defmodule StripeMock.API.Refund do
|> validate_required(:amount)
|> validate_amount()
|> validate_reason()
|> put_common_fields()
end

@doc false
Expand Down
3 changes: 3 additions & 0 deletions lib/stripe_mock/api/source.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ defmodule StripeMock.API.Source do
schema "sources" do
belongs_to :card, API.Card
belongs_to :token, API.Card
common_fields()
timestamps()
end

@doc false
def changeset(token, attrs) do
token
|> cast(attrs, [:card_id, :token_id])
|> put_common_fields()
end
end
2 changes: 2 additions & 0 deletions lib/stripe_mock/api/token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule StripeMock.API.Token do

belongs_to :card, API.Card

common_fields()
timestamps()
end

Expand All @@ -18,6 +19,7 @@ defmodule StripeMock.API.Token do
|> cast_assoc(:card, with: &API.Card.token_changeset/2)
|> set_type()
|> validate_required([:type])
|> put_common_fields()
end

defp set_type(changeset) do
Expand Down
35 changes: 33 additions & 2 deletions lib/stripe_mock/id.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,42 @@ defmodule StripeMock.ID do
@moduledoc """
Generates a somewhat Stripe-esque random ID string. Pretty much a base52-encoded UUID.
"""
alias StripeMock.API

@spec generate() :: String.t()
def generate() do
:crypto.strong_rand_bytes(16)
from_bytes(:crypto.strong_rand_bytes(16))
end

@spec from_uuid(String.t()) :: String.t() | none()
def from_uuid(uuid) do
{:ok, bytes} = Ecto.UUID.dump(uuid)
from_bytes(bytes)
end

defp from_bytes(bytes) do
bytes
|> :erlang.binary_to_list()
|> Enum.reduce(1, &(&1 * &2))
|> Enum.reduce(1, &if(&1 > 0, do: &1 * &2, else: &2))
|> StripeMock.Base52.encode()
end

@spec type(module()) :: atom()
def type(%{} = map), do: type(map.__struct__)
def type(API.Card), do: :card
def type(API.Charge), do: :charge
def type(API.Customer), do: :customer
def type(API.PaymentMethod), do: :payment_method
def type(API.PaymentIntent), do: :payment_intent
def type(API.Refund), do: :refund
def type(API.Token), do: :token

@spec type(map()) :: String.t()
def prefix(%API.Card{}), do: "card"
def prefix(%API.Charge{}), do: "ch"
def prefix(%API.Customer{}), do: "cus"
def prefix(%API.PaymentMethod{}), do: "pm"
def prefix(%API.PaymentIntent{}), do: "pi"
def prefix(%API.Refund{}), do: "re"
def prefix(%API.Token{}), do: "tok"
end
7 changes: 1 addition & 6 deletions lib/stripe_mock/migrator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,7 @@ defmodule StripeMock.Migrator do
IO.puts("Starting dependencies")
Enum.each(@start_apps, &Application.ensure_all_started/1)

Application.load(:basic_space)

# Start the Repo(s) for app
# Switch pool_size to 2 for ecto > 3.0
IO.puts("Starting repos")
Enum.each(@repos, & &1.start_link(pool_size: 2))
Application.load(:stripe_mock)

# Run migrations
Enum.each(@repos, &run_migrations_for/1)
Expand Down
38 changes: 13 additions & 25 deletions lib/stripe_mock/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule StripeMock.Repo do
alias StripeMock.API

@impl true
def init(_type, config) do
def init(:supervisor, config) do
{uri, _} = System.cmd("pg_tmp", ["-t"])

[[username, host, port, database]] =
Expand All @@ -24,16 +24,23 @@ defmodule StripeMock.Repo do
{:ok, config}
end

def init(_type, config) do
{:ok, config}
end

def fetch(schema, id) do
with true <- valid_id?(id),
%{} = object <- get(schema, id) do
{:ok, object}
if valid_uuid?(id) do
get(schema, id)
else
_ -> {:error, :not_found}
get_by(schema, stripe_id: id)
end
|> case do
nil -> {:error, :not_found}
object -> {:ok, object}
end
end

defp valid_id?(id) do
defp valid_uuid?(id) do
case Ecto.UUID.cast(id) do
{:ok, _} -> true
_ -> false
Expand Down Expand Up @@ -61,23 +68,4 @@ defmodule StripeMock.Repo do
changeset
end
end

defp generate_id(schema) do
prefix(schema) <> "_" <> StripeMock.ID.generate()
end

def type(%{} = map), do: type(map.__struct__)
def type(API.Card), do: :card
def type(API.Charge), do: :charge
def type(API.Customer), do: :customer
def type(API.PaymentIntent), do: :payment_intent
def type(API.Refund), do: :refund
def type(API.Token), do: :token

def prefix(%API.Card{}), do: "card"
def prefix(%API.Charge{}), do: "ch"
def prefix(%API.Customer{}), do: "cus"
def prefix(%API.PaymentIntent{}), do: "pi"
def prefix(%API.Refund{}), do: "re"
def prefix(%API.Token{}), do: "tok"
end
31 changes: 27 additions & 4 deletions lib/stripe_mock/schema.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
defmodule StripeMock.Schema do
import Ecto.Changeset
alias StripeMock.ID

defmacro __using__(_) do
quote do
use Ecto.Schema
Expand All @@ -7,22 +10,42 @@ defmodule StripeMock.Schema do
import unquote(__MODULE__)
alias StripeMock.{API, Repo}

Module.put_attribute(__MODULE__, :primary_key, {:id, :binary_id, autogenerate: true})
Module.put_attribute(__MODULE__, :primary_key, {:id, :binary_id, autogenerate: false})
Module.put_attribute(__MODULE__, :foreign_key_type, :binary_id)
Module.put_attribute(__MODULE__, :timestamps_opts, inserted_at: :created, updated_at: false)
end
end

defmacro common_fields() do
quote do
field :stripe_id, :string
field :description, :string
field :metadata, :map, default: %{}
end
end

import Ecto.Changeset

def put_common_fields(changeset) do
validate_required(changeset, [:metadata])
changeset
|> validate_required(required_fields(changeset))
|> put_ids()
end

defp required_fields(changeset) do
Enum.filter([:metadata], &Map.has_key?(changeset.data, &1))
end

defp put_ids(changeset) do
case get_field(changeset, :id) do
nil -> do_put_stripe_id(changeset)
_ -> changeset
end
end

defp do_put_stripe_id(changeset) do
uuid = Ecto.UUID.generate()
prefix = ID.prefix(changeset.data)
stripe_id = prefix <> "_" <> StripeMock.ID.from_uuid(uuid)

change(changeset, %{id: uuid, stripe_id: stripe_id})
end
end
9 changes: 7 additions & 2 deletions lib/stripe_mock_web/view_helpers.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
defmodule StripeMockWeb.ViewHelpers do
def as_map(struct) do
struct
def as_map(struct, object \\ nil) do
if is_bitstring(object) do
Map.put(struct, :object, object)
else
struct
end
|> update_created()
|> Map.delete(:__struct__)
|> Map.delete(:__meta__)
|> Map.put(:id, struct.stripe_id)
end

defp update_created(%{created: %{} = timestamp} = struct) do
Expand Down
5 changes: 2 additions & 3 deletions lib/stripe_mock_web/views/card_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ defmodule StripeMockWeb.CardView do

def render("card.json", %{card: card}) do
card
|> Map.take(~w(id created deleted exp_month exp_year metadata last4 brand)a)
|> Map.put("object", "card")
|> as_map("card")
|> Map.take(~w(id object created deleted exp_month exp_year metadata last4 brand)a)
|> Map.put("customer", card.customer_id)
|> as_map()
end

def render("delete.json", %{card: card}) do
Expand Down
21 changes: 8 additions & 13 deletions lib/stripe_mock_web/views/charge_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,16 @@ defmodule StripeMockWeb.ChargeView do
end

def render("charge.json", %{charge: charge}) do
%{
id: charge.id,
amount: charge.amount,
currency: charge.currency,
capture: charge.capture,
charge
|> as_map("charge")
|> Map.take(
~w(id object amount currency capture description metadata statement_descriptor transfer_group)a
)
|> Map.merge(%{
customer: charge.customer_id,
description: charge.description,
metadata: charge.metadata,
object: "charge",
outcome: render_outcome(charge),
payment_method: render(StripeMockWeb.CardView, "card.json", card: charge.card),
statement_descriptor: charge.statement_descriptor,
transfer_group: charge.transfer_group
}
|> as_map()
payment_method: render(StripeMockWeb.CardView, "card.json", card: charge.card)
})
end

defp render_outcome(_charge) do
Expand Down
2 changes: 1 addition & 1 deletion lib/stripe_mock_web/views/customer_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule StripeMockWeb.CustomerView do
sources = API.list_cards(customer) |> Pagination.paginate()

customer
|> as_map()
|> as_map("customer")
|> Map.put(:sources, render_page(conn, sources, CardView, "card.json"))
end
end
4 changes: 2 additions & 2 deletions lib/stripe_mock_web/views/payment_intent_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ defmodule StripeMockWeb.PaymentIntentView do

def render("payment_intent.json", %{payment_intent: payment_intent}) do
payment_intent
|> as_map()
|> as_map("payment_intent")
|> Map.take(
~w(amount capture_method confirmation_method currency description id metadata payment_method_types statement_descriptor status transfer_group)a
~w(id object amount capture_method confirmation_method currency description metadata payment_method_types statement_descriptor status transfer_group)a
)
|> Map.put("customer", payment_intent.customer_id)
|> Map.merge(%{
Expand Down
4 changes: 2 additions & 2 deletions lib/stripe_mock_web/views/payment_method_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ defmodule StripeMockWeb.PaymentMethodView do
payment_object = render(StripeMockWeb.CardView, "card.json", card: card)

payment_method
|> Map.take(~w(id created description metadata)a)
|> as_map()
|> as_map("payment_method")
|> Map.take(~w(id object created description metadata)a)
|> Map.put(:card, payment_object)
end

Expand Down
3 changes: 2 additions & 1 deletion lib/stripe_mock_web/views/refund_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ defmodule StripeMockWeb.RefundView do

def render("refund.json", %{refund: refund}) do
refund
|> Map.take(~w(id created amount metadata reason)a)
|> as_map("refund")
|> Map.take(~w(id object created amount metadata reason)a)
|> Map.put(:charge, refund.charge_id)
end
end
Loading

0 comments on commit 9c117dd

Please sign in to comment.