├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config ├── config.exs ├── dev.exs ├── prod.exs └── test.exs ├── lib ├── monzo.ex └── monzo │ ├── account.ex │ ├── address.ex │ ├── balance.ex │ ├── client.ex │ ├── error.ex │ ├── feed.ex │ ├── merchant.ex │ ├── transaction.ex │ └── webhook.ex ├── mix.exs ├── mix.lock └── test ├── monzo ├── account_test.exs ├── balance_test.exs ├── client_test.exs ├── feed_test.exs ├── transaction_test.exs └── webhook_test.exs ├── monzo_test.exs ├── support └── factory.ex └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.2 4 | - 1.3 5 | otp_release: 6 | - 18.2 7 | - 19.0 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ## v0.3.0 - 2016-09-12 4 | 5 | * Mondo is now Monzo 6 | * Bump HTTPoison to 0.9 7 | * Bump Plug to 1.2 8 | 9 | ## v0.2.0 - 2016-06-25 10 | ### Added 11 | * Optionally expand merchant info when fetching transactions 12 | * Add missing fields on Mondo.Transaction: attachments, local_amount, local_currency 13 | * Add Mondo.refresh/1 for refreshing access token 14 | 15 | ### Changed 16 | * Use Plug.Conn.Query module instead of URI to encode URL params 17 | * Bump poison to 2.2 18 | 19 | ### Removed 20 | * Remove `Mondo.Client.authenticate/4` in favor of `Mondo.Client.authenticate/3`. Mondo has dropped support for 21 | username/password auth from their API. 22 | 23 | ## v0.1.0 24 | 25 | * Initial version 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steve Domin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monzo Elixir 2 | 3 | [![Build Status](https://travis-ci.org/stevedomin/monzo_elixir.svg?branch=master)](https://travis-ci.org/stevedomin/monzo_elixir) 4 | 5 | An Elixir client for the [Monzo](https://getmonzo.co.uk/) API. 6 | 7 | ## Installation 8 | 9 | Add monzo to your list of dependencies in `mix.exs`: 10 | 11 | ```elixir 12 | def deps do 13 | [{:monzo, "~> 0.3.0"}] 14 | end 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```elixir 20 | iex> {:ok, client} = Monzo.Client.authenticate("client_id", "client_secret", "authorization_code") 21 | {:ok, 22 | %Monzo.Client{access_token: "access_token", 23 | client_id: "client_id", expires_in: 21600, 24 | refresh_token: "refresh_token", 25 | token_type: "Bearer", user_id: "user_id"}} 26 | 27 | iex> {:ok, [account]} = Monzo.Account.list(client) 28 | {:ok, 29 | [%Monzo.Account{created: "2008-05-02T19:00:00.000Z", 30 | description: "Tony Stark", id: "acc_01"}]} 31 | 32 | iex> Monzo.Transaction.List(client, account.id) 33 | {:ok, 34 | [%Monzo.Transaction{account_balance: 10000, amount: -5000, category: "entertainment", 35 | created: "2008-05-09T18:00:00Z", currency: "GBP", decline_reason: nil, 36 | description: "AVENGERS LTD", id: "tx_01", 37 | is_load: true, merchant: "merch_01", metadata: %{}, notes: "", 38 | settled: "2008-05-09T19:00:00Z"}, 39 | %Monzo.Transaction{account_balance: 4000, amount: -1000, category: "cash", 40 | created: "2008-05-10T18:00:00Z", currency: "GBP", decline_reason: nil, 41 | description: "NEW AVENGERS", 42 | id: "tx_02", is_load: false, 43 | merchant: "merch_02", metadata: %{}, notes: "", 44 | settled: "2008-05-10Y19:00:00Z"}, 45 | ``` 46 | 47 | ## TODO 48 | 49 | * Pagination 50 | * Attachments 51 | * Annotate transaction 52 | * Better support for error code: 405, 406, 429, 500, 504 53 | 54 | ## LICENSE 55 | 56 | See [LICENSE](https://github.com/stevedomin/monzo_elixir/blob/master/LICENSE) 57 | 58 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :monzo, endpoint: "https://api.monzo.com" 4 | 5 | import_config "#{Mix.env}.exs" 6 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | -------------------------------------------------------------------------------- /lib/monzo.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo do 2 | @version "0.3.0" 3 | def version, do: @version 4 | end 5 | -------------------------------------------------------------------------------- /lib/monzo/account.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Account do 2 | @moduledoc """ 3 | [Monzo API reference](https://monzo.com/docs/#accounts) 4 | """ 5 | 6 | @endpoint "accounts" 7 | 8 | defstruct created: nil, description: nil, id: nil 9 | 10 | @type t :: %__MODULE__{ 11 | created: String.t, 12 | description: String.t, 13 | id: String.t 14 | } 15 | 16 | @doc """ 17 | List accounts 18 | """ 19 | @spec list(MonzoClient.t) :: {:ok, [t]} | {:error, Monzo.Error.t} 20 | def list(client) do 21 | with {:ok, body} <- Monzo.Client.get(client, @endpoint), 22 | {:ok, %{"accounts" => accounts}} <- Poison.decode(body, as: %{"accounts" => [%Monzo.Account{}]}), 23 | do: {:ok, accounts} 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/monzo/address.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Address do 2 | defstruct address: nil, approximate: nil, city: nil, country: nil, 3 | formatted: nil, latitude: nil, longitude: nil, postcode: nil, 4 | region: nil, short_formatted: nil, zoom_level: nil 5 | 6 | @type t :: %__MODULE__{ 7 | address: String.t, 8 | approximate: boolean, 9 | city: String.t, 10 | country: String.t, 11 | formatted: String.t, 12 | latitude: String.t, 13 | longitude: String.t, 14 | postcode: String.t, 15 | region: String.t, 16 | short_formatted: String.t, 17 | zoom_level: integer 18 | } 19 | end 20 | -------------------------------------------------------------------------------- /lib/monzo/balance.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Balance do 2 | @moduledoc """ 3 | [Monzo API reference](https://monzo.com/docs/#balance) 4 | """ 5 | 6 | @endpoint "balance" 7 | 8 | defstruct balance: nil, currency: nil, spend_today: nil 9 | 10 | @type t :: %__MODULE__{ 11 | balance: integer, 12 | currency: String.t, 13 | spend_today: integer 14 | } 15 | 16 | @doc """ 17 | Get balance 18 | """ 19 | @spec get(Monzo.Client.t, String.t) :: {:ok, t} | {:error, Monzo.Error.t} 20 | def get(client, account_id) do 21 | with {:ok, body} <- Monzo.Client.get(client, @endpoint, %{"account_id" => account_id}), 22 | {:ok, balance} <- Poison.decode(body, as: %Monzo.Balance{}), 23 | do: {:ok, balance} 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/monzo/client.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Client do 2 | @moduledoc """ 3 | """ 4 | 5 | defstruct access_token: nil, client_id: nil, client_secret: nil, 6 | expires_in: nil, refresh_token: nil, token_type: nil, user_id: nil 7 | 8 | @type t :: %__MODULE__{ 9 | access_token: String.t, 10 | client_id: String.t, 11 | client_secret: String.t, 12 | expires_in: non_neg_integer(), 13 | refresh_token: String.t, 14 | token_type: String.t, 15 | user_id: String.t 16 | } 17 | 18 | @user_agent "monzo-elixir" 19 | 20 | def authenticate(client_id, client_secret, authorization_code) do 21 | req_body = %{ 22 | grant_type: :authorization_code, 23 | client_id: client_id, 24 | client_secret: client_secret, 25 | code: authorization_code 26 | } 27 | 28 | with {:ok, body} <- post(nil, "oauth2/token", req_body), 29 | {:ok, client} <- Poison.decode(body, as: %Monzo.Client{}) do 30 | {:ok, %Monzo.Client{client | client_secret: client_secret}} 31 | end 32 | end 33 | 34 | def refresh(client) do 35 | req_body = %{ 36 | grant_type: :refresh_token, 37 | client_id: client.client_id, 38 | client_secret: client.client_secret, 39 | refresh_token: client.refresh_token 40 | } 41 | 42 | with {:ok, body} <- post(nil, "oauth2/token", req_body), 43 | {:ok, new_client} <- Poison.decode(body, as: %Monzo.Client{}) do 44 | {:ok, %Monzo.Client{new_client | client_secret: client.client_secret}} 45 | end 46 | end 47 | 48 | def ping(client) do 49 | with {:ok, body} <- get(client, "ping/whoami"), 50 | {:ok, whoami} <- Poison.decode(body), 51 | do: {:ok, whoami} 52 | end 53 | 54 | def request(client, method, path, params \\ :empty, body \\ "", headers \\ []) do 55 | url = url(path, params) 56 | headers = 57 | headers 58 | |> put_headers_default 59 | |> put_headers_access_token(client) 60 | |> put_headers_for_method(method) 61 | body = prepare_body(body, method) 62 | 63 | case HTTPoison.request(method, url, body, headers) do 64 | {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> 65 | {:ok, body} 66 | {:ok, %HTTPoison.Response{status_code: 400, body: body}} -> 67 | IO.inspect body 68 | {:ok, %HTTPoison.Response{status_code: 401, body: body}} -> 69 | {:error, Poison.decode!(body, as: Monzo.Error)} 70 | {:ok, %HTTPoison.Response{status_code: 403, body: body}} -> 71 | {:error, Poison.decode!(body, as: Monzo.Error)} 72 | {:ok, %HTTPoison.Response{status_code: 404, body: body}} -> 73 | {:error, Poison.decode!(body, as: Monzo.Error)} 74 | {:ok, _response} = resp -> resp 75 | {:error, %HTTPoison.Error{} = err} -> err 76 | end 77 | end 78 | 79 | def get(client, path, params \\ :empty) do 80 | request(client, :get, path, params) 81 | end 82 | 83 | def post(client, path, body, params \\ :empty) do 84 | request(client, :post, path, params, body) 85 | end 86 | 87 | def delete(client, path, params \\ :empty) do 88 | request(client, :delete, path, params) 89 | end 90 | 91 | def url(path, :empty), do: "#{Application.get_env(:monzo, :endpoint)}/#{path}" 92 | def url(path, params) do 93 | uri = 94 | url(path, :empty) 95 | |> URI.parse 96 | |> Map.put(:query, Plug.Conn.Query.encode(params)) 97 | URI.to_string(uri) 98 | end 99 | 100 | defp put_headers_default(headers) do 101 | [{"User-Agent", "#{@user_agent}/#{Monzo.version}"} | headers] 102 | end 103 | 104 | defp put_headers_access_token(headers, nil), do: headers 105 | defp put_headers_access_token(headers, %{access_token: nil}), do: headers 106 | defp put_headers_access_token(headers, %{access_token: access_token}) do 107 | [{"Authorization", "Bearer #{access_token}"} | headers] 108 | end 109 | 110 | defp put_headers_for_method(headers, :post) do 111 | [{"Content-Type", "application/x-www-form-urlencoded; charset=utf-8"} | headers] 112 | end 113 | defp put_headers_for_method(headers, _), do: headers 114 | 115 | defp prepare_body(body, :post), do: Plug.Conn.Query.encode(body) 116 | defp prepare_body(body, _), do: body 117 | end 118 | -------------------------------------------------------------------------------- /lib/monzo/error.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Error do 2 | @moduledoc """ 3 | [Monzo API reference](https://monzo.com/docs/#errors) 4 | """ 5 | 6 | defstruct code: nil, message: nil, params: nil 7 | 8 | @type t :: %__MODULE__{ 9 | code: String.t, 10 | message: String.t, 11 | params: map() 12 | } 13 | end 14 | -------------------------------------------------------------------------------- /lib/monzo/feed.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Feed do 2 | @moduledoc """ 3 | [Monzo API reference](https://monzo.com/docs/#feed) 4 | """ 5 | 6 | @endpoint "feed" 7 | 8 | defmodule BasicItem do 9 | defstruct background_color: nil, body: nil, body_color: nil, 10 | image_url: nil, title: nil, title_color: nil 11 | 12 | @type t :: %__MODULE__{ 13 | background_color: integer, 14 | body: String.t, 15 | body_color: integer, 16 | image_url: String.t, 17 | title: String.t, 18 | title_color: integer 19 | } 20 | end 21 | 22 | @doc """ 23 | Create a basic feed item 24 | """ 25 | @spec create(Monzo.Client.t, String.t, Monzo.Feed.BasicItem.t, String.t) :: :ok | {:error, Monzo.Error.t} 26 | def create(client, account_id, %Monzo.Feed.BasicItem{} = item, url) do 27 | req_body = %{ 28 | account_id: account_id, 29 | type: :basic, 30 | params: Map.delete(item, :__struct__), 31 | url: url 32 | } 33 | case Monzo.Client.post(client, @endpoint, req_body) do 34 | {:ok, _body} -> :ok 35 | {:error, _reason} = err -> err 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/monzo/merchant.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Merchant do 2 | defstruct address: nil, category: nil, created: nil, emoji: nil, group_id: nil, 3 | id: nil, logo: nil, name: nil, online: nil 4 | 5 | @type t :: %__MODULE__{ 6 | address: Monzo.Address.t, 7 | category: String.t, 8 | created: String.t, 9 | emoji: String.t, 10 | group_id: String.t, 11 | id: String.t, 12 | logo: String.t, 13 | name: String.t, 14 | online: boolean 15 | } 16 | end 17 | 18 | -------------------------------------------------------------------------------- /lib/monzo/transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Transaction do 2 | @moduledoc """ 3 | [Monzo API reference](https://monzo.com/docs/#transactions) 4 | """ 5 | 6 | @endpoint "transactions" 7 | 8 | defstruct account_balance: nil, amount: nil, attachments: nil, category: nil, 9 | created: nil, currency: nil, decline_reason: nil, description: nil, 10 | id: nil, is_load: nil, local_amount: nil, local_currency: nil, 11 | merchant: nil, metadata: nil, notes: nil, settled: nil 12 | 13 | @type t :: %__MODULE__{ 14 | account_balance: integer, 15 | amount: integer, 16 | attachments: list, 17 | category: String.t, 18 | created: String.t, 19 | currency: String.t, 20 | decline_reason: String.t, 21 | description: String.t, 22 | id: String.t, 23 | is_load: boolean, 24 | local_amount: String.t, 25 | local_currency: String.t, 26 | merchant: Monzo.Merchant.t, 27 | metadata: map, 28 | notes: String.t, 29 | settled: boolean 30 | } 31 | 32 | @doc """ 33 | List transactions 34 | """ 35 | @spec list(Monzo.Client.t, String.t) :: {:ok, [Monzo.Transaction.t]} | {:error, Monzo.Error.t} 36 | def list(client, account_id, opts \\ []) do 37 | {params, as} = Keyword.get(opts, :merchant, false) |> with_merchant(%{"account_id" => account_id}) 38 | with {:ok, body} <- Monzo.Client.get(client, @endpoint, params), 39 | {:ok, %{"transactions" => transactions}} <- Poison.decode(body, as: %{"transactions" => [as]}), 40 | do: {:ok, transactions} 41 | end 42 | 43 | @doc """ 44 | Get a transaction 45 | """ 46 | @spec get(Monzo.Client.t, String.t) :: {:ok, Monzo.Transaction.t} | {:error, Monzo.Error.t} 47 | def get(client, transaction_id, opts \\ []) do 48 | {params, as} = Keyword.get(opts, :merchant, false) |> with_merchant(%{}) 49 | 50 | with {:ok, body} <- Monzo.Client.get(client, @endpoint <> "/" <> transaction_id, params), 51 | {:ok, %{"transaction" => transaction}} <- Poison.decode(body, as: %{"transaction" => as}), 52 | do: {:ok, transaction} 53 | end 54 | 55 | @doc false 56 | @spec with_merchant(boolean, map) :: {map, Monzo.Transaction.t} 57 | defp with_merchant(true, params) do 58 | params = Map.put(params, :expand, ["merchant"]) 59 | as = %Monzo.Transaction{merchant: %Monzo.Merchant{address: %Monzo.Address{}}} 60 | {params, as} 61 | end 62 | defp with_merchant(_, params) do 63 | as = %Monzo.Transaction{} 64 | {params, as} 65 | end 66 | end 67 | 68 | -------------------------------------------------------------------------------- /lib/monzo/webhook.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Webhook do 2 | @moduledoc """ 3 | [Monzo API reference](https://monzo.com/docs/#webhooks) 4 | """ 5 | 6 | @endpoint "webhooks" 7 | 8 | defstruct account_id: nil, id: nil, url: nil 9 | 10 | @type t :: %__MODULE__{ 11 | account_id: String.t, 12 | id: String.t, 13 | url: String.t 14 | } 15 | 16 | @doc """ 17 | List all web hooks registered on an account 18 | """ 19 | @spec list(Monzo.Client.t, String.t) :: {:ok, [Monzo.Webhook.t]} | {:error, Monzo.Error.t} 20 | def list(client, account_id) do 21 | with {:ok, body} <- Monzo.Client.get(client, @endpoint, %{"account_id" => account_id}), 22 | {:ok, %{"webhooks" => webhooks}} <- Poison.decode(body, as: %{"webhooks" => [%Monzo.Webhook{}]}), 23 | do: {:ok, webhooks} 24 | end 25 | 26 | @doc """ 27 | Register a web hook 28 | """ 29 | @spec register(Monzo.Client.t, String.t, String.t) :: {:ok, Monzo.Webhook.t} | {:error, Monzo.Error.t} 30 | def register(client, account_id, url) do 31 | req_body = %{ 32 | account_id: account_id, 33 | url: url 34 | } 35 | with {:ok, body} <- Monzo.Client.post(client, @endpoint, req_body), 36 | {:ok, %{"webhook" => webhook}} <- Poison.decode(body, as: %{"webhook" => %Monzo.Webhook{}}), 37 | do: {:ok, webhook} 38 | end 39 | 40 | @doc """ 41 | Delete a webhook 42 | """ 43 | @spec delete(Monzo.Client.t, String.t) :: :ok | {:error, Monzo.Error.t} 44 | def delete(client, webhook_id) do 45 | case Monzo.Client.delete(client, @endpoint <> "/" <> webhook_id) do 46 | {:ok, _body} -> :ok 47 | {:error, _reason} = err -> err 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :monzo, 6 | version: "0.3.0", 7 | elixir: "~> 1.2", 8 | elixirc_paths: elixirc_paths(Mix.env), 9 | build_embedded: Mix.env == :prod, 10 | start_permanent: Mix.env == :prod, 11 | deps: deps, 12 | description: description, 13 | package: package] 14 | end 15 | 16 | # Configuration for the OTP application 17 | # 18 | # Type "mix help compile.app" for more information 19 | def application do 20 | applications = [:logger, :httpoison] 21 | 22 | applications = if Mix.env == :test do 23 | [:ex_machina] ++ applications 24 | else 25 | applications 26 | end 27 | 28 | [applications: applications] 29 | end 30 | 31 | # Specifies which paths to compile per environment. 32 | defp elixirc_paths(:test), do: ["lib", "test/support"] 33 | defp elixirc_paths(_), do: ["lib"] 34 | 35 | # Dependencies can be Hex packages: 36 | # 37 | # {:mydep, "~> 0.3.0"} 38 | # 39 | # Or git/path repositories: 40 | # 41 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 42 | # 43 | # Type "mix help deps" for more examples and options 44 | defp deps do 45 | [{:httpoison, "~> 1.0"}, 46 | {:plug, "~> 1.2"}, 47 | {:poison, "~> 3.1"}, 48 | {:ex_machina, "~> 2.2", only: :test}, 49 | {:bypass, "~> 1.0", only: :test}, 50 | {:ex_doc, "~> 0.20.1", only: :docs}] 51 | end 52 | 53 | defp description do 54 | """ 55 | An Elixir client for the Monzo API. 56 | """ 57 | end 58 | 59 | defp package do 60 | [maintainers: ["Steve Domin"], 61 | licenses: ["MIT"], 62 | links: %{"GitHub" => "https://github.com/stevedomin/monzo_elixir"}] 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, 4 | "cowboy": {:hex, :cowboy, "2.6.0", "dc1ff5354c89e36a3e3ef8d10433396dcff0dcbb1d4223b58c64c2d51a6d88d9", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"}, 6 | "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, 7 | "ex_doc": {:hex, :ex_doc, "0.20.1", "88eaa16e67c505664fd6a66f42ddb962d424ad68df586b214b71443c69887123", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 8 | "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, 9 | "hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, 10 | "httpoison": {:hex, :httpoison, "1.4.0", "e0b3c2ad6fa573134e42194d13e925acfa8f89d138bc621ffb7b1989e6d22e73", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, 11 | "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, 12 | "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 13 | "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, 14 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, 15 | "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [], [], "hexpm"}, 16 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, 17 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, 18 | "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, 19 | "plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, 20 | "plug_cowboy": {:hex, :plug_cowboy, "2.0.0", "ab0c92728f2ba43c544cce85f0f220d8d30fc0c90eaa1e6203683ab039655062", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 21 | "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, 22 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, 23 | "ranch": {:hex, :ranch, "1.7.0", "9583f47160ca62af7f8d5db11454068eaa32b56eeadf984d4f46e61a076df5f2", [:rebar3], [], "hexpm"}, 24 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, 25 | "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}, 26 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, 27 | } 28 | -------------------------------------------------------------------------------- /test/monzo/account_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Monzo.AccountTest do 2 | use ExUnit.Case 3 | import Monzo.Factory 4 | 5 | alias Monzo.Account 6 | 7 | setup_all do 8 | bypass = Bypass.open 9 | Application.put_env(:monzo, :endpoint, "http://localhost:#{bypass.port}") 10 | client = build(:client) 11 | {:ok, bypass: bypass, client: client} 12 | end 13 | 14 | test "list accounts", %{bypass: bypass, client: client} do 15 | accounts = build_list(3, :account) 16 | Bypass.expect bypass, fn conn -> 17 | assert "/accounts" == conn.request_path 18 | assert "GET" == conn.method 19 | Plug.Conn.resp(conn, 200, Poison.encode!(%{"accounts" => accounts})) 20 | end 21 | assert Account.list(client) == {:ok, accounts} 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/monzo/balance_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Monzo.BalanceTest do 2 | use ExUnit.Case 3 | import Monzo.Factory 4 | 5 | alias Monzo.Balance 6 | 7 | setup_all do 8 | bypass = Bypass.open 9 | Application.put_env(:monzo, :endpoint, "http://localhost:#{bypass.port}") 10 | client = build(:client) 11 | {:ok, bypass: bypass, client: client} 12 | end 13 | 14 | test "get balance for a valid account", %{bypass: bypass, client: client} do 15 | balance = build(:balance) 16 | account = build(:account) 17 | Bypass.expect bypass, fn conn -> 18 | conn = Plug.Conn.fetch_query_params(conn) 19 | assert "/balance" == conn.request_path 20 | assert %{"account_id" => account.id} == conn.query_params 21 | assert "GET" == conn.method 22 | Plug.Conn.resp(conn, 200, Poison.encode!(balance)) 23 | end 24 | assert Balance.get(client, account.id) == {:ok, balance} 25 | end 26 | 27 | test "get balance for an inexistent account", %{bypass: bypass, client: client} do 28 | Bypass.expect bypass, fn conn -> 29 | conn = Plug.Conn.fetch_query_params(conn) 30 | assert %{"account_id" => "123"} == conn.query_params 31 | assert "/balance" == conn.request_path 32 | assert "GET" == conn.method 33 | Plug.Conn.resp(conn, 403, "{}") 34 | end 35 | assert {:error, _reason} = Balance.get(client, "123") 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/monzo/client_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Monzo.ClientTest do 2 | use ExUnit.Case 3 | import Monzo.Factory 4 | 5 | setup do 6 | bypass = Bypass.open 7 | Application.put_env(:monzo, :endpoint, "http://localhost:#{bypass.port}") 8 | client = build(:client) 9 | {:ok, bypass: bypass, client: client} 10 | end 11 | 12 | test "successful authentication", %{bypass: bypass} do 13 | Bypass.expect bypass, fn conn -> 14 | assert "/oauth2/token" == conn.request_path 15 | assert "POST" == conn.method 16 | Plug.Conn.resp(conn, 200, Poison.encode!(%Monzo.Client{})) 17 | end 18 | assert {:ok, _client} = 19 | Monzo.Client.authenticate("client_id", "client_secret", "authorization_code") 20 | end 21 | 22 | test "failed auth" do 23 | end 24 | 25 | test "refresh token", %{bypass: bypass, client: client} do 26 | Bypass.expect bypass, fn conn -> 27 | assert "/oauth2/token" == conn.request_path 28 | assert "POST" == conn.method 29 | new_client = %Monzo.Client{client | access_token: "new_token"} 30 | Plug.Conn.resp(conn, 200, Poison.encode!(new_client)) 31 | end 32 | assert {:ok, client} = 33 | Monzo.Client.refresh(client) 34 | assert client.access_token == "new_token" 35 | end 36 | 37 | test "ping with an authenticated client", %{bypass: bypass, client: client} do 38 | response = %{ 39 | "authenticated" => true, 40 | "client_id" => client.client_id, 41 | "user_id" => client.user_id 42 | } 43 | Bypass.expect bypass, fn conn -> 44 | assert "/ping/whoami" == conn.request_path 45 | assert "GET" == conn.method 46 | Plug.Conn.resp(conn, 200, Poison.encode!(response)) 47 | end 48 | assert {:ok, response} == Monzo.Client.ping(client) 49 | end 50 | 51 | test "ping with an unauthenticated client", %{bypass: bypass} do 52 | response = %{"authenticated" => false} 53 | Bypass.expect bypass, fn conn -> 54 | assert "/ping/whoami" == conn.request_path 55 | assert "GET" == conn.method 56 | Plug.Conn.resp(conn, 200, Poison.encode!(response)) 57 | end 58 | assert {:ok, response} == Monzo.Client.ping(%Monzo.Client{}) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/monzo/feed_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Monzo.FeedTest do 2 | use ExUnit.Case 3 | import Monzo.Factory 4 | 5 | alias Monzo.Feed 6 | 7 | def parse(conn, opts \\ []) do 8 | opts = Keyword.put_new(opts, :parsers, [Plug.Parsers.URLENCODED]) 9 | Plug.Parsers.call(conn, Plug.Parsers.init(opts)) 10 | end 11 | 12 | setup_all do 13 | bypass = Bypass.open 14 | Application.put_env(:monzo, :endpoint, "http://localhost:#{bypass.port}") 15 | client = build(:client) 16 | {:ok, bypass: bypass, client: client} 17 | end 18 | 19 | test "create a basic feed item", %{bypass: bypass, client: client} do 20 | account = build(:account) 21 | basic_item = build(:feed_basic_item) 22 | url = "https://theavengers.com/monthly" 23 | Bypass.expect bypass, fn conn -> 24 | conn = parse(conn) 25 | expected_body_params = %{ 26 | "account_id" => account.id, 27 | "params" => %{ 28 | "background_color" => to_string(basic_item.background_color), 29 | "body" => basic_item.body, 30 | "body_color" => to_string(basic_item.body_color), 31 | "image_url" => basic_item.image_url, 32 | "title" => basic_item.title, 33 | "title_color" => to_string(basic_item.title_color) 34 | }, 35 | "type" => "basic", 36 | "url" => url 37 | } 38 | assert expected_body_params == conn.body_params 39 | assert "/feed" == conn.request_path 40 | assert "POST" == conn.method 41 | Plug.Conn.resp(conn, 200, "{}") 42 | end 43 | assert :ok == Feed.create(client, account.id, basic_item, url) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/monzo/transaction_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Monzo.TransactionTest do 2 | use ExUnit.Case 3 | import Monzo.Factory 4 | 5 | alias Monzo.Transaction 6 | 7 | setup_all do 8 | bypass = Bypass.open 9 | Application.put_env(:monzo, :endpoint, "http://localhost:#{bypass.port}") 10 | client = build(:client) 11 | {:ok, bypass: bypass, client: client} 12 | end 13 | 14 | test "list transactions with a valid account", %{bypass: bypass, client: client} do 15 | transactions = build_list(3, :transaction) 16 | account = build(:account) 17 | Bypass.expect bypass, fn conn -> 18 | conn = Plug.Conn.fetch_query_params(conn) 19 | assert "/transactions" == conn.request_path 20 | assert %{"account_id" => account.id} == conn.query_params 21 | assert "GET" == conn.method 22 | Plug.Conn.resp(conn, 200, Poison.encode!(%{"transactions" => transactions})) 23 | end 24 | assert Transaction.list(client, account.id) == {:ok, transactions} 25 | end 26 | 27 | test "list transactions with an invalid account", %{bypass: bypass, client: client} do 28 | Bypass.expect bypass, fn conn -> 29 | conn = Plug.Conn.fetch_query_params(conn) 30 | assert "/transactions" == conn.request_path 31 | assert %{"account_id" => "123"} == conn.query_params 32 | assert "GET" == conn.method 33 | Plug.Conn.resp(conn, 403, "{}") 34 | end 35 | assert {:error, _reason} = Transaction.list(client, "123") 36 | end 37 | 38 | test "list transactions with merchants info", %{bypass: bypass, client: client} do 39 | transactions = build_list(3, :transaction, merchant: build(:merchant)) 40 | account = build(:account) 41 | Bypass.expect bypass, fn conn -> 42 | conn = Plug.Conn.fetch_query_params(conn) 43 | assert "/transactions" == conn.request_path 44 | assert %{"account_id" => account.id, "expand" => ["merchant"]} == conn.query_params 45 | assert "GET" == conn.method 46 | Plug.Conn.resp(conn, 200, Poison.encode!(%{"transactions" => transactions})) 47 | end 48 | assert {:ok, transactions} == Transaction.list(client, account.id, merchant: true) 49 | end 50 | 51 | test "get an existing transaction", %{bypass: bypass, client: client} do 52 | transaction = build(:transaction) 53 | Bypass.expect bypass, fn conn -> 54 | assert "/transactions/#{transaction.id}" == conn.request_path 55 | assert "GET" == conn.method 56 | Plug.Conn.resp(conn, 200, Poison.encode!(%{"transaction" => transaction})) 57 | end 58 | assert Transaction.get(client, transaction.id) == {:ok, transaction} 59 | end 60 | 61 | test "get a non-existing transaction", %{bypass: bypass, client: client} do 62 | Bypass.expect bypass, fn conn -> 63 | assert "/transactions/123" == conn.request_path 64 | assert "GET" == conn.method 65 | Plug.Conn.resp(conn, 404, "{}") 66 | end 67 | assert {:error, _reason} = Transaction.get(client, "123") 68 | end 69 | 70 | test "get an existing transaction with merchant info", %{bypass: bypass, client: client} do 71 | transaction = build(:transaction, merchant: build(:merchant)) 72 | Bypass.expect bypass, fn conn -> 73 | conn = Plug.Conn.fetch_query_params(conn) 74 | assert "/transactions/#{transaction.id}" == conn.request_path 75 | assert %{"expand" => ["merchant"]} == conn.query_params 76 | assert "GET" == conn.method 77 | Plug.Conn.resp(conn, 200, Poison.encode!(%{"transaction" => transaction})) 78 | end 79 | assert {:ok, transaction} == Transaction.get(client, transaction.id, merchant: true) 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /test/monzo/webhook_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Monzo.WebhookTest do 2 | use ExUnit.Case 3 | import Monzo.Factory 4 | 5 | alias Monzo.Webhook 6 | 7 | def parse(conn, opts \\ []) do 8 | opts = Keyword.put_new(opts, :parsers, [Plug.Parsers.URLENCODED]) 9 | Plug.Parsers.call(conn, Plug.Parsers.init(opts)) 10 | end 11 | 12 | setup_all do 13 | bypass = Bypass.open 14 | Application.put_env(:monzo, :endpoint, "http://localhost:#{bypass.port}") 15 | client = build(:client) 16 | {:ok, bypass: bypass, client: client} 17 | end 18 | 19 | test "list webhooks for a valid account", %{bypass: bypass, client: client} do 20 | webhooks = build_list(3, :webhook) 21 | account = build(:account) 22 | Bypass.expect bypass, fn conn -> 23 | conn = Plug.Conn.fetch_query_params(conn) 24 | assert "/webhooks" == conn.request_path 25 | assert %{"account_id" => account.id} == conn.query_params 26 | assert "GET" == conn.method 27 | Plug.Conn.resp(conn, 200, Poison.encode!(%{"webhooks" => webhooks})) 28 | end 29 | assert Webhook.list(client, account.id) == {:ok, webhooks} 30 | end 31 | 32 | test "list webhooks for an inexistent account", %{bypass: bypass, client: client} do 33 | Bypass.expect bypass, fn conn -> 34 | conn = Plug.Conn.fetch_query_params(conn) 35 | assert "/webhooks" == conn.request_path 36 | assert %{"account_id" => "123"} == conn.query_params 37 | assert "GET" == conn.method 38 | Plug.Conn.resp(conn, 403, Poison.encode!("{}")) 39 | end 40 | assert {:error, _reason} = Webhook.list(client, "123") 41 | end 42 | 43 | test "register webhook for a valid account", %{bypass: bypass, client: client} do 44 | webhook = build(:webhook) 45 | account = build(:account) 46 | Bypass.expect bypass, fn conn -> 47 | conn = parse(conn) 48 | assert "/webhooks" == conn.request_path 49 | expected_body_params = %{ 50 | "account_id" => account.id, 51 | "url" => webhook.url 52 | } 53 | assert expected_body_params == conn.body_params 54 | assert "POST" == conn.method 55 | Plug.Conn.resp(conn, 200, Poison.encode!(%{"webhook" => webhook})) 56 | end 57 | assert Webhook.register(client, account.id, webhook.url) == {:ok, webhook} 58 | end 59 | 60 | test "register webhook for an inexistent account", %{bypass: bypass, client: client} do 61 | Bypass.expect bypass, fn conn -> 62 | conn = parse(conn) 63 | assert "/webhooks" == conn.request_path 64 | expected_body_params = %{ 65 | "account_id" => "123", 66 | "url" => "" 67 | } 68 | assert expected_body_params == conn.body_params 69 | assert "POST" == conn.method 70 | Plug.Conn.resp(conn, 403, Poison.encode!("{}")) 71 | end 72 | assert {:error, _reason} = Webhook.register(client, "123", "") 73 | end 74 | 75 | test "delete an existing webhook", %{bypass: bypass, client: client} do 76 | webhook = build(:webhook) 77 | Bypass.expect bypass, fn conn -> 78 | assert "/webhooks/#{webhook.id}" == conn.request_path 79 | assert "DELETE" == conn.method 80 | Plug.Conn.resp(conn, 200, "{}") 81 | end 82 | assert Webhook.delete(client, webhook.id) == :ok 83 | end 84 | 85 | test "delete an inexistent webhook", %{bypass: bypass, client: client} do 86 | Bypass.expect bypass, fn conn -> 87 | assert "/webhooks/123" == conn.request_path 88 | assert "DELETE" == conn.method 89 | Plug.Conn.resp(conn, 404, "{}") 90 | end 91 | assert {:error, _reason} = Webhook.delete(client, "123") 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /test/monzo_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MonzoTest do 2 | use ExUnit.Case 3 | doctest Monzo 4 | end 5 | -------------------------------------------------------------------------------- /test/support/factory.ex: -------------------------------------------------------------------------------- 1 | defmodule Monzo.Factory do 2 | use ExMachina 3 | 4 | def account_factory() do 5 | %Monzo.Account{ 6 | created: "2015-10-30T23:00:00Z", 7 | description: "Tony Stark's Account", 8 | id: sequence(:id, &"id-#{&1}") 9 | } 10 | end 11 | 12 | def balance_factory() do 13 | %Monzo.Balance{ 14 | balance: 5000, 15 | currency: "GBP", 16 | spend_today: 0 17 | } 18 | end 19 | 20 | def client_factory() do 21 | %Monzo.Client{ 22 | access_token: sequence(:access_token, &"access-token-#{&1}"), 23 | client_id: sequence(:client_id, &"client-id-#{&1}"), 24 | expires_in: 120, 25 | refresh_token: sequence(:refresh_token, &"refresh-token-#{&1}"), 26 | token_type: "Bearer", 27 | user_id: sequence(:user_id, &"user-id-#{&1}") 28 | } 29 | end 30 | 31 | def feed_basic_item_factory() do 32 | %Monzo.Feed.BasicItem{ 33 | background_color: 0xDC1405, 34 | body: "The arc reactor is costing you about 10m a day", 35 | body_color: 0xEFCE0B, 36 | image_url: "https://theavengers.com/jarvis.png", 37 | title: sequence(:title, &"Jarvis Monthly Report - #{&1}"), 38 | title_color: 0xFCFE05, 39 | } 40 | end 41 | 42 | def merchant_factory() do 43 | %Monzo.Merchant{ 44 | address: nil, 45 | category: "shopping", 46 | created: "2015-10-30T10:00:00.000Z", 47 | emoji: "", 48 | group_id: sequence(:group_id, &"grp_#{&1}"), 49 | id: sequence(:id, &"merch_#{&1}"), 50 | logo: "https://theavengers.com/logo.png", 51 | name: "Avengers Ltd", 52 | online: true 53 | } 54 | end 55 | 56 | def address_factory() do 57 | %Monzo.Address{ 58 | address: "85 Albert Embankment", 59 | approximate: false, 60 | city: "London", 61 | country: "GBR", 62 | formatted: "85 Albert Embankment, London SE1 7TP, United-Kingdom", 63 | latitude: "51.4873", 64 | longitude: "0.1243", 65 | postcode: "SE1 7TP", 66 | region: "Greater London", 67 | short_formatted: "85 Albert Embankment, London SE1 7TP", 68 | zoom_level: 10 69 | } 70 | end 71 | 72 | def transaction_factory() do 73 | %Monzo.Transaction{ 74 | account_balance: 100000, 75 | amount: 50000, 76 | created: "2015-12-24T23:00:00Z", 77 | category: "transport", 78 | currency: "GBP", 79 | decline_reason: nil, 80 | description: "Iron Man armor", 81 | id: sequence(:id, &"tx_#{&1}"), 82 | merchant: sequence(:merchant, &"merch_#{&1}"), 83 | metadata: %{}, 84 | notes: "Collector item", 85 | is_load: false, 86 | settled: true 87 | } 88 | end 89 | 90 | def webhook_factory() do 91 | %Monzo.Webhook{ 92 | account_id: "acc_01", 93 | id: sequence(:id, &"webhook_#{&1}"), 94 | url: "https://theavengers.com/webhook", 95 | } 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | 3 | Application.ensure_all_started(:ex_machina) 4 | Application.ensure_all_started(:bypass) 5 | 6 | --------------------------------------------------------------------------------