From 42566e61f3947e75949087bff705f922c111419f Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 4 Apr 2018 17:18:57 -0300 Subject: [PATCH 01/32] Implement bank channel --- lib/core/validator/validator.ex | 2 +- lib/universe/bank/henforcer/bank.ex | 37 ++++- lib/universe/bank/model/bank_account.ex | 5 +- lib/universe/bank/public/bank.ex | 35 +++++ lib/universe/bank/public/index.ex | 33 +++++ lib/universe/bank/websocket/channel/bank.ex | 81 +++++++++++ .../bank/websocket/channel/bank/join.ex | 137 ++++++++++++++++++ lib/websocket/websocket.ex | 1 + test/support/channel/helper.ex | 3 + test/support/universe/bank/helper.ex | 6 + test/support/universe/bank/setup.ex | 4 +- test/universe/bank/henforcer/bank_test.exs | 85 +++++++++++ .../bank/websocket/channel/bank/join_test.exs | 107 ++++++++++++++ 13 files changed, 528 insertions(+), 8 deletions(-) create mode 100644 lib/universe/bank/public/bank.ex create mode 100644 lib/universe/bank/public/index.ex create mode 100644 lib/universe/bank/websocket/channel/bank.ex create mode 100644 lib/universe/bank/websocket/channel/bank/join.ex create mode 100644 test/universe/bank/websocket/channel/bank/join_test.exs diff --git a/lib/core/validator/validator.ex b/lib/core/validator/validator.ex index 9a788c5a..a0bf603d 100644 --- a/lib/core/validator/validator.ex +++ b/lib/core/validator/validator.ex @@ -15,7 +15,7 @@ defmodule Helix.Core.Validator do This is a generic function meant to validate external input that does not conform to a specific shape or format (like internal IDs or IP addresses). - The `element` argument identifies what the input is supposed to represent, and + The `type` argument identifies what the input is supposed to represent, and we leverage this information to customize the validation for different kinds of input. """ diff --git a/lib/universe/bank/henforcer/bank.ex b/lib/universe/bank/henforcer/bank.ex index 925abca8..c8be06b1 100644 --- a/lib/universe/bank/henforcer/bank.ex +++ b/lib/universe/bank/henforcer/bank.ex @@ -5,6 +5,7 @@ defmodule Helix.Universe.Bank.Henforcer.Bank do alias Helix.Server.Model.Server alias Helix.Universe.Bank.Model.ATM alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Entity.Henforcer.Entity, as: EntityHenforcer alias Helix.Universe.Bank.Query.Bank, as: BankQuery @type account_exists_relay :: %{bank_account: BankAccount.t} @@ -12,18 +13,46 @@ defmodule Helix.Universe.Bank.Henforcer.Bank do @type account_exists_error :: {false, {:bank_account, :not_found}, account_exists_relay_partial} - @spec account_exists?(ATM.id, BankAccount.account) :: + @spec account_exists?(ATM.idt, BankAccount.account) :: {true, account_exists_relay} | account_exists_error @doc """ Henforces that the given bank account ({atm_id, account_number}) exists. """ def account_exists?(atm_id = %Server.ID{}, account_number) do - with bank_acc = %{} <- BankQuery.fetch_account(atm_id, account_number) do - reply_ok(%{bank_account: bank_acc}) - else + case BankQuery.fetch_account(atm_id, account_number) do + bank_acc = %BankAccount{} -> + reply_ok(%{bank_account: bank_acc}) _ -> reply_error({:bank_account, :not_found}) end end + + + @type password_valid_relay :: %{} + @type password_valid_relay_partial :: %{} + @type password_valid_error :: + {false, {:password, :invalid}, password_valid_relay_partial} + + @spec password_valid?(BankAccount.t, BankAccount.password) :: + {true, password_valid_relay} + | password_valid_error + @doc """ + + """ + def password_valid?(%BankAccount{password: password}, password), + do: reply_ok(%{password: password}) + def password_valid?(_, _), + do: reply_error({:password, :invalid}) + + def can_join?(atm_id, account_number, password, entity_id) do + with \ + {true, r1} <- account_exists?(atm_id, account_number), + bank_account = r1.bank_account, + {true, r2} <- password_valid?(bank_account, password), + {true, r3} <- EntityHenforcer.entity_exists?(entity_id) + do + {true, relay([r1, r2, r3])} + end + end end diff --git a/lib/universe/bank/model/bank_account.ex b/lib/universe/bank/model/bank_account.ex index de2c723c..bae34db8 100644 --- a/lib/universe/bank/model/bank_account.ex +++ b/lib/universe/bank/model/bank_account.ex @@ -19,13 +19,14 @@ defmodule Helix.Universe.Bank.Model.BankAccount do account_number: account, bank_id: NPC.id, atm_id: ATM.id, - password: String.t, + password: password, balance: balance, owner_id: Account.id } @type balance :: non_neg_integer @type amount :: pos_integer + @type password :: String.t @type creation_params :: %{ bank_id: NPC.idtb, @@ -136,7 +137,7 @@ defmodule Helix.Universe.Bank.Model.BankAccount do do: Enum.random(@account_range) @spec generate_account_password :: - String.t + BankAccount.password defp generate_account_password, do: Password.generate(:bank_account) diff --git a/lib/universe/bank/public/bank.ex b/lib/universe/bank/public/bank.ex new file mode 100644 index 00000000..8071e315 --- /dev/null +++ b/lib/universe/bank/public/bank.ex @@ -0,0 +1,35 @@ +defmodule Helix.Universe.Bank.Public.Bank do + + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Model.ATM + + #TODO: Add Transfer History + @type bootstrap :: + %{ + balance: BankAccount.balance + } + + @type rendered_bootstrap :: + %{ + balance: BankAccount.balance + } + + @spec bootstrap({ATM.id, BankAccount.account}) :: + bootstrap + + def bootstrap({atm_id, account_number}) do + bank_account = BankQuery.fetch_account(atm_id, account_number) + + %{ + balance: bank_account.balance + } + end + + @spec render_bootstrap(bootstrap) :: + rendered_bootstrap + + def render_bootstrap(bootstrap) do + bootstrap + end +end diff --git a/lib/universe/bank/public/index.ex b/lib/universe/bank/public/index.ex new file mode 100644 index 00000000..b36c908d --- /dev/null +++ b/lib/universe/bank/public/index.ex @@ -0,0 +1,33 @@ +defmodule Helix.Universe.Bank.Public.Index do + + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Model.BankAccount + + @type index :: + %{ + balance: BankAccount.balance + } + + @type rendered_index :: + %{ + balance: BankAccount.balance + } + + @spec index(ATM.id, BankAccount.account) :: + index + + def index(atm_id, account_number) do + bank_account = BankQuery.fetch_account(atm_id, account_number) + balance = bank_account.balance + + %{ + balance: balance + } + end + + @spec render_index(index) :: + rendered_index + + def render_index(index), + do: index +end diff --git a/lib/universe/bank/websocket/channel/bank.ex b/lib/universe/bank/websocket/channel/bank.ex new file mode 100644 index 00000000..4a4b7b60 --- /dev/null +++ b/lib/universe/bank/websocket/channel/bank.ex @@ -0,0 +1,81 @@ +import Helix.Websocket.Channel + +channel Helix.Universe.Bank.Websocket.Channel.Bank do + @moduledoc """ + `BankChannel` handles incoming and outgoing messages between players and ] + bank servers + + Base errors (applicable to all requests expected to replay something): + + - "bad_request" - One or more request params are invalid + - "internal" - Something unexpected happened + """ + alias Helix.Universe.Bank.Websocket.Channel.Bank.Join, as: BankJoin + #alias Helix.Universe.Bank.Requests.Bootstrap, as: BootstrapRequest + #alias Helix.Universe.Bank.Requests.Transfer, as: BankTransfer + + @doc """ + Join the Bank Channel + + Topic: "bank:@" + + Params: + - *password: Target account password. + + Returns: BankAccountBootstrap + + Errors: + + Henforcer: + - "password_invalid": Password is invalid. + + Input: + - "bank_account_non_ecziste" + + base errors + """ + + join "bank:" <> _, BankJoin + + @doc """ + Starts a financial transaction. + + Params: + - *to_bank_ip: Receiving bank ip. + - *to_bank_net: Receiving bank network. + - *to_acc: Account number that receives money. + - *password: Sending Bank Account password. + - *value: Amount of money that is being sent. + + Returns: :ok + + Errors: + - "insufficient_money" + - "receiving_bank_not_found" + - "receiving_account_not_found" + - "sending_bank_not_found" + - "sending_account_not_found" + - "receiving_bank_is_not_a_bank" + - "sending_bank_is_not_a_bank" + """ + # topic "bank.transfer", BankTransfer + + @doc """ + Forces a bootstrap to happen. It is the exact same operation ran during join. + Useful if the client wants to force a resynchronization of the local data. + + Params: none + + Returns: BankBootstrap + + Errors: + + base errors + """ + # topic "bootstrap", BootstrapRequest + + + event_handler "event" + + def terminate(_reason, _socket) do + + end +end diff --git a/lib/universe/bank/websocket/channel/bank/join.ex b/lib/universe/bank/websocket/channel/bank/join.ex new file mode 100644 index 00000000..1d5c3c3f --- /dev/null +++ b/lib/universe/bank/websocket/channel/bank/join.ex @@ -0,0 +1,137 @@ +import Helix.Websocket.Join + +join Helix.Universe.Bank.Websocket.Channel.Bank.Join do + @moduledoc """ + Joinable implementation for the Bank channel + """ + + use Helix.Logger + + import HELL.Macros + + alias Helix.Websocket.Utils, as: WebsocketUtils + alias Helix.Account.Model.Account + alias Helix.Entity.Query.Entity, as: EntityQuery + alias Helix.Server.Model.Server + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + + def check_params(request, _socket) do + with \ + "bank:" <> bank_account_id <- request.topic, + + # Get ATM.id and account number from topic + {account_number, atm_id} <- get_bank_account_id(bank_account_id), + + # Verify ATM.id parameter + {:ok, atm_id} <- Server.ID.cast(atm_id), + + # Verify account number parameter + account_number <- String.to_integer(account_number), + {:ok, account_number} <- BankAccount.cast(account_number), + + # Validate Account.id + {:ok, account_id} <- Account.ID.cast(request.unsafe["entity_id"]), + + # Validate password + {:ok, password} <- validate_input(request.unsafe["password"], :password) + do + params = %{ + atm_id: atm_id, + account_number: account_number, + password: password, + account_id: account_id + } + + update_params(request, params, reply: true) + else + _ -> + bad_request(request) + end + end + + def check_permissions(request, _socket) do + atm_id = request.params.atm_id + account_number = request.params.account_number + account_id = request.params.account_id + password = request.params.password + entity_id = EntityQuery.get_entity_id(account_id) + + with \ + {true, _} <- + BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + do + reply_ok(request) + else + {false, reason, _} -> + reply_error(request, reason) + _ -> + bad_request(request) + end + end + + @doc """ + Joins a BankAccount. + """ + def join(request, socket, assign) do + account_id = request.params.account_id + + atm_id = request.params.atm_id + account_number = request.params.account_number + bank_account_id = {atm_id, account_number} + + bootstrap = + bank_account_id + |> BankPublic.bootstrap() + |> BankPublic.render_bootstrap() + |> WebsocketUtils.wrap_data() + + socket = + socket + |> assign.(:atm_id, atm_id) + |> assign.(:account_number, account_number) + |> assign.(:account_id, account_id) + + "bank:" <> topic_name = request.topic + + log :join, topic_name, + relay: request.relay, + data: %{ + channel: :bank, + bank_account: topic_name, + status: :ok + } + + {:ok, bootstrap, socket} + end + + docp """ + Gets the atm_id and account_number united by an `@` separate then and return + as a Tuple + """ + @spec get_bank_account_id(String.t) :: + {String.t, String.t} + + defp get_bank_account_id(bank_account_id) do + bank_account_id + |> String.split("@") + |> List.to_tuple + end + + def log_error(request, _socket, reason) do + "bank:" <> topic_name = request.topic + + id = + if Enum.empty?(request.params) do + nil + else + topic_name + end + + log :join, id, + relay: request.relay, + data: %{channel: :bank, status: :error, reason: reason} + end + +end diff --git a/lib/websocket/websocket.ex b/lib/websocket/websocket.ex index be74082c..2218d273 100644 --- a/lib/websocket/websocket.ex +++ b/lib/websocket/websocket.ex @@ -35,6 +35,7 @@ defmodule Helix.Websocket do channel "account:*", Helix.Account.Websocket.Channel.Account channel "server:*", Helix.Server.Websocket.Channel.Server + channel "bank:*", Helix.Universe.Bank.Websocket.Channel.Bank unless Mix.env == :prod do channel "logflix", HELL.Logflix diff --git a/test/support/channel/helper.ex b/test/support/channel/helper.ex index 91d7ac5a..4c914e5b 100644 --- a/test/support/channel/helper.ex +++ b/test/support/channel/helper.ex @@ -11,4 +11,7 @@ defmodule Helix.Test.Channel.Helper do "server:" <> network_id <> "@" <> ip <> "#" <> counter end + + def bank_topic_name(atm_id = %Server.ID{}, account_number), + do: "bank:" <> to_string(account_number) <> "@" <> to_string(atm_id) end diff --git a/test/support/universe/bank/helper.ex b/test/support/universe/bank/helper.ex index c5a65240..1085f667 100644 --- a/test/support/universe/bank/helper.ex +++ b/test/support/universe/bank/helper.ex @@ -20,4 +20,10 @@ defmodule Helix.Test.Universe.Bank.Helper do """ def amount, do: Random.number(min: 1, max: 5000) + + @doc """ + Generates a random password + """ + def password, + do: Random.string(min: 8, max: 20) end diff --git a/test/support/universe/bank/setup.ex b/test/support/universe/bank/setup.ex index d393d874..c3f0bb0d 100644 --- a/test/support/universe/bank/setup.ex +++ b/test/support/universe/bank/setup.ex @@ -80,13 +80,15 @@ defmodule Helix.Test.Universe.Bank.Setup do 0 end + password = Keyword.get(opts, :password, BankHelper.password()) + acc = %BankAccount{ account_number: number, balance: balance, bank_id: bank.id, atm_id: atm_id, - password: "secret", + password: password, owner_id: owner_id, creation_date: DateTime.utc_now() } diff --git a/test/universe/bank/henforcer/bank_test.exs b/test/universe/bank/henforcer/bank_test.exs index 102a9f04..fdffe7ff 100644 --- a/test/universe/bank/henforcer/bank_test.exs +++ b/test/universe/bank/henforcer/bank_test.exs @@ -8,6 +8,8 @@ defmodule Helix.Universe.Bank.Henforcer.BankTest do alias Helix.Test.Universe.Bank.Helper, as: BankHelper alias Helix.Test.Universe.Bank.Setup, as: BankSetup + alias Helix.Test.Entity.Setup, as: EntitySetup + alias Helix.Test.Entity.Helper, as: EntityHelper describe "account_exists?/1" do test "accepts when account exists" do @@ -30,4 +32,87 @@ defmodule Helix.Universe.Bank.Henforcer.BankTest do assert reason == {:bank_account, :not_found} end end + describe "password_valid?/2" do + test "accepts when password is the same as the bank account's password" do + bank_acc = BankSetup.account!() + + assert {true, relay} = + BankHenforcer.password_valid?(bank_acc, bank_acc.password) + + assert relay.password == bank_acc.password + + assert_relay relay, [:password] + end + test "rejects when password is not the same as the bank account's password" do + bank_acc = BankSetup.account!() + + assert {false, reason, _} = + BankHenforcer.password_valid?(bank_acc, BankHelper.password()) + + assert reason == {:password, :invalid} + + end + end + describe "can_join?/4" do + test "accepts when account and entity exists and the password is valid" do + {entity, _} = EntitySetup.entity() + + bank_acc = BankSetup.account!() + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + password = bank_acc.password + entity_id = entity.entity_id + + assert {true, relay} = + BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + + assert relay.bank_account == bank_acc + assert relay.password == password + assert relay.entity == entity + + assert_relay relay, [:bank_account, :password, :entity] + end + test "rejects when account does not exists" do + {entity, _} = EntitySetup.entity() + + atm_id = BankHelper.atm_id() + account_number = BankHelper.account_number + password = BankHelper.password + entity_id = entity.entity_id + + assert {false, reason, _} = + BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + + assert reason == {:bank_account, :not_found} + + end + test "rejects when entity does not exists" do + bank_acc = BankSetup.account!() + + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + password = bank_acc.password + entity_id = EntitySetup.id() + + assert {false, reason, _} = + BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + + assert reason == {:entity, :not_found} + end + test "rejects when password does not match" do + {entity, _} = EntitySetup.entity() + bank_acc = BankSetup.account!() + + entity_id = entity.entity_id + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + password = BankHelper.password() + + assert {false, reason, _} = + BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + + assert reason == {:password, :invalid} + + end + end end diff --git a/test/universe/bank/websocket/channel/bank/join_test.exs b/test/universe/bank/websocket/channel/bank/join_test.exs new file mode 100644 index 00000000..0287d997 --- /dev/null +++ b/test/universe/bank/websocket/channel/bank/join_test.exs @@ -0,0 +1,107 @@ +defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias HELL.TestHelper.Random + alias Helix.Test.Cache.Helper, as: CacheHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Network.Helper, as: NetworkHelper + alias Helix.Test.Network.Setup, as: NetworkSetup + alias Helix.Test.Server.Helper, as: ServerHelper + alias Helix.Test.Server.Setup, as: ServerSetup + alias Helix.Test.Universe.Bank.Helper, as: BankHelper + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + @moduletag :driver + + @internet_id NetworkHelper.internet_id() + + describe "BankJoin" do + test "can join a bank account with valid data" do + {socket, %{server: gateway}} = ChannelSetup.create_socket() + + entity_id = socket.assigns.entity_id + + {bank_account, _} = BankSetup.account() + + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => bank_account.password + } + + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Joins the channel + assert {:ok, bootstrap, new_socket} = join(socket, topic, payload) + + # `atm_id` and `account_number` are valid + assert new_socket.assigns.atm_id == atm_id + assert new_socket.assigns.account_number == account_number + + # `entity` data is valid + assert new_socket.assigns.entity_id == entity_id + + # Socket related stuff + assert new_socket.joined + assert new_socket.topic == topic + + # It returned the bank account bootstrap + assert bootstrap.data.balance + end + + test "can not join with the wrong password" do + {socket, %{server: gateway}} = ChannelSetup.create_socket() + + entity_id = socket.assigns.entity_id + + {bank_account, _} = BankSetup.account() + + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => BankHelper.password() + } + + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Try to join the channel + assert {:error, reason} = join(socket, topic, payload) + + assert reason.data == "password_invalid" + end + + test "can not join with an account that does not exist" do + {socket, %{server: gateway}} = ChannelSetup.create_socket() + + entity_id = socket.assigns.entity_id + + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => BankHelper.password() + } + + atm_id = BankHelper.atm_id() + account_number = BankHelper.account_number() + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Try to join the channel + assert {:error, reason} = + join(socket, topic, payload) + + assert reason.data == "bank_account_not_found" + end + end + +end From df3b4409474d8f1ae134c57d9e5331aca8849504 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Fri, 6 Apr 2018 16:01:57 -0300 Subject: [PATCH 02/32] Update .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 403bbea2..cae306cf 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,12 @@ erl_crash.dump xref_graph.dot /graphs +# Visual Code +.elixir_ls/ + +# Elixir +.iex.exs + **GPATH **GRTAGS **GTAGS From 042820841842081fa4cd19241ed7ce90690c5296 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:43:36 -0300 Subject: [PATCH 03/32] Add login with token on bank channel --- lib/core/validator/validator.ex | 21 + lib/universe/bank/henforcer/bank.ex | 306 ++++++++++++++- lib/universe/bank/websocket/channel/bank.ex | 99 ++++- .../bank/websocket/channel/bank/join.ex | 299 ++++++++++++-- test/universe/bank/henforcer/bank_test.exs | 371 +++++++++++++++++- .../bank/websocket/channel/bank/join_test.exs | 101 ++++- 6 files changed, 1113 insertions(+), 84 deletions(-) diff --git a/lib/core/validator/validator.ex b/lib/core/validator/validator.ex index a0bf603d..c3762f1a 100644 --- a/lib/core/validator/validator.ex +++ b/lib/core/validator/validator.ex @@ -8,6 +8,8 @@ defmodule Helix.Core.Validator do @regex_hostname ~r/^[a-zA-Z0-9-_.@#]{1,20}$/ + @regex_token ~r/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/ + @spec validate_input(input :: String.t, input_type, opts :: term) :: {:ok, validated_input :: String.t} | :error @@ -24,6 +26,9 @@ defmodule Helix.Core.Validator do def validate_input(input, :password, _), do: validate_password(input) + def validate_input(input, :money, _), + do: validate_money(input) + def validate_input(input, :hostname, _), do: validate_hostname(input) @@ -33,6 +38,9 @@ defmodule Helix.Core.Validator do def validate_input(input, :reply_id, _), do: validate_reply_id(input) + def validate_input(input, :token, _), + do: validate_token(input) + defp validate_hostname(v) when not is_binary(v), do: :error defp validate_hostname(v) do @@ -43,6 +51,16 @@ defmodule Helix.Core.Validator do end end + defp validate_token(v) when not is_binary(v), + do: :error + defp validate_token(v) do + if Regex.match?(@regex_token, v) do + {:ok, v} + else + :error + end + end + defp validate_password(input), do: validate_hostname(input) # TODO @@ -51,4 +69,7 @@ defmodule Helix.Core.Validator do defp validate_reply_id(v), do: validate_hostname(v) # TODO + + defp validate_money(v), + do: validate_hostname(v) end diff --git a/lib/universe/bank/henforcer/bank.ex b/lib/universe/bank/henforcer/bank.ex index c8be06b1..ca189fe8 100644 --- a/lib/universe/bank/henforcer/bank.ex +++ b/lib/universe/bank/henforcer/bank.ex @@ -2,10 +2,15 @@ defmodule Helix.Universe.Bank.Henforcer.Bank do import Helix.Henforcer + alias Helix.Account.Model.Account + alias Helix.Entity.Model.Entity alias Helix.Server.Model.Server + alias Helix.Universe.Bank.Model.Bank alias Helix.Universe.Bank.Model.ATM alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Model.BankToken alias Helix.Entity.Henforcer.Entity, as: EntityHenforcer + alias Helix.Network.Henforcer.Network, as: NetworkHenforcer alias Helix.Universe.Bank.Query.Bank, as: BankQuery @type account_exists_relay :: %{bank_account: BankAccount.t} @@ -38,21 +43,316 @@ defmodule Helix.Universe.Bank.Henforcer.Bank do {true, password_valid_relay} | password_valid_error @doc """ - + Henforces that the given password is the same as given account's password """ def password_valid?(%BankAccount{password: password}, password), do: reply_ok(%{password: password}) def password_valid?(_, _), do: reply_error({:password, :invalid}) - def can_join?(atm_id, account_number, password, entity_id) do + @type can_join_password_relay :: %{} + @type can_join_password_error :: + password_valid_error + | account_exists_error + @type can_join_password_partial :: %{} + + @spec can_join_password?(ATM.id, BankAccount.number, String.t, Entity.id) :: + {true, can_join_password_relay} + | {false, can_join_password_error, can_join_password_partial} + @doc """ + Henforces that the player can join with given password + """ + def can_join_password?(atm_id, account_number, password, entity_id) do with \ {true, r1} <- account_exists?(atm_id, account_number), bank_account = r1.bank_account, {true, r2} <- password_valid?(bank_account, password), {true, r3} <- EntityHenforcer.entity_exists?(entity_id) do - {true, relay([r1, r2, r3])} + reply_ok(relay([r1, r2, r3])) + end + end + + @type can_join_token_relay :: %{} + @type can_join_token_error :: + token_exists_error + | token_valid_error + | account_exists_error + @type can_join_token_partial :: %{} + + @spec can_join_token?(ATM.id, BankAccount.number, BankToken.id, Entity.id) :: + {true, can_join_token_relay} + | {false, can_join_token_error, can_join_token_partial} + @doc """ + Henforces that the player can join with the given token + """ + def can_join_token?(atm_id, account_number, token, entity_id) do + with \ + {true, r1} <- account_exists?(atm_id, account_number), + bank_account = r1.bank_account, + {true, r2} <- token_valid?(bank_account, token), + {true, r3} <- EntityHenforcer.entity_exists?(entity_id) + do + reply_ok(relay([r1, r2, r3])) + end + end + + @type token_valid_relay :: %{token: BankToken.t} + @type token_valid_relay_partial :: %{} + @type invalid_error :: + {false, {:token, :not_belongs}, token_valid_relay_partial} + @type token_valid_error :: + invalid_error + | token_exists_error + | token_not_expired_error + + @spec token_valid?(BankAccount.t, BankToken.id) :: + {true, token_valid_relay} + | token_valid_error + @doc """ + Henforces that given token exists, is not expired and matches the bank account + data + """ + def token_valid?(bank_account, token) do + with \ + {true, r1} <- token_exists?(token), + token = r1.token, + true <- token.atm_id == bank_account.atm_id, + true <- token.account_number == bank_account.account_number, + {true, r2} <- token_not_expired?(token) + do + reply_ok(r2) + else + {false, reason, _} -> + reply_error(reason) + false -> + reply_error({:token, :not_belongs}) + end + end + + @type token_exists_relay :: %{} + @type token_exists_error :: + {false, {:token, :not_found}, token_exists_relay_partial} + @type token_exists_relay_partial :: %{} + + @spec token_exists?(BankToken.id) :: + {true, token_exists_relay} + | token_exists_error + @doc """ + Henforces that given token exists on the database + """ + def token_exists?(nil), + do: reply_error{:token, :not_found} + def token_exists?(token) do + case BankQuery.fetch_token(token) do + token = %BankToken{} -> + reply_ok(%{token: token}) + _ -> + reply_error({:token, :not_found}) + end + end + + @type token_not_expired_relay :: %{} + @type token_not_expired_error :: + {false, {:token, :expired}, token_not_expired_partial_relay} + @type token_not_expired_partial_relay :: %{} + + @spec token_exists?(BankToken.t) :: + {true, token_not_expired_relay} + | token_not_expired_error + @doc """ + Henforces that the given token is not expired + """ + def token_not_expired?(token) do + time_now = + DateTime.utc_now() + + token_exp = + token.expiration_date + + case DateTime.compare(time_now, token_exp) do + :gt -> + reply_error{:token, :expired} + _ -> + reply_ok(%{token: token}) + end + end + + @type transfer_error :: + account_exists_error + | password_valid_error + | transfer_no_funds_error + + @type can_transfer_relay :: + %{ + amount: BankAccount.amount, + password: BankAccount.password, + to_account: BankAccount.t + } + @type transfer_no_funds_error :: + {:bank_account, :no_funds} + @spec can_transfer?( + {Network.ID, Network.ip}, + BankAccount.account, + {ATM.idt, BankAccount.account}, + non_neg_integer, + BankAccount.password + ) :: + {true, can_transfer_relay} + | {false, transfer_error, %{}} + @doc """ + Henforces that given account can transfer to another given account + """ + def can_transfer?( + {net_id, ip}, + to_acc_num, + {atm_id, acc_num}, + amount, + password) + do + with \ + {true, r1} <- NetworkHenforcer.nip_exists?(net_id, ip), + {true, r2} <- is_a_bank?(r1.server.server_id), + to_atm_id = r2.atm.atm_id, + {true, r3} <- account_exists?(to_atm_id, to_acc_num), + {_r3, to_account} <- get_and_drop(r3, :bank_account), + {true, r4} <- account_exists?(atm_id, acc_num), + {true, r5} <- has_enough_funds?(r4.bank_account, amount), + {true, r6} <- password_valid?(r4.bank_account, password) + do + relay = + %{ + to_account: to_account + } + reply_ok(relay([relay, r5, r6])) + end + end + + @type has_enough_funds_relay :: %{amount: BankAccount.amount} + @type has_enough_funds_partial_relay :: %{} + + @spec has_enough_funds?(BankAccount.t, BankAccount.amount) :: + {true, has_enough_funds_relay} + | {false, transfer_no_funds_error, has_enough_funds_partial_relay} + @doc """ + Henforces that given account has given amount in the balance + """ + def has_enough_funds?(account, amount) do + if account.balance >= amount do + reply_ok() + else + reply_error({:bank_account, :no_funds}) + end + |> wrap_relay(%{amount: amount}) + end + + @type owns_account_relay :: %{bank_account: BankAccount.t} + @type owns_account_partial_relay :: %{} + @type owns_account_error :: + {false, {:bank_account, :not_belongs}, owns_account_partial_relay} + def owns_account?(entity = %Entity{}, account = %BankAccount{}) do + with \ + entity_id = to_string(entity.entity_id), + true <- account.owner_id == Account.ID.cast!(entity_id) + do + reply_ok(%{bank_account: account}) + else + _ -> + reply_error({:bank_account, :not_belongs}) + end + end + + @type is_empty_relay :: %{bank_account: BankAccount.t} + @type is_empty_partial_relay :: %{} + @type is_empty_error :: + {false, {:bank_account, :not_empty}, is_empty_partial_relay} + + @spec is_empty?(BankAccount.t) :: + {true, is_empty_relay} + | is_empty_error + @doc """ + Henforces that the given account is empty. + """ + def is_empty?(account = %BankAccount{}) do + if account.balance == 0 do + reply_ok() + else + reply_error({:bank_account, :not_empty}) + end + |> wrap_relay(%{bank_account: account}) + end + + @type can_close_relay :: %{} + @type can_close_partial_relay :: %{} + @type can_close_error :: + account_exists_error + | is_empty_error + + @spec can_close?(Server.id, BankAccount.account) :: + {true, can_close_relay} + | can_close_error + @doc """ + Henforces that player can close account + """ + def can_close?(atm_id = %Server.ID{}, account_number) do + with \ + {true, r1} <- account_exists?(atm_id, account_number), + {true, _r2} <- is_empty?(r1.bank_account) + do + reply_ok() + end + end + + @type is_a_bank_relay :: %{} + @type is_a_bank_partial_relay :: %{} + @type is_a_bank_error :: {false, {:atm, :not_a_bank}, %{}} + + @spec is_a_bank?(Server.id) :: + is_a_bank_relay + | is_a_bank_error + @doc """ + Henforces that given server is a bank + """ + def is_a_bank?(server_id = %Server.ID{}) do + case BankQuery.fetch_atm(server_id) do + atm = %ATM{} -> + reply_ok(%{atm: atm}) + _ -> + reply_error({:atm, :not_a_bank}) + end + end + + @type can_create_account_relay :: + %{ + bank_id: Bank.id, + atm_id: ATM.id + } + @type can_create_account_partial_relay :: %{} + @type can_create_account_error :: + is_a_bank_error + + @spec can_create_account?(Server.t) :: + {true, can_create_account_relay} + | can_create_account_error + @doc """ + Henforces that player can create a account + """ + def can_create_account?(atm = %Server{}) do + with \ + {true, r1} <- is_a_bank?(atm.server_id), + bank_id = r1.atm.bank_id, + atm_id = r1.atm.atm_id + do + relay = + %{ + bank_id: bank_id, + atm_id: atm_id + } + + reply_ok(relay) + else + {false, reason, _} -> + reply_error(reason) end end end diff --git a/lib/universe/bank/websocket/channel/bank.ex b/lib/universe/bank/websocket/channel/bank.ex index 4a4b7b60..35269559 100644 --- a/lib/universe/bank/websocket/channel/bank.ex +++ b/lib/universe/bank/websocket/channel/bank.ex @@ -11,8 +11,15 @@ channel Helix.Universe.Bank.Websocket.Channel.Bank do - "internal" - Something unexpected happened """ alias Helix.Universe.Bank.Websocket.Channel.Bank.Join, as: BankJoin - #alias Helix.Universe.Bank.Requests.Bootstrap, as: BootstrapRequest - #alias Helix.Universe.Bank.Requests.Transfer, as: BankTransfer + alias Helix.Universe.Bank.Websocket.Requests.Bootstrap, as: BootstrapRequest + alias Helix.Universe.Bank.Websocket.Requests.Transfer, as: BankTransferRequest + alias Helix.Universe.Bank.Websocket.Requests.CloseAccount, + as: BankCloseAccountRequest + alias Helix.Universe.Bank.Websocket.Requests.ChangePassword, + as: BankChangePasswordRequest + alias Helix.Universe.Bank.Websocket.Requests.RevealPassword, + as: BankRevealPasswordRequest + alias Helix.Universe.Bank.Websocket.Requests.Logout, as: LogoutRequest @doc """ Join the Bank Channel @@ -20,7 +27,7 @@ channel Helix.Universe.Bank.Websocket.Channel.Bank do Topic: "bank:@" Params: - - *password: Target account password. + *password: Target account password. Returns: BankAccountBootstrap @@ -28,9 +35,9 @@ channel Helix.Universe.Bank.Websocket.Channel.Bank do Henforcer: - "password_invalid": Password is invalid. + - "bank_account_not_found" Input: - - "bank_account_non_ecziste" + base errors """ @@ -40,24 +47,65 @@ channel Helix.Universe.Bank.Websocket.Channel.Bank do Starts a financial transaction. Params: - - *to_bank_ip: Receiving bank ip. - - *to_bank_net: Receiving bank network. - - *to_acc: Account number that receives money. - - *password: Sending Bank Account password. - - *value: Amount of money that is being sent. + *to_bank_ip: Receiving bank ip. + *to_bank_net: Receiving bank network. + *to_acc: Account number that receives money. + *password: Sending Bank Account password. + *value: Amount of money that is being sent. Returns: :ok Errors: - - "insufficient_money" - - "receiving_bank_not_found" - - "receiving_account_not_found" - - "sending_bank_not_found" - - "sending_account_not_found" - - "receiving_bank_is_not_a_bank" - - "sending_bank_is_not_a_bank" + + Henforcer: + - "atm_not_a_bank" + - "bank_account_no_funds" + - "bank_account_not_found" + """ + topic "bank.transfer", BankTransferRequest + + @doc """ + Starts a password changing request + + Params: none + + Returns: :ok + + Errors: + - internal + + Henforcer: + - "bank_account_not_belongs" + """ + topic "bank.changepass", BankChangePasswordRequest + + @doc """ + Closes a Logged in BankAccount. + + Params: none + + Returns: :ok + + Errors: + - internal + + Henforcer: + - "bank_account_not_belongs" + """ + topic "bank.closeacc", BankCloseAccountRequest + + @doc """ + Starts a RevealPasswordProcess. + + Params: + *token: token.id for logged in account + + Henforcer: + - "token_not_belongs" + - "token_expired" + - "token_not_found" """ - # topic "bank.transfer", BankTransfer + topic "bank.reveal", BankRevealPasswordRequest @doc """ Forces a bootstrap to happen. It is the exact same operation ran during join. @@ -70,12 +118,25 @@ channel Helix.Universe.Bank.Websocket.Channel.Bank do Errors: + base errors """ - # topic "bootstrap", BootstrapRequest + topic "bootstrap", BootstrapRequest + @doc """ + Logs out from the channel + + Params: nil + + Returns: :ok + + **Channel will be closed** + + Errors: + - internal + """ + topic "bank.logout", LogoutRequest event_handler "event" def terminate(_reason, _socket) do - + :ok end end diff --git a/lib/universe/bank/websocket/channel/bank/join.ex b/lib/universe/bank/websocket/channel/bank/join.ex index 1d5c3c3f..674bd8a5 100644 --- a/lib/universe/bank/websocket/channel/bank/join.ex +++ b/lib/universe/bank/websocket/channel/bank/join.ex @@ -2,7 +2,30 @@ import Helix.Websocket.Join join Helix.Universe.Bank.Websocket.Channel.Bank.Join do @moduledoc """ - Joinable implementation for the Bank channel + Joinable implementation for the Bank channel. + + There are two main methods to joining the Bank channel: + - using password + - using token + + On Logging with password the player has access for unlimited time on the + BankAccount that he logged in. + + On Logging with token the player has access for the time that the token is not + expired, when logged with a token the player can convert the token to the + password. + + Params: + - password: Password for given account. + - token: Token for the given account. + - *entity_id: Entity.id for the logging player. + - *gateway_id: Server.id for the logging server. + - bounce_id: Bounce id, if is nil logs with no bounce. + + The given password or token must match the given BankAccount, otherwise the + user is denied access to the BankAccount. + + It Returns Bootstrap for the logged BankAccount. """ use Helix.Logger @@ -11,13 +34,30 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do alias Helix.Websocket.Utils, as: WebsocketUtils alias Helix.Account.Model.Account + alias Helix.Entity.Henforcer.Entity, as: EntityHenforcer alias Helix.Entity.Query.Entity, as: EntityQuery alias Helix.Server.Model.Server + alias Helix.Server.Henforcer.Server, as: ServerHenforcer + alias Helix.Network.Henforcer.Bounce, as: BounceHenforcer + alias Helix.Universe.Bank.Action.Flow.BankAccount, as: BankAccountFlow alias Helix.Universe.Bank.Model.BankAccount alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer alias Helix.Universe.Bank.Public.Bank, as: BankPublic def check_params(request, _socket) do + {_, password} = + if request.unsafe["password"] do + validate_input(request.unsafe["password"], :password) + else + {:ok, nil} + end + + {_, token} = + if request.unsafe["token"] do + validate_input(request.unsafe["token"], :token) + else + {:ok, nil} + end with \ "bank:" <> bank_account_id <- request.topic, @@ -34,15 +74,32 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do # Validate Account.id {:ok, account_id} <- Account.ID.cast(request.unsafe["entity_id"]), - # Validate password - {:ok, password} <- validate_input(request.unsafe["password"], :password) + # Validate Gateway.id + {:ok, gateway_id} <- Server.ID.cast(request.unsafe["gateway_id"]), + + # Validate Bounce.id + {:ok, bounce_id} <- validate_bounce(request.unsafe["bounce_id"]), + + # Check if has password or token + true <- not is_nil(password) or not is_nil(token) do - params = %{ + params = %{ atm_id: atm_id, account_number: account_number, - password: password, - account_id: account_id - } + account_id: account_id, + gateway_id: gateway_id, + bounce_id: bounce_id + } + + params = + cond do + password != nil -> + Map.put(params, :password, password) + token != nil -> + Map.put(params, :token, token) + true -> + params + end update_params(request, params, reply: true) else @@ -51,18 +108,94 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do end end - def check_permissions(request, _socket) do + def check_permissions(request = %{params: %{token: token}}, _socket) do atm_id = request.params.atm_id account_number = request.params.account_number account_id = request.params.account_id password = request.params.password entity_id = EntityQuery.get_entity_id(account_id) + gateway_id = request.params.gateway_id + bounce_id = request.params.bounce_id with \ - {true, _} <- - BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + {true, r1} <- + BankHenforcer.can_join_token?(atm_id, account_number, token, entity_id), + bank_account = r1.bank_account, + entity_id = r1.entity.entity_id, + token = r1.token, + + # Checking if gateway exists + {true, r2} <- ServerHenforcer.server_exists?(gateway_id), + gateway = r2.server, + + # Checking if gateway belongs to entity + {true, r3} <- EntityHenforcer.owns_server?(entity_id, gateway), + gateway = r3.server, + + # Checking if bounce exists + {true, r4} <- BounceHenforcer.can_use_bounce?(entity_id, bounce_id), + bounce = r4.bounce do - reply_ok(request) + meta = + %{ + gateway: gateway, + account_id: account_id, + entity_id: entity_id, + bank_account: bank_account, + token: token, + bounce: bounce + } + update_meta(request, meta, reply: true) + else + {false, reason, _} -> + reply_error(request, reason) + _ -> + bad_request(request) + end + end + + def check_permissions(request = %{params: %{password: password}}, _socket) do + atm_id = request.params.atm_id + account_number = request.params.account_number + account_id = request.params.account_id + entity_id = EntityQuery.get_entity_id(account_id) + gateway_id = request.params.gateway_id + bounce_id = request.params.bounce_id + + with \ + {true, r1} <- + BankHenforcer.can_join_password?( + atm_id, + account_number, + password, + entity_id + ), + bank_account = r1.bank_account, + password = r1.password, + entity_id = r1.entity.entity_id, + + # Checking if gateway exists + {true, r2} <- ServerHenforcer.server_exists?(gateway_id), + gateway = r2.server, + + # Checking if gateway belongs to entity + {true, r3} <- EntityHenforcer.owns_server?(entity_id, gateway), + gateway = r3.server, + + # Checking if bounce exists + {true, r4} <- BounceHenforcer.can_use_bounce?(entity_id, bounce_id), + bounce = r4.bounce + do + meta = + %{ + gateway: gateway, + account_id: account_id, + entity_id: entity_id, + bank_account: bank_account, + password: password, + bounce: bounce + } + update_meta(request, meta, reply: true) else {false, reason, _} -> reply_error(request, reason) @@ -74,36 +207,126 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do @doc """ Joins a BankAccount. """ - def join(request, socket, assign) do - account_id = request.params.account_id + def join(request = %{meta: %{password: password}}, socket, assign) do + account_id = request.meta.account_id + gateway = request.meta.gateway + bounce = request.meta.bounce + entity_id = request.meta.entity_id + atm_id = request.meta.bank_account.atm_id + account_number = request.meta.bank_account.account_number + bank_account_id = {atm_id, account_number} - atm_id = request.params.atm_id - account_number = request.params.account_number + bounce_id = + if bounce do + bounce.bounce_id + else + nil + end + + with \ + {:ok, tunnel, connection} <- + BankAccountFlow.login_password( + atm_id, + account_number, + gateway.server_id, + bounce_id, + password + ) + do + gateway_data = + %{ + server_id: gateway.server_id, + entity_id: entity_id + } + + bootstrap = + bank_account_id + |> BankPublic.bootstrap() + |> BankPublic.render_bootstrap() + |> WebsocketUtils.wrap_data() + + socket = + socket + |> assign.(:atm_id, atm_id) + |> assign.(:account_number, account_number) + |> assign.(:account_id, account_id) + |> assign.(:gateway, gateway_data) + |> assign.(:tunnel, tunnel) + |> assign.(:bank_login, connection) + + "bank:" <> topic_name = request.topic + + log :join, topic_name, + relay: request.relay, + data: %{ + channel: :bank, + bank_account: topic_name, + status: :ok + } + + {:ok, bootstrap, socket} + end + end + + def join(request = %{meta: %{token: token}}, socket, assign) do + account_id = request.meta.account_id + gateway = request.meta.gateway + bounce = request.meta.bounce + entity_id = request.meta.entity_id + atm_id = request.meta.bank_account.atm_id + account_number = request.meta.bank_account.account_number bank_account_id = {atm_id, account_number} - bootstrap = - bank_account_id - |> BankPublic.bootstrap() - |> BankPublic.render_bootstrap() - |> WebsocketUtils.wrap_data() + bounce_id = + if bounce do + bounce.bounce_id + else + nil + end + + with \ + {:ok, tunnel, connection} <- + BankAccountFlow.login_token( + atm_id, + account_number, + gateway.server_id, + bounce_id, + token + ) + do + gateway_data = + %{ + server_id: gateway.server_id, + entity_id: entity_id + } + + bootstrap = + bank_account_id + |> BankPublic.bootstrap() + |> BankPublic.render_bootstrap() + |> WebsocketUtils.wrap_data() - socket = - socket - |> assign.(:atm_id, atm_id) - |> assign.(:account_number, account_number) - |> assign.(:account_id, account_id) + socket = + socket + |> assign.(:atm_id, atm_id) + |> assign.(:account_number, account_number) + |> assign.(:account_id, account_id) + |> assign.(:gateway, gateway_data) + |> assign.(:tunnel, tunnel) + |> assign.(:bank_login, connection) - "bank:" <> topic_name = request.topic + "bank:" <> topic_name = request.topic - log :join, topic_name, - relay: request.relay, - data: %{ - channel: :bank, - bank_account: topic_name, - status: :ok - } + log :join, topic_name, + relay: request.relay, + data: %{ + channel: :bank, + bank_account: topic_name, + status: :ok + } - {:ok, bootstrap, socket} + {:ok, bootstrap, socket} + end end docp """ @@ -131,7 +354,13 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do log :join, id, relay: request.relay, - data: %{channel: :bank, status: :error, reason: reason} + data: + %{ + channel: :bank, + type: request.type, + status: :error, + reason: reason + } end end diff --git a/test/universe/bank/henforcer/bank_test.exs b/test/universe/bank/henforcer/bank_test.exs index fdffe7ff..a42ecfe8 100644 --- a/test/universe/bank/henforcer/bank_test.exs +++ b/test/universe/bank/henforcer/bank_test.exs @@ -4,115 +4,464 @@ defmodule Helix.Universe.Bank.Henforcer.BankTest do import Helix.Test.Henforcer.Macros + alias Helix.Server.Query.Server, as: ServerQuery alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Test.Network.Helper, as: NetworkHelper + alias Helix.Test.Server.Helper, as: ServerHelper + alias Helix.Test.Server.Setup, as: ServerSetup alias Helix.Test.Universe.Bank.Helper, as: BankHelper alias Helix.Test.Universe.Bank.Setup, as: BankSetup alias Helix.Test.Entity.Setup, as: EntitySetup - alias Helix.Test.Entity.Helper, as: EntityHelper + + @internet_id NetworkHelper.internet_id() describe "account_exists?/1" do test "accepts when account exists" do + # Setups a BankAccount for testing bank_acc = BankSetup.account!() + # Asserts that henforcer accepts when account exists assert {true, relay} = BankHenforcer.account_exists?(bank_acc.atm_id, bank_acc.account_number) + # Asserts that bank account on relay is the same + # as created before assert relay.bank_account == bank_acc + # Assert that relay only contains :bank_account key assert_relay relay, [:bank_account] end test "rejects when account does not exist" do + # Asserts that henforcer returns error when account does not exist assert {false, reason, _} = BankHenforcer.account_exists?( BankHelper.atm_id(), BankHelper.account_number() ) + # Asserts that the reason for failing is `{:bank_account, :not_found}` assert reason == {:bank_account, :not_found} end end + describe "password_valid?/2" do test "accepts when password is the same as the bank account's password" do + # Setups a BankAccount for testing bank_acc = BankSetup.account!() + # Asserts that henforcer accepts when passwords match assert {true, relay} = BankHenforcer.password_valid?(bank_acc, bank_acc.password) + # Asserts that relay's password is the same as BankAccounts's Password assert relay.password == bank_acc.password + # Asserts that relay only contains the :password key assert_relay relay, [:password] end + test "rejects when password is not the same as the bank account's password" do + # Setups a BankAccount for testing bank_acc = BankSetup.account!() + # Asserts that henforcer returns error when password does not match assert {false, reason, _} = BankHenforcer.password_valid?(bank_acc, BankHelper.password()) + # Asserts that the reason for failing is `{:password, :invalid}` assert reason == {:password, :invalid} - end end - describe "can_join?/4" do + + describe "can_join_password?/4" do test "accepts when account and entity exists and the password is valid" do + # Setups an Entity for testing {entity, _} = EntitySetup.entity() + entity_id = entity.entity_id + # Setups a BankAccount for testing bank_acc = BankSetup.account!() atm_id = bank_acc.atm_id account_number = bank_acc.account_number password = bank_acc.password - entity_id = entity.entity_id + # Asserts that henforcer accepts when account and entity exists and + # the passwords match assert {true, relay} = - BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + BankHenforcer.can_join_password?( + atm_id, + account_number, + password, + entity_id + ) + # Asserts that relay information is correct assert relay.bank_account == bank_acc assert relay.password == password assert relay.entity == entity + # Asserts that relay only contains desired keys assert_relay relay, [:bank_account, :password, :entity] end + test "rejects when account does not exists" do + # Setups an Entity for testing {entity, _} = EntitySetup.entity() + # Setups fake BankAccount Values atm_id = BankHelper.atm_id() account_number = BankHelper.account_number password = BankHelper.password entity_id = entity.entity_id + # Asserts that henforcer rejects trying to join a account that does not + # exist assert {false, reason, _} = - BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + BankHenforcer.can_join_password?( + atm_id, + account_number, + password, + entity_id + ) + # Asserts that reason to fail is `{:bank_account, :not_found}` + # Because the account does't exists assert reason == {:bank_account, :not_found} - end + test "rejects when entity does not exists" do + # Setups BankAccount for testing bank_acc = BankSetup.account!() - atm_id = bank_acc.atm_id account_number = bank_acc.account_number password = bank_acc.password + + # Generates an entity id that not exists on database entity_id = EntitySetup.id() + # Asserts that henforcer rejects when trying to join with a entity that + # does not exist assert {false, reason, _} = - BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + BankHenforcer.can_join_password?( + atm_id, + account_number, + password, + entity_id + ) + # Asserts that reason for fail is `{:entity, :not_found}` assert reason == {:entity, :not_found} end + test "rejects when password does not match" do + # Setups an Entity and a BankAccount for testing {entity, _} = EntitySetup.entity() bank_acc = BankSetup.account!() - entity_id = entity.entity_id atm_id = bank_acc.atm_id account_number = bank_acc.account_number + + # Generates a password that not match BankAccount's password password = BankHelper.password() + # Asserts the henforcer rejects when passwords does not match assert {false, reason, _} = - BankHenforcer.can_join?(atm_id, account_number, password, entity_id) + BankHenforcer.can_join_password?( + atm_id, + account_number, + password, + entity_id + ) + # Asserts that reason for fail is `{:password, :invalid}` assert reason == {:password, :invalid} + end + end + + describe "can_join_token?/4" do + test "accepts when account and entity exists and the token is valid" do + # Setups an Entity for testing + {entity, _} = EntitySetup.entity() + entity_id = entity.entity_id + + # Setups a BankAccount for testing + bank_acc = BankSetup.account!() + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Setups a Token for testing + token = BankSetup.token!(acc: bank_acc) + + # Asserts that accepts when both Entity, Account and Token are valid. + assert {true, relay} = + BankHenforcer.can_join_token?( + atm_id, + account_number, + token.token_id, + entity_id + ) + + # Asserts that token is the same as the token created before. + assert relay.token.token_id == token.token_id + assert relay.token.atm_id == atm_id + assert relay.token.account_number == account_number + + # Asserts that relay's account still the same as when created. + assert relay.bank_account.atm_id == atm_id + assert relay.bank_account.account_number == account_number + + # Asserts that relay's entity still the same as when created. + assert relay.entity == entity + + # Asserts that relay only contains the :token key. + assert_relay relay, [:token, :bank_account, :entity] + end + + test "rejects when token is not related to given account" do + # Setups an Entity for testing + {entity, _} = EntitySetup.entity() + entity_id = entity.entity_id + + # Setups a BankAccount for trying to join + bank_acc = BankSetup.account!() + + # Setups a BankAccount for generate token + bank_acc_token = BankSetup.account!() + + # Gets the trying to join BankAccount information + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Setup a Token for the `bank_acc_token` BankAccount + token = BankSetup.token!(acc: bank_acc_token) + + # Asserts that henforcer rejects because the given token + # does not belongs for the BankAccount used to try the join + assert {false, reason, _} = + BankHenforcer.can_join_token?( + atm_id, + account_number, + token.token_id, + entity_id + ) + + # Asserts that the reason is `{:token, :not_belongs}` + assert reason == {:token, :not_belongs} + end + + test "rejects when token is expired" do + # Setups an Entity for testing + {entity, _} = EntitySetup.entity() + entity_id = entity.entity_id + + # Setups a BankAccount for testing + bank_acc = BankSetup.account!() + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Setups an expired Token + token = BankSetup.token!(acc: bank_acc, expired: true) + token_id = token.token_id + + # Asserts that henforcer rejects because the token is expired + assert {false, reason, _} = + BankHenforcer.can_join_token?( + atm_id, + account_number, + token_id, + entity_id + ) + + # Asserts that the reason for failing is `{:token, :expired}` + assert reason == {:token, :expired} + end + + test "rejects when token does not exists" do + # Setups an Entity for testing + {entity, _} = EntitySetup.entity() + entity_id = entity.entity_id + + # Setups a BankAccount for testing + bank_acc = BankSetup.account!() + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Setups an token id that does not exist + token_id = Ecto.UUID.generate() + + # Asserts that henforcer rejects because token does not exist + assert {false, reason, _} = + BankHenforcer.can_join_token?( + atm_id, + account_number, + token_id, + entity_id + ) + + # Asserts that the failing reason is `{:token, :not_found}` + assert reason == {:token, :not_found} + end + end + + describe "owns_account?/2" do + test "accepts when the entity owns the account" do + # Setups an Entity and a BankAccount that belongs to that Entity + {entity, _} = EntitySetup.entity() + bank_acc = BankSetup.account!(owner_id: entity.entity_id) + + # Asserts that henforcer accepts because `bank_acc` belongs to `entity` + assert {true, relay} = + BankHenforcer.owns_account?(entity, bank_acc) + + # Asserts that relay's BankAccount is the same as created BankAccount + assert relay.bank_account == bank_acc + + # Asserts that relay only contains :bank_account key + assert_relay relay, [:bank_account] + end + + test "rejects when the entity not owns the account" do + # Setups an Entity for testing + {entity, _} = EntitySetup.entity() + + # Setups a BankAccount which not belongs to that entity + bank_acc = BankSetup.account!() + + # Asserts that henforcer rejects because entity does not own the + # BankAccount + assert{false, reason, _} = + BankHenforcer.owns_account?(entity, bank_acc) + + # Asserts that reason for failing is `{:bank_account, :not_found}` + assert reason == {:bank_account, :not_belongs} + end + end + + describe "is_empty?/1" do + test "accepts when the account has no funds" do + # Setups a BankAccount for testing + bank_acc = BankSetup.account!() + + # Asserts that henforcer accepts because the BankAccount has no funds + assert {true, relay} = + BankHenforcer.is_empty?(bank_acc) + + # Asserts that the relay's BankAccount is the same as `bank_acc` + assert relay.bank_account == bank_acc + + # Asserts that relay only contains :bank_account key + assert_relay relay, [:bank_account] + end + + test "rejects when account has funds" do + # Setups an BankAccount that has funds + bank_acc = BankSetup.account!(balance: 500) + + # Asserts that henforcer rejects because the BankAccount has funds + assert {false, reason, _} = + BankHenforcer.is_empty?(bank_acc) + + # Asserts that reason for failing is `{:bank_account, :not_empty}` + assert reason == {:bank_account, :not_empty} + end + end + + describe "has_enough_funds?/2" do + test "accepts when account has enough funds" do + # Setups a BankAccount that has enough funds for tranfer + bank_acc = BankSetup.account!(balance: 500) + + # Asserts that henforcer accepts because the BankAccount has the required + # funds to do the transfer + assert {true, relay} = + BankHenforcer.has_enough_funds?(bank_acc, 500) + + # Asserts that relay's amount is the same as given + assert relay.amount == 500 + + # Asserts that relay only contains :amount key + assert_relay relay, [:amount] + end + + test "rejects when account has not enough funds" do + # Setups a BankAccount that does not have enough funds + bank_acc = BankSetup.account!(balance: 500) + + # Asserts that henforcer reject because the BankAccount + # has no enough funds + assert {false, reason, _} = + BankHenforcer.has_enough_funds?(bank_acc, 501) + + # Asserts that reason for failing is `{:bank_account, :no_funds}` + assert reason == {:bank_account, :no_funds} + end + end + + describe "can_transfer?/5" do + test "accepts when everything is ok" do + # Setups a BankAccount to send money + snd_acc = BankSetup.account!(balance: 500) + snd_acc_id = {snd_acc.atm_id, snd_acc.account_number} + + # Setups a BankAccount to receive money + rcv_acc = BankSetup.account!() + + # Gets the receiving BankAccount's ATM server information + atm = ServerQuery.fetch(rcv_acc.atm_id) + atm_ip = ServerHelper.get_ip(atm) + atm_nip = {@internet_id, atm_ip} + + # Asserts that henforcer accepts because both + # BankAccount exists, the sending BankAccount has enough money, + # the ATM exists and is a bank + assert {true, relay} = + BankHenforcer.can_transfer?( + atm_nip, + rcv_acc.account_number, + snd_acc_id, + 500, + snd_acc.password + ) + + # Asserts that relay value are all correct + assert relay.amount == 500 + assert relay.password == snd_acc.password + assert relay.to_account == rcv_acc + + # Asserts that relay only contains the neccessary keys + assert_relay relay, [:amount, :password, :to_account] + end + end + + describe "is_a_bank?" do + test "accepts when the given server is a bank" do + # Setups a BankAccount for getting an atm_id that + # really comes from a bank + bank_acc = BankSetup.account!() + + # Asserts that henforcer accepts because the atm_id belongs to a bank + assert {true, relay} = + BankHenforcer.is_a_bank?(bank_acc.atm_id) + + # Asserts that relay values are correct + assert relay.atm == BankQuery.fetch_atm(bank_acc.atm_id) + + # Asserts that relay only contains needed values + assert relay, [:atm] + end + + test "rejects when the given server is not a bank" do + # Setups a Server that is not a bank + server = ServerSetup.server!() + + # Asserts the henforcer rejects because the server is not a bank + assert {false, reason, _} = + BankHenforcer.is_a_bank?(server.server_id) + # Asserts that reason for failing is `{:atm, :not_a_bank}` + assert reason == {:atm, :not_a_bank} end end end diff --git a/test/universe/bank/websocket/channel/bank/join_test.exs b/test/universe/bank/websocket/channel/bank/join_test.exs index 0287d997..67d7df71 100644 --- a/test/universe/bank/websocket/channel/bank/join_test.exs +++ b/test/universe/bank/websocket/channel/bank/join_test.exs @@ -4,33 +4,31 @@ defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do import Phoenix.ChannelTest - alias HELL.TestHelper.Random - alias Helix.Test.Cache.Helper, as: CacheHelper alias Helix.Test.Channel.Helper, as: ChannelHelper alias Helix.Test.Channel.Setup, as: ChannelSetup - alias Helix.Test.Network.Helper, as: NetworkHelper - alias Helix.Test.Network.Setup, as: NetworkSetup - alias Helix.Test.Server.Helper, as: ServerHelper + alias Helix.Test.Network.Setup.Bounce, as: BounceSetup alias Helix.Test.Server.Setup, as: ServerSetup alias Helix.Test.Universe.Bank.Helper, as: BankHelper alias Helix.Test.Universe.Bank.Setup, as: BankSetup @moduletag :driver - @internet_id NetworkHelper.internet_id() - describe "BankJoin" do test "can join a bank account with valid data" do - {socket, %{server: gateway}} = ChannelSetup.create_socket() + {socket, %{account: player, server: gateway}} = + ChannelSetup.create_socket() entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!(entity_id: entity_id) - {bank_account, _} = BankSetup.account() + bank_account = BankSetup.account!() payload = %{ "entity_id" => to_string(entity_id), - "password" => bank_account.password + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id), + "bounce_id" => to_string(bounce.bounce_id) } atm_id = bank_account.atm_id @@ -48,6 +46,17 @@ defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do # `entity` data is valid assert new_socket.assigns.entity_id == entity_id + # `account` data is valid + assert new_socket.assigns.account_id == + player.account_id + + # `gateway` data is valid + assert new_socket.assigns.gateway.server_id == gateway.server_id + + # connection is being created + assert Map.has_key?(new_socket.assigns, :tunnel) + assert Map.has_key?(new_socket.assigns, :bank_login) + # Socket related stuff assert new_socket.joined assert new_socket.topic == topic @@ -60,14 +69,17 @@ defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do {socket, %{server: gateway}} = ChannelSetup.create_socket() entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!(entity_id: entity_id) - {bank_account, _} = BankSetup.account() + bank_account = BankSetup.account!() payload = - %{ - "entity_id" => to_string(entity_id), - "password" => BankHelper.password() - } + %{ + "entity_id" => to_string(entity_id), + "password" => BankHelper.password(), + "gateway_id" => to_string(gateway.server_id), + "bounce_id" => to_string(bounce.bounce_id) + } atm_id = bank_account.atm_id account_number = bank_account.account_number @@ -84,11 +96,14 @@ defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do {socket, %{server: gateway}} = ChannelSetup.create_socket() entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!(entity_id: entity_id) payload = %{ "entity_id" => to_string(entity_id), - "password" => BankHelper.password() + "password" => BankHelper.password(), + "gateway_id" => to_string(gateway.server_id), + "bounce_id" => to_string(bounce.bounce_id) } atm_id = BankHelper.atm_id() @@ -102,6 +117,60 @@ defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do assert reason.data == "bank_account_not_found" end + test "can not join with a not owned bounce" do + {socket, %{server: gateway}} = ChannelSetup.create_socket() + + entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!() + + bank_account = BankSetup.account!() + + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id), + "bounce_id" => to_string(bounce.bounce_id) + } + + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Try to join the channel + assert {:error, reason} = join(socket, topic, payload) + + assert reason.data == "bounce_not_belongs" + + end + test "can not join with a not owned gateway" do + {socket, _} = ChannelSetup.create_socket() + + random_server = ServerSetup.server!() + entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!(entity_id: entity_id) + + bank_account = BankSetup.account!() + + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(random_server.server_id), + "bounce_id" => to_string(bounce.bounce_id) + } + + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Try to join the channel + assert {:error, reason} = join(socket, topic, payload) + + assert reason.data == "server_not_belongs" + end end end From ca8f334006db4d70a16340a181d354915281e20b Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:45:25 -0300 Subject: [PATCH 04/32] Fix account channel join topic --- lib/account/websocket/channel/account.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/account/websocket/channel/account.ex b/lib/account/websocket/channel/account.ex index 088dfd78..2443da87 100644 --- a/lib/account/websocket/channel/account.ex +++ b/lib/account/websocket/channel/account.ex @@ -27,7 +27,7 @@ channel Helix.Account.Websocket.Channel.Account do authenticated on the socket. + base errors """ - join _, AccountJoin + join "account:" <> _, AccountJoin @doc """ Forces a bootstrap to happen. It is the exact same operation ran during join. From 51dcc2b5e1f1480357f534db50a34485dd4d517a Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:46:40 -0300 Subject: [PATCH 05/32] Implement bank public --- lib/universe/bank/public/bank.ex | 128 +++++++++++++- lib/universe/bank/public/index.ex | 6 + test/universe/bank/public/bank_test.exs | 222 ++++++++++++++++++++++++ 3 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 test/universe/bank/public/bank_test.exs diff --git a/lib/universe/bank/public/bank.ex b/lib/universe/bank/public/bank.ex index 8071e315..201d5170 100644 --- a/lib/universe/bank/public/bank.ex +++ b/lib/universe/bank/public/bank.ex @@ -1,7 +1,18 @@ defmodule Helix.Universe.Bank.Public.Bank do + alias Helix.Event + alias Helix.Account.Model.Account + alias Helix.Server.Model.Server + alias Helix.Network.Model.Tunnel + alias Helix.Universe.Bank.Action.Flow.BankAccount, + as: BankAccountFlow + alias Helix.Universe.Bank.Action.Flow.BankTransfer, + as: BankTransferFlow + alias Helix.Universe.Bank.Internal.BankAccount, + as: BankAccountInternal alias Helix.Universe.Bank.Query.Bank, as: BankQuery alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Model.BankToken alias Helix.Universe.Bank.Model.ATM #TODO: Add Transfer History @@ -17,7 +28,9 @@ defmodule Helix.Universe.Bank.Public.Bank do @spec bootstrap({ATM.id, BankAccount.account}) :: bootstrap - + @doc """ + Gets the BankAccount information and puts into a map. + """ def bootstrap({atm_id, account_number}) do bank_account = BankQuery.fetch_account(atm_id, account_number) @@ -29,7 +42,120 @@ defmodule Helix.Universe.Bank.Public.Bank do @spec render_bootstrap(bootstrap) :: rendered_bootstrap + @doc """ + Gets Bootstrap information and turns to client friendly format. + """ def render_bootstrap(bootstrap) do bootstrap end + + @spec change_password( + BankAccount.t, + Server.t, + Server.t, + Event.relay + ) :: + {:ok, Process.t} + | {:error, :internal} + @doc """ + Starts a ChangePasswordProcess. + """ + def change_password(account, gateway, atm, relay) do + password_change = + BankAccountFlow.change_password(account, gateway, atm, relay) + case password_change do + {:ok, process} -> + {:ok, process} + _ -> + {:error, :internal} + end + end + + @spec reveal_password( + BankAccount.t, + BankToken.token_id, + Server.t, + Server.t, + Event.relay + ) :: + {:ok, Process.t} + | {:error, :internal} + @doc """ + Starts a RevealPasswordProcess. + """ + def reveal_password(bank_account, token, gateway, atm, relay) do + BankAccountFlow.reveal_password( + bank_account, + token, + gateway, + atm, + relay + ) + end + + @spec open_account(Account.id, Server.id) :: + {:ok, BankAccount.t} + | {:error, :internal} + @doc """ + Opens a BankAccount on given atm to given account_id. + """ + def open_account(account_id, atm_id) do + # TODO: Make as a process + BankAccountFlow.open(account_id, atm_id) + end + + @spec close_account(BankAccount.t) :: + :ok + | {:error, {:bank_account, :not_found}} + | {:error, {:bank_account, :not_empty}} + @doc """ + Closes given account. + """ + defdelegate close_account(account), + # TODO: Make as process + to: BankAccountInternal, + as: :close + + @spec transfer( + BankAccount.t, + BankAccount.t, + BankAccount.amount, + Account.t, + Server.t, + Tunnel.t, + Event.relay + ) :: + {:ok, Process.t} + | {:error, :internal} + @doc """ + Starts a BankTransferProcess. + """ + def transfer( + from_account, + to_account, + amount, + started_by, + gateway, + tunnel, + relay) + do + transfer = + BankTransferFlow.start( + from_account, + to_account, + amount, + started_by, + gateway, + tunnel, + relay + ) + + case transfer do + {:ok, process} -> + {:ok, process} + + {:error, _} -> + {:error, :internal} + end + end end diff --git a/lib/universe/bank/public/index.ex b/lib/universe/bank/public/index.ex index b36c908d..a4ffe16d 100644 --- a/lib/universe/bank/public/index.ex +++ b/lib/universe/bank/public/index.ex @@ -16,6 +16,9 @@ defmodule Helix.Universe.Bank.Public.Index do @spec index(ATM.id, BankAccount.account) :: index + @doc """ + Creates index for given BankAccount{atm_id, account_number}. + """ def index(atm_id, account_number) do bank_account = BankQuery.fetch_account(atm_id, account_number) balance = bank_account.balance @@ -28,6 +31,9 @@ defmodule Helix.Universe.Bank.Public.Index do @spec render_index(index) :: rendered_index + @doc """ + Renders index to client-friendly format. + """ def render_index(index), do: index end diff --git a/test/universe/bank/public/bank_test.exs b/test/universe/bank/public/bank_test.exs new file mode 100644 index 00000000..44469791 --- /dev/null +++ b/test/universe/bank/public/bank_test.exs @@ -0,0 +1,222 @@ +defmodule Helix.Test.Universe.Bank.Public.BankTest do + + use Helix.Test.Case.Integration + + alias Helix.Process.Query.Process, as: ProcessQuery + alias Helix.Server.Query.Server, as: ServerQuery + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + alias Helix.Test.Account.Setup, as: AccountSetup + alias Helix.Test.Network.Setup, as: NetworkSetup + alias Helix.Test.Process.TOPHelper + alias Helix.Test.Server.Setup, as: ServerSetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + alias Helix.Test.Universe.Bank.Helper, as: BankHelper + alias Helix.Test.Universe.NPC.Helper, as: NPCHelper + + @relay nil + + describe "bootstrap/2" do + test "bootstrap is created" do + # Setups a BankAccount. + bank_account = BankSetup.account!(balance: BankHelper.amount) + atm_id = bank_account.atm_id + account_number = bank_account.account_number + account_id = {atm_id, account_number} + + # Gets Bootstrap. + bootstrap = BankPublic.bootstrap(account_id) + + # Asserts that the bootstrap's balance is equals to BankAccount's balance. + assert bootstrap.balance == bank_account.balance + end + end + + describe "render_bootstrap/1" do + test "bootstrap is being properly rendered" do + # Setups a BankAccount. + bank_account = BankSetup.account!(balance: BankHelper.amount) + atm_id = bank_account.atm_id + account_number = bank_account.account_number + account_id = {atm_id, account_number} + + # Gets Bootstrap. + bootstrap = BankPublic.bootstrap(account_id) + + # Renders Bootstrap. + rendered_bootstrap = BankPublic.render_bootstrap(bootstrap) + + # Asserts that Bootstrap information is correct. + assert rendered_bootstrap.balance == bank_account.balance + end + end + + describe "change_password/4" do + test "create the process" do + # Setups a BankAccount. + bank_account = BankSetup.account!() + + # Stores the password for comparing later. + old_password = bank_account.password + + # Setups a gateway. + {gateway, _} = ServerSetup.server() + + # Fetchs ATM server. + atm = ServerQuery.fetch(bank_account.atm_id) + + # Asserts that process has been created. + assert {:ok, process} = + BankPublic.change_password(bank_account, gateway, atm, @relay) + + # Asserts that process properties are correct. + assert process.gateway_id == gateway.server_id + assert process.target_id == atm.server_id + assert process.type == :bank_change_password + assert process.src_atm_id == bank_account.atm_id + assert process.src_acc_number == bank_account.account_number + refute process.src_file_id + refute process.tgt_connection_id + + # Gets process' id + process_id = process.process_id + + # Forces process completion + TOPHelper.force_completion(process_id) + + # Process no longer exists + refute ProcessQuery.fetch(process_id) + + bank_account = + BankQuery.fetch_account(atm.server_id, bank_account.account_number) + + # Refutes if password is equals to old password + refute bank_account.password == old_password + end + end + + describe "transfer/6" do + test "create the process" do + # Setups a gateway and gets it's entity. + {gateway, _} = ServerSetup.server() + + # Setups sending bank account. + bank_acc_snd = BankSetup.account!(balance: 500) + + # Setups eceiving bank account. + bank_acc_rec = BankSetup.account!() + + # Connection Related Stuff. + atm = ServerQuery.fetch(bank_acc_snd.atm_id) + + # Setups a Tunnel. + tunnel = NetworkSetup.tunnel!( + gateway_id: gateway.server_id, + destination_id: atm.server_id + ) + + # Setups an Account. + account = AccountSetup.account!() + + # Asserts the transfer starts correctly. + assert {:ok, process} = + BankPublic.transfer( + bank_acc_snd, + bank_acc_rec, + 300, + account, + gateway, + tunnel, + @relay + ) + + # Asserts that process properties are correct. + assert process.gateway_id == gateway.server_id + assert process.target_id == bank_acc_rec.atm_id + assert process.type == :wire_transfer + assert process.src_connection_id + refute process.src_file_id + refute process.tgt_connection_id + + TOPHelper.top_stop(gateway.server_id) + end + end + + describe "reveal_password/5" do + test "creates the process" do + # Setups a Gateway. + {gateway, _} = ServerSetup.server() + + # Setups a BankAccount. + bank_account = BankSetup.account!() + + # Setups a Token. + token = BankSetup.token!(acc: bank_account) + token_id = token.token_id + + # Fetches ATM Server. + atm = ServerQuery.fetch(bank_account.atm_id) + + # Asserts the reveal password process is being created. + assert {:ok, process} = + BankPublic.reveal_password( + bank_account, + token_id, + gateway, + atm, + @relay + ) + + # Asserts that process properties are correct. + assert process.gateway_id == gateway.server_id + assert process.target_id == atm.server_id + assert process.type == :bank_reveal_password + assert process.data.token_id == token.token_id + assert process.data.atm_id == atm.server_id + assert process.data.account_number == bank_account.account_number + refute process.src_file_id + refute process.tgt_connection_id + end + end + + describe "open_account/2" do + test "creates the account" do + # Setups an Account. + account = AccountSetup.account!() + account_id = account.account_id + + # Gets the Bank Server. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Asserts that BankAccount is being created. + assert {:ok, bank_account} = + BankPublic.open_account(account_id, atm_id) + + # Asserts that BankAccount's Owner is the created account. + assert bank_account.owner_id == account_id + + # Assert that BankAccount is on database. + assert BankQuery.fetch_account(atm_id, bank_account.account_number) + end + end + + describe "close_account/1" do + test "deletes the account" do + # Setups a BankAccount. + bank_account = BankSetup.account!() + + # Stores atm_id and account_number for trying fetch after deleting. + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Asserts the BankAccount is being deleted. + assert :ok = + BankPublic.close_account(bank_account) + + # Refutes if BankAccount still exists on database. + refute BankQuery.fetch_account(atm_id, account_number) + end + end +end From c975278705f422470f6bad6890aa12225f16c8d8 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:49:41 -0300 Subject: [PATCH 06/32] Improve test helpers --- test/support/channel/setup.ex | 51 +++++++++++++++++++++++++++++ test/support/process/setup/data.ex | 21 ++++++++++-- test/support/universe/bank/setup.ex | 12 +++++-- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/test/support/channel/setup.ex b/test/support/channel/setup.ex index 65caf198..5a3408c1 100644 --- a/test/support/channel/setup.ex +++ b/test/support/channel/setup.ex @@ -20,8 +20,11 @@ defmodule Helix.Test.Channel.Setup do alias Helix.Test.Cache.Helper, as: CacheHelper alias Helix.Test.Entity.Setup, as: EntitySetup alias Helix.Test.Network.Helper, as: NetworkHelper + alias Helix.Test.Network.Setup, as: NetworkSetup + alias Helix.Test.Network.Setup.Connection, as: ConnectionSetup alias Helix.Test.Server.Setup, as: ServerSetup alias Helix.Test.Software.Setup, as: SoftwareSetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup alias Helix.Test.Channel.Helper, as: ChannelHelper @endpoint Helix.Endpoint @@ -386,6 +389,54 @@ defmodule Helix.Test.Channel.Setup do } end + def mock_bank_socket(opts \\ []) do + gateway_id = Keyword.get(opts, :gateway_id, ServerSetup.id) + gateway_entity_id = Keyword.get(opts, :gateway_entity_id, EntitySetup.id()) + + account = BankSetup.account!() + atm_id = Keyword.get(opts, :atm_id, account.atm_id) + account_number = Keyword.get(opts, :account_number, account.account_number) + + account = AccountSetup.account!() + account_id = account.account_id + tunnel = + NetworkSetup.tunnel!( + gateway_id: gateway_id, + target_id: atm_id + ) + + connection = + ConnectionSetup.connection( + entity_id: gateway_entity_id + ) + + gateway_data = + %{ + server_id: gateway_id, + entity_id: gateway_entity_id + } + + assigns = + %{ + atm_id: atm_id, + account_number: account_number, + account_id: account_id, + gateway: gateway_data, + tunnel: tunnel, + bank_login: connection + } + + assigns = + (opts[:connect_opts] || []) + |> fake_connection_socket_assigns() + |> Map.merge(assigns) + + %{ + assigns: assigns, + joined: true + } + end + @doc """ Opts: diff --git a/test/support/process/setup/data.ex b/test/support/process/setup/data.ex index fff5eb6e..066827f5 100644 --- a/test/support/process/setup/data.ex +++ b/test/support/process/setup/data.ex @@ -26,11 +26,14 @@ defmodule Helix.Test.Process.Data.Setup do alias Helix.Software.Model.SoftwareType.LogForge alias Helix.Software.Process.File.Transfer, as: FileTransferProcess alias Helix.Software.Process.File.Install, as: FileInstallProcess + alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, + as: BankChangePassword alias HELL.TestHelper.Random alias Helix.Test.Log.Helper, as: LogHelper + alias Helix.Test.Server.Setup, as: ServerSetup alias Helix.Test.Process.Helper.TOP, as: TOPHelper - + alias Helix.Test.Universe.Bank.Setup, as: BankSetup @doc """ Chooses a random implementation and uses it. Beware that `data_opts`, used by `custom/3`, is always an empty list when called from `random/1`. @@ -166,6 +169,20 @@ defmodule Helix.Test.Process.Data.Setup do {:cracker_bruteforce, data, meta, resources} end + def custom(:bank_change_password, _data_opts, meta) do + data = BankChangePassword.new(%{}) + + resources = + %{ + l_dynamic: [:cpu], + r_dynamic: [], + static: TOPHelper.Resources.random_static(), + objective: TOPHelper.Resources.objective(cpu: 500) + } + + {:bank_login, data, meta, resources} + end + @doc """ Probably does not work """ @@ -196,7 +213,7 @@ defmodule Helix.Test.Process.Data.Setup do - operation: :edit | :create. Defaults to :edit. - target_log_id: Which log to edit. Won't generate a real one. - message: Revision message. - + All others are automatically derived from process meta data. """ def custom(:forge, data_opts, meta) do diff --git a/test/support/universe/bank/setup.ex b/test/support/universe/bank/setup.ex index c3f0bb0d..804bf564 100644 --- a/test/support/universe/bank/setup.ex +++ b/test/support/universe/bank/setup.ex @@ -177,6 +177,11 @@ defmodule Helix.Test.Universe.Bank.Setup do {inserted, related} end + def token!(opts \\ []) do + {inserted, _related} = token(opts) + + inserted + end @doc """ - acc: Associate that account to the token (BankAccount.t) - connection_id: Specify the connection ID. @@ -208,7 +213,10 @@ defmodule Helix.Test.Universe.Bank.Setup do |> Kernel.+(-1) |> DateTime.from_unix!(:second) else - nil + DateTime.utc_now() + |> DateTime.to_unix(:second) + |> Kernel.+(60 * 5) + |> DateTime.from_unix!(:second) end token = @@ -264,7 +272,7 @@ defmodule Helix.Test.Universe.Bank.Setup do {server, %{entity: entity}} = ServerSetup.server() # Login with the right password - {:ok, connection} = + {:ok, _, connection} = BankAccountFlow.login_password( acc.atm_id, acc.account_number, server.server_id, nil, acc.password ) From db904f236187d52a44f46d3c88068ac98f050d4c Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:52:12 -0300 Subject: [PATCH 07/32] Add support for getting src and tgt bank information --- lib/process/executable.ex | 17 ++++++++++++----- lib/process/model/process.ex | 2 ++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/process/executable.ex b/lib/process/executable.ex index 6f0c274b..1d460997 100644 --- a/lib/process/executable.ex +++ b/lib/process/executable.ex @@ -318,8 +318,12 @@ defmodule Helix.Process.Executable do src_acc_number: BankAccount.account | nil } @doc false - defp get_source_bank_account(_, _, _, _), - do: %{src_atm_id: nil, src_acc_number: nil} + defp get_source_bank_account(_, _, _, meta) do + %{ + src_atm_id: Map.get(meta, :src_atm_id, nil), + src_acc_number: Map.get(meta, :src_acc_number, nil) + } + end @spec get_target_bank_account(Server.t, Server.t, params, meta) :: %{ @@ -327,9 +331,12 @@ defmodule Helix.Process.Executable do tgt_acc_number: BankAccount.account | nil } @doc false - defp get_target_bank_account(_, _, _, _), - do: %{tgt_atm_id: nil, tgt_acc_number: nil} - + defp get_target_bank_account(_, _, _, meta) do + %{ + tgt_atm_id: Map.get(meta, :tgt_atm_id, nil), + tgt_acc_number: Map.get(meta, :tgt_acc_number, nil) + } + end @spec get_target_process(Server.t, Server.t, params, meta) :: %{tgt_process_id: Process.t | nil} @doc false diff --git a/lib/process/model/process.ex b/lib/process/model/process.ex index a9938606..590ad95f 100644 --- a/lib/process/model/process.ex +++ b/lib/process/model/process.ex @@ -77,6 +77,8 @@ defmodule Helix.Process.Model.Process do | :cracker_bruteforce | :cracker_overflow | :install_virus + | :bank_change_password + | :bank_reveal_password @typedoc """ List of signals a process may receive during its lifetime. From 29d03fbd528783795607694dbe67158fd823363f Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:57:11 -0300 Subject: [PATCH 08/32] Implement bank transfer --- lib/universe/bank/action/flow/bank_account.ex | 49 ++++- .../bank/action/flow/bank_transfer.ex | 6 +- .../websocket/channel/requests/transfer.ex | 117 ++++++++++ .../bank/action/flow/bank_account_test.exs | 38 +++- .../bank/websocket/requests/transfer_test.exs | 202 ++++++++++++++++++ 5 files changed, 406 insertions(+), 6 deletions(-) create mode 100644 lib/universe/bank/websocket/channel/requests/transfer.ex create mode 100644 test/universe/bank/websocket/requests/transfer_test.exs diff --git a/lib/universe/bank/action/flow/bank_account.ex b/lib/universe/bank/action/flow/bank_account.ex index 3c7e3548..ec0d0491 100644 --- a/lib/universe/bank/action/flow/bank_account.ex +++ b/lib/universe/bank/action/flow/bank_account.ex @@ -5,6 +5,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do alias Helix.Event alias Helix.Entity.Query.Entity, as: EntityQuery alias Helix.Network.Action.Flow.Tunnel, as: TunnelFlow + alias Helix.Network.Model.Connection alias Helix.Network.Query.Network, as: NetworkQuery alias Helix.Process.Model.Process alias Helix.Server.Model.Server @@ -15,6 +16,8 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do alias Helix.Universe.Bank.Process.Bank.Account.RevealPassword, as: BankAccountRevealPasswordProcess + alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, + as: BankAccountChangePasswordProcess @typep relay :: Event.relay @@ -29,7 +32,12 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do Emits: ProcessCreatedEvent """ - @spec reveal_password(BankAccount.t, BankToken.id, Server.t, Server.t, relay) :: + @spec reveal_password( + BankAccount.t, + BankToken.id, + Server.t, + Server.t, + relay) :: {:ok, Process.t} | BankAccountRevealPasswordProcess.executable_error def reveal_password(account, token_id, gateway, atm, relay) do @@ -46,6 +54,41 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do BankAccountRevealPasswordProcess.execute(gateway, atm, params, meta, relay) end + @spec open(Account.id, ATM.id) :: + {:ok, BankAccount.t} + | {:error, :internal} + @doc """ + Opens a new BankAccount to given Account.id + """ + def open(account_id, atm_id) do + bank_account = + BankAction.open_account(account_id, atm_id) + case bank_account do + {:ok, bank_account} -> + {:ok, bank_account} + {:error, _} -> + {:error, :internal} + end + end + + @spec change_password(BankAccount.t, Server.t, Server.t, relay) :: + {:ok, Process.t} + | BankAccountChangePasswordProcess.executable_error + @doc """ + Starts a ChangePasswordProcess + + Emits: ProcessCreatedEvent + """ + def change_password(account, gateway, atm, relay) do + meta = %{ + network_id: NetworkQuery.internet().network_id, + src_atm_id: atm.server_id, + src_acc_number: account.account_number, + bounce: nil + } + + BankAccountChangePasswordProcess.execute(gateway, atm, %{}, meta, relay) + end @doc """ Logs into a bank account using a password. If the given password matches the current account password, the login is successful, in which case a BankLogin @@ -74,9 +117,9 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do {:ok, _, events} <- BankAction.login_password(acc, password, entity), on_success(fn -> Event.emit(events) end), - {:ok, _, connection} <- start_connection.() + {:ok, tunnel, connection} <- start_connection.() do - {:ok, connection} + {:ok, tunnel, connection} end end end diff --git a/lib/universe/bank/action/flow/bank_transfer.ex b/lib/universe/bank/action/flow/bank_transfer.ex index 939ec8f8..dc03d008 100644 --- a/lib/universe/bank/action/flow/bank_transfer.ex +++ b/lib/universe/bank/action/flow/bank_transfer.ex @@ -63,7 +63,11 @@ defmodule Helix.Universe.Bank.Action.Flow.BankTransfer do meta = %{ network_id: tunnel.network_id, - bounce: tunnel.bounce_id + bounce: tunnel.bounce_id, + src_atm_id: from_account.atm_id, + src_acc_number: from_account.account_number, + tgt_atm_id: to_account.atm_id, + tgt_acc_number: to_account.account_number } BankTransferProcess.execute(gateway, target_atm, params, meta, relay) diff --git a/lib/universe/bank/websocket/channel/requests/transfer.ex b/lib/universe/bank/websocket/channel/requests/transfer.ex new file mode 100644 index 00000000..c1af3803 --- /dev/null +++ b/lib/universe/bank/websocket/channel/requests/transfer.ex @@ -0,0 +1,117 @@ +import Helix.Websocket.Request + +request Helix.Universe.Bank.Websocket.Requests.Transfer do + @moduledoc """ + BankTransferRequest is used when the player transfer money from an account to other. + + It Returns :ok or :error + """ + + alias HELL.IPv4 + alias Helix.Account.Query.Account, as: AccountQuery + alias Helix.Network.Model.Network + alias Helix.Server.Query.Server, as: ServerQuery + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + + def check_params(request, _socket) do + with \ + receiving_bank_acc <- request.unsafe["to_acc"], + {:ok, receiving_bank_acc} <- BankAccount.cast(receiving_bank_acc), + {:ok, network_id} <- Network.ID.cast(request.unsafe["to_bank_net"]), + {:ok, ip} <- IPv4.cast(request.unsafe["to_bank_ip"]), + {:ok, password} <- validate_input(request.unsafe["password"], :password), + amount <- request.unsafe["amount"], + {true, amount} <- (fn amount -> {amount > 0, amount} end).(amount) + do + params = + %{ + bank_account: receiving_bank_acc, + bank_ip: ip, + bank_net: network_id, + password: password, + amount: amount + } + update_params(request, params, reply: true) + else + _ -> + bad_request(request) + end + end + + def check_permissions(request, socket) do + nip = {request.params.bank_net, request.params.bank_ip} + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + sending_acc = {socket.assigns.atm_id, socket.assigns.account_number} + receiving_acc = request.params.bank_account + amount = request.params.amount + password = request.params.password + entity_id = socket.assigns.entity_id + gateway_id = socket.assigns.gateway.server_id + account_id = socket.assigns.account_id + + with \ + {true, relay} <- + BankHenforcer.can_transfer?( + nip, + receiving_acc, + sending_acc, + amount, + password + ), + amount = relay.amount, + to_account = relay.to_account + do + meta = + %{ + to_account: to_account, + from_account: BankQuery.fetch_account(atm_id, account_number), + amount: amount, + started_by: AccountQuery.fetch(account_id), + gateway: ServerQuery.fetch(gateway_id) + } + + update_meta(request, meta, reply: true) + else + {false, reason, _} -> + reply_error(request, reason) + _ -> + bad_request(request) + end + end + + def handle_request(request, socket) do + to_account = request.meta.to_account + from_account = request.meta.from_account + amount = request.meta.amount + started_by = request.meta.started_by + relay = request.relay + gateway = request.meta.gateway + tunnel = socket.assigns.tunnel + + transfer = + BankPublic.transfer( + from_account, + to_account, + amount, + started_by, + gateway, + tunnel, + relay + ) + + case transfer do + {:ok, process} -> + update_meta(request, %{process: process}, reply: true) + + {:error, reason} -> + reply_error(request, reason) + end + end + + render_empty() + +end diff --git a/test/universe/bank/action/flow/bank_account_test.exs b/test/universe/bank/action/flow/bank_account_test.exs index 0e633414..c51ecf88 100644 --- a/test/universe/bank/action/flow/bank_account_test.exs +++ b/test/universe/bank/action/flow/bank_account_test.exs @@ -8,6 +8,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do alias Helix.Process.Query.Process, as: ProcessQuery alias Helix.Server.Query.Server, as: ServerQuery alias Helix.Universe.Bank.Action.Flow.BankAccount, as: BankAccountFlow + alias Helix.Universe.Bank.Query.Bank, as: BankQuery alias Helix.Test.Universe.Bank.Setup, as: BankSetup alias Helix.Test.Entity.Database.Setup, as: DatabaseSetup @@ -58,6 +59,39 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do end end + describe "change_password/4" do + test "default life cycle" do + bank_account = BankSetup.account!() + {gateway, _} = ServerSetup.server() + + atm = ServerQuery.fetch(bank_account.atm_id) + old_password = bank_account.password + + # Create process to change password + {:ok, process} = + BankAccountFlow.change_password(bank_account, gateway, atm, @relay) + + # Ensure process is valid + assert process.gateway_id == gateway.server_id + assert process.target_id == bank_account.atm_id + + assert process.src_atm_id == bank_account.atm_id + assert process.src_acc_number == bank_account.account_number + + TOPHelper.force_completion(process) + + # Process no longer exists + refute ProcessQuery.fetch(process.process_id) + + # Ensure it changed the `BankAccount`'s password + atm_id = bank_account.atm_id + account_number = bank_account.account_number + bank_account = BankQuery.fetch_account(atm_id, account_number) + + refute bank_account.password == old_password + end + end + describe "login_password/5" do test "login with valid password on third-party account" do time_before_event = Utils.date_before(1) @@ -65,7 +99,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do {server, %{entity: entity}} = ServerSetup.server() # Login with the right password - assert {:ok, connection} = + assert {:ok, _tunnel, connection} = BankAccountFlow.login_password( acc.atm_id, acc.account_number, server.server_id, nil, acc.password ) @@ -96,7 +130,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do assert to_string(acc.owner_id) == to_string(entity.entity_id) # Login with the right password - assert {:ok, connection} = + assert {:ok, _tunnel, connection} = BankAccountFlow.login_password( acc.atm_id, acc.account_number, server.server_id, nil, acc.password ) diff --git a/test/universe/bank/websocket/requests/transfer_test.exs b/test/universe/bank/websocket/requests/transfer_test.exs new file mode 100644 index 00000000..fb03df18 --- /dev/null +++ b/test/universe/bank/websocket/requests/transfer_test.exs @@ -0,0 +1,202 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.TransferTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Account.Query.Account, as: AccountQuery + alias Helix.Websocket.Requestable + alias Helix.Universe.Bank.Websocket.Requests.Transfer, as: BankTransferRequest + + alias Helix.Test.Channel.Request.Helper, as: RequestHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Network.Helper, as: NetworkHelper + alias Helix.Test.Process.TOPHelper + alias Helix.Test.Server.Helper, as: ServerHelper + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + @internet_id NetworkHelper.internet_id() + + describe "BankTransferRequest.check_params/2" do + test "validates expected data" do + # Setups an Account socket and gets the gateway + # from the related information. + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + # Setups sending BankAccount. + sending_acc = BankSetup.account!(balance: 600) + atm_id = sending_acc.atm_id + account_number = sending_acc.account_number + + # Setups receiving BankAccount. + to_account = BankSetup.account!() + + # Gets ip for the receiving BankAccount's ATM.id. + bank_ip = ServerHelper.get_ip(to_account.atm_id) + + # Gets entity id from the socket assigns. + entity_id = socket.assigns.entity_id + + # Creates topic for connecting on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates parameters for joining the bank channel. + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => sending_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bank_socket} = + join(socket, topic, payload) + + # Creates parameters for BankTransferRequest. + params = + %{ + "to_acc" => to_account.account_number, + "to_bank_net" => to_string(@internet_id), + "to_bank_ip" => bank_ip, + "password" => to_account.password, + "amount" => 300 + } + + # Creates BankTransferRequest with parameters. + request = BankTransferRequest.new(params) + + # Asserts that parameters are in the correct format. + assert {:ok, _request} = Requestable.check_params(request, bank_socket) + end + end + + describe "BankTransferRequest.check_permissions/2" do + test "accepts when everything is OK" do + # Setups an Account socket and gets the related gateway. + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + # Gets the Entity related to the socket. + entity_id = socket.assigns.entity_id + + # Setups the sending BankAccount. + sending_acc = BankSetup.account!(balance: 600) + atm_id = sending_acc.atm_id + account_number = sending_acc.account_number + + # Setups the receiving BankAccount. + to_account = BankSetup.account!() + + # Gets the Bank's ip and sets the amount to transfer. + bank_ip = ServerHelper.get_ip(to_account.atm_id) + amount = 300 + + # Creates topic for connecting on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates parameters for joining the bank channel. + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => sending_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Gets the player's account_id from socket. + account_id = bnk_socket.assigns.account_id + + # Creates parameters for the request. + params = + %{ + bank_account: to_account.account_number, + bank_ip: bank_ip, + bank_net: @internet_id, + password: sending_acc.password, + amount: amount + } + + # Mocks the BankTransferRequest pasing parameters. + request = RequestHelper.mock_request(BankTransferRequest, params) + + # Asserts that request passes on permission test. + assert {:ok, request} = Requestable.check_permissions(request, bnk_socket) + + # Asserts that meta fields are all valid. + assert request.meta.to_account == to_account + assert request.meta.from_account == sending_acc + assert request.meta.amount == amount + assert request.meta.started_by == AccountQuery.fetch(account_id) + assert request.meta.gateway == gateway + end + end + + describe "BankTransferRequest.handle_request/2" do + test "starts the process" do + # Setups an Account socket and gets related gateway. + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + # Gets entity_id from socket. + entity_id = socket.assigns.entity_id + + # Setups sending BankAccount. + sending_acc = BankSetup.account!(balance: 600) + atm_id = sending_acc.atm_id + account_number = sending_acc.account_number + + # Setups receiving BankAccount. + to_account = BankSetup.account!() + + # Gets receiving BankAccount's ATM ip. + bank_ip = ServerHelper.get_ip(to_account.atm_id) + + # Sets the amount to be tranfered. + amount = 300 + + # Creates topic for connecting on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the parameters for joining bank channel. + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => sending_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Create parameters to request. + params = + %{ + bank_account: to_account.account_number, + bank_ip: bank_ip, + bank_net: @internet_id, + password: sending_acc.password, + amount: amount + } + + # Mocks BankTranferRequest. + request = RequestHelper.mock_request(BankTransferRequest, params) + + # Checks permissions to the player do the tranfer. + {:ok, request} = Requestable.check_permissions(request, bnk_socket) + + # Asserts that request is handled correctly. + assert {:ok, request} = Requestable.handle_request(request, bnk_socket) + + # Asserts that process has been created. + assert request.meta.process + + TOPHelper.top_stop(gateway) + end + end +end From 0516695716efb3d92f048fa9d22c4d433736330c Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:53:33 -0300 Subject: [PATCH 09/32] Implement BankAccount bootstrap --- .../websocket/channel/requests/bootstrap.ex | 40 ++++++++++++ .../websocket/requests/bootstrap_test.exs | 65 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 lib/universe/bank/websocket/channel/requests/bootstrap.ex create mode 100644 test/universe/bank/websocket/requests/bootstrap_test.exs diff --git a/lib/universe/bank/websocket/channel/requests/bootstrap.ex b/lib/universe/bank/websocket/channel/requests/bootstrap.ex new file mode 100644 index 00000000..5c3f1b49 --- /dev/null +++ b/lib/universe/bank/websocket/channel/requests/bootstrap.ex @@ -0,0 +1,40 @@ +import Helix.Websocket.Request + +request Helix.Universe.Bank.Websocket.Requests.Bootstrap do + @moduledoc """ + BankBootstrapRequest is used to allow the client to resync its local data + with the Helix server. + + It returns the BankBootstrap, which is the exact same struct returned after + joining a local or remote bank Channel. + """ + alias Helix.Server.Model.Server + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + + def check_params(request, _socket) do + reply_ok(request) + end + + def check_permissions(request, _socket) do + reply_ok(request) + end + + def handle_request(request, socket) do + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + bank_account = {atm_id, account_number} + + bootstrap = + BankPublic.bootstrap(bank_account) + + update_meta(request, %{bootstrap: bootstrap}, reply: true) + end + + render(request, _socket) do + data = BankPublic.render_bootstrap(request.meta.bootstrap) + + {:ok, data} + end +end diff --git a/test/universe/bank/websocket/requests/bootstrap_test.exs b/test/universe/bank/websocket/requests/bootstrap_test.exs new file mode 100644 index 00000000..04b63af3 --- /dev/null +++ b/test/universe/bank/websocket/requests/bootstrap_test.exs @@ -0,0 +1,65 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.BootstrapTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Universe.Bank.Websocket.Requests.Bootstrap, + as: BankBootstrapRequest + alias Helix.Websocket.Requestable + + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + alias Helix.Test.Universe.Bank.Helper, as: BankHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + + describe "BankBootstrapRequest.handle_request/2" do + test "bootstrap" do + # Setups an Account socket. + {socket, %{entity: entity, server: gateway}} = + ChannelSetup.create_socket() + + # Setups a BankAccount. + bank_acc = BankSetup.account!(balance: BankHelper.amount) + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Creates topic to log in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Create the payload which will be used to join the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks a BankBootstrapRequest. + request = + BankBootstrapRequest.new(%{}) + + # Request Checking Parameters (which does not exists in this case). + {:ok, request} = + Requestable.check_params(request, bnk_socket) + + # Request Checking Permissions (which does nothing in this case + # because receives no parameters and is already logged in the + # Bank channel). + {:ok, request} = + Requestable.check_permissions(request, bnk_socket) + + # Asserts that request has been handled successfully. + assert {:ok, request} = + Requestable.handle_request(request, bnk_socket) + + # Asserts that bootstrap information is correct. + assert request.meta.bootstrap + assert request.meta.bootstrap.balance == bank_acc.balance + end + end +end From c9b4eed70a1e4f933c1a08c4351434b6d941e4fb Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 17:59:53 -0300 Subject: [PATCH 10/32] Implement BankAccount password change --- lib/event/dispatcher.ex | 8 + lib/universe/bank/action/bank.ex | 28 +++- .../bank/event/bank/account/password.ex | 22 +++ lib/universe/bank/event/change_password.ex | 43 +++++ .../bank/event/handler/bank/account.ex | 23 +++ .../process/bank/account/password_change.ex | 92 ++++++++++ .../channel/requests/change_password.ex | 57 +++++++ test/universe/bank/action/bank_test.exs | 6 +- .../bank/process/change_password_test.exs | 59 +++++++ .../requests/change_password_test.exs | 158 ++++++++++++++++++ 10 files changed, 489 insertions(+), 7 deletions(-) create mode 100644 lib/universe/bank/event/change_password.ex create mode 100644 lib/universe/bank/process/bank/account/password_change.ex create mode 100644 lib/universe/bank/websocket/channel/requests/change_password.ex create mode 100644 test/universe/bank/process/change_password_test.exs create mode 100644 test/universe/bank/websocket/requests/change_password_test.exs diff --git a/lib/event/dispatcher.ex b/lib/event/dispatcher.ex index b9dc7a2d..4ad519da 100644 --- a/lib/event/dispatcher.ex +++ b/lib/event/dispatcher.ex @@ -301,10 +301,18 @@ defmodule Helix.Event.Dispatcher do BankHandler.Bank.Account, :password_reveal_processed + event BankEvent.ChangePassword.Processed, + BankHandler.Bank.Account, + :password_change_processed + event BankEvent.Bank.Account.Password.Revealed, EntityHandler.Database, :bank_password_revealed + event BankEvent.Bank.Account.Password.Changed, + BankHandler.Bank.Account, + :bank_password_changed + event BankEvent.Bank.Account.Login, EntityHandler.Database, :bank_account_login diff --git a/lib/universe/bank/action/bank.ex b/lib/universe/bank/action/bank.ex index 831b7a6d..a0a7242f 100644 --- a/lib/universe/bank/action/bank.ex +++ b/lib/universe/bank/action/bank.ex @@ -10,6 +10,7 @@ defmodule Helix.Universe.Bank.Action.Bank do alias Helix.Universe.Bank.Internal.BankAccount, as: BankAccountInternal alias Helix.Universe.Bank.Internal.BankToken, as: BankTokenInternal alias Helix.Universe.Bank.Internal.BankTransfer, as: BankTransferInternal + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer alias Helix.Universe.Bank.Model.ATM alias Helix.Universe.Bank.Model.BankAccount alias Helix.Universe.Bank.Model.BankToken @@ -22,6 +23,8 @@ defmodule Helix.Universe.Bank.Action.Bank do as: BankAccountUpdatedEvent alias Helix.Universe.Bank.Event.Bank.Account.Password.Revealed, as: BankAccountPasswordRevealedEvent + alias Helix.Universe.Bank.Event.Bank.Account.Password.Changed, + as: BankAccountPasswordChangedEvent alias Helix.Universe.Bank.Event.Bank.Account.Token.Acquired, as: BankAccountTokenAcquiredEvent @@ -185,9 +188,7 @@ defmodule Helix.Universe.Bank.Action.Bank do """ def reveal_password(account, token_id, revealed_by) do with \ - token = %{} <- BankQuery.fetch_token(token_id), - true <- account.account_number == token.account_number, - true <- account.atm_id == token.atm_id + {true, relay} <- BankHenforcer.token_valid?(account, token_id) do event = BankAccountPasswordRevealedEvent.new(account, revealed_by) @@ -198,6 +199,21 @@ defmodule Helix.Universe.Bank.Action.Bank do end end + @spec change_password(BankAccount.t, Entity.id) :: + {:ok, BankAccount.t, [BankAccountPasswordChangedEvent.t]} + | {:error, :internal} + def change_password(account, changed_by) do + event = BankAccountPasswordChangedEvent.new(account, changed_by) + {:ok, account, [event]} + end + + @spec update_password(BankAccount.t) :: + {:ok, BankAccount.t} + | {:error, :internal} + defdelegate update_password(account), + to: BankAccountInternal, + as: :change_password + @spec login_password(BankAccount.t, String.t, Entity.idt) :: {:ok, BankAccount.t, [BankAccountLoginEvent.t]} | term @@ -226,12 +242,14 @@ defmodule Helix.Universe.Bank.Action.Bank do | term def login_token(account, token_id, login_by) do with \ - token = %{} <- BankQuery.fetch_token(token_id), - true <- token.account_number == account.account_number + {true, relay} <- BankHenforcer.token_valid?(account, token_id) do event = BankAccountLoginEvent.new(account, login_by, token_id) {:ok, account, [event]} + else + {false, reason, _} -> + {:error, reason} end end diff --git a/lib/universe/bank/event/bank/account/password.ex b/lib/universe/bank/event/bank/account/password.ex index 4cf0d8e6..0b22544b 100644 --- a/lib/universe/bank/event/bank/account/password.ex +++ b/lib/universe/bank/event/bank/account/password.ex @@ -23,4 +23,26 @@ defmodule Helix.Universe.Bank.Event.Bank.Account.Password do } end end + + event Changed do + + alias Helix.Entity.Model.Entity + alias Helix.Universe.Bank.Model.BankAccount + + @type t :: %__MODULE__{ + entity_id: Entity.id, + account: BankAccount.t + } + + event_struct [:entity_id, :account] + + @spec new(BankAccount.t, Entity.id) :: + t + def new(account = %BankAccount{}, entity_id) do + %__MODULE__{ + entity_id: entity_id, + account: account + } + end + end end diff --git a/lib/universe/bank/event/change_password.ex b/lib/universe/bank/event/change_password.ex new file mode 100644 index 00000000..207f136e --- /dev/null +++ b/lib/universe/bank/event/change_password.ex @@ -0,0 +1,43 @@ +defmodule Helix.Universe.Bank.Event.ChangePassword do + + import Helix.Event + + event Processed do + + alias Helix.Process.Model.Process + alias Helix.Server.Model.Server + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, + as: ChangePasswordProcess + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + @type t :: %__MODULE__{ + gateway_id: Server.id, + account: BankAccount.t + } + + event_struct [:gateway_id, :account] + + @spec new(BankAccount.t, Server.id) :: + t + def new(account = %BankAccount{}, gateway_id) do + %__MODULE__{ + gateway_id: gateway_id, + account: account + } + end + + @spec new(Process.t, ChangePasswordProcess.t) :: + t + def new(process = %Process{}, _data = %ChangePasswordProcess{}) do + atm_id = process.src_atm_id + account_number = process.src_acc_number + account = BankQuery.fetch_account(atm_id, account_number) + + %__MODULE__{ + gateway_id: process.gateway_id, + account: account + } + end + end +end diff --git a/lib/universe/bank/event/handler/bank/account.ex b/lib/universe/bank/event/handler/bank/account.ex index 3589125d..35922a96 100644 --- a/lib/universe/bank/event/handler/bank/account.ex +++ b/lib/universe/bank/event/handler/bank/account.ex @@ -9,6 +9,10 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do alias Helix.Software.Event.Virus.Collected, as: VirusCollectedEvent alias Helix.Universe.Bank.Event.RevealPassword.Processed, as: RevealPasswordProcessedEvent + alias Helix.Universe.Bank.Event.ChangePassword.Processed, + as: ChangePasswordProcessedEvent + alias Helix.Universe.Bank.Event.Bank.Account.Password.Changed, + as: BankPasswordChangedEvent @doc """ Handles the conclusion of a `PasswordRevealProcess`, described at @@ -34,6 +38,25 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do end end + def password_change_processed(event = %ChangePasswordProcessedEvent{}) do + flowing do + with \ + changed_by = %{} <- EntityQuery.fetch_by_server(event.gateway_id), + {:ok, _bank_account, events} <- + BankAction.change_password( + event.account, changed_by.entity_id + ), + on_success(fn -> Event.emit(events, from: event) end) + do + :ok + end + end + end + + def bank_password_changed(event = %BankPasswordChangedEvent{}) do + BankAction.update_password(event.account) + end + @doc """ When the rewards of a virus have been successfully collected, it's time to update their bank account. That's what we do here. diff --git a/lib/universe/bank/process/bank/account/password_change.ex b/lib/universe/bank/process/bank/account/password_change.ex new file mode 100644 index 00000000..b6ed31e9 --- /dev/null +++ b/lib/universe/bank/process/bank/account/password_change.ex @@ -0,0 +1,92 @@ +import Helix.Process + +process Helix.Universe.Bank.Process.Bank.Account.ChangePassword do + + process_struct [] + + @process_type :bank_change_password + + @type t :: + %__MODULE__{} + + @type creation_params :: + %{} + + @type objective :: %{cpu: resource_usage} + + @type resources :: + %{ + objective: objective, + static: map, + l_dynamic: [:cpu], + r_dynamic: [] + } + + @type resources_params :: + %{} + + @spec new(creation_params) :: + t + def new(%{}) do + %__MODULE__{} + end + + @spec resources(resources_params) :: + resources + def resources(params = %{}), + do: get_resources params + + processable do + + alias Helix.Universe.Bank.Event.ChangePassword.Processed, + as: ChangePasswordProcessedEvent + + on_completion(process, data) do + event = ChangePasswordProcessedEvent.new(process, data) + + {:delete, [event]} + end + end + + resourceable do + + alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, + as: ChangePasswordProcess + + @type params :: ChangePasswordProcess.resources_params + @type factors :: term + + # TODO proper balance + get_factors(%{}) do end + + # TODO: Use Time, not CPU #364 + cpu(_) do + 1 + end + + dynamic do + [:cpu] + end + end + + executable do + + alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, + as: ChangePasswordProcess + + @type params :: ChangePasswordProcess.creation_params + + @type meta :: + %{ + optional(atom) => term + } + + resources(_gateway, _atm, %{}, _meta) do + %{} + end + + source_connection(_gateway, _target, _params, _meta) do + {:create, :bank_login} + end + end +end diff --git a/lib/universe/bank/websocket/channel/requests/change_password.ex b/lib/universe/bank/websocket/channel/requests/change_password.ex new file mode 100644 index 00000000..ec620018 --- /dev/null +++ b/lib/universe/bank/websocket/channel/requests/change_password.ex @@ -0,0 +1,57 @@ +import Helix.Websocket.Request + +request Helix.Universe.Bank.Websocket.Requests.ChangePassword do + @moduledoc """ + BankChangePasswordRequest is used when the player wants to change + he's BankAccount's password. + + It Returns :ok or :error + """ + alias Helix.Entity.Query.Entity, as: EntityQuery + alias Helix.Server.Query.Server, as: ServerQuery + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + + def check_params(request, _socket) do + reply_ok(request) + end + + @doc """ + Checks if Logged player owns the BankAccount, most of logic is delegated to + BankHenforcer.owns_account?/2 + """ + def check_permissions(request, socket) do + entity = EntityQuery.fetch(socket.assigns.gateway.entity_id) + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + bank_account = BankQuery.fetch_account(atm_id, account_number) + with \ + {true, relay} <- BankHenforcer.owns_account?(entity, bank_account) + do + reply_ok(request) + else + {false, reason, _} -> + reply_error(request, reason) + end + end + + def handle_request(request, socket) do + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + account = BankQuery.fetch_account(atm_id, account_number) + atm = ServerQuery.fetch(atm_id) + gateway = ServerQuery.fetch(socket.assigns.gateway.server_id) + password_change = + BankPublic.change_password(account, gateway, atm, request.relay) + + case password_change do + {:ok, process} -> + update_meta(request, %{process: process}, reply: true) + {:error, reason} -> + reply_error(request, reason) + end + end + + render_empty() +end diff --git a/test/universe/bank/action/bank_test.exs b/test/universe/bank/action/bank_test.exs index ba1021d4..d22140da 100644 --- a/test/universe/bank/action/bank_test.exs +++ b/test/universe/bank/action/bank_test.exs @@ -254,14 +254,16 @@ defmodule Helix.Universe.Bank.Action.BankTest do {token, _} = BankSetup.token() entity_id = Entity.ID.generate() - refute BankAction.login_token(acc, token.token_id, entity_id) + assert {:error, {:token, :not_belongs}} = + BankAction.login_token(acc, token.token_id, entity_id) end test "login fails when given token is expired" do {token, %{acc: acc}} = BankSetup.token([expired: true]) entity_id = Entity.ID.generate() - refute BankAction.login_token(acc, token.token_id, entity_id) + assert {:error, {:token, :expired}} = + BankAction.login_token(acc, token.token_id, entity_id) end end diff --git a/test/universe/bank/process/change_password_test.exs b/test/universe/bank/process/change_password_test.exs new file mode 100644 index 00000000..4c1753ef --- /dev/null +++ b/test/universe/bank/process/change_password_test.exs @@ -0,0 +1,59 @@ +defmodule Helix.Universe.Bank.Process.Bank.Account.ChangePasswordTest do + + use Helix.Test.Case.Integration + + alias Helix.Network.Query.Tunnel, as: TunnelQuery + alias Helix.Server.Query.Server, as: ServerQuery + alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, + as: BankChangePasswordProcess + + alias Helix.Test.Server.Helper, as: ServerHelper + alias Helix.Test.Server.Setup, as: ServerSetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + @relay nil + + describe "Process.Executable" do + test "starts a change password process when everything is OK" do + {gateway, %{entity: entity}} = ServerSetup.server() + bank_account = BankSetup.account!(owner_id: entity.entity_id) + atm = ServerQuery.fetch(bank_account.atm_id) + atm_nip = ServerHelper.get_nip(atm) + + params = + %{ + account: bank_account, + gateway: gateway + } + + meta = + %{ + network_id: atm_nip.network_id, + bounce: nil + } + + assert {:ok, process} = + BankChangePasswordProcess.execute( + gateway, atm, params, meta, @relay + ) + assert process.src_connection_id + assert process.type == :bank_change_password + assert process.gateway_id == gateway.server_id + assert process.source_entity_id == entity.entity_id + assert process.target_id == atm.server_id + assert process.network_id == atm_nip.network_id + + refute process.src_file_id + + connection = TunnelQuery.fetch_connection process.src_connection_id + + assert connection.connection_type == :bank_login + + tunnel = TunnelQuery.fetch(connection.tunnel_id) + + assert tunnel.gateway_id == gateway.server_id + assert tunnel.target_id == atm.server_id + assert tunnel.network_id == atm_nip.network_id + end + end +end diff --git a/test/universe/bank/websocket/requests/change_password_test.exs b/test/universe/bank/websocket/requests/change_password_test.exs new file mode 100644 index 00000000..7c1c9720 --- /dev/null +++ b/test/universe/bank/websocket/requests/change_password_test.exs @@ -0,0 +1,158 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.ChangePasswordTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Websocket.Requestable + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Websocket.Requests.ChangePassword, + as: BankChangePasswordRequest + + alias Helix.Test.Channel.Request.Helper, as: RequestHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Process.TOPHelper + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "BankChangePasswordRequest.check_permissions/2" do + test "accepts when account belongs to player" do + # Setups an Account socket and get gateway and entity + # from related information. + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Setups a BankAccount for testing. + bank_account = + BankSetup.account!(owner_id: entity.entity_id) + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Creates the topic to join on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the payload for joining the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel with the created BankAccount. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks a BankChangePasswordRequest that does not receive any parameters. + request = + RequestHelper.mock_request(BankChangePasswordRequest, %{}) + + # Asserts that get permissions to continue with the request flow. + assert {:ok, _request} = + Requestable.check_permissions(request, bnk_socket) + end + + test "rejects when account does not belongs to player" do + # Setups an Account socket and get gateway and entity + # from related information. + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Setups a BankAccount that not belongs to entity. + bank_account = + BankSetup.account!() + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Creates a bank channel topic based on BankAccount information. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the payload for logging in the bank channel on created + # BankAccount. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks a BankChangePasswordRequest that does not receive any parameters. + request = + RequestHelper.mock_request(BankChangePasswordRequest, %{}) + + # Asserts that get no permissions to change password because the entity + # does not own the given BankAccount. + assert {:error, reason, _} = + Requestable.check_permissions(request, bnk_socket) + + # Asserts the reason for failing is "bank_account_not_belong". + assert reason.message == "bank_account_not_belongs" + end + end + + describe "BankChangePasswordRequest.handle_request/2" do + test "accepts if the password changes after process ends" do + # Setups an Account socket and gets gateway and entity from + # related information. + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Setups a BankAccount for testing. + bank_account = + BankSetup.account!(owner_id: entity.entity_id) + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Stores the old password on a variable to compare after the process. + old_password = bank_account.password + + # Creates the topic used for logging in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the payload used as parameters to log in on bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks BankChangePasswordRequest that does not receive any parameters. + request = + RequestHelper.mock_request(BankChangePasswordRequest, %{}) + + # Checks the permissions for given Entity on BankAccount. + {:ok, request} = + Requestable.check_permissions(request, bnk_socket) + + # Asserts that request is handled correctly. + assert {:ok, request} = + Requestable.handle_request(request, bnk_socket) + + # Gets the process' id for forcing it's termination. + process_id = request.meta.process.process_id + + # Forces the process to complete. + TOPHelper.force_completion(process_id) + + # Fetchs the BankAccount from the database. + bank_account = + BankQuery.fetch_account( + bank_account.atm_id, + bank_account.account_number + ) + + # Refutes if the current BankAccount password is equals + # to the old_password. + refute bank_account.password == old_password + end + end +end From 43069fa303d31a0c8a806500d52470157b6aeee8 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 18:07:21 -0300 Subject: [PATCH 11/32] Implement BankAccount close request --- .../channel/requests/close_account.ex | 49 +++++++++++++++++++ .../bank/websocket/requests/close_account.exs | 19 +++++++ 2 files changed, 68 insertions(+) create mode 100644 lib/universe/bank/websocket/channel/requests/close_account.ex create mode 100644 test/universe/bank/websocket/requests/close_account.exs diff --git a/lib/universe/bank/websocket/channel/requests/close_account.ex b/lib/universe/bank/websocket/channel/requests/close_account.ex new file mode 100644 index 00000000..fefc8688 --- /dev/null +++ b/lib/universe/bank/websocket/channel/requests/close_account.ex @@ -0,0 +1,49 @@ +import Helix.Websocket.Request + +request Helix.Universe.Bank.Websocket.Requests.CloseAccount do + @moduledoc """ + BankCloseAccountRequest is used when player wants to close the + BankAccount. + + It Returs :ok or :error + """ + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + def check_params(request, _socket) do + reply_ok(request) + end + + @doc """ + Checks if player can close BankAccount, most of logic is delegated to + BankHenforcer.can_close?/2 + """ + def check_permissions(request, socket) do + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + with \ + {true, _relay} <- BankHenforcer.can_close?(atm_id, account_number) + do + reply_ok(request) + else + {false, reason, _} -> + reply_error(request, reason) + end + end + + def handle_request(request, socket) do + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + bank_account = BankQuery.fetch_account(atm_id, account_number) + close_account = BankPublic.close_account(bank_account) + + case close_account do + :ok -> + reply_ok(request) + {:error, reason} -> + reply_error(request, reason) + end + end + render_empty() +end diff --git a/test/universe/bank/websocket/requests/close_account.exs b/test/universe/bank/websocket/requests/close_account.exs new file mode 100644 index 00000000..6c1575df --- /dev/null +++ b/test/universe/bank/websocket/requests/close_account.exs @@ -0,0 +1,19 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.CloseAccount do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Websocket.Requestable + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Requests.CloseAccount, as: BankCloseAccountRequest + + alias Helix.Test.Channel.Request.Helper, as: RequestHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "BankCloseAccountRequest.handle_request/2" do + # Tested on BankAccountInternal + end +end From 46fb8faeb4abbdfc43d6c5b270d2521bd6fa25a6 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 18:09:24 -0300 Subject: [PATCH 12/32] Implement BankAccount password reveal --- lib/universe/bank/internal/bank_token.ex | 2 +- .../channel/requests/reveal_password.ex | 76 +++++++ .../bank/internal/bank_token_test.exs | 11 - .../requests/reveal_password_test.exs | 193 ++++++++++++++++++ 4 files changed, 270 insertions(+), 12 deletions(-) create mode 100644 lib/universe/bank/websocket/channel/requests/reveal_password.ex create mode 100644 test/universe/bank/websocket/requests/reveal_password_test.exs diff --git a/lib/universe/bank/internal/bank_token.ex b/lib/universe/bank/internal/bank_token.ex index 6f3d856f..e1d8bc8c 100644 --- a/lib/universe/bank/internal/bank_token.ex +++ b/lib/universe/bank/internal/bank_token.ex @@ -11,7 +11,7 @@ defmodule Helix.Universe.Bank.Internal.BankToken do def fetch(token) do token |> BankToken.Query.by_token() - |> BankToken.Query.filter_expired() + #|> BankToken.Query.filter_expired() |> Repo.one() end diff --git a/lib/universe/bank/websocket/channel/requests/reveal_password.ex b/lib/universe/bank/websocket/channel/requests/reveal_password.ex new file mode 100644 index 00000000..9a7a5346 --- /dev/null +++ b/lib/universe/bank/websocket/channel/requests/reveal_password.ex @@ -0,0 +1,76 @@ +import Helix.Websocket.Request + +request Helix.Universe.Bank.Websocket.Requests.RevealPassword do + @moduledoc """ + This request is called when the player wants to get the password for + a bank account which he's logged in by using a token + """ + + alias Helix.Server.Query.Server, as: ServerQuery + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + def check_params(request, _socket) do + # Check if token value has the correct format + with \ + {:ok, token} <- validate_input(request.unsafe["token"], :token) + do + update_params(request, %{token: token}, reply: true) + else + _ -> + bad_request(request) + end + end + + @doc """ + Verifies the permission for the password reveal. Most of the perimssion + logic has been delegated to `BankHenforcer.token_valid?/2` + + This is where we the token.account_number, token.atm_id is the same + as the logged in bank account and if expiration date is not expired + """ + def check_permissions(request, socket) do + token = request.params.token + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + bank_account = BankQuery.fetch_account(atm_id, account_number) + case BankHenforcer.token_valid?(bank_account, token) do + {true, relay} -> + token = relay.token + update_meta(request, %{token: token}, reply: true) + {false, reason, _} -> + reply_error(request, reason) + end + end + + def handle_request(request, socket) do + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + bank_account = BankQuery.fetch_account(atm_id, account_number) + token = request.meta.token.token_id + gateway_id = socket.assigns.gateway.server_id + gateway = ServerQuery.fetch(gateway_id) + atm = ServerQuery.fetch(atm_id) + relay = request.relay + + reveal_password = + BankPublic.reveal_password( + bank_account, + token, + gateway, + atm, + relay + ) + + case reveal_password do + {:ok, process} -> + update_meta(request, %{process: process}, reply: true) + {:error, reason} -> + reply_error(request, reason) + end + end + + render_empty() + +end diff --git a/test/universe/bank/internal/bank_token_test.exs b/test/universe/bank/internal/bank_token_test.exs index e0ec699f..ee068d8d 100644 --- a/test/universe/bank/internal/bank_token_test.exs +++ b/test/universe/bank/internal/bank_token_test.exs @@ -21,14 +21,6 @@ defmodule Helix.Universe.Bank.Internal.BankTokenTest do assert token2.token_id == token.token_id end - test "wont fetch expired token" do - {expired, _} = BankSetup.token([expired: true]) - - token2 = BankTokenInternal.fetch(expired.token_id) - - refute token2 - end - test "with non-existing token" do refute BankTokenInternal.fetch(UUID.generate()) end @@ -74,9 +66,6 @@ defmodule Helix.Universe.Bank.Internal.BankTokenTest do test "updates the token expiration" do {token, _} = BankSetup.token() - # No expiration_date set - refute token.expiration_date - assert {:ok, token2} = BankTokenInternal.set_expiration(token) # Expiration date set diff --git a/test/universe/bank/websocket/requests/reveal_password_test.exs b/test/universe/bank/websocket/requests/reveal_password_test.exs new file mode 100644 index 00000000..177354ff --- /dev/null +++ b/test/universe/bank/websocket/requests/reveal_password_test.exs @@ -0,0 +1,193 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.RevealPassword do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Websocket.Requestable + alias Helix.Universe.Bank.Websocket.Requests.RevealPassword, + as: BankRevealPasswordRequest + + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "BankRevealPasswordRequest.check_params/2" do + test "accepts when token is in valid format" do + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Create a Expiration date for the token that will expire + # in 5 minutes. + expiration = + DateTime.utc_now() + |> DateTime.to_unix(:second) + |> Kernel.+(60 * 5) + |> DateTime.from_unix!(:second) + + # Create Bank Account and get it's information. + bank_account = BankSetup.account!() + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Create the topic to log in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Create a token with signied to previous created bank account + # and expiration date. + {token, _} = + BankSetup.token(acc: bank_account, expiration_date: expiration) + + # Create the payload which will be used to join the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + params = + %{ + "token" => token.token_id + } + + # Creates a BankRevealPasswordRequest. + request = BankRevealPasswordRequest.new(params) + + assert {:ok, request} = + Requestable.check_params(request, bnk_socket) + + # Check if token.id still the same as before after checking. + assert request.params.token == token.token_id + end + end + + describe "BankRevealPasswordRequest.check_perimssions/2" do + test "accepts when token exists and belongs to bank account" do + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Create a Expiration date for the token that will expire + # in 5 minutes. + expiration = + DateTime.utc_now() + |> DateTime.to_unix(:second) + |> Kernel.+(60 * 5) + |> DateTime.from_unix!(:second) + + # Create Bank Account and get it's information. + bank_account = BankSetup.account!() + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Create the topic to log in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Create a token with signied to previous created bank account + # and expiration date. + {token, _} = + BankSetup.token(acc: bank_account, expiration_date: expiration) + + # Create the payload which will be used to join the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + params = + %{ + "token" => token.token_id + } + + # Creates a BankRevealPasswordRequest. + request = BankRevealPasswordRequest.new(params) + + {:ok, request} = + Requestable.check_params(request, bnk_socket) + + assert {:ok, request} = + Requestable.check_permissions(request, bnk_socket) + + # Checks if token information is correct. + assert request.meta.token.atm_id == token.atm_id + assert request.meta.token.token_id == token.token_id + assert request.meta.token.account_number == token.account_number + end + end + + describe "BankRevealPasswordRequest.handle_request/2" do + test "creates reveal password process" do + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Create a Expiration date for the token that will expire + # in 5 Minutes. + expiration = + DateTime.utc_now() + |> DateTime.to_unix(:second) + |> Kernel.+(60 * 5) + |> DateTime.from_unix!(:second) + + # Create Bank Account and get it's information. + bank_account = BankSetup.account!() + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Create the topic to log in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Create a token with signied to previous created bank account + # and expiration date. + {token, _} = + BankSetup.token(acc: bank_account, expiration_date: expiration) + + # Create the payload which will be used to join the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + params = + %{ + "token" => token.token_id + } + + # Creates a BankRevealPasswordRequest. + request = BankRevealPasswordRequest.new(params) + + # Checks if params isn't invalid. + {:ok, request} = + Requestable.check_params(request, bnk_socket) + + # Checks if token exists and is assigned to the correct account. + {:ok, request} = + Requestable.check_permissions(request, bnk_socket) + + assert {:ok, request} = + Requestable.handle_request(request, bnk_socket) + + # Checks if process information is correct. + assert request.meta.process + assert request.meta.process.type == :bank_reveal_password + assert request.meta.process.data.atm_id == bank_account.atm_id + assert request.meta.process.data.account_number == + bank_account.account_number + end + end +end From f31acdcde20b2c8740e12c59ae97ffbc66df72e7 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 18:19:18 -0300 Subject: [PATCH 13/32] Implement bank logout request --- lib/server/websocket/channel/server.ex | 2 +- .../bank/websocket/channel/requests/logout.ex | 23 +++++++++ .../bank/websocket/requests/logout_test.exs | 47 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 lib/universe/bank/websocket/channel/requests/logout.ex create mode 100644 test/universe/bank/websocket/requests/logout_test.exs diff --git a/lib/server/websocket/channel/server.ex b/lib/server/websocket/channel/server.ex index 039c2d99..47e996e4 100644 --- a/lib/server/websocket/channel/server.ex +++ b/lib/server/websocket/channel/server.ex @@ -293,7 +293,7 @@ channel Helix.Server.Websocket.Channel.Server do Disables the PublicFTP server of the player. Params: none - + Returns: %{} Errors: diff --git a/lib/universe/bank/websocket/channel/requests/logout.ex b/lib/universe/bank/websocket/channel/requests/logout.ex new file mode 100644 index 00000000..b2da3249 --- /dev/null +++ b/lib/universe/bank/websocket/channel/requests/logout.ex @@ -0,0 +1,23 @@ +import Helix.Websocket.Request + +request Helix.Universe.Bank.Websocket.Requests.Logout do + + alias Helix.Websocket + + def check_params(request, _socket), + do: reply_ok(request) + + def check_permissions(request, _socket), + do: reply_ok(request) + + def handle_request(request, socket) do + socket + |> Websocket.id() + |> Helix.Endpoint.broadcast("disconnect", %{}) + + reply_ok(request) + end + + def reply(_request, _socket), + do: {:stop, :shutdown} +end diff --git a/test/universe/bank/websocket/requests/logout_test.exs b/test/universe/bank/websocket/requests/logout_test.exs new file mode 100644 index 00000000..e8900752 --- /dev/null +++ b/test/universe/bank/websocket/requests/logout_test.exs @@ -0,0 +1,47 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.LogoutTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + + describe "bootstrap" do + test "returns expected result" do + # Setups an Account socket. + {socket, %{entity: entity, server: gateway}} = + ChannelSetup.create_socket() + + # Setups a BankAccount. + bank_acc = BankSetup.account!() + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Creates topic to log in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Create the payload which will be used to join the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Request logout + push bnk_socket, "bank.logout", %{} + + # Wait process teardown. Required + :timer.sleep(50) + + # Channel no longer exists + refute Process.alive? bnk_socket.channel_pid + end + end +end From 22b18a4789528cc48be53a990798daf9dfb1f9ab Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Wed, 25 Apr 2018 18:27:59 -0300 Subject: [PATCH 14/32] Implement bank account create request --- lib/account/websocket/channel/account.ex | 20 ++ lib/universe/bank/query/bank.ex | 5 + .../channel/requests/create_account.ex | 69 ++++++ .../websocket/requests/create_account.exs | 197 ++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 lib/universe/bank/websocket/channel/requests/create_account.ex create mode 100644 test/universe/bank/websocket/requests/create_account.exs diff --git a/lib/account/websocket/channel/account.ex b/lib/account/websocket/channel/account.ex index 2443da87..c2b0f92e 100644 --- a/lib/account/websocket/channel/account.ex +++ b/lib/account/websocket/channel/account.ex @@ -14,6 +14,8 @@ channel Helix.Account.Websocket.Channel.Account do alias Helix.Network.Websocket.Requests.Bounce.Remove, as: BounceRemoveRequest alias Helix.Software.Websocket.Requests.Virus.Collect, as: VirusCollectRequest alias Helix.Story.Websocket.Requests.Email.Reply, as: EmailReplyRequest + alias Helix.Universe.Websocket.Channel.Requests.CreateAccount, + as: BankAccountCreateRequest @doc """ Joins the Account channel. @@ -233,6 +235,24 @@ channel Helix.Account.Websocket.Channel.Account do """ topic "virus.collect", VirusCollectRequest + @doc """ + Creates a BankAccount + + Params: + *atm_id: ATM.id related to bank that player wants to create account in + + Returns: :ok + + Errors: + + Henforcer: + - atm_not_a_bank: Given `atm_id` is not a bank + + Input: + + base errors + """ + topic "bank.createacc", BankAccountCreateRequest + @doc """ Intercepts and handles outgoing events. """ diff --git a/lib/universe/bank/query/bank.ex b/lib/universe/bank/query/bank.ex index e221c271..f6ae5b2c 100644 --- a/lib/universe/bank/query/bank.ex +++ b/lib/universe/bank/query/bank.ex @@ -5,6 +5,7 @@ defmodule Helix.Universe.Bank.Query.Bank do alias Helix.Universe.Bank.Internal.BankAccount, as: BankAccountInternal alias Helix.Universe.Bank.Internal.BankToken, as: BankTokenInternal alias Helix.Universe.Bank.Internal.BankTransfer, as: BankTransferInternal + alias Helix.Universe.Bank.Internal.ATM, as: ATMInternal alias Helix.Universe.Bank.Model.ATM alias Helix.Universe.Bank.Model.BankAccount alias Helix.Universe.Bank.Model.BankTransfer @@ -39,6 +40,10 @@ defmodule Helix.Universe.Bank.Query.Bank do fetch_account(atm_id, account_number) end + defdelegate fetch_atm(atm_id), + to: ATMInternal, + as: :fetch + @spec get_account_balance(BankAccount.t) :: non_neg_integer @doc """ diff --git a/lib/universe/bank/websocket/channel/requests/create_account.ex b/lib/universe/bank/websocket/channel/requests/create_account.ex new file mode 100644 index 00000000..946c942a --- /dev/null +++ b/lib/universe/bank/websocket/channel/requests/create_account.ex @@ -0,0 +1,69 @@ +import Helix.Websocket.Request + +request Helix.Universe.Bank.Websocket.Requests.CreateAccount do + @moduledoc """ + BankCreateAccountRequest is used when the player want to create + a BankAccount on given bank. + + It Returns :ok or :error + """ + alias Helix.Network.Model.Network + alias Helix.Server.Model.Server + alias Helix.Server.Query.Server, as: ServerQuery + alias Helix.Universe.Bank.Public.Bank, as: BankPublic + alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer + + def check_params(request, _socket) do + with \ + {:ok, atm_id} <- Server.ID.cast(request.unsafe["atm_id"]) + do + params = + %{ + atm_id: atm_id + } + + update_params(request, params, reply: true) + else + _ -> + bad_request(request) + end + end + + @doc """ + Checks if player can open BankAccount, most of logic is delegated to + BankHenforcer.can_create_account?/1 + """ + def check_permissions(request, _socket) do + atm = ServerQuery.fetch(request.params.atm_id) + with \ + {true, relay} <- BankHenforcer.can_create_account?(atm), + atm_id = relay.atm_id + do + meta = + %{ + atm_id: atm_id + } + + update_meta(request, meta, reply: true) + else + {false, reason, _} -> + reply_error(request, reason) + _ -> + bad_request(request) + end + end + + def handle_request(request, socket) do + atm_id = request.meta.atm_id + account_id = socket.assigns.account_id + + bank_account = BankPublic.open_account(account_id, atm_id) + case bank_account do + {:ok, bank_account} -> + {:ok, bank_account} + error -> + error + end + end + render_empty() +end diff --git a/test/universe/bank/websocket/requests/create_account.exs b/test/universe/bank/websocket/requests/create_account.exs new file mode 100644 index 00000000..465628ab --- /dev/null +++ b/test/universe/bank/websocket/requests/create_account.exs @@ -0,0 +1,197 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do + + use Helix.Test.Case.Integration + + alias Helix.Websocket.Requestable + alias Helix.Network.Query.Network, as: NetworkQuery + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Websocket.Requests.CreateAccount, + as: BankCreateAccountRequest + + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Server.Setup, as: ServerSetup + alias Helix.Test.Universe.NPC.Helper, as: NPCHelper + + @internet_id NetworkQuery.internet().network_id + + describe "BankCreateAccountRequest.check_params/2" do + test "accepts when receives valid information" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Gets a Bank from database. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Creates the parameters for creating a account. + payload = + %{ + "atm_id" => to_string(atm_id), + "network_id" => to_string(@internet_id) + } + + # Creates a new BankCreateAccountRequest with the parameters. + request = + BankCreateAccountRequest.new(payload) + + # Asserts that given parameters are okay. + assert {:ok, request} = + Requestable.check_params(request, socket) + + # Assert the parameters after checking are right. + assert request.params.network_id == @internet_id + assert request.params.atm_id == atm_id + end + + test "rejects when receives invalid atm_id" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Creates invalid parameters. + payload = + %{ + "atm_id" => "DROP TABLE helix_dev_accounts;", + "network_id" => to_string(@internet_id) + } + + # Create BankCreateAccountRequest with invalid parameters. + request = + BankCreateAccountRequest.new(payload) + + # Asserts that given parameters are rejected. + assert {:error, reason, _} = + Requestable.check_params(request, socket) + + # Asserts that the error message is "bad_request. + assert reason.message == "bad_request" + end + + test "rejects when receives invalid network_id" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Gets a Bank and it's atm_id. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Creates invalid parameters. + payload = + %{ + "atm_id" => to_string(atm_id), + "network_id" => "DROP TABLE helix_dev_accounts;" + } + + # Create BankCreateAccountRequest with invalid parameters. + request = + BankCreateAccountRequest.new(payload) + + # Asserts that given parameters are rejected. + assert {:error, reason, _} = + Requestable.check_params(request, socket) + + # Asserts that the error message is "bad_request". + assert reason.message == "bad_request" + end + end + + describe "BankCreateAccountRequest.check_permissions/2" do + test "accepts when the ATM is a bank" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Gets a Bank and it's atm_id. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Create valid parameters. + payload = + %{ + "atm_id" => to_string(atm_id), + "network_id" => to_string(@internet_id) + } + + # Create BankCreateAccountRequest with valid parameters. + request = + BankCreateAccountRequest.new(payload) + + # Checks if parameters are valid. + {:ok, request} = + Requestable.check_params(request, socket) + + # Asserts that Bank is a Bank. + assert {:ok, request} = + Requestable.check_permissions(request, socket) + + # Asserts that atm_id is on meta. + assert request.meta.atm_id == atm_id + end + + test "rejects when the ATM is not a server" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Setups a ordinary server. + {not_bank, _} = ServerSetup.server() + + # Create parameters passing the ordinary server's id as + # an atm_id. + payload = + %{ + "atm_id" => to_string(not_bank.server_id), + "network_id" => to_string(@internet_id) + } + + # Create BankCreateAccountRequest with parameters. + request = + BankCreateAccountRequest.new(payload) + + # Asserts the parameters are in right format. + assert {:ok, request} = + Requestable.check_params(request, socket) + + # Asserts error because the ordinary server is not a bank. + assert {:error, reason, _} = + Requestable.check_permissions(request, socket) + + # Asserts the error message is "atm_not_a_bank". + assert reason.message == "atm_not_a_bank" + end + end + + describe "BankCreateAccountRequest.handle_request/2" do + test "creates BankAccount on database" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Gets a Bank and it's atm_id. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Creates valid parameters. + payload = + %{ + "atm_id" => to_string(atm_id), + "network_id" => to_string(@internet_id), + } + + # Creates a BankCreateAccountRequest with the parameters. + request = + BankCreateAccountRequest.new(payload) + + # Checks if parameters are in correct format. + {:ok, request} = + Requestable.check_params(request, socket) + + # Checks if the given bank is a bank. + {:ok, request} = + Requestable.check_permissions(request, socket) + + # Asserts that the request is handled correctly. + assert {:ok, acc} = + Requestable.handle_request(request, socket) + + # Asserts that the BankAccount is being created on the database. + assert BankQuery.fetch_account(acc.atm_id, acc.account_number) + end + end +end From 63ef969b4f0bd2505aba921fcc9f5f579ed9a83b Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:02:31 -0300 Subject: [PATCH 15/32] Implement login with token --- lib/core/validator/validator.ex | 1 + .../bank/websocket/channel/bank/join.ex | 9 +- .../bank/websocket/channel/bank/join_test.exs | 115 +++++++++++++++++- 3 files changed, 119 insertions(+), 6 deletions(-) mode change 100644 => 100755 test/universe/bank/websocket/channel/bank/join_test.exs diff --git a/lib/core/validator/validator.ex b/lib/core/validator/validator.ex index c3762f1a..4fd13b7b 100644 --- a/lib/core/validator/validator.ex +++ b/lib/core/validator/validator.ex @@ -5,6 +5,7 @@ defmodule Helix.Core.Validator do | :hostname | :bounce_name | :reply_id + | :token @regex_hostname ~r/^[a-zA-Z0-9-_.@#]{1,20}$/ diff --git a/lib/universe/bank/websocket/channel/bank/join.ex b/lib/universe/bank/websocket/channel/bank/join.ex index 674bd8a5..50b5040d 100644 --- a/lib/universe/bank/websocket/channel/bank/join.ex +++ b/lib/universe/bank/websocket/channel/bank/join.ex @@ -54,7 +54,7 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do {_, token} = if request.unsafe["token"] do - validate_input(request.unsafe["token"], :token) + Ecto.UUID.cast(request.unsafe["token"]) else {:ok, nil} end @@ -93,9 +93,9 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do params = cond do - password != nil -> + not is_nil(password) -> Map.put(params, :password, password) - token != nil -> + not is_nil(token) -> Map.put(params, :token, token) true -> params @@ -112,7 +112,6 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do atm_id = request.params.atm_id account_number = request.params.account_number account_id = request.params.account_id - password = request.params.password entity_id = EntityQuery.get_entity_id(account_id) gateway_id = request.params.gateway_id bounce_id = request.params.bounce_id @@ -291,7 +290,7 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do account_number, gateway.server_id, bounce_id, - token + token.token_id ) do gateway_data = diff --git a/test/universe/bank/websocket/channel/bank/join_test.exs b/test/universe/bank/websocket/channel/bank/join_test.exs old mode 100644 new mode 100755 index 67d7df71..157207bf --- a/test/universe/bank/websocket/channel/bank/join_test.exs +++ b/test/universe/bank/websocket/channel/bank/join_test.exs @@ -14,7 +14,7 @@ defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do @moduletag :driver describe "BankJoin" do - test "can join a bank account with valid data" do + test "can join a bank account with password" do {socket, %{account: player, server: gateway}} = ChannelSetup.create_socket() @@ -65,6 +65,119 @@ defmodule Helix.Universe.Bank.Websocket.Channel.Bank.JoinTest do assert bootstrap.data.balance end + test "can join a bank account with token" do + {socket, %{account: player, server: gateway}} = + ChannelSetup.create_socket() + + entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!(entity_id: entity_id) + + bank_account = BankSetup.account!() + token = BankSetup.token!(acc: bank_account) + token_id = token.token_id + + payload = + %{ + "entity_id" => to_string(entity_id), + "token" => token_id, + "gateway_id" => to_string(gateway.server_id), + "bounce_id" => to_string(bounce.bounce_id) + } + + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Joins the channel + assert {:ok, bootstrap, new_socket} = join(socket, topic, payload) + + # `atm_id` and `account_number` are valid + assert new_socket.assigns.atm_id == atm_id + assert new_socket.assigns.account_number == account_number + + # `entity` data is valid + assert new_socket.assigns.entity_id == entity_id + + # `account` data is valid + assert new_socket.assigns.account_id == + player.account_id + + # `gateway` data is valid + assert new_socket.assigns.gateway.server_id == gateway.server_id + + # connection is being created + assert Map.has_key?(new_socket.assigns, :tunnel) + assert Map.has_key?(new_socket.assigns, :bank_login) + + # Socket related stuff + assert new_socket.joined + assert new_socket.topic == topic + + # It returned the bank account bootstrap + assert bootstrap.data.balance + end + + test "can't join bank account with expired token" do + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!(entity_id: entity_id) + + bank_account = BankSetup.account!() + token = BankSetup.token!(acc: bank_account, expired: true) + token_id = token.token_id + + payload = + %{ + "entity_id" => to_string(entity_id), + "token" => token_id, + "gateway_id" => to_string(gateway.server_id), + "bounce_id" => to_string(bounce.bounce_id) + } + + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Joins the channel + assert {:error, reason} = join(socket, topic, payload) + + assert reason.data == "token_expired" + end + + test "can't join bank account with not matching token" do + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + entity_id = socket.assigns.entity_id + bounce = BounceSetup.bounce!(entity_id: entity_id) + + bank_account = BankSetup.account!() + token = BankSetup.token!(expired: true) + token_id = token.token_id + + payload = + %{ + "entity_id" => to_string(entity_id), + "token" => token_id, + "gateway_id" => to_string(gateway.server_id), + "bounce_id" => to_string(bounce.bounce_id) + } + + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Joins the channel + assert {:error, reason} = join(socket, topic, payload) + + assert reason.data == "token_not_belongs" + end + test "can not join with the wrong password" do {socket, %{server: gateway}} = ChannelSetup.create_socket() From af7464bb91b37a01ec47a07378994c4579fd3cc9 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:04:10 -0300 Subject: [PATCH 16/32] Update bank internal --- lib/universe/bank/internal/bank_account.ex | 15 ++++++++------- lib/universe/bank/internal/bank_token.ex | 2 -- lib/universe/bank/internal/bank_transfer.ex | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/universe/bank/internal/bank_account.ex b/lib/universe/bank/internal/bank_account.ex index f32d3e56..0004e0bd 100644 --- a/lib/universe/bank/internal/bank_account.ex +++ b/lib/universe/bank/internal/bank_account.ex @@ -2,6 +2,7 @@ defmodule Helix.Universe.Bank.Internal.BankAccount do alias Helix.Account.Model.Account alias Helix.Universe.Bank.Model.ATM + alias Helix.Universe.Bank.Model.Bank alias Helix.Universe.Bank.Model.BankAccount alias Helix.Universe.Repo @@ -63,11 +64,11 @@ defmodule Helix.Universe.Bank.Internal.BankAccount do total || 0 end - @spec create(BankAccount.creation_params) :: + @spec create(Account.id, ATM.id, Bank.id) :: {:ok, BankAccount.t} | {:error, Ecto.Changeset.t} - def create(params) do - params + def create(owner, atm, bank_id) do + %{owner_id: owner, atm_id: atm, bank_id: bank_id} |> BankAccount.create_changeset() |> Repo.insert() end @@ -128,8 +129,8 @@ defmodule Helix.Universe.Bank.Internal.BankAccount do @spec close(BankAccount.t) :: :ok - | {:error, {:account, :notfound}} - | {:error, {:account, :notempty}} + | {:error, {:bank_account, :not_found}} + | {:error, {:bank_account, :not_empty}} def close(account) do trans = Repo.transaction(fn -> @@ -138,8 +139,8 @@ defmodule Helix.Universe.Bank.Internal.BankAccount do account = fetch_for_update(account.atm_id, account.account_number) with \ - true <- not is_nil(account) || {:account, :notfound}, - true <- account.balance == 0 || {:account, :notempty} + true <- not is_nil(account) || {:bank_account, :not_found}, + true <- account.balance == 0 || {:bank_account, :not_empty} do delete(account) else diff --git a/lib/universe/bank/internal/bank_token.ex b/lib/universe/bank/internal/bank_token.ex index e1d8bc8c..17c48703 100644 --- a/lib/universe/bank/internal/bank_token.ex +++ b/lib/universe/bank/internal/bank_token.ex @@ -11,7 +11,6 @@ defmodule Helix.Universe.Bank.Internal.BankToken do def fetch(token) do token |> BankToken.Query.by_token() - #|> BankToken.Query.filter_expired() |> Repo.one() end @@ -21,7 +20,6 @@ defmodule Helix.Universe.Bank.Internal.BankToken do def fetch_by_connection(connection) do connection |> BankToken.Query.by_connection() - |> BankToken.Query.filter_expired() |> Repo.one() end diff --git a/lib/universe/bank/internal/bank_transfer.ex b/lib/universe/bank/internal/bank_transfer.ex index 230e007e..0df979e0 100644 --- a/lib/universe/bank/internal/bank_transfer.ex +++ b/lib/universe/bank/internal/bank_transfer.ex @@ -34,7 +34,7 @@ defmodule Helix.Universe.Bank.Internal.BankTransfer do @spec start(BankAccount.t, BankAccount.t, pos_integer, Account.idt) :: {:ok, BankTransfer.t} | {:error, {:funds, :insufficient}} - | {:error, {:account, :notfound}} + | {:error, {:bank_account, :not_found}} | {:error, Ecto.Changeset.t} def start(from_acc, to_acc, amount, started_by) do Repo.transaction(fn -> From ab683b82b272bd4dbc205ad9521ae9258bdfc29a Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:06:02 -0300 Subject: [PATCH 17/32] Improve testing tooling --- test/support/event/setup/bank.ex | 48 +++++++++++++++++++++++ test/support/event/setup/bank_transfer.ex | 24 ++++++++++++ test/support/process/setup/data.ex | 2 - test/support/universe/bank/setup.ex | 6 +++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 test/support/event/setup/bank_transfer.ex mode change 100644 => 100755 test/support/universe/bank/setup.ex diff --git a/test/support/event/setup/bank.ex b/test/support/event/setup/bank.ex index b3d6a636..f0079fd9 100644 --- a/test/support/event/setup/bank.ex +++ b/test/support/event/setup/bank.ex @@ -5,8 +5,20 @@ defmodule Helix.Test.Event.Setup.Bank do alias Helix.Universe.Bank.Event.Bank.Account.Login, as: BankAccountLoginEvent + alias Helix.Universe.Bank.Event.Bank.Account.Removed, + as: BankAccountRemovedEvent + alias Helix.Universe.Bank.Event.Bank.Account.Updated, + as: BankAccountUpdatedEvent + alias Helix.Universe.Bank.Event.Bank.Account.Logout, + as: BankAccountLogoutEvent alias Helix.Universe.Bank.Event.Bank.Account.Password.Revealed, as: BankAccountPasswordRevealedEvent + alias Helix.Universe.Bank.Event.Bank.Account.Password.Changed, + as: BankAccountPasswordChangedEvent + alias Helix.Universe.Bank.Event.ChangePassword.Processed, + as: ChangePasswordProcessedEvent + alias Helix.Universe.Bank.Event.RevealPassword.Processed, + as: RevealPasswordProcessedEvent alias Helix.Universe.Bank.Event.Bank.Account.Token.Acquired, as: BankAccountTokenAcquiredEvent @@ -46,9 +58,45 @@ defmodule Helix.Test.Event.Setup.Bank do %{event| account: account} end + @doc """ + Accepts (BankAccount.t, Server.id) + """ + def password_change_processed(account, gateway_id), + do: ChangePasswordProcessedEvent.new(account, gateway_id) + + @doc """ + Accepts (BankAccount.t, Server.id) + """ + def password_reveal_processed(account, gateway_id, token_id), + do: RevealPasswordProcessedEvent.new(account, gateway_id, token_id) + + @doc """ + Accepts (BankAccount.t, Entity.id) + """ + def password_changed(account, entity_id), + do: BankAccountPasswordChangedEvent.new(account, entity_id) + @doc """ Accepts: (BankAccount.t, Entity.id) """ def login(account, entity_id, token_id \\ nil), do: BankAccountLoginEvent.new(account, entity_id, token_id) + + @doc """ + Accepts: (BankAccount.t, Entity.id) + """ + def logout(account, entity_id), + do: BankAccountLogoutEvent.new(account, entity_id) + + @doc """ + Accepts: (BankAccount.t, term) + """ + def updated(account, reason), + do: BankAccountUpdatedEvent.new(account, reason) + + @doc """ + Accepts: (BankAccount.t) + """ + def removed(account), + do: BankAccountRemovedEvent.new(account) end diff --git a/test/support/event/setup/bank_transfer.ex b/test/support/event/setup/bank_transfer.ex new file mode 100644 index 00000000..ad86cfba --- /dev/null +++ b/test/support/event/setup/bank_transfer.ex @@ -0,0 +1,24 @@ +defmodule Helix.Test.Event.Setup.BankTransfer do + + alias Helix.Universe.Bank.Event.Bank.Transfer.Processed, + as: BankTransferProcessedEvent + alias Helix.Universe.Bank.Event.Bank.Transfer.Aborted, + as: BankTransferAbortedEvent + alias Helix.Universe.Bank.Event.Bank.Transfer.Successful, + as: BankTransferSuccessfulEvent + alias Helix.Universe.Bank.Event.Bank.Transfer.Failed, + as: BankTransferFailedEvent + + def processed(process, transfer_proc), + do: BankTransferProcessedEvent.new(process, transfer_proc) + + def aborted(process, transfer_proc), + do: BankTransferAbortedEvent.new(process, transfer_proc) + + def successful(transfer_id), + do: BankTransferSuccessfulEvent.new(transfer_id) + + def failed(transfer_id, reason), + do: BankTransferFailedEvent.new(transfer_id, reason) + +end diff --git a/test/support/process/setup/data.ex b/test/support/process/setup/data.ex index 066827f5..d133a49d 100644 --- a/test/support/process/setup/data.ex +++ b/test/support/process/setup/data.ex @@ -31,9 +31,7 @@ defmodule Helix.Test.Process.Data.Setup do alias HELL.TestHelper.Random alias Helix.Test.Log.Helper, as: LogHelper - alias Helix.Test.Server.Setup, as: ServerSetup alias Helix.Test.Process.Helper.TOP, as: TOPHelper - alias Helix.Test.Universe.Bank.Setup, as: BankSetup @doc """ Chooses a random implementation and uses it. Beware that `data_opts`, used by `custom/3`, is always an empty list when called from `random/1`. diff --git a/test/support/universe/bank/setup.ex b/test/support/universe/bank/setup.ex old mode 100644 new mode 100755 index 804bf564..6115bf2e --- a/test/support/universe/bank/setup.ex +++ b/test/support/universe/bank/setup.ex @@ -116,6 +116,12 @@ defmodule Helix.Test.Universe.Bank.Setup do {inserted, related} end + def transfer!(opts \\ []) do + {transfer, related} = transfer(opts) + + transfer + end + @doc """ - transfer_id: Force the transfer to have this id. - amount: Specify transfer amount. Defaults to a random number. From ece497e73abc2a064786997864f577e9dca9e05c Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:07:35 -0300 Subject: [PATCH 18/32] Improve Bank Public --- lib/universe/bank/public/bank.ex | 27 +++--------- lib/universe/bank/public/index.ex | 44 ++++++++++++++++--- test/universe/bank/public/index_test.exs | 56 ++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 27 deletions(-) mode change 100644 => 100755 lib/universe/bank/public/bank.ex mode change 100644 => 100755 lib/universe/bank/public/index.ex create mode 100755 test/universe/bank/public/index_test.exs diff --git a/lib/universe/bank/public/bank.ex b/lib/universe/bank/public/bank.ex old mode 100644 new mode 100755 index 201d5170..2a3d8257 --- a/lib/universe/bank/public/bank.ex +++ b/lib/universe/bank/public/bank.ex @@ -8,23 +8,15 @@ defmodule Helix.Universe.Bank.Public.Bank do as: BankAccountFlow alias Helix.Universe.Bank.Action.Flow.BankTransfer, as: BankTransferFlow - alias Helix.Universe.Bank.Internal.BankAccount, - as: BankAccountInternal - alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Public.Index, as: BankIndex alias Helix.Universe.Bank.Model.BankAccount alias Helix.Universe.Bank.Model.BankToken alias Helix.Universe.Bank.Model.ATM #TODO: Add Transfer History - @type bootstrap :: - %{ - balance: BankAccount.balance - } + @type bootstrap :: BankIndex.index - @type rendered_bootstrap :: - %{ - balance: BankAccount.balance - } + @type rendered_bootstrap :: BankIndex.rendered_index @spec bootstrap({ATM.id, BankAccount.account}) :: bootstrap @@ -32,11 +24,7 @@ defmodule Helix.Universe.Bank.Public.Bank do Gets the BankAccount information and puts into a map. """ def bootstrap({atm_id, account_number}) do - bank_account = BankQuery.fetch_account(atm_id, account_number) - - %{ - balance: bank_account.balance - } + BankIndex.index(atm_id, account_number) end @spec render_bootstrap(bootstrap) :: @@ -46,7 +34,7 @@ defmodule Helix.Universe.Bank.Public.Bank do Gets Bootstrap information and turns to client friendly format. """ def render_bootstrap(bootstrap) do - bootstrap + BankIndex.render_index(bootstrap) end @spec change_password( @@ -111,10 +99,9 @@ defmodule Helix.Universe.Bank.Public.Bank do @doc """ Closes given account. """ - defdelegate close_account(account), + def close_account(account), # TODO: Make as process - to: BankAccountInternal, - as: :close + do: BankAccountFlow.close(account) @spec transfer( BankAccount.t, diff --git a/lib/universe/bank/public/index.ex b/lib/universe/bank/public/index.ex old mode 100644 new mode 100755 index a4ffe16d..33da29c3 --- a/lib/universe/bank/public/index.ex +++ b/lib/universe/bank/public/index.ex @@ -1,5 +1,6 @@ defmodule Helix.Universe.Bank.Public.Index do + alias HELL.ClientUtils alias Helix.Universe.Bank.Query.Bank, as: BankQuery alias Helix.Universe.Bank.Model.BankAccount @@ -10,30 +11,59 @@ defmodule Helix.Universe.Bank.Public.Index do @type rendered_index :: %{ - balance: BankAccount.balance + balance: BankAccount.balance, + } + + @type rendered_transfer :: + %{ + transfer_id: String.t, + account_from: String.t, + account_to: String.t, + atm_from: String.t, + atm_to: String.t, + amount: BankTransfer.amount, + started_by: String.t, + started_time: Float.t } @spec index(ATM.id, BankAccount.account) :: index - @doc """ Creates index for given BankAccount{atm_id, account_number}. """ def index(atm_id, account_number) do bank_account = BankQuery.fetch_account(atm_id, account_number) - balance = bank_account.balance %{ - balance: balance + balance: bank_account.balance, } end @spec render_index(index) :: rendered_index - @doc """ Renders index to client-friendly format. """ - def render_index(index), - do: index + def render_index(index) do + %{ + balance: index.balance, + } + end + + @spec render_transfer(BankTransfer.t) :: rendered_transfer + @doc """ + Renders a BankTransfer to client-friendly format. + """ + def render_transfer(transfer) do + %{ + transfer_id: to_string(transfer.transfer_id), + account_from: to_string(transfer.account_from), + account_to: to_string(transfer.account_to), + atm_from: to_string(transfer.atm_from), + atm_to: to_string(transfer.atm_to), + amount: transfer.amount, + started_by: to_string(transfer.started_by), + started_time: ClientUtils.to_timestamp(transfer.started_time) + } + end end diff --git a/test/universe/bank/public/index_test.exs b/test/universe/bank/public/index_test.exs new file mode 100755 index 00000000..bc9cd24d --- /dev/null +++ b/test/universe/bank/public/index_test.exs @@ -0,0 +1,56 @@ +defmodule Helix.Universe.Bank.Public.IndexTest do + + use Helix.Test.Case.Integration + + alias Helix.Universe.Bank.Public.Index, as: BankIndex + + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "index/2" do + test "returns expected data" do + # Setups a BankAccount. + bank_account = BankSetup.account!() + atm_id = bank_account.atm_id + account_num = bank_account.account_number + + # Gets BankAccount's Index. + index = BankIndex.index(atm_id, account_num) + + assert index.balance == bank_account.balance + end + end + + describe "render_index/1" do + test "returns JSON-friendly index" do + # Setups a BankAccount. + bank_account = BankSetup.account!() + atm_id = bank_account.atm_id + account_num = bank_account.account_number + + # Renders BankAccount's Index. + rendered_index = + BankIndex.index(atm_id, account_num) + |> BankIndex.render_index() + + assert rendered_index.balance == bank_account.balance + end + end + + describe "render_transfer/1" do + # Setups a BankTransfer. + transfer = BankSetup.transfer!() + + # Renders BankTransfer. + rendered_transfer = + BankIndex.render_transfer(transfer) + + assert is_binary(rendered_transfer.transfer_id) + assert is_binary(rendered_transfer.account_from) + assert is_binary(rendered_transfer.account_to) + assert is_binary(rendered_transfer.atm_from) + assert is_binary(rendered_transfer.atm_to) + assert rendered_transfer.amount == transfer.amount + assert is_binary(rendered_transfer.started_by) + assert is_float(rendered_transfer.started_time) + end +end From cad510d12299352a5e09a6cc0e48ebc6abf26b67 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:10:03 -0300 Subject: [PATCH 19/32] Improve bank internal testing --- test/universe/bank/internal/bank_account_test.exs | 13 ++++--------- test/universe/bank/internal/bank_token_test.exs | 8 -------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/test/universe/bank/internal/bank_account_test.exs b/test/universe/bank/internal/bank_account_test.exs index 182fca75..84bf409e 100644 --- a/test/universe/bank/internal/bank_account_test.exs +++ b/test/universe/bank/internal/bank_account_test.exs @@ -85,13 +85,8 @@ defmodule Helix.Universe.Bank.Internal.BankAccountTest do atm_id = Enum.random(bank.servers).id owner_id = Random.pk() - params = %{ - bank_id: bank.id, - atm_id: atm_id, - owner_id: owner_id - } - - assert {:ok, acc} = BankAccountInternal.create(params) + assert {:ok, acc} = + BankAccountInternal.create(owner_id, atm_id, bank.id) assert_id acc.atm_id, atm_id assert_id acc.bank_id, bank.id assert_id acc.owner_id, owner_id @@ -170,7 +165,7 @@ defmodule Helix.Universe.Bank.Internal.BankAccountTest do assert BankAccountInternal.fetch(acc.atm_id, acc.account_number) assert {:error, reason} = BankAccountInternal.close(acc) - assert reason == {:account, :notempty} + assert reason == {:bank_account, :not_empty} assert BankAccountInternal.fetch(acc.atm_id, acc.account_number) end @@ -178,7 +173,7 @@ defmodule Helix.Universe.Bank.Internal.BankAccountTest do {fake_acc, _} = BankSetup.fake_account() assert {:error, reason} = BankAccountInternal.close(fake_acc) - assert reason == {:account, :notfound} + assert reason == {:bank_account, :not_found} end end end diff --git a/test/universe/bank/internal/bank_token_test.exs b/test/universe/bank/internal/bank_token_test.exs index ee068d8d..c3006b9c 100644 --- a/test/universe/bank/internal/bank_token_test.exs +++ b/test/universe/bank/internal/bank_token_test.exs @@ -36,14 +36,6 @@ defmodule Helix.Universe.Bank.Internal.BankTokenTest do assert token2.token_id == token.token_id end - test "wont fetch expired token" do - {token, _} = BankSetup.token([expired: true]) - - token2 = BankTokenInternal.fetch_by_connection(token.connection_id) - - refute token2 - end - test "with non-existing connection" do refute BankTokenInternal.fetch_by_connection(Connection.ID.generate()) end From 466445a8a1e6a10dcab1ae11df0b104ceb6bbc85 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:13:48 -0300 Subject: [PATCH 20/32] Improve bank events --- lib/account/websocket/channel/account.ex | 2 +- lib/event/dispatcher.ex | 8 +- lib/universe/bank/event/bank/account.ex | 111 ++++++++++++++- lib/universe/bank/event/bank/transfer.ex | 133 +++++++++++++++++- .../bank/event/handler/bank/account.ex | 21 ++- .../bank/event/handler/bank/transfer.ex | 12 +- .../bank/event/bank/account/password_test.exs | 33 +++++ .../bank/event/bank/account/token_test.exs | 40 ++++++ .../universe/bank/event/bank/account_test.exs | 36 +++++ .../bank/event/change_password_test.exs | 41 ++++++ .../bank/event/reveal_password_test.exs | 48 +++++++ 11 files changed, 478 insertions(+), 7 deletions(-) mode change 100644 => 100755 lib/universe/bank/event/bank/transfer.ex create mode 100644 test/universe/bank/event/bank/account/password_test.exs create mode 100644 test/universe/bank/event/bank/account/token_test.exs create mode 100755 test/universe/bank/event/bank/account_test.exs create mode 100644 test/universe/bank/event/change_password_test.exs create mode 100644 test/universe/bank/event/reveal_password_test.exs diff --git a/lib/account/websocket/channel/account.ex b/lib/account/websocket/channel/account.ex index c2b0f92e..800721dc 100644 --- a/lib/account/websocket/channel/account.ex +++ b/lib/account/websocket/channel/account.ex @@ -14,7 +14,7 @@ channel Helix.Account.Websocket.Channel.Account do alias Helix.Network.Websocket.Requests.Bounce.Remove, as: BounceRemoveRequest alias Helix.Software.Websocket.Requests.Virus.Collect, as: VirusCollectRequest alias Helix.Story.Websocket.Requests.Email.Reply, as: EmailReplyRequest - alias Helix.Universe.Websocket.Channel.Requests.CreateAccount, + alias Helix.Universe.Bank.Websocket.Requests.CreateAccount, as: BankAccountCreateRequest @doc """ diff --git a/lib/event/dispatcher.ex b/lib/event/dispatcher.ex index 4ad519da..4b51b5ae 100644 --- a/lib/event/dispatcher.ex +++ b/lib/event/dispatcher.ex @@ -272,16 +272,20 @@ defmodule Helix.Event.Dispatcher do # All event BankEvent.Bank.Account.Login event BankEvent.Bank.Account.Updated + event BankEvent.Bank.Account.Removed event BankEvent.Bank.Account.Password.Revealed + event BankEvent.Bank.Account.Password.Changed event BankEvent.Bank.Account.Token.Acquired event BankEvent.Bank.Transfer.Processed event BankEvent.Bank.Transfer.Aborted event BankEvent.RevealPassword.Processed + event BankEvent.ChangePassword.Processed # Custom handlers event BankEvent.Bank.Transfer.Processed, BankHandler.Bank.Transfer, :transfer_processed + event BankEvent.Bank.Transfer.Processed, NetworkHandler.Connection, :bank_transfer_processed @@ -289,6 +293,7 @@ defmodule Helix.Event.Dispatcher do event BankEvent.Bank.Transfer.Aborted, BankHandler.Bank.Transfer, :transfer_aborted + event BankEvent.Bank.Transfer.Aborted, SoftwareHandler.Cracker, :bank_transfer_aborted @@ -311,9 +316,10 @@ defmodule Helix.Event.Dispatcher do event BankEvent.Bank.Account.Password.Changed, BankHandler.Bank.Account, - :bank_password_changed + :password_changed event BankEvent.Bank.Account.Login, EntityHandler.Database, :bank_account_login + end diff --git a/lib/universe/bank/event/bank/account.ex b/lib/universe/bank/event/bank/account.ex index 4d1c3ef6..1afb0c88 100644 --- a/lib/universe/bank/event/bank/account.ex +++ b/lib/universe/bank/event/bank/account.ex @@ -2,6 +2,47 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do import Helix.Event + event Removed do + + alias Helix.Universe.Bank.Model.BankAccount + + event_struct [:account] + + @type t :: + %__MODULE__{ + account: BankAccount.t + } + + @spec new(BankAccount.t) :: + t + + def new(account = %BankAccount{}) do + %__MODULE__{ + account: account + } + end + + notify do + + @event :bank_account_removed + + @doc false + def generate_payload(event, _socket) do + data = + %{ + atm_id: to_string(event.account.atm_id), + account_number: event.account.account_number + } + + {:ok, data} + end + + @doc false + def whom_to_notify(event), + do: %{account: event.account.owner_id} + end + end + event Updated do @moduledoc """ `BankAccountUpdatedEvent` is fired when the underlying bank account has @@ -19,7 +60,7 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do reason: reason } - @type reason :: :balance | :password + @type reason :: :balance | :password | :created @spec new(BankAccount.t, reason) :: t @@ -81,5 +122,73 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do token_id: token_id } end + + notify do + @moduledoc """ + Notifies the client of bank account login, so it can properly update + the local data. + """ + + @event :bank_login_event + + @doc false + def generate_payload(event, _socket) do + data = + %{ + atm_id: to_string(event.account.atm_id), + account_number: event.account.account_number, + balance: event.account.balance, + password: event.account.password + } + + {:ok, data} + end + + def whom_to_notify(event) do + %{ + account: event.entity_id + } + end + end + end + + event Logout do + + alias Helix.Entity.Model.Entity + alias Helix.Universe.Bank.Model.BankAccount + + event_struct [:account, :entity_id] + + @type t :: %__MODULE__{ + account: BankAccount.t, + entity_id: Entity.id + } + + @spec new(BankAccount.t, Entity.id) :: + t + def new(account = %BankAccount{}, entity_id = %Entity.ID{}) do + %__MODULE__{ + account: account, + entity_id: entity_id + } + end + + notify do + + @event :bank_logout_event + + def generate_payload(event, _socket) do + data = + %{ + atm_id: to_string(event.account.atm_id), + account_number: event.account.account_number + } + + {:ok, data} + end + + def whom_to_notify(event), + do: %{account: event.entity_id} + end end end diff --git a/lib/universe/bank/event/bank/transfer.ex b/lib/universe/bank/event/bank/transfer.ex old mode 100644 new mode 100755 index 7fa74bc3..c34b6491 --- a/lib/universe/bank/event/bank/transfer.ex +++ b/lib/universe/bank/event/bank/transfer.ex @@ -49,15 +49,146 @@ defmodule Helix.Universe.Bank.Event.Bank.Transfer do connection_id: Connection.id } - event_struct [:transfer_id, :connection_id] + event_struct [:transfer_id, :started_by, :connection_id] @spec new(Process.t, BankTransferProcess.t) :: t def new(process = %Process{}, data = %BankTransferProcess{}) do %__MODULE__{ transfer_id: data.transfer_id, + started_by: data.started_by, connection_id: process.src_connection_id } end + + notify do + + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + @event :bank_transfer_aborted + + def generate_payload(event, _socket) do + transfer = BankQuery.fetch_transfer(event.transfer_id) + + data = + %{ + atm_id: to_string(transfer.atm_to), + account_number: transfer.account_to, + transfer_id: event.transfer_id + } + + {:ok, data} + end + + def whom_to_notify(event), + do: %{account: event.started_by} + end + end + + event Successful do + + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Model.BankTransfer + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + @type t :: + %__MODULE__{ + transfer: BankTransfer.t, + account: BankAccount.t + } + + event_struct [:transfer, :account] + + @spec new(BankTransfer.t) :: + t + def new(transfer = %BankTransfer{}) do + account = + BankQuery.fetch_account(transfer.atm_from, transfer.account_from) + + %__MODULE__{ + transfer: transfer, + account: account + } + end + + notify do + + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Public.Index, as: BankIndex + + @event :bank_transfer_successful + + def generate_payload(event, _socket) do + transfer = BankIndex.render_transfer(event.transfer) + + data = + %{ + transfer: transfer + } + + {:ok, data} + end + + def whom_to_notify(event) do + transfer = BankQuery.fetch_transfer(event.transfer_id) + %{ + account: transfer.started_by + } + end + end + end + + event Failed do + + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Model.BankTransfer + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + @type reason :: term + + @type t :: + %__MODULE__{ + transfer: BankTransfer.t, + account: BankAccount.t, + reason: reason + } + + event_struct [:transfer, :account, :reason] + + @spec new(BankTransfer.t, reason) :: + t + def new(transfer = %BankTransfer{}, reason) do + account = + BankQuery.fetch_account(transfer.atm_from, transfer.account_from) + + %__MODULE__{ + transfer: transfer, + account: account, + reason: reason + } + end + + notify do + + alias Helix.Universe.Bank.Public.Index, as: BankIndex + + @event :bank_transfer_failed + + def generate_payload(event, _socket) do + data = + %{ + transfer: BankIndex.render_transfer(event.transfer), + reason: to_string(event.reason) + } + + {:ok, data} + end + + def whom_to_notify(event) do + %{ + account: event.transfer.started_by + } + end + end end end diff --git a/lib/universe/bank/event/handler/bank/account.ex b/lib/universe/bank/event/handler/bank/account.ex index 35922a96..46645510 100644 --- a/lib/universe/bank/event/handler/bank/account.ex +++ b/lib/universe/bank/event/handler/bank/account.ex @@ -7,6 +7,10 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do alias Helix.Universe.Bank.Action.Bank, as: BankAction alias Helix.Software.Event.Virus.Collected, as: VirusCollectedEvent + alias Helix.Universe.Bank.Event.Bank.Account.Removed, + as: BankAccountRemovedEvent + alias Helix.Universe.Bank.Event.Bank.Account.Updated, + as: BankAccountUpdatedEvent alias Helix.Universe.Bank.Event.RevealPassword.Processed, as: RevealPasswordProcessedEvent alias Helix.Universe.Bank.Event.ChangePassword.Processed, @@ -38,6 +42,12 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do end end + @doc """ + Handles the conclusion of a `PasswordChangeProcess`, described at + `BankAccountFlow` + + Emits: `BankAccountPasswordChangedEvent` + """ def password_change_processed(event = %ChangePasswordProcessedEvent{}) do flowing do with \ @@ -53,8 +63,15 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do end end - def bank_password_changed(event = %BankPasswordChangedEvent{}) do - BankAction.update_password(event.account) + @doc """ + Emits BankAccountUpdatedEvent with reason :password to client update local + information + + Emits: `BankAccountUpdatedEvent` + """ + def password_changed(event = %BankPasswordChangedEvent{}) do + account = event.account + Event.emit(BankAccountUpdatedEvent.new(account, :password), from: event) end @doc """ diff --git a/lib/universe/bank/event/handler/bank/transfer.ex b/lib/universe/bank/event/handler/bank/transfer.ex index 03974b60..7b3a8e3b 100644 --- a/lib/universe/bank/event/handler/bank/transfer.ex +++ b/lib/universe/bank/event/handler/bank/transfer.ex @@ -1,5 +1,8 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Transfer do + import HELF.Flow + + alias Helix.Event alias Helix.Universe.Bank.Action.Bank, as: BankAction alias Helix.Universe.Bank.Query.Bank, as: BankQuery alias Helix.Universe.Bank.Event.Bank.Transfer.Aborted, @@ -9,7 +12,14 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Transfer do def transfer_processed(event = %BankTransferProcessedEvent{}) do transfer = BankQuery.fetch_transfer(event.transfer_id) - BankAction.complete_transfer(transfer) + flowing do + with \ + {:ok, _transfer, events} <- BankAction.complete_transfer(transfer), + on_success(fn -> Event.emit(events, from: event) end) + do + :ok + end + end end def transfer_aborted(event = %BankTransferAbortedEvent{}) do diff --git a/test/universe/bank/event/bank/account/password_test.exs b/test/universe/bank/event/bank/account/password_test.exs new file mode 100644 index 00000000..84f60ea7 --- /dev/null +++ b/test/universe/bank/event/bank/account/password_test.exs @@ -0,0 +1,33 @@ +defmodule Helix.Universe.Bank.Event.PasswordTest do + + use Helix.Test.Case.Integration + + alias Helix.Event + + alias Helix.Entity.Query.Database, as: DatabaseQuery + + alias Helix.Test.Event.Setup, as: EventSetup + alias Helix.Test.Entity.Setup, as: EntitySetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "event reactions" do + test "accepts if password is added to database when event is fired" do + # Setups a Bank Account + bank_acc = BankSetup.account!() + + # Setups a new Entity and gets it's id + entity_id = EntitySetup.entity!().entity_id + + # Setups a BankAccountPasswordRevealedEvent + event = EventSetup.Bank.password_revealed(bank_acc, entity_id) + + # Emits BankAccountPasswordRevealedEvent + Event.emit(event) + + assert password = + DatabaseQuery.fetch_bank_account(entity_id, bank_acc).password + + assert password == bank_acc.password + end + end +end diff --git a/test/universe/bank/event/bank/account/token_test.exs b/test/universe/bank/event/bank/account/token_test.exs new file mode 100644 index 00000000..3929ade5 --- /dev/null +++ b/test/universe/bank/event/bank/account/token_test.exs @@ -0,0 +1,40 @@ +defmodule Helix.Universe.Bank.Event.TokenTest do + + use Helix.Test.Case.Integration + + alias Helix.Event + + alias Helix.Entity.Query.Database, as: DatabaseQuery + + alias Helix.Test.Event.Setup, as: EventSetup + alias Helix.Test.Entity.Setup, as: EntitySetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "event reactions" do + test "accepts when token is created on database after event been fired" do + # Setups a Bank Account + bank_acc = BankSetup.account!() + + # Setups a new Entity and gets it's id + entity_id = EntitySetup.entity!().entity_id + + # Setups a new Token for earlier created Bank Account + token_id = BankSetup.token!(acc: bank_acc).token_id + + # Setups a BankAccountTokenAcquiredEvent + event = EventSetup.Bank.token_acquired(token_id, bank_acc, entity_id) + + # Refutes if the Bank Account already exists on Entity's Database + refute DatabaseQuery.fetch_bank_account(entity_id, bank_acc) + + # Emits BankAccountTokenAcquiredEvent + Event.emit(event) + + # Fetchs the BankAccount from Entity's Database + db_bank_acc = DatabaseQuery.fetch_bank_account(entity_id, bank_acc) + + # Asserts that token exists on Entity's Database + assert db_bank_acc.token == token_id + end + end +end diff --git a/test/universe/bank/event/bank/account_test.exs b/test/universe/bank/event/bank/account_test.exs new file mode 100755 index 00000000..86b0e200 --- /dev/null +++ b/test/universe/bank/event/bank/account_test.exs @@ -0,0 +1,36 @@ +defmodule Helix.Universe.Bank.Event.AccountTest do + + use Helix.Test.Case.Integration + + alias Helix.Event + + alias Helix.Entity.Query.Database, as: DatabaseQuery + + alias Helix.Test.Event.Setup, as: EventSetup + alias Helix.Test.Entity.Setup, as: EntitySetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "event reactions" do + test "login reactions for logging from non-owner entity" do + # Setups a Bank Account + bank_acc = BankSetup.account!() + + # Setups a new Entity and gets it's id + entity_id = EntitySetup.entity!().entity_id + + # Setups a BankAccountLoginEvent for earlier created Bank Account + event = EventSetup.Bank.login(bank_acc, entity_id) + + # Emit login event + Event.emit(event) + + # Asserts that the Bank Account has been created on created entity's + # database and that Bank Account's information are the same + assert db_acc = DatabaseQuery.fetch_bank_account(entity_id, bank_acc) + assert bank_acc.account_number == db_acc.account_number + assert bank_acc.atm_id == db_acc.atm_id + assert bank_acc.password == db_acc.password + assert bank_acc.balance == db_acc.known_balance + end + end +end diff --git a/test/universe/bank/event/change_password_test.exs b/test/universe/bank/event/change_password_test.exs new file mode 100644 index 00000000..4843392a --- /dev/null +++ b/test/universe/bank/event/change_password_test.exs @@ -0,0 +1,41 @@ +defmodule Helix.Universe.Bank.Event.ChangePasswordTest do + + use Helix.Test.Case.Integration + + alias Helix.Event + + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + + alias Helix.Test.Event.Setup, as: EventSetup + alias Helix.Test.Server.Setup, as: ServerSetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "event reactions" do + test "accepts if account's password changes after fired" do + # Setups a Gateway + {gateway, %{entity: entity}} = ServerSetup.server() + + # Gets gateway's server_id + gateway_id = gateway.server_id + + # Setups a BankAccount + bank_acc = BankSetup.account!(owner_id: entity.entity_id) + + # Stores BankAccount's password for checking later + old_password = bank_acc.password + + # Setups a ChangePasswordProcessedEvent + event = EventSetup.Bank.password_change_processed(bank_acc, gateway_id) + + # Emits ChangePasswordProcessedEvent + Event.emit(event) + + # Fetchs the new BankAccount + assert bank_acc = + BankQuery.fetch_account(bank_acc.atm_id, bank_acc.account_number) + + # Refutes if the BankAccount's password still the same as before + refute bank_acc.password == old_password + end + end +end diff --git a/test/universe/bank/event/reveal_password_test.exs b/test/universe/bank/event/reveal_password_test.exs new file mode 100644 index 00000000..8c0bea13 --- /dev/null +++ b/test/universe/bank/event/reveal_password_test.exs @@ -0,0 +1,48 @@ +defmodule Helix.Universe.Bank.Event.RevealPasswordTest do + + use Helix.Test.Case.Integration + + alias Helix.Event + + alias Helix.Entity.Query.Database, as: DatabaseQuery + + alias Helix.Test.Event.Setup, as: EventSetup + alias Helix.Test.Server.Setup, as: ServerSetup + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "event reactions" do + test "accepts if account's password is revealed" do + # Setups a Gateway + {gateway, %{entity: entity}} = ServerSetup.server() + + # Gets gateway's server_id + gateway_id = gateway.server_id + + # Setups a BankAccount + bank_acc = BankSetup.account!() + + # Setups a BankAccount Token + token_id = BankSetup.token!(acc: bank_acc).token_id + + # Setups a RevealPasswordProcessedEvent + event = + EventSetup.Bank.password_reveal_processed( + bank_acc, + gateway_id, + token_id + ) + + # Refutes that earlier created bank account exists. + refute DatabaseQuery.fetch_bank_account(entity, bank_acc) + + # Emits RevealPasswordProcessedEvent + Event.emit(event) + + # Asserts that account exists on Entity's database + assert db_bank_acc = DatabaseQuery.fetch_bank_account(entity, bank_acc) + + # Asserts that password is revealed on Entity's database + assert db_bank_acc.password == bank_acc.password + end + end +end From ced90758648c953a72d40a395b6da04cd5d99026 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:16:12 -0300 Subject: [PATCH 21/32] Improve bank action --- lib/universe/bank/action/bank.ex | 66 ++++++++++++++----- lib/universe/bank/action/flow/bank_account.ex | 46 +++++++++---- test/universe/bank/action/bank_test.exs | 19 +++--- .../bank/action/flow/bank_account_test.exs | 31 ++++++++- 4 files changed, 123 insertions(+), 39 deletions(-) diff --git a/lib/universe/bank/action/bank.ex b/lib/universe/bank/action/bank.ex index a0a7242f..3a5c1107 100644 --- a/lib/universe/bank/action/bank.ex +++ b/lib/universe/bank/action/bank.ex @@ -27,11 +27,17 @@ defmodule Helix.Universe.Bank.Action.Bank do as: BankAccountPasswordChangedEvent alias Helix.Universe.Bank.Event.Bank.Account.Token.Acquired, as: BankAccountTokenAcquiredEvent + alias Helix.Universe.Bank.Event.Bank.Account.Removed, + as: BankAccountRemovedEvent + alias Helix.Universe.Bank.Event.Bank.Transfer.Successful, + as: BankTransferSuccessfulEvent + alias Helix.Universe.Bank.Event.Bank.Transfer.Failed, + as: BankTransferFailedEvent @spec start_transfer(BankAccount.t, BankAccount.t, pos_integer, Account.idt) :: {:ok, BankTransfer.t} | {:error, {:funds, :insufficient}} - | {:error, {:account, :notfound}} + | {:error, {:bank_account, :not_found}} | {:error, Ecto.Changeset.t} @doc """ Starts a bank transfer. @@ -51,9 +57,8 @@ defmodule Helix.Universe.Bank.Action.Bank do as: :start @spec complete_transfer(BankTransfer.t) :: - :ok - | {:error, {:transfer, :notfound}} - | {:error, :internal} + {:ok, BankTransfer.t, [BankTransferSuccessfulEvent.t]} + | {:error, term, [BankTransferFailedEvent.t]} @doc """ Completes the transfer. @@ -66,9 +71,19 @@ defmodule Helix.Universe.Bank.Action.Bank do This function should not be called directly by Public. Instead, it must be triggered by the BankTransferCompletedEvent. """ - defdelegate complete_transfer(transfer), - to: BankTransferInternal, - as: :complete + def complete_transfer(transfer) do + case BankTransferInternal.complete(transfer) do + :ok -> + {:ok, transfer, [BankTransferSuccessfulEvent.new(transfer)]} + + {:error, reason} -> + { + :error, + reason, + [BankTransferFailedEvent.new(transfer, reason)] + } + end + end @spec abort_transfer(BankTransfer.t) :: :ok @@ -91,7 +106,7 @@ defmodule Helix.Universe.Bank.Action.Bank do as: :abort @spec open_account(Account.idt, ATM.id) :: - {:ok, BankAccount.t} + {:ok, BankAccount.t, [BankAccountUpdatedEvent.t]} | {:error, Ecto.Changeset.t} @doc """ Opens a bank account. @@ -103,23 +118,34 @@ defmodule Helix.Universe.Bank.Action.Bank do |> Map.get(:entity_id) |> NPCQuery.fetch() - %{owner_id: owner, atm_id: atm, bank_id: bank} - |> BankAccountInternal.create() + case BankAccountInternal.create(owner, atm, bank) do + {:ok, bank_acc} -> + {:ok, bank_acc, [BankAccountUpdatedEvent.new(bank_acc, :created)]} + + error -> + error + end end @spec close_account(BankAccount.t) :: :ok - | {:error, {:account, :notfound}} - | {:error, {:account, :notempty}} + | {:error, {:bank_account, :not_found}} + | {:error, {:bank_account, :not_empty}} @doc """ Closes a bank account. May fail if the account is invalid or not empty. In order to close an account, its balance must be empty. """ - defdelegate close_account(account), - to: BankAccountInternal, - as: :close + def close_account(account) do + case BankAccountInternal.close(account) do + :ok -> + {:ok, [BankAccountRemovedEvent.new(account)]} + + {:error, reason} -> + {:error, reason} + end + end @spec direct_deposit(BankAccount.t, BankAccount.amount) :: {:ok, BankAccount.t, [BankAccountUpdatedEvent.t]} @@ -203,8 +229,14 @@ defmodule Helix.Universe.Bank.Action.Bank do {:ok, BankAccount.t, [BankAccountPasswordChangedEvent.t]} | {:error, :internal} def change_password(account, changed_by) do - event = BankAccountPasswordChangedEvent.new(account, changed_by) - {:ok, account, [event]} + with {:ok, account} <- update_password(account) do + event = BankAccountPasswordChangedEvent.new(account, changed_by) + + {:ok, account, [event]} + else + _ -> + {:error, :internal} + end end @spec update_password(BankAccount.t) :: diff --git a/lib/universe/bank/action/flow/bank_account.ex b/lib/universe/bank/action/flow/bank_account.ex index ec0d0491..264b0dab 100644 --- a/lib/universe/bank/action/flow/bank_account.ex +++ b/lib/universe/bank/action/flow/bank_account.ex @@ -5,7 +5,6 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do alias Helix.Event alias Helix.Entity.Query.Entity, as: EntityQuery alias Helix.Network.Action.Flow.Tunnel, as: TunnelFlow - alias Helix.Network.Model.Connection alias Helix.Network.Query.Network, as: NetworkQuery alias Helix.Process.Model.Process alias Helix.Server.Model.Server @@ -61,13 +60,32 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do Opens a new BankAccount to given Account.id """ def open(account_id, atm_id) do - bank_account = - BankAction.open_account(account_id, atm_id) - case bank_account do - {:ok, bank_account} -> - {:ok, bank_account} - {:error, _} -> - {:error, :internal} + flowing do + with \ + {:ok, bank_acc, events} <- BankAction.open_account(account_id, atm_id), + on_success(fn -> Event.emit(events) end) + do + {:ok, bank_acc} + else + _ -> + {:error, :internal} + end + end + end + + @spec close(BankAccount.t) :: + :ok + | {:error, {:bank_account, :not_found}} + | {:error, {:bank_account, :not_empty}} + def close(bank_account) do + flowing do + with \ + true <- not is_nil(bank_account), + {:ok, events} <- BankAction.close_account(bank_account), + on_success(fn -> Event.emit(events) end) + do + :ok + end end end @@ -75,7 +93,13 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do {:ok, Process.t} | BankAccountChangePasswordProcess.executable_error @doc """ - Starts a ChangePasswordProcess + Starts the `bank_change_password` process. + + This game mechanic happens on the BankAccount control panel where the + BankAccount's owner can change the BankAccount's Password + + It is a process managed by TOP. in the sense that the password change does not + happen instantly. Completion is handled by `BankAccountEvent`. Emits: ProcessCreatedEvent """ @@ -152,9 +176,9 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do {:ok, _, events} <- BankAction.login_token(acc, token, entity), on_success(fn -> Event.emit(events) end), - {:ok, _, connection} <- start_connection.() + {:ok, tunnel, connection} <- start_connection.() do - {:ok, connection} + {:ok, tunnel, connection} end end end diff --git a/test/universe/bank/action/bank_test.exs b/test/universe/bank/action/bank_test.exs index d22140da..743967a4 100644 --- a/test/universe/bank/action/bank_test.exs +++ b/test/universe/bank/action/bank_test.exs @@ -56,9 +56,9 @@ defmodule Helix.Universe.Bank.Action.BankTest do {transfer, %{acc1: account_from, acc2: account_to}} = BankSetup.transfer([amount: amount]) - assert :ok == BankAction.complete_transfer(transfer) + assert {:ok, transfer, _events} = BankAction.complete_transfer(transfer) - refute BankTransferInternal.fetch(transfer) + refute BankTransferInternal.fetch(transfer.transfer_id) assert BankAccountInternal.get_balance(account_from) == 0 assert BankAccountInternal.get_balance(account_to) == amount end @@ -66,7 +66,9 @@ defmodule Helix.Universe.Bank.Action.BankTest do test "with invalid data" do {fake_transfer, _} = BankSetup.fake_transfer() - assert {:error, reason} = BankAction.complete_transfer(fake_transfer) + assert {:error, reason, _events} = + BankAction.complete_transfer(fake_transfer) + assert reason == {:transfer, :notfound} end end @@ -77,7 +79,7 @@ defmodule Helix.Universe.Bank.Action.BankTest do {transfer, %{acc1: account_from, acc2: account_to}} = BankSetup.transfer([amount: amount]) - assert :ok == BankAction.abort_transfer(transfer) + assert :ok = BankAction.abort_transfer(transfer) refute BankTransferInternal.fetch(transfer) assert BankAccountInternal.get_balance(account_from) == amount @@ -103,7 +105,7 @@ defmodule Helix.Universe.Bank.Action.BankTest do |> Map.get(:id) |> ServerQuery.fetch() - assert {:ok, acc} = BankAction.open_account(player, atm) + assert {:ok, acc, _events} = BankAction.open_account(player, atm) assert acc.account_number assert acc.owner_id == player.account_id @@ -117,8 +119,7 @@ defmodule Helix.Universe.Bank.Action.BankTest do test "closes the account" do {acc, _} = BankSetup.account() - assert BankAccountInternal.fetch(acc.atm_id, acc.account_number) - assert :ok == BankAction.close_account(acc) + assert {:ok, _events} = BankAction.close_account(acc) refute BankAccountInternal.fetch(acc.atm_id, acc.account_number) end @@ -127,14 +128,14 @@ defmodule Helix.Universe.Bank.Action.BankTest do assert BankAccountInternal.fetch(acc.atm_id, acc.account_number) assert {:error, reason} = BankAction.close_account(acc) - assert reason == {:account, :notempty} + assert reason == {:bank_account, :not_empty} assert BankAccountInternal.fetch(acc.atm_id, acc.account_number) end test "with invalid data" do {fake_acc, _} = BankSetup.fake_account() assert {:error, reason} = BankAction.close_account(fake_acc) - assert reason == {:account, :notfound} + assert reason == {:bank_account, :not_found} end end diff --git a/test/universe/bank/action/flow/bank_account_test.exs b/test/universe/bank/action/flow/bank_account_test.exs index c51ecf88..fb09d1fb 100644 --- a/test/universe/bank/action/flow/bank_account_test.exs +++ b/test/universe/bank/action/flow/bank_account_test.exs @@ -10,6 +10,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do alias Helix.Universe.Bank.Action.Flow.BankAccount, as: BankAccountFlow alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Test.Account.Setup, as: AccountSetup alias Helix.Test.Universe.Bank.Setup, as: BankSetup alias Helix.Test.Entity.Database.Setup, as: DatabaseSetup alias Helix.Test.Process.TOPHelper @@ -180,7 +181,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do {server, %{entity: entity}} = ServerSetup.server() # Login with the right credentials - assert {:ok, connection} = + assert {:ok, _tunnel, connection} = BankAccountFlow.login_token( acc.atm_id, acc.account_number, server.server_id, nil, token.token_id ) @@ -213,7 +214,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do assert to_string(acc.owner_id) == to_string(entity.entity_id) # Login with the right token - assert {:ok, connection} = + assert {:ok, _tunnel, connection} = BankAccountFlow.login_token( acc.atm_id, acc.account_number, server.server_id, nil, token.token_id ) @@ -270,4 +271,30 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do refute DatabaseQuery.fetch_bank_account(entity, acc) end end + + describe "open/2" do + test "opens account when everything is OK" do + account = AccountSetup.account!() + account_id = account.account_id + + bank_acc = BankSetup.account!() + atm_id = bank_acc.atm_id + + assert {:ok, bank_acc} = BankAccountFlow.open(account_id, atm_id) + assert bank_acc.atm_id == atm_id + assert BankQuery.fetch_account(atm_id, bank_acc.account_number) + end + end + + describe "close/2" do + test "closes account" do + bank_acc = BankSetup.account!() + + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + assert :ok = BankAccountFlow.close(bank_acc) + refute BankQuery.fetch_account(atm_id, account_number) + end + end end From 1a43058073e33ec86042bdc13d5e69addecc0971 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:16:55 -0300 Subject: [PATCH 22/32] Improve bank bootstrap request --- .../websocket/channel/requests/bootstrap.ex | 3 - .../websocket/requests/bootstrap_test.exs | 65 ------------------- 2 files changed, 68 deletions(-) delete mode 100644 test/universe/bank/websocket/requests/bootstrap_test.exs diff --git a/lib/universe/bank/websocket/channel/requests/bootstrap.ex b/lib/universe/bank/websocket/channel/requests/bootstrap.ex index 5c3f1b49..d287c3bf 100644 --- a/lib/universe/bank/websocket/channel/requests/bootstrap.ex +++ b/lib/universe/bank/websocket/channel/requests/bootstrap.ex @@ -8,9 +8,6 @@ request Helix.Universe.Bank.Websocket.Requests.Bootstrap do It returns the BankBootstrap, which is the exact same struct returned after joining a local or remote bank Channel. """ - alias Helix.Server.Model.Server - alias Helix.Universe.Bank.Model.BankAccount - alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer alias Helix.Universe.Bank.Public.Bank, as: BankPublic def check_params(request, _socket) do diff --git a/test/universe/bank/websocket/requests/bootstrap_test.exs b/test/universe/bank/websocket/requests/bootstrap_test.exs deleted file mode 100644 index 04b63af3..00000000 --- a/test/universe/bank/websocket/requests/bootstrap_test.exs +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Helix.Test.Universe.Bank.Websocket.Requests.BootstrapTest do - - use Helix.Test.Case.Integration - - import Phoenix.ChannelTest - - alias Helix.Universe.Bank.Websocket.Requests.Bootstrap, - as: BankBootstrapRequest - alias Helix.Websocket.Requestable - - alias Helix.Test.Universe.Bank.Setup, as: BankSetup - alias Helix.Test.Universe.Bank.Helper, as: BankHelper - alias Helix.Test.Channel.Helper, as: ChannelHelper - alias Helix.Test.Channel.Setup, as: ChannelSetup - - describe "BankBootstrapRequest.handle_request/2" do - test "bootstrap" do - # Setups an Account socket. - {socket, %{entity: entity, server: gateway}} = - ChannelSetup.create_socket() - - # Setups a BankAccount. - bank_acc = BankSetup.account!(balance: BankHelper.amount) - atm_id = bank_acc.atm_id - account_number = bank_acc.account_number - - # Creates topic to log in the bank channel. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Create the payload which will be used to join the bank channel. - payload = - %{ - "entity_id" => to_string(entity.entity_id), - "password" => bank_acc.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins the bank channel. - {:ok, _bootstrap, bnk_socket} = - join(socket, topic, payload) - - # Mocks a BankBootstrapRequest. - request = - BankBootstrapRequest.new(%{}) - - # Request Checking Parameters (which does not exists in this case). - {:ok, request} = - Requestable.check_params(request, bnk_socket) - - # Request Checking Permissions (which does nothing in this case - # because receives no parameters and is already logged in the - # Bank channel). - {:ok, request} = - Requestable.check_permissions(request, bnk_socket) - - # Asserts that request has been handled successfully. - assert {:ok, request} = - Requestable.handle_request(request, bnk_socket) - - # Asserts that bootstrap information is correct. - assert request.meta.bootstrap - assert request.meta.bootstrap.balance == bank_acc.balance - end - end -end From 2a7847286eec8976ea7b17e541dd4178030d621a Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:17:28 -0300 Subject: [PATCH 23/32] Improve bank change password request --- .../channel/requests/change_password.ex | 9 +- .../requests/change_password_test.exs | 158 ------------------ 2 files changed, 5 insertions(+), 162 deletions(-) delete mode 100644 test/universe/bank/websocket/requests/change_password_test.exs diff --git a/lib/universe/bank/websocket/channel/requests/change_password.ex b/lib/universe/bank/websocket/channel/requests/change_password.ex index ec620018..0c3236eb 100644 --- a/lib/universe/bank/websocket/channel/requests/change_password.ex +++ b/lib/universe/bank/websocket/channel/requests/change_password.ex @@ -27,9 +27,10 @@ request Helix.Universe.Bank.Websocket.Requests.ChangePassword do account_number = socket.assigns.account_number bank_account = BankQuery.fetch_account(atm_id, account_number) with \ - {true, relay} <- BankHenforcer.owns_account?(entity, bank_account) + {true, relay} <- BankHenforcer.owns_account?(entity, bank_account), + bank_account = relay.bank_account do - reply_ok(request) + update_meta(request, %{bank_account: bank_account}, reply: true) else {false, reason, _} -> reply_error(request, reason) @@ -38,10 +39,10 @@ request Helix.Universe.Bank.Websocket.Requests.ChangePassword do def handle_request(request, socket) do atm_id = socket.assigns.atm_id - account_number = socket.assigns.account_number - account = BankQuery.fetch_account(atm_id, account_number) + account = request.meta.bank_account atm = ServerQuery.fetch(atm_id) gateway = ServerQuery.fetch(socket.assigns.gateway.server_id) + password_change = BankPublic.change_password(account, gateway, atm, request.relay) diff --git a/test/universe/bank/websocket/requests/change_password_test.exs b/test/universe/bank/websocket/requests/change_password_test.exs deleted file mode 100644 index 7c1c9720..00000000 --- a/test/universe/bank/websocket/requests/change_password_test.exs +++ /dev/null @@ -1,158 +0,0 @@ -defmodule Helix.Test.Universe.Bank.Websocket.Requests.ChangePasswordTest do - - use Helix.Test.Case.Integration - - import Phoenix.ChannelTest - - alias Helix.Websocket.Requestable - alias Helix.Universe.Bank.Query.Bank, as: BankQuery - alias Helix.Universe.Bank.Websocket.Requests.ChangePassword, - as: BankChangePasswordRequest - - alias Helix.Test.Channel.Request.Helper, as: RequestHelper - alias Helix.Test.Channel.Helper, as: ChannelHelper - alias Helix.Test.Channel.Setup, as: ChannelSetup - alias Helix.Test.Process.TOPHelper - alias Helix.Test.Universe.Bank.Setup, as: BankSetup - - describe "BankChangePasswordRequest.check_permissions/2" do - test "accepts when account belongs to player" do - # Setups an Account socket and get gateway and entity - # from related information. - {socket, %{server: gateway, entity: entity}} = - ChannelSetup.create_socket() - - # Setups a BankAccount for testing. - bank_account = - BankSetup.account!(owner_id: entity.entity_id) - atm_id = bank_account.atm_id - account_number = bank_account.account_number - - # Creates the topic to join on bank channel. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Creates the payload for joining the bank channel. - payload = - %{ - "entity_id" => to_string(entity.entity_id), - "password" => bank_account.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins the bank channel with the created BankAccount. - {:ok, _bootstrap, bnk_socket} = - join(socket, topic, payload) - - # Mocks a BankChangePasswordRequest that does not receive any parameters. - request = - RequestHelper.mock_request(BankChangePasswordRequest, %{}) - - # Asserts that get permissions to continue with the request flow. - assert {:ok, _request} = - Requestable.check_permissions(request, bnk_socket) - end - - test "rejects when account does not belongs to player" do - # Setups an Account socket and get gateway and entity - # from related information. - {socket, %{server: gateway, entity: entity}} = - ChannelSetup.create_socket() - - # Setups a BankAccount that not belongs to entity. - bank_account = - BankSetup.account!() - atm_id = bank_account.atm_id - account_number = bank_account.account_number - - # Creates a bank channel topic based on BankAccount information. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Creates the payload for logging in the bank channel on created - # BankAccount. - payload = - %{ - "entity_id" => to_string(entity.entity_id), - "password" => bank_account.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins the bank channel. - {:ok, _bootstrap, bnk_socket} = - join(socket, topic, payload) - - # Mocks a BankChangePasswordRequest that does not receive any parameters. - request = - RequestHelper.mock_request(BankChangePasswordRequest, %{}) - - # Asserts that get no permissions to change password because the entity - # does not own the given BankAccount. - assert {:error, reason, _} = - Requestable.check_permissions(request, bnk_socket) - - # Asserts the reason for failing is "bank_account_not_belong". - assert reason.message == "bank_account_not_belongs" - end - end - - describe "BankChangePasswordRequest.handle_request/2" do - test "accepts if the password changes after process ends" do - # Setups an Account socket and gets gateway and entity from - # related information. - {socket, %{server: gateway, entity: entity}} = - ChannelSetup.create_socket() - - # Setups a BankAccount for testing. - bank_account = - BankSetup.account!(owner_id: entity.entity_id) - atm_id = bank_account.atm_id - account_number = bank_account.account_number - - # Stores the old password on a variable to compare after the process. - old_password = bank_account.password - - # Creates the topic used for logging in the bank channel. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Creates the payload used as parameters to log in on bank channel. - payload = - %{ - "entity_id" => to_string(entity.entity_id), - "password" => bank_account.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins the bank channel. - {:ok, _bootstrap, bnk_socket} = - join(socket, topic, payload) - - # Mocks BankChangePasswordRequest that does not receive any parameters. - request = - RequestHelper.mock_request(BankChangePasswordRequest, %{}) - - # Checks the permissions for given Entity on BankAccount. - {:ok, request} = - Requestable.check_permissions(request, bnk_socket) - - # Asserts that request is handled correctly. - assert {:ok, request} = - Requestable.handle_request(request, bnk_socket) - - # Gets the process' id for forcing it's termination. - process_id = request.meta.process.process_id - - # Forces the process to complete. - TOPHelper.force_completion(process_id) - - # Fetchs the BankAccount from the database. - bank_account = - BankQuery.fetch_account( - bank_account.atm_id, - bank_account.account_number - ) - - # Refutes if the current BankAccount password is equals - # to the old_password. - refute bank_account.password == old_password - end - end -end From 54f73c22d42ae45850231f6f0d1ce0885273ce43 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:17:54 -0300 Subject: [PATCH 24/32] Improve bank create password request --- .../channel/requests/create_account.ex | 7 +- .../websocket/requests/create_account.exs | 197 ------------------ 2 files changed, 3 insertions(+), 201 deletions(-) delete mode 100644 test/universe/bank/websocket/requests/create_account.exs diff --git a/lib/universe/bank/websocket/channel/requests/create_account.ex b/lib/universe/bank/websocket/channel/requests/create_account.ex index 946c942a..a0338262 100644 --- a/lib/universe/bank/websocket/channel/requests/create_account.ex +++ b/lib/universe/bank/websocket/channel/requests/create_account.ex @@ -7,7 +7,6 @@ request Helix.Universe.Bank.Websocket.Requests.CreateAccount do It Returns :ok or :error """ - alias Helix.Network.Model.Network alias Helix.Server.Model.Server alias Helix.Server.Query.Server, as: ServerQuery alias Helix.Universe.Bank.Public.Bank, as: BankPublic @@ -60,9 +59,9 @@ request Helix.Universe.Bank.Websocket.Requests.CreateAccount do bank_account = BankPublic.open_account(account_id, atm_id) case bank_account do {:ok, bank_account} -> - {:ok, bank_account} - error -> - error + update_meta(request, %{bank_account: bank_account}, reply: true) + {:error, reason} -> + reply_error(request, reason) end end render_empty() diff --git a/test/universe/bank/websocket/requests/create_account.exs b/test/universe/bank/websocket/requests/create_account.exs deleted file mode 100644 index 465628ab..00000000 --- a/test/universe/bank/websocket/requests/create_account.exs +++ /dev/null @@ -1,197 +0,0 @@ -defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do - - use Helix.Test.Case.Integration - - alias Helix.Websocket.Requestable - alias Helix.Network.Query.Network, as: NetworkQuery - alias Helix.Universe.Bank.Query.Bank, as: BankQuery - alias Helix.Universe.Bank.Websocket.Requests.CreateAccount, - as: BankCreateAccountRequest - - alias Helix.Test.Channel.Setup, as: ChannelSetup - alias Helix.Test.Server.Setup, as: ServerSetup - alias Helix.Test.Universe.NPC.Helper, as: NPCHelper - - @internet_id NetworkQuery.internet().network_id - - describe "BankCreateAccountRequest.check_params/2" do - test "accepts when receives valid information" do - # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() - - # Gets a Bank from database. - {bank, _} = NPCHelper.bank - atm_id = List.first(bank.servers).id - - # Creates the parameters for creating a account. - payload = - %{ - "atm_id" => to_string(atm_id), - "network_id" => to_string(@internet_id) - } - - # Creates a new BankCreateAccountRequest with the parameters. - request = - BankCreateAccountRequest.new(payload) - - # Asserts that given parameters are okay. - assert {:ok, request} = - Requestable.check_params(request, socket) - - # Assert the parameters after checking are right. - assert request.params.network_id == @internet_id - assert request.params.atm_id == atm_id - end - - test "rejects when receives invalid atm_id" do - # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() - - # Creates invalid parameters. - payload = - %{ - "atm_id" => "DROP TABLE helix_dev_accounts;", - "network_id" => to_string(@internet_id) - } - - # Create BankCreateAccountRequest with invalid parameters. - request = - BankCreateAccountRequest.new(payload) - - # Asserts that given parameters are rejected. - assert {:error, reason, _} = - Requestable.check_params(request, socket) - - # Asserts that the error message is "bad_request. - assert reason.message == "bad_request" - end - - test "rejects when receives invalid network_id" do - # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() - - # Gets a Bank and it's atm_id. - {bank, _} = NPCHelper.bank - atm_id = List.first(bank.servers).id - - # Creates invalid parameters. - payload = - %{ - "atm_id" => to_string(atm_id), - "network_id" => "DROP TABLE helix_dev_accounts;" - } - - # Create BankCreateAccountRequest with invalid parameters. - request = - BankCreateAccountRequest.new(payload) - - # Asserts that given parameters are rejected. - assert {:error, reason, _} = - Requestable.check_params(request, socket) - - # Asserts that the error message is "bad_request". - assert reason.message == "bad_request" - end - end - - describe "BankCreateAccountRequest.check_permissions/2" do - test "accepts when the ATM is a bank" do - # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() - - # Gets a Bank and it's atm_id. - {bank, _} = NPCHelper.bank - atm_id = List.first(bank.servers).id - - # Create valid parameters. - payload = - %{ - "atm_id" => to_string(atm_id), - "network_id" => to_string(@internet_id) - } - - # Create BankCreateAccountRequest with valid parameters. - request = - BankCreateAccountRequest.new(payload) - - # Checks if parameters are valid. - {:ok, request} = - Requestable.check_params(request, socket) - - # Asserts that Bank is a Bank. - assert {:ok, request} = - Requestable.check_permissions(request, socket) - - # Asserts that atm_id is on meta. - assert request.meta.atm_id == atm_id - end - - test "rejects when the ATM is not a server" do - # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() - - # Setups a ordinary server. - {not_bank, _} = ServerSetup.server() - - # Create parameters passing the ordinary server's id as - # an atm_id. - payload = - %{ - "atm_id" => to_string(not_bank.server_id), - "network_id" => to_string(@internet_id) - } - - # Create BankCreateAccountRequest with parameters. - request = - BankCreateAccountRequest.new(payload) - - # Asserts the parameters are in right format. - assert {:ok, request} = - Requestable.check_params(request, socket) - - # Asserts error because the ordinary server is not a bank. - assert {:error, reason, _} = - Requestable.check_permissions(request, socket) - - # Asserts the error message is "atm_not_a_bank". - assert reason.message == "atm_not_a_bank" - end - end - - describe "BankCreateAccountRequest.handle_request/2" do - test "creates BankAccount on database" do - # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() - - # Gets a Bank and it's atm_id. - {bank, _} = NPCHelper.bank - atm_id = List.first(bank.servers).id - - # Creates valid parameters. - payload = - %{ - "atm_id" => to_string(atm_id), - "network_id" => to_string(@internet_id), - } - - # Creates a BankCreateAccountRequest with the parameters. - request = - BankCreateAccountRequest.new(payload) - - # Checks if parameters are in correct format. - {:ok, request} = - Requestable.check_params(request, socket) - - # Checks if the given bank is a bank. - {:ok, request} = - Requestable.check_permissions(request, socket) - - # Asserts that the request is handled correctly. - assert {:ok, acc} = - Requestable.handle_request(request, socket) - - # Asserts that the BankAccount is being created on the database. - assert BankQuery.fetch_account(acc.atm_id, acc.account_number) - end - end -end From cce64311e45c59ec4955f360941459cfa4e8ff7f Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:19:05 -0300 Subject: [PATCH 25/32] Improve logout request --- .../bank/websocket/channel/requests/logout.ex | 16 +++++++ .../bank/websocket/requests/logout_test.exs | 47 ------------------- 2 files changed, 16 insertions(+), 47 deletions(-) delete mode 100644 test/universe/bank/websocket/requests/logout_test.exs diff --git a/lib/universe/bank/websocket/channel/requests/logout.ex b/lib/universe/bank/websocket/channel/requests/logout.ex index b2da3249..8339eafc 100644 --- a/lib/universe/bank/websocket/channel/requests/logout.ex +++ b/lib/universe/bank/websocket/channel/requests/logout.ex @@ -2,8 +2,13 @@ import Helix.Websocket.Request request Helix.Universe.Bank.Websocket.Requests.Logout do + alias Helix.Event alias Helix.Websocket + alias Helix.Universe.Bank.Event.Bank.Account.Logout, + as: BankAccountLogoutEvent + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + def check_params(request, _socket), do: reply_ok(request) @@ -11,10 +16,21 @@ request Helix.Universe.Bank.Websocket.Requests.Logout do do: reply_ok(request) def handle_request(request, socket) do + atm_id = socket.assigns.atm_id + account_number = socket.assigns.account_number + account = BankQuery.fetch_account(atm_id, account_number) + + entity_id = socket.assigns.gateway.entity_id + + events = + [BankAccountLogoutEvent.new(account, entity_id)] + socket |> Websocket.id() |> Helix.Endpoint.broadcast("disconnect", %{}) + Event.emit(events) + reply_ok(request) end diff --git a/test/universe/bank/websocket/requests/logout_test.exs b/test/universe/bank/websocket/requests/logout_test.exs deleted file mode 100644 index e8900752..00000000 --- a/test/universe/bank/websocket/requests/logout_test.exs +++ /dev/null @@ -1,47 +0,0 @@ -defmodule Helix.Test.Universe.Bank.Websocket.Requests.LogoutTest do - - use Helix.Test.Case.Integration - - import Phoenix.ChannelTest - - alias Helix.Test.Universe.Bank.Setup, as: BankSetup - alias Helix.Test.Channel.Helper, as: ChannelHelper - alias Helix.Test.Channel.Setup, as: ChannelSetup - - describe "bootstrap" do - test "returns expected result" do - # Setups an Account socket. - {socket, %{entity: entity, server: gateway}} = - ChannelSetup.create_socket() - - # Setups a BankAccount. - bank_acc = BankSetup.account!() - atm_id = bank_acc.atm_id - account_number = bank_acc.account_number - - # Creates topic to log in the bank channel. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Create the payload which will be used to join the bank channel. - payload = - %{ - "entity_id" => to_string(entity.entity_id), - "password" => bank_acc.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins the bank channel. - {:ok, _bootstrap, bnk_socket} = - join(socket, topic, payload) - - # Request logout - push bnk_socket, "bank.logout", %{} - - # Wait process teardown. Required - :timer.sleep(50) - - # Channel no longer exists - refute Process.alive? bnk_socket.channel_pid - end - end -end From d72dd0561f493c1ab7b5a5c32b8d9e1099d102c0 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:22:26 -0300 Subject: [PATCH 26/32] Improved bank transfer process --- lib/universe/bank/model/bank_account.ex | 0 lib/universe/bank/process/bank/transfer.ex | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 100755 lib/universe/bank/model/bank_account.ex diff --git a/lib/universe/bank/model/bank_account.ex b/lib/universe/bank/model/bank_account.ex old mode 100644 new mode 100755 diff --git a/lib/universe/bank/process/bank/transfer.ex b/lib/universe/bank/process/bank/transfer.ex index 166b125e..b87c8a46 100644 --- a/lib/universe/bank/process/bank/transfer.ex +++ b/lib/universe/bank/process/bank/transfer.ex @@ -2,15 +2,17 @@ import Helix.Process process Helix.Universe.Bank.Process.Bank.Transfer do + alias Helix.Account.Model.Account alias Helix.Universe.Bank.Model.BankTransfer - process_struct [:transfer_id, :amount] + process_struct [:transfer_id, :started_by, :amount] @process_type :wire_transfer @type t :: %__MODULE__{ transfer_id: BankTransfer.id, + started_by: Account.id, amount: BankTransfer.amount } @@ -38,6 +40,7 @@ process Helix.Universe.Bank.Process.Bank.Transfer do def new(%{transfer: transfer = %BankTransfer{}}) do %__MODULE__{ transfer_id: transfer.transfer_id, + started_by: transfer.started_by, amount: transfer.amount } end From 30b3890393edf9ce9ce5178b2af0823903583f52 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:25:24 -0300 Subject: [PATCH 27/32] Improve bank transfer request --- .../websocket/channel/requests/transfer.ex | 1 - .../channel/requests/transfer_test.exs | 202 ++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 test/universe/bank/websocket/channel/requests/transfer_test.exs diff --git a/lib/universe/bank/websocket/channel/requests/transfer.ex b/lib/universe/bank/websocket/channel/requests/transfer.ex index c1af3803..50b50296 100644 --- a/lib/universe/bank/websocket/channel/requests/transfer.ex +++ b/lib/universe/bank/websocket/channel/requests/transfer.ex @@ -49,7 +49,6 @@ request Helix.Universe.Bank.Websocket.Requests.Transfer do receiving_acc = request.params.bank_account amount = request.params.amount password = request.params.password - entity_id = socket.assigns.entity_id gateway_id = socket.assigns.gateway.server_id account_id = socket.assigns.account_id diff --git a/test/universe/bank/websocket/channel/requests/transfer_test.exs b/test/universe/bank/websocket/channel/requests/transfer_test.exs new file mode 100644 index 00000000..fb03df18 --- /dev/null +++ b/test/universe/bank/websocket/channel/requests/transfer_test.exs @@ -0,0 +1,202 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.TransferTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Account.Query.Account, as: AccountQuery + alias Helix.Websocket.Requestable + alias Helix.Universe.Bank.Websocket.Requests.Transfer, as: BankTransferRequest + + alias Helix.Test.Channel.Request.Helper, as: RequestHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Network.Helper, as: NetworkHelper + alias Helix.Test.Process.TOPHelper + alias Helix.Test.Server.Helper, as: ServerHelper + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + @internet_id NetworkHelper.internet_id() + + describe "BankTransferRequest.check_params/2" do + test "validates expected data" do + # Setups an Account socket and gets the gateway + # from the related information. + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + # Setups sending BankAccount. + sending_acc = BankSetup.account!(balance: 600) + atm_id = sending_acc.atm_id + account_number = sending_acc.account_number + + # Setups receiving BankAccount. + to_account = BankSetup.account!() + + # Gets ip for the receiving BankAccount's ATM.id. + bank_ip = ServerHelper.get_ip(to_account.atm_id) + + # Gets entity id from the socket assigns. + entity_id = socket.assigns.entity_id + + # Creates topic for connecting on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates parameters for joining the bank channel. + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => sending_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bank_socket} = + join(socket, topic, payload) + + # Creates parameters for BankTransferRequest. + params = + %{ + "to_acc" => to_account.account_number, + "to_bank_net" => to_string(@internet_id), + "to_bank_ip" => bank_ip, + "password" => to_account.password, + "amount" => 300 + } + + # Creates BankTransferRequest with parameters. + request = BankTransferRequest.new(params) + + # Asserts that parameters are in the correct format. + assert {:ok, _request} = Requestable.check_params(request, bank_socket) + end + end + + describe "BankTransferRequest.check_permissions/2" do + test "accepts when everything is OK" do + # Setups an Account socket and gets the related gateway. + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + # Gets the Entity related to the socket. + entity_id = socket.assigns.entity_id + + # Setups the sending BankAccount. + sending_acc = BankSetup.account!(balance: 600) + atm_id = sending_acc.atm_id + account_number = sending_acc.account_number + + # Setups the receiving BankAccount. + to_account = BankSetup.account!() + + # Gets the Bank's ip and sets the amount to transfer. + bank_ip = ServerHelper.get_ip(to_account.atm_id) + amount = 300 + + # Creates topic for connecting on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates parameters for joining the bank channel. + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => sending_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Gets the player's account_id from socket. + account_id = bnk_socket.assigns.account_id + + # Creates parameters for the request. + params = + %{ + bank_account: to_account.account_number, + bank_ip: bank_ip, + bank_net: @internet_id, + password: sending_acc.password, + amount: amount + } + + # Mocks the BankTransferRequest pasing parameters. + request = RequestHelper.mock_request(BankTransferRequest, params) + + # Asserts that request passes on permission test. + assert {:ok, request} = Requestable.check_permissions(request, bnk_socket) + + # Asserts that meta fields are all valid. + assert request.meta.to_account == to_account + assert request.meta.from_account == sending_acc + assert request.meta.amount == amount + assert request.meta.started_by == AccountQuery.fetch(account_id) + assert request.meta.gateway == gateway + end + end + + describe "BankTransferRequest.handle_request/2" do + test "starts the process" do + # Setups an Account socket and gets related gateway. + {socket, %{server: gateway}} = + ChannelSetup.create_socket() + + # Gets entity_id from socket. + entity_id = socket.assigns.entity_id + + # Setups sending BankAccount. + sending_acc = BankSetup.account!(balance: 600) + atm_id = sending_acc.atm_id + account_number = sending_acc.account_number + + # Setups receiving BankAccount. + to_account = BankSetup.account!() + + # Gets receiving BankAccount's ATM ip. + bank_ip = ServerHelper.get_ip(to_account.atm_id) + + # Sets the amount to be tranfered. + amount = 300 + + # Creates topic for connecting on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the parameters for joining bank channel. + payload = + %{ + "entity_id" => to_string(entity_id), + "password" => sending_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Create parameters to request. + params = + %{ + bank_account: to_account.account_number, + bank_ip: bank_ip, + bank_net: @internet_id, + password: sending_acc.password, + amount: amount + } + + # Mocks BankTranferRequest. + request = RequestHelper.mock_request(BankTransferRequest, params) + + # Checks permissions to the player do the tranfer. + {:ok, request} = Requestable.check_permissions(request, bnk_socket) + + # Asserts that request is handled correctly. + assert {:ok, request} = Requestable.handle_request(request, bnk_socket) + + # Asserts that process has been created. + assert request.meta.process + + TOPHelper.top_stop(gateway) + end + end +end From f0cf0065d9d4293384a9755dadde37ada72ba10d Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:27:17 -0300 Subject: [PATCH 28/32] Move bank tests to proper directory --- .../channel/requests/bootstrap_test.exs | 65 ++++++ .../channel/requests/change_password_test.exs | 162 ++++++++++++++ .../requests/close_account_test.exs} | 0 .../channel/requests/create_account_test.exs | 166 ++++++++++++++ .../channel/requests/logout_test.exs | 47 ++++ .../requests/reveal_password_test.exs | 0 .../bank/websocket/requests/transfer_test.exs | 202 ------------------ 7 files changed, 440 insertions(+), 202 deletions(-) create mode 100755 test/universe/bank/websocket/channel/requests/bootstrap_test.exs create mode 100644 test/universe/bank/websocket/channel/requests/change_password_test.exs rename test/universe/bank/websocket/{requests/close_account.exs => channel/requests/close_account_test.exs} (100%) create mode 100644 test/universe/bank/websocket/channel/requests/create_account_test.exs create mode 100644 test/universe/bank/websocket/channel/requests/logout_test.exs rename test/universe/bank/websocket/{ => channel}/requests/reveal_password_test.exs (100%) delete mode 100644 test/universe/bank/websocket/requests/transfer_test.exs diff --git a/test/universe/bank/websocket/channel/requests/bootstrap_test.exs b/test/universe/bank/websocket/channel/requests/bootstrap_test.exs new file mode 100755 index 00000000..04b63af3 --- /dev/null +++ b/test/universe/bank/websocket/channel/requests/bootstrap_test.exs @@ -0,0 +1,65 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.BootstrapTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Universe.Bank.Websocket.Requests.Bootstrap, + as: BankBootstrapRequest + alias Helix.Websocket.Requestable + + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + alias Helix.Test.Universe.Bank.Helper, as: BankHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + + describe "BankBootstrapRequest.handle_request/2" do + test "bootstrap" do + # Setups an Account socket. + {socket, %{entity: entity, server: gateway}} = + ChannelSetup.create_socket() + + # Setups a BankAccount. + bank_acc = BankSetup.account!(balance: BankHelper.amount) + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Creates topic to log in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Create the payload which will be used to join the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks a BankBootstrapRequest. + request = + BankBootstrapRequest.new(%{}) + + # Request Checking Parameters (which does not exists in this case). + {:ok, request} = + Requestable.check_params(request, bnk_socket) + + # Request Checking Permissions (which does nothing in this case + # because receives no parameters and is already logged in the + # Bank channel). + {:ok, request} = + Requestable.check_permissions(request, bnk_socket) + + # Asserts that request has been handled successfully. + assert {:ok, request} = + Requestable.handle_request(request, bnk_socket) + + # Asserts that bootstrap information is correct. + assert request.meta.bootstrap + assert request.meta.bootstrap.balance == bank_acc.balance + end + end +end diff --git a/test/universe/bank/websocket/channel/requests/change_password_test.exs b/test/universe/bank/websocket/channel/requests/change_password_test.exs new file mode 100644 index 00000000..3ebdab4c --- /dev/null +++ b/test/universe/bank/websocket/channel/requests/change_password_test.exs @@ -0,0 +1,162 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.ChangePasswordTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Websocket.Requestable + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Websocket.Requests.ChangePassword, + as: BankChangePasswordRequest + + alias Helix.Test.Channel.Request.Helper, as: RequestHelper + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Process.TOPHelper + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + + describe "BankChangePasswordRequest.check_permissions/2" do + test "accepts when account belongs to player" do + # Setups an Account socket and get gateway and entity + # from related information. + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Setups a BankAccount for testing. + bank_account = + BankSetup.account!(owner_id: entity.entity_id) + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Creates the topic to join on bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the payload for joining the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel with the created BankAccount. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks a BankChangePasswordRequest that does not receive any parameters. + request = + RequestHelper.mock_request(BankChangePasswordRequest, %{}) + + # Asserts that get permissions to continue with the request flow. + assert {:ok, request} = + Requestable.check_permissions(request, bnk_socket) + + # Asserts that meta contains the BankAccount which is necessary for + # changing the BankAccount's password + assert request.meta.bank_account == bank_account + end + + test "rejects when account does not belongs to player" do + # Setups an Account socket and get gateway and entity + # from related information. + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Setups a BankAccount that not belongs to entity. + bank_account = + BankSetup.account!() + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Creates a bank channel topic based on BankAccount information. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the payload for logging in the bank channel on created + # BankAccount. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks a BankChangePasswordRequest that does not receive any parameters. + request = + RequestHelper.mock_request(BankChangePasswordRequest, %{}) + + # Asserts that get no permissions to change password because the entity + # does not own the given BankAccount. + assert {:error, reason, _} = + Requestable.check_permissions(request, bnk_socket) + + # Asserts the reason for failing is "bank_account_not_belong". + assert reason.message == "bank_account_not_belongs" + end + end + + describe "BankChangePasswordRequest.handle_request/2" do + test "accepts if the password changes after process ends" do + # Setups an Account socket and gets gateway and entity from + # related information. + {socket, %{server: gateway, entity: entity}} = + ChannelSetup.create_socket() + + # Setups a BankAccount for testing. + bank_account = + BankSetup.account!(owner_id: entity.entity_id) + atm_id = bank_account.atm_id + account_number = bank_account.account_number + + # Stores the old password on a variable to compare after the process. + old_password = bank_account.password + + # Creates the topic used for logging in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Creates the payload used as parameters to log in on bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_account.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Mocks BankChangePasswordRequest that does not receive any parameters. + request = + RequestHelper.mock_request(BankChangePasswordRequest, %{}) + + # Checks the permissions for given Entity on BankAccount. + {:ok, request} = + Requestable.check_permissions(request, bnk_socket) + + # Asserts that request is handled correctly. + assert {:ok, request} = + Requestable.handle_request(request, bnk_socket) + + # Gets the process' id for forcing it's termination. + process_id = request.meta.process.process_id + + # Forces the process to complete. + TOPHelper.force_completion(process_id) + + # Fetchs the BankAccount from the database. + bank_account = + BankQuery.fetch_account( + bank_account.atm_id, + bank_account.account_number + ) + + # Refutes if the current BankAccount password is equals + # to the old_password. + refute bank_account.password == old_password + end + end +end diff --git a/test/universe/bank/websocket/requests/close_account.exs b/test/universe/bank/websocket/channel/requests/close_account_test.exs similarity index 100% rename from test/universe/bank/websocket/requests/close_account.exs rename to test/universe/bank/websocket/channel/requests/close_account_test.exs diff --git a/test/universe/bank/websocket/channel/requests/create_account_test.exs b/test/universe/bank/websocket/channel/requests/create_account_test.exs new file mode 100644 index 00000000..cb126af2 --- /dev/null +++ b/test/universe/bank/websocket/channel/requests/create_account_test.exs @@ -0,0 +1,166 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do + + use Helix.Test.Case.Integration + + alias Helix.Websocket.Requestable + alias Helix.Network.Query.Network, as: NetworkQuery + alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Websocket.Requests.CreateAccount, + as: BankCreateAccountRequest + + alias Helix.Test.Channel.Setup, as: ChannelSetup + alias Helix.Test.Server.Setup, as: ServerSetup + alias Helix.Test.Universe.NPC.Helper, as: NPCHelper + + @internet_id NetworkQuery.internet().network_id + + describe "BankCreateAccountRequest.check_params/2" do + test "accepts when receives valid information" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Gets a Bank from database. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Creates the parameters for creating a account. + payload = + %{ + "atm_id" => to_string(atm_id) + } + + # Creates a new BankCreateAccountRequest with the parameters. + request = + BankCreateAccountRequest.new(payload) + + # Asserts that given parameters are okay. + assert {:ok, request} = + Requestable.check_params(request, socket) + + # Assert the parameters after checking are right. + assert request.params.atm_id == atm_id + end + + test "rejects when receives invalid atm_id" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Creates invalid parameters. + payload = + %{ + "atm_id" => "DROP TABLE helix_dev_accounts;", + } + + # Create BankCreateAccountRequest with invalid parameters. + request = + BankCreateAccountRequest.new(payload) + + # Asserts that given parameters are rejected. + assert {:error, reason, _} = + Requestable.check_params(request, socket) + + # Asserts that the error message is "bad_request. + assert reason.message == "bad_request" + end + end + + describe "BankCreateAccountRequest.check_permissions/2" do + test "accepts when the ATM is a bank" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Gets a Bank and it's atm_id. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Create valid parameters. + payload = + %{ + "atm_id" => to_string(atm_id), + } + + # Create BankCreateAccountRequest with valid parameters. + request = + BankCreateAccountRequest.new(payload) + + # Checks if parameters are valid. + {:ok, request} = + Requestable.check_params(request, socket) + + # Asserts that Bank is a Bank. + assert {:ok, request} = + Requestable.check_permissions(request, socket) + + # Asserts that atm_id is on meta. + assert request.meta.atm_id == atm_id + end + + test "rejects when the ATM is not a server" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Setups a ordinary server. + {not_bank, _} = ServerSetup.server() + + # Create parameters passing the ordinary server's id as + # an atm_id. + payload = + %{ + "atm_id" => to_string(not_bank.server_id), + } + + # Create BankCreateAccountRequest with parameters. + request = + BankCreateAccountRequest.new(payload) + + # Asserts the parameters are in right format. + assert {:ok, request} = + Requestable.check_params(request, socket) + + # Asserts error because the ordinary server is not a bank. + assert {:error, reason, _} = + Requestable.check_permissions(request, socket) + + # Asserts the error message is "atm_not_a_bank". + assert reason.message == "atm_not_a_bank" + end + end + + describe "BankCreateAccountRequest.handle_request/2" do + test "creates BankAccount on database" do + # Setups an Account socket. + {socket, _} = ChannelSetup.create_socket() + + # Gets a Bank and it's atm_id. + {bank, _} = NPCHelper.bank + atm_id = List.first(bank.servers).id + + # Creates valid parameters. + payload = + %{ + "atm_id" => to_string(atm_id), + } + + # Creates a BankCreateAccountRequest with the parameters. + request = + BankCreateAccountRequest.new(payload) + + # Checks if parameters are in correct format. + {:ok, request} = + Requestable.check_params(request, socket) + + # Checks if the given bank is a bank. + {:ok, request} = + Requestable.check_permissions(request, socket) + + # Asserts that the request is handled correctly. + assert {:ok, request} = + Requestable.handle_request(request, socket) + + acc = request.meta.bank_account + + # Asserts that the BankAccount is being created on the database. + assert BankQuery.fetch_account(acc.atm_id, acc.account_number) + end + end +end diff --git a/test/universe/bank/websocket/channel/requests/logout_test.exs b/test/universe/bank/websocket/channel/requests/logout_test.exs new file mode 100644 index 00000000..e8900752 --- /dev/null +++ b/test/universe/bank/websocket/channel/requests/logout_test.exs @@ -0,0 +1,47 @@ +defmodule Helix.Test.Universe.Bank.Websocket.Requests.LogoutTest do + + use Helix.Test.Case.Integration + + import Phoenix.ChannelTest + + alias Helix.Test.Universe.Bank.Setup, as: BankSetup + alias Helix.Test.Channel.Helper, as: ChannelHelper + alias Helix.Test.Channel.Setup, as: ChannelSetup + + describe "bootstrap" do + test "returns expected result" do + # Setups an Account socket. + {socket, %{entity: entity, server: gateway}} = + ChannelSetup.create_socket() + + # Setups a BankAccount. + bank_acc = BankSetup.account!() + atm_id = bank_acc.atm_id + account_number = bank_acc.account_number + + # Creates topic to log in the bank channel. + topic = ChannelHelper.bank_topic_name(atm_id, account_number) + + # Create the payload which will be used to join the bank channel. + payload = + %{ + "entity_id" => to_string(entity.entity_id), + "password" => bank_acc.password, + "gateway_id" => to_string(gateway.server_id) + } + + # Joins the bank channel. + {:ok, _bootstrap, bnk_socket} = + join(socket, topic, payload) + + # Request logout + push bnk_socket, "bank.logout", %{} + + # Wait process teardown. Required + :timer.sleep(50) + + # Channel no longer exists + refute Process.alive? bnk_socket.channel_pid + end + end +end diff --git a/test/universe/bank/websocket/requests/reveal_password_test.exs b/test/universe/bank/websocket/channel/requests/reveal_password_test.exs similarity index 100% rename from test/universe/bank/websocket/requests/reveal_password_test.exs rename to test/universe/bank/websocket/channel/requests/reveal_password_test.exs diff --git a/test/universe/bank/websocket/requests/transfer_test.exs b/test/universe/bank/websocket/requests/transfer_test.exs deleted file mode 100644 index fb03df18..00000000 --- a/test/universe/bank/websocket/requests/transfer_test.exs +++ /dev/null @@ -1,202 +0,0 @@ -defmodule Helix.Test.Universe.Bank.Websocket.Requests.TransferTest do - - use Helix.Test.Case.Integration - - import Phoenix.ChannelTest - - alias Helix.Account.Query.Account, as: AccountQuery - alias Helix.Websocket.Requestable - alias Helix.Universe.Bank.Websocket.Requests.Transfer, as: BankTransferRequest - - alias Helix.Test.Channel.Request.Helper, as: RequestHelper - alias Helix.Test.Channel.Helper, as: ChannelHelper - alias Helix.Test.Channel.Setup, as: ChannelSetup - alias Helix.Test.Network.Helper, as: NetworkHelper - alias Helix.Test.Process.TOPHelper - alias Helix.Test.Server.Helper, as: ServerHelper - alias Helix.Test.Universe.Bank.Setup, as: BankSetup - - @internet_id NetworkHelper.internet_id() - - describe "BankTransferRequest.check_params/2" do - test "validates expected data" do - # Setups an Account socket and gets the gateway - # from the related information. - {socket, %{server: gateway}} = - ChannelSetup.create_socket() - - # Setups sending BankAccount. - sending_acc = BankSetup.account!(balance: 600) - atm_id = sending_acc.atm_id - account_number = sending_acc.account_number - - # Setups receiving BankAccount. - to_account = BankSetup.account!() - - # Gets ip for the receiving BankAccount's ATM.id. - bank_ip = ServerHelper.get_ip(to_account.atm_id) - - # Gets entity id from the socket assigns. - entity_id = socket.assigns.entity_id - - # Creates topic for connecting on bank channel. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Creates parameters for joining the bank channel. - payload = - %{ - "entity_id" => to_string(entity_id), - "password" => sending_acc.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins the bank channel. - {:ok, _bootstrap, bank_socket} = - join(socket, topic, payload) - - # Creates parameters for BankTransferRequest. - params = - %{ - "to_acc" => to_account.account_number, - "to_bank_net" => to_string(@internet_id), - "to_bank_ip" => bank_ip, - "password" => to_account.password, - "amount" => 300 - } - - # Creates BankTransferRequest with parameters. - request = BankTransferRequest.new(params) - - # Asserts that parameters are in the correct format. - assert {:ok, _request} = Requestable.check_params(request, bank_socket) - end - end - - describe "BankTransferRequest.check_permissions/2" do - test "accepts when everything is OK" do - # Setups an Account socket and gets the related gateway. - {socket, %{server: gateway}} = - ChannelSetup.create_socket() - - # Gets the Entity related to the socket. - entity_id = socket.assigns.entity_id - - # Setups the sending BankAccount. - sending_acc = BankSetup.account!(balance: 600) - atm_id = sending_acc.atm_id - account_number = sending_acc.account_number - - # Setups the receiving BankAccount. - to_account = BankSetup.account!() - - # Gets the Bank's ip and sets the amount to transfer. - bank_ip = ServerHelper.get_ip(to_account.atm_id) - amount = 300 - - # Creates topic for connecting on bank channel. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Creates parameters for joining the bank channel. - payload = - %{ - "entity_id" => to_string(entity_id), - "password" => sending_acc.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins bank channel. - {:ok, _bootstrap, bnk_socket} = - join(socket, topic, payload) - - # Gets the player's account_id from socket. - account_id = bnk_socket.assigns.account_id - - # Creates parameters for the request. - params = - %{ - bank_account: to_account.account_number, - bank_ip: bank_ip, - bank_net: @internet_id, - password: sending_acc.password, - amount: amount - } - - # Mocks the BankTransferRequest pasing parameters. - request = RequestHelper.mock_request(BankTransferRequest, params) - - # Asserts that request passes on permission test. - assert {:ok, request} = Requestable.check_permissions(request, bnk_socket) - - # Asserts that meta fields are all valid. - assert request.meta.to_account == to_account - assert request.meta.from_account == sending_acc - assert request.meta.amount == amount - assert request.meta.started_by == AccountQuery.fetch(account_id) - assert request.meta.gateway == gateway - end - end - - describe "BankTransferRequest.handle_request/2" do - test "starts the process" do - # Setups an Account socket and gets related gateway. - {socket, %{server: gateway}} = - ChannelSetup.create_socket() - - # Gets entity_id from socket. - entity_id = socket.assigns.entity_id - - # Setups sending BankAccount. - sending_acc = BankSetup.account!(balance: 600) - atm_id = sending_acc.atm_id - account_number = sending_acc.account_number - - # Setups receiving BankAccount. - to_account = BankSetup.account!() - - # Gets receiving BankAccount's ATM ip. - bank_ip = ServerHelper.get_ip(to_account.atm_id) - - # Sets the amount to be tranfered. - amount = 300 - - # Creates topic for connecting on bank channel. - topic = ChannelHelper.bank_topic_name(atm_id, account_number) - - # Creates the parameters for joining bank channel. - payload = - %{ - "entity_id" => to_string(entity_id), - "password" => sending_acc.password, - "gateway_id" => to_string(gateway.server_id) - } - - # Joins bank channel. - {:ok, _bootstrap, bnk_socket} = - join(socket, topic, payload) - - # Create parameters to request. - params = - %{ - bank_account: to_account.account_number, - bank_ip: bank_ip, - bank_net: @internet_id, - password: sending_acc.password, - amount: amount - } - - # Mocks BankTranferRequest. - request = RequestHelper.mock_request(BankTransferRequest, params) - - # Checks permissions to the player do the tranfer. - {:ok, request} = Requestable.check_permissions(request, bnk_socket) - - # Asserts that request is handled correctly. - assert {:ok, request} = Requestable.handle_request(request, bnk_socket) - - # Asserts that process has been created. - assert request.meta.process - - TOPHelper.top_stop(gateway) - end - end -end From 1a6bd56091c41a3b9ecd4a3c97c3a1328ff828c5 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 31 May 2018 00:29:40 -0300 Subject: [PATCH 29/32] Add information on whom_to_notify --- lib/event/notificable/notificable.ex | 4 +++- lib/event/notification_handler.ex | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/event/notificable/notificable.ex b/lib/event/notificable/notificable.ex index aa3424b4..b480bae9 100644 --- a/lib/event/notificable/notificable.ex +++ b/lib/event/notificable/notificable.ex @@ -63,7 +63,7 @@ defprotocol Helix.Event.Notificable do action, also receive special treatment. Hence: - third AT attack_target - Third party on victim's server - - third AT attack_source - Third party on attacker's server. + - third AT attack_source - Third party on attacker's server. - third AT random_server - Third party on unrelated (random) server. Last but not least, in some cases we do not have an attacker/victim @@ -98,6 +98,8 @@ defprotocol Helix.Event.Notificable do alias Helix.Account.Model.Account alias Helix.Server.Model.Server + alias Helix.Universe.Bank.Model.ATM + alias Helix.Universe.Bank.Model.BankAccount @type whom_to_notify :: %{ diff --git a/lib/event/notification_handler.ex b/lib/event/notification_handler.ex index 38cf2b0b..da242d25 100644 --- a/lib/event/notification_handler.ex +++ b/lib/event/notification_handler.ex @@ -8,6 +8,8 @@ defmodule Helix.Event.NotificationHandler do alias Helix.Entity.Model.Entity alias Helix.Server.Model.Server alias Helix.Server.State.Websocket.Channel, as: ServerWebsocketChannelState + alias Helix.Universe.Bank.Model.ATM + alias Helix.Universe.Bank.Model.BankAccount @type channel_account_id :: Account.id | Entity.id From b70c5a16aecd3b5c36945e0b79ee66d03bab0b77 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 7 Jun 2018 12:10:20 -0300 Subject: [PATCH 30/32] Add AccountCreate process --- lib/event/dispatcher.ex | 6 ++ lib/process/model/process.ex | 1 + lib/universe/bank/action/flow/bank_account.ex | 27 +++--- lib/universe/bank/event/account_create.ex | 39 +++++++++ .../bank/event/handler/bank/account.ex | 14 ++++ .../process/bank/account/create_account.ex | 84 +++++++++++++++++++ .../channel/requests/create_account.ex | 10 ++- 7 files changed, 162 insertions(+), 19 deletions(-) create mode 100644 lib/universe/bank/event/account_create.ex create mode 100644 lib/universe/bank/process/bank/account/create_account.ex diff --git a/lib/event/dispatcher.ex b/lib/event/dispatcher.ex index 4b51b5ae..37ae7c81 100644 --- a/lib/event/dispatcher.ex +++ b/lib/event/dispatcher.ex @@ -278,6 +278,7 @@ defmodule Helix.Event.Dispatcher do event BankEvent.Bank.Account.Token.Acquired event BankEvent.Bank.Transfer.Processed event BankEvent.Bank.Transfer.Aborted + event BankEvent.AccountCreate.Processed event BankEvent.RevealPassword.Processed event BankEvent.ChangePassword.Processed @@ -310,6 +311,10 @@ defmodule Helix.Event.Dispatcher do BankHandler.Bank.Account, :password_change_processed + event BankEvent.AccountCreate.Processed, + BankHandler.Bank.Account, + :account_create_processed + event BankEvent.Bank.Account.Password.Revealed, EntityHandler.Database, :bank_password_revealed @@ -322,4 +327,5 @@ defmodule Helix.Event.Dispatcher do EntityHandler.Database, :bank_account_login + end diff --git a/lib/process/model/process.ex b/lib/process/model/process.ex index 590ad95f..b64e1b00 100644 --- a/lib/process/model/process.ex +++ b/lib/process/model/process.ex @@ -79,6 +79,7 @@ defmodule Helix.Process.Model.Process do | :install_virus | :bank_change_password | :bank_reveal_password + | :bank_account_create @typedoc """ List of signals a process may receive during its lifetime. diff --git a/lib/universe/bank/action/flow/bank_account.ex b/lib/universe/bank/action/flow/bank_account.ex index 264b0dab..f0cae781 100644 --- a/lib/universe/bank/action/flow/bank_account.ex +++ b/lib/universe/bank/action/flow/bank_account.ex @@ -53,24 +53,21 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do BankAccountRevealPasswordProcess.execute(gateway, atm, params, meta, relay) end - @spec open(Account.id, ATM.id) :: - {:ok, BankAccount.t} - | {:error, :internal} + @spec open(Server.t, Account.id, Server.t, relay) :: + {:ok, Process.t} + | BankAccountAccountChangeProcess.executable_error @doc """ Opens a new BankAccount to given Account.id """ - def open(account_id, atm_id) do - flowing do - with \ - {:ok, bank_acc, events} <- BankAction.open_account(account_id, atm_id), - on_success(fn -> Event.emit(events) end) - do - {:ok, bank_acc} - else - _ -> - {:error, :internal} - end - end + def open(gateway, account_id, atm, relay) do + entity_id = Entity.ID.cast!(to_string(account_id.id)) + + meta = %{ + src_atm_id: atm.server_id, + source_entity_id: entity_id + } + + BankAccountAccountCreateProcess.execute(gateway, atm, %{}, meta, relay) end @spec close(BankAccount.t) :: diff --git a/lib/universe/bank/event/account_create.ex b/lib/universe/bank/event/account_create.ex new file mode 100644 index 00000000..bbcc2684 --- /dev/null +++ b/lib/universe/bank/event/account_create.ex @@ -0,0 +1,39 @@ +defmodule Helix.Universe.Bank.Event.AccountCreate do + + import Helix.Event + + event Processed do + + alias Helix.Entity.Model.Entity + alias Helix.Process.Model.Process + alias Helix.Universe.Bank.Model.ATM + alias Helix.Universe.Bank.Process.Bank.Account.AccountCreate, + as: AccountCreateProcess + + @type t :: %__MODULE__{ + atm_id: ATM.id, + requester: Entity.id + } + + event_struct [:atm_id, :requester] + + @spec new(ATM.id, Entity.idt) :: t + + def new(atm_id, entity = %Entity{}) do + %__MODULE__{ + atm_id: atm_id, + requester: entity.id + } + end + + def new(atm_id, entity_id = %Entity.ID{}), + do: %__MODULE__{atm_id: atm_id, requester: entity_id} + + @spec new(Process.t, AccountCreateProcess.t) :: t + def new(process = %Process{}, data = %AccountCreateProcess{}) do + %__MODULE__{ + atm_id: process.src_atm_id, + requester: process.source_entity_id + } + end + end diff --git a/lib/universe/bank/event/handler/bank/account.ex b/lib/universe/bank/event/handler/bank/account.ex index 46645510..3bb23531 100644 --- a/lib/universe/bank/event/handler/bank/account.ex +++ b/lib/universe/bank/event/handler/bank/account.ex @@ -11,6 +11,8 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do as: BankAccountRemovedEvent alias Helix.Universe.Bank.Event.Bank.Account.Updated, as: BankAccountUpdatedEvent + alias Helix.Universe.Bank.Event.AccountCreate.Processed, + as: AccountCreateProcessedEvent alias Helix.Universe.Bank.Event.RevealPassword.Processed, as: RevealPasswordProcessedEvent alias Helix.Universe.Bank.Event.ChangePassword.Processed, @@ -18,6 +20,18 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do alias Helix.Universe.Bank.Event.Bank.Account.Password.Changed, as: BankPasswordChangedEvent + def account_create_processed(event = %AccountCreateProcessedEvent{}) do + flowing do + with \ + {:ok, _bank_account, events} <- + BankAction.open_account(event.requester, event.atm), + on_success(fn -> Event.emit(events, from: event) end) + do + :ok + end + end + end + @doc """ Handles the conclusion of a `PasswordRevealProcess`, described at `BankAccountFlow`. Note that actually *displaying* the password to the user diff --git a/lib/universe/bank/process/bank/account/create_account.ex b/lib/universe/bank/process/bank/account/create_account.ex new file mode 100644 index 00000000..20e273da --- /dev/null +++ b/lib/universe/bank/process/bank/account/create_account.ex @@ -0,0 +1,84 @@ +import Helix.Process + +process Helix.Universe.Bank.Process.Bank.Account.AccountCreate do + + alias Helix.Universe.Bank.Model.ATM + alias Helix.Universe.Bank.Model.BankAccount + + process_struct [:atm_id] + + @process_type :bank_create_account + + @type t :: %__MODULE__{} + + @type creation_params :: %{} + + @type objective :: %{cpu: resource_usage} + + @type resource :: %{ + objective: objective, + static: map, + l_dynamic: [:cpu], + r_dynamic: [] + } + + @type resources_params :: %{} + + @spec new(creation_params) :: t + def new do + %__MODULE__{} + end + + @spec resources(resource_params) :: resources + def resources(params = %{}), + do: get_resources params + + processable do + + alias Helix.Universe.Bank.Event.Bank.Account.AccountCreate.Processed, + as: AccountCreateProcess + + on_completion(process, data) do + event = AccountCreateProcess.new(process, data) + + {:delete, [event]} + end + end + + resourceable do + + alias Helix.Universe.Bank.Process.Bank.Account.AccountCreate, + as: AccountCreateProcess + + @type params :: AccountCreateProcess.resources_params + @type factors :: term + + # TODO proper balance + get_factors(%{}) do end + + cpu(_) do + 1 + end + + dynamic do + [:cpu] + end + + executable do + + alias Helix.Universe.Bank.Process.Bank.Account.AccountCreate, + as: AccountCreateProcess + + @type params :: AccountCreateProcess.creation_params + + @type meta :: + ${ + optional(atom) => term + } + + resources(_, _, %{}) do + %{} + end + end + end +end diff --git a/lib/universe/bank/websocket/channel/requests/create_account.ex b/lib/universe/bank/websocket/channel/requests/create_account.ex index a0338262..c3103f0b 100644 --- a/lib/universe/bank/websocket/channel/requests/create_account.ex +++ b/lib/universe/bank/websocket/channel/requests/create_account.ex @@ -55,11 +55,13 @@ request Helix.Universe.Bank.Websocket.Requests.CreateAccount do def handle_request(request, socket) do atm_id = request.meta.atm_id account_id = socket.assigns.account_id + gateway = ServerQuery.fetch(socket.assigns.gateway.server_id) + relay = request.relay - bank_account = BankPublic.open_account(account_id, atm_id) - case bank_account do - {:ok, bank_account} -> - update_meta(request, %{bank_account: bank_account}, reply: true) + process = BankPublic.open_account(gateway, account_id, atm, relay) + case process do + {:ok, process} -> + update_meta(request, %{process: process}, reply: true) {:error, reason} -> reply_error(request, reason) end From 1a2e58e3b7c4882d5c3f139741d30fde18d74b90 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Thu, 7 Jun 2018 12:58:18 -0300 Subject: [PATCH 31/32] Add AccountClose process --- lib/event/dispatcher.ex | 5 +- lib/universe/bank/action/flow/bank_account.ex | 33 ++++---- lib/universe/bank/event/account_close.ex | 36 ++++++++ .../bank/event/handler/bank/account.ex | 13 +++ .../process/bank/account/close_account.ex | 82 +++++++++++++++++++ .../channel/requests/close_account.ex | 11 ++- 6 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 lib/universe/bank/event/account_close.ex create mode 100644 lib/universe/bank/process/bank/account/close_account.ex diff --git a/lib/event/dispatcher.ex b/lib/event/dispatcher.ex index 37ae7c81..0d24198f 100644 --- a/lib/event/dispatcher.ex +++ b/lib/event/dispatcher.ex @@ -315,6 +315,10 @@ defmodule Helix.Event.Dispatcher do BankHandler.Bank.Account, :account_create_processed + event BankEvent.AccountClose.Processed, + BankHandler.Bank.Account, + :account_close_processed + event BankEvent.Bank.Account.Password.Revealed, EntityHandler.Database, :bank_password_revealed @@ -327,5 +331,4 @@ defmodule Helix.Event.Dispatcher do EntityHandler.Database, :bank_account_login - end diff --git a/lib/universe/bank/action/flow/bank_account.ex b/lib/universe/bank/action/flow/bank_account.ex index f0cae781..9fd2c273 100644 --- a/lib/universe/bank/action/flow/bank_account.ex +++ b/lib/universe/bank/action/flow/bank_account.ex @@ -13,6 +13,8 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do alias Helix.Universe.Bank.Model.BankToken alias Helix.Universe.Bank.Query.Bank, as: BankQuery + alias Helix.Universe.Bank.Process.Bank.Account.AccountCreate, + as: BankAccountCreateProcess alias Helix.Universe.Bank.Process.Bank.Account.RevealPassword, as: BankAccountRevealPasswordProcess alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, @@ -55,9 +57,9 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do @spec open(Server.t, Account.id, Server.t, relay) :: {:ok, Process.t} - | BankAccountAccountChangeProcess.executable_error + | BankAccountChangeProcess.executable_error @doc """ - Opens a new BankAccount to given Account.id + Starts the `bank_account_create` process. """ def open(gateway, account_id, atm, relay) do entity_id = Entity.ID.cast!(to_string(account_id.id)) @@ -67,23 +69,22 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do source_entity_id: entity_id } - BankAccountAccountCreateProcess.execute(gateway, atm, %{}, meta, relay) + BankAccountCreateProcess.execute(gateway, atm, %{}, meta, relay) end @spec close(BankAccount.t) :: - :ok - | {:error, {:bank_account, :not_found}} - | {:error, {:bank_account, :not_empty}} - def close(bank_account) do - flowing do - with \ - true <- not is_nil(bank_account), - {:ok, events} <- BankAction.close_account(bank_account), - on_success(fn -> Event.emit(events) end) - do - :ok - end - end + {:ok, Process.t} + | BankAccountCloseProcess.executable_error + @doc """ + Starts the `bank_account_close` process. + """ + def close(gateway, bank_account, atm, relay) do + meta = %{ + src_atm_id: bank_account.server_id, + src_account_number: bank_account.account_number + } + + BankAccountCloseProcess.execute(gateway, atm, %{}, meta, relay) end @spec change_password(BankAccount.t, Server.t, Server.t, relay) :: diff --git a/lib/universe/bank/event/account_close.ex b/lib/universe/bank/event/account_close.ex new file mode 100644 index 00000000..f1726d6c --- /dev/null +++ b/lib/universe/bank/event/account_close.ex @@ -0,0 +1,36 @@ +defmodule Helix.Universe.Bank.Event.AccountClose do + + import Helix.Event + + event Processed do + + alias Helix.Process.Model.Process + alias Helix.Universe.Bank.Model.ATM + alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Universe.Bank.Process.Bank.Account.AccountClose, + as: AccountCloseProcess + + @type t :: %__MODULE__{ + atm_id: ATM.id, + account_number: BankAccount.account + } + + event_struct [:atm_id, :account_number] + + @spec new(ATM.id, BankAccount.account) :: t + + def new(atm_id, account_number) do + %__MODULE__{ + atm_id: atm_id, + account_number: account_number + } + end + + @spec new(Process.t, AccountCloseProcess.t) :: t + def new(process = %Process{}, data = %AccountCloseProcess{}) do + %__MODULE__{ + atm_id: process.src_atm_id, + account_number: process.src_acc_number + } + end + end diff --git a/lib/universe/bank/event/handler/bank/account.ex b/lib/universe/bank/event/handler/bank/account.ex index 3bb23531..fc1618c6 100644 --- a/lib/universe/bank/event/handler/bank/account.ex +++ b/lib/universe/bank/event/handler/bank/account.ex @@ -32,6 +32,19 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do end end + def account_close_processed(event = %AccountCloseProcessedEvent{}) do + flowing + with \ + bank_account = BankQuery.fetch_account(event.atm_id, event.account_number) + true <- not is_nil(bank_account), + {:ok, events} <- BankAction.close_account(bank_account), + on_success(fn -> Event.emit(events) end) + do + :ok + end + end + end + @doc """ Handles the conclusion of a `PasswordRevealProcess`, described at `BankAccountFlow`. Note that actually *displaying* the password to the user diff --git a/lib/universe/bank/process/bank/account/close_account.ex b/lib/universe/bank/process/bank/account/close_account.ex new file mode 100644 index 00000000..b7b17e59 --- /dev/null +++ b/lib/universe/bank/process/bank/account/close_account.ex @@ -0,0 +1,82 @@ +import Helix.Process + +process Helix.Universe.Bank.Process.Bank.Account.AccountClose do + + alias Helix.Universe.Bank.Model.ATM + alias Helix.Universe.Bank.Model.BankAccount + + process_struct [:account] + + @process_type :bank_close_account + + @type t :: %__MODULE__{} + + @type creation_params :: %{} + + @type objective :: %{cpu: resource_usage} + + @type resource :: %{ + objective: objective, + static: map, + l_dynamic: [:cpu], + r_dynamic: [] + } + + @type resources_params :: %{} + + @spec new(creation_params) :: t + def new(%{}) do + %__MODULE__{} + end + + @spec resources(resources_params) :: resources + def resources(params = %{}) + do: get_resources params + + processable do + + alias Helix.Universe.Bank.Event.AccountClose.Processed, + as: AccountCloseProcessedEvent + + on_completion(process, data) do + event = AccountCloseProcessedEvent.new(process, data) + + {:delete, [event]} + end + + resourceable do + alias Heli.Universe.Bank.Process.Bank.Account.AccountClose, + as: AccountCloseProcess + + @type params :: AccountCloseProcess.resources_params + @type factors :: term + + # TODO proper balance + get_factors(%{}) do end + + cpu(_) do + + end + + dynamic do + [:cpu] + end + end + + executable do + + alias Helix.Universe.Bank.Process.Bank.Account.AccountClose, + as: AccountCloseProcess + + @type params :: AccountCloseProcess.creation_params + + @type meta :: + %{ + optional(atom) => term + } + + resources(_, _, %{}, _) do + %{} + end + end + end diff --git a/lib/universe/bank/websocket/channel/requests/close_account.ex b/lib/universe/bank/websocket/channel/requests/close_account.ex index fefc8688..c3e69194 100644 --- a/lib/universe/bank/websocket/channel/requests/close_account.ex +++ b/lib/universe/bank/websocket/channel/requests/close_account.ex @@ -7,6 +7,7 @@ request Helix.Universe.Bank.Websocket.Requests.CloseAccount do It Returs :ok or :error """ + alias Helix.Server.Query.Server, as: ServerQuery alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer alias Helix.Universe.Bank.Public.Bank, as: BankPublic alias Helix.Universe.Bank.Query.Bank, as: BankQuery @@ -36,11 +37,13 @@ request Helix.Universe.Bank.Websocket.Requests.CloseAccount do atm_id = socket.assigns.atm_id account_number = socket.assigns.account_number bank_account = BankQuery.fetch_account(atm_id, account_number) - close_account = BankPublic.close_account(bank_account) + atm = ServerQuery.fetch(atm_id) + relay = request.relay + process = BankPublic.close_account(gateway, bank_account, atm, relay) - case close_account do - :ok -> - reply_ok(request) + case process do + {:ok, process} -> + update_meta(request, %{process: process}, reply: true) {:error, reason} -> reply_error(request, reason) end From e6d301dab9af0c46013c27d7b57b2f5550390be7 Mon Sep 17 00:00:00 2001 From: Marcelo Amancio de Lima Santos Date: Mon, 11 Jun 2018 13:05:37 -0300 Subject: [PATCH 32/32] update BankAccount open and close tests --- lib/core/validator/validator.ex | 5 +- lib/event/notificable/notificable.ex | 3 +- lib/event/notification_handler.ex | 20 +++++ lib/software/model/file.ex | 4 +- lib/universe/bank/action/bank.ex | 23 +++--- lib/universe/bank/action/flow/bank_account.ex | 32 +++++--- lib/universe/bank/event/account_close.ex | 19 +++-- lib/universe/bank/event/account_create.ex | 3 +- lib/universe/bank/event/bank/account.ex | 60 +++++++++++--- .../bank/event/bank/account/password.ex | 64 +++++++++++---- lib/universe/bank/event/bank/account/token.ex | 19 +++++ .../bank/event/handler/bank/account.ex | 19 +++-- lib/universe/bank/henforcer/bank.ex | 17 +--- .../process/bank/account/close_account.ex | 82 ++++++++++++------- .../process/bank/account/create_account.ex | 47 +++++++---- .../process/bank/account/password_change.ex | 6 ++ .../process/bank/account/password_reveal.ex | 6 ++ lib/universe/bank/process/bank/transfer.ex | 6 ++ lib/universe/bank/public/bank.ex | 15 ++-- .../bank/websocket/channel/bank/join.ex | 6 +- .../channel/requests/close_account.ex | 1 + .../channel/requests/create_account.ex | 18 ++-- .../websocket/channel/requests/transfer.ex | 6 +- lib/universe/npc/internal/web.ex | 10 ++- test/support/channel/setup.ex | 10 +-- test/support/event/setup/bank.ex | 6 +- test/support/software/helper.ex | 4 +- test/support/universe/bank/setup.ex | 2 +- .../bank/action/flow/bank_account_test.exs | 44 +++++++--- test/universe/bank/public/bank_test.exs | 40 +-------- .../channel/requests/close_account_test.exs | 9 -- .../channel/requests/create_account_test.exs | 44 +++++++--- .../channel/requests/logout_test.exs | 4 +- 33 files changed, 416 insertions(+), 238 deletions(-) diff --git a/lib/core/validator/validator.ex b/lib/core/validator/validator.ex index 4fd13b7b..f59f2918 100644 --- a/lib/core/validator/validator.ex +++ b/lib/core/validator/validator.ex @@ -9,7 +9,7 @@ defmodule Helix.Core.Validator do @regex_hostname ~r/^[a-zA-Z0-9-_.@#]{1,20}$/ - @regex_token ~r/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/ + @uuid ~r/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/ @spec validate_input(input :: String.t, input_type, opts :: term) :: {:ok, validated_input :: String.t} @@ -55,7 +55,7 @@ defmodule Helix.Core.Validator do defp validate_token(v) when not is_binary(v), do: :error defp validate_token(v) do - if Regex.match?(@regex_token, v) do + if Regex.match?(@uuid, v) do {:ok, v} else :error @@ -73,4 +73,5 @@ defmodule Helix.Core.Validator do defp validate_money(v), do: validate_hostname(v) + end diff --git a/lib/event/notificable/notificable.ex b/lib/event/notificable/notificable.ex index b480bae9..4254cbd4 100644 --- a/lib/event/notificable/notificable.ex +++ b/lib/event/notificable/notificable.ex @@ -104,7 +104,8 @@ defprotocol Helix.Event.Notificable do @type whom_to_notify :: %{ optional(:server) => [Server.id], - optional(:account) => [Account.id] + optional(:account) => [Account.id], + optional(:bank_acc) => [term] } @spec generate_payload(event :: struct, Socket.t) :: diff --git a/lib/event/notification_handler.ex b/lib/event/notification_handler.ex index da242d25..119ff9e3 100644 --- a/lib/event/notification_handler.ex +++ b/lib/event/notification_handler.ex @@ -12,6 +12,7 @@ defmodule Helix.Event.NotificationHandler do alias Helix.Universe.Bank.Model.BankAccount @type channel_account_id :: Account.id | Entity.id + @type channel_bank_id :: {ATM.id, BankAccount.account} @doc """ Handler responsible for guiding the event through the Notificable flow. It @@ -36,6 +37,18 @@ defmodule Helix.Event.NotificationHandler do @spec channel_mapper(Notificable.whom_to_notify) :: channels :: [String.t] defp channel_mapper(whom_to_notify, acc \\ []) + defp channel_mapper(notify = %{bank_acc: bank_accs}, acc) do + acc = + bank_accs + |> Utils.ensure_list() + |> Enum.uniq() + |> get_bank_channels() + |> List.flatten() + |> Kernel.++(acc) + + channel_mapper(Map.delete(notify, :bank_acc), acc) + end + defp channel_mapper(notify = %{server: servers}, acc) do acc = servers @@ -96,6 +109,13 @@ defmodule Helix.Event.NotificationHandler do defp get_account_channels(account_id), do: ["account:" <> to_string(account_id)] + @spec get_bank_channels([channel_bank_id] | channel_bank_id) :: + channels :: [String.t] + defp get_bank_channels(bank_accs) when is_list(bank_accs), + do: Enum.map(bank_accs, &get_bank_channels/1) + defp get_bank_channels({atm_id, account_number}), + do: ["bank:" <> to_string(account_number) <> "@" <> to_string(atm_id)] + defp concat(a, b), do: a <> to_string(b) end diff --git a/lib/software/model/file.ex b/lib/software/model/file.ex index 16af7df7..176f0878 100644 --- a/lib/software/model/file.ex +++ b/lib/software/model/file.ex @@ -214,8 +214,8 @@ defmodule Helix.Software.Model.File do path_size = (byte_size(path) - 1) * 8 case path do - <> <> "/" -> - <> + <> <> "/" -> + <> path -> path end diff --git a/lib/universe/bank/action/bank.ex b/lib/universe/bank/action/bank.ex index 3a5c1107..11f0c06a 100644 --- a/lib/universe/bank/action/bank.ex +++ b/lib/universe/bank/action/bank.ex @@ -105,25 +105,26 @@ defmodule Helix.Universe.Bank.Action.Bank do to: BankTransferInternal, as: :abort - @spec open_account(Account.idt, ATM.id) :: + @spec open_account(Account.id, ATM.id) :: {:ok, BankAccount.t, [BankAccountUpdatedEvent.t]} - | {:error, Ecto.Changeset.t} + | {:error, :internal} @doc """ Opens a bank account. """ - def open_account(owner, atm) do - bank = - atm + def open_account(owner, atm_id) do + bank_id = + atm_id |> EntityQuery.fetch_by_server() |> Map.get(:entity_id) |> NPCQuery.fetch() + |> Map.get(:npc_id) - case BankAccountInternal.create(owner, atm, bank) do + case BankAccountInternal.create(owner, atm_id, bank_id) do {:ok, bank_acc} -> {:ok, bank_acc, [BankAccountUpdatedEvent.new(bank_acc, :created)]} - error -> - error + {:error, _} -> + {:error, :internal} end end @@ -225,12 +226,12 @@ defmodule Helix.Universe.Bank.Action.Bank do end end - @spec change_password(BankAccount.t, Entity.id) :: + @spec change_password(BankAccount.t) :: {:ok, BankAccount.t, [BankAccountPasswordChangedEvent.t]} | {:error, :internal} - def change_password(account, changed_by) do + def change_password(account) do with {:ok, account} <- update_password(account) do - event = BankAccountPasswordChangedEvent.new(account, changed_by) + event = BankAccountPasswordChangedEvent.new(account) {:ok, account, [event]} else diff --git a/lib/universe/bank/action/flow/bank_account.ex b/lib/universe/bank/action/flow/bank_account.ex index 9fd2c273..01b3947c 100644 --- a/lib/universe/bank/action/flow/bank_account.ex +++ b/lib/universe/bank/action/flow/bank_account.ex @@ -3,6 +3,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do import HELF.Flow alias Helix.Event + alias Helix.Entity.Model.Entity alias Helix.Entity.Query.Entity, as: EntityQuery alias Helix.Network.Action.Flow.Tunnel, as: TunnelFlow alias Helix.Network.Query.Network, as: NetworkQuery @@ -15,6 +16,8 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do alias Helix.Universe.Bank.Process.Bank.Account.AccountCreate, as: BankAccountCreateProcess + alias Helix.Universe.Bank.Process.Bank.Account.AccountClose, + as: BankAccountCloseProcess alias Helix.Universe.Bank.Process.Bank.Account.RevealPassword, as: BankAccountRevealPasswordProcess alias Helix.Universe.Bank.Process.Bank.Account.ChangePassword, @@ -62,17 +65,19 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do Starts the `bank_account_create` process. """ def open(gateway, account_id, atm, relay) do - entity_id = Entity.ID.cast!(to_string(account_id.id)) + entity_id = Entity.ID.cast!(to_string(account_id)) + atm_id = atm.server_id meta = %{ - src_atm_id: atm.server_id, + network_id: NetworkQuery.internet().network_id, + bounce: nil, source_entity_id: entity_id } - BankAccountCreateProcess.execute(gateway, atm, %{}, meta, relay) + BankAccountCreateProcess.execute(gateway, atm, %{atm_id: atm_id}, meta, relay) end - @spec close(BankAccount.t) :: + @spec close(Server.t, BankAccount.t, Server.t, relay) :: {:ok, Process.t} | BankAccountCloseProcess.executable_error @doc """ @@ -80,11 +85,16 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do """ def close(gateway, bank_account, atm, relay) do meta = %{ - src_atm_id: bank_account.server_id, - src_account_number: bank_account.account_number + network_id: NetworkQuery.internet().network_id, + bounce: nil + } + + params = %{ + atm_id: bank_account.atm_id, + account_number: bank_account.account_number } - BankAccountCloseProcess.execute(gateway, atm, %{}, meta, relay) + BankAccountCloseProcess.execute(gateway, atm, params, meta, relay) end @spec change_password(BankAccount.t, Server.t, Server.t, relay) :: @@ -118,7 +128,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do Emits: BankAccountLoginEvent, (ConnectionStartedEvent) """ - def login_password(atm_id, account_number, gateway_id, bounce_id, password) do + def login_password(atm_id, account_number, gateway_id, bounce_id, password, relay) do acc = BankQuery.fetch_account(atm_id, account_number) entity = EntityQuery.fetch_by_server(gateway_id) @@ -137,7 +147,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do with \ true <- not is_nil(acc), {:ok, _, events} <- BankAction.login_password(acc, password, entity), - on_success(fn -> Event.emit(events) end), + on_success(fn -> Event.emit(events, from: relay) end), {:ok, tunnel, connection} <- start_connection.() do @@ -153,7 +163,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do Emits: BankAccountLoginEvent, (ConnectionStartedEvent) """ - def login_token(atm_id, account_number, gateway_id, bounce_id, token) do + def login_token(atm_id, account_number, gateway_id, bounce_id, token, relay) do acc = BankQuery.fetch_account(atm_id, account_number) entity = EntityQuery.fetch_by_server(gateway_id) @@ -172,7 +182,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccount do with \ true <- not is_nil(acc), {:ok, _, events} <- BankAction.login_token(acc, token, entity), - on_success(fn -> Event.emit(events) end), + on_success(fn -> Event.emit(events, from: relay) end), {:ok, tunnel, connection} <- start_connection.() do diff --git a/lib/universe/bank/event/account_close.ex b/lib/universe/bank/event/account_close.ex index f1726d6c..403c1566 100644 --- a/lib/universe/bank/event/account_close.ex +++ b/lib/universe/bank/event/account_close.ex @@ -17,20 +17,21 @@ defmodule Helix.Universe.Bank.Event.AccountClose do event_struct [:atm_id, :account_number] - @spec new(ATM.id, BankAccount.account) :: t - - def new(atm_id, account_number) do + @spec new(Process.t, AccountCloseProcess.t) :: t + def new(process = %Process{}, data = %AccountCloseProcess{}) do %__MODULE__{ - atm_id: atm_id, - account_number: account_number + atm_id: data.atm_id, + account_number: data.account_number } end - @spec new(Process.t, AccountCloseProcess.t) :: t - def new(process = %Process{}, data = %AccountCloseProcess{}) do + @spec new(ATM.id, BankAccount.account):: t + + def new(atm_id, account_number) do %__MODULE__{ - atm_id: process.src_atm_id, - account_number: process.src_acc_number + atm_id: atm_id, + account_number: account_number } end end +end diff --git a/lib/universe/bank/event/account_create.ex b/lib/universe/bank/event/account_create.ex index bbcc2684..1c929173 100644 --- a/lib/universe/bank/event/account_create.ex +++ b/lib/universe/bank/event/account_create.ex @@ -32,8 +32,9 @@ defmodule Helix.Universe.Bank.Event.AccountCreate do @spec new(Process.t, AccountCreateProcess.t) :: t def new(process = %Process{}, data = %AccountCreateProcess{}) do %__MODULE__{ - atm_id: process.src_atm_id, + atm_id: process.data.atm_id, requester: process.source_entity_id } end end +end diff --git a/lib/universe/bank/event/bank/account.ex b/lib/universe/bank/event/bank/account.ex index 1afb0c88..911f7bc0 100644 --- a/lib/universe/bank/event/bank/account.ex +++ b/lib/universe/bank/event/bank/account.ex @@ -1,4 +1,4 @@ -defmodule Helix.Universe.Bank.Event.Bank.Account do + defmodule Helix.Universe.Bank.Event.Bank.Account do import Helix.Event @@ -101,11 +101,13 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do event Login do + alias Helix.Account.Model.Account alias Helix.Entity.Model.Entity + alias Helix.Entity.Query.Entity, as: EntityQuery alias Helix.Universe.Bank.Model.BankAccount alias Helix.Universe.Bank.Model.BankToken - event_struct [:entity_id, :account, :token_id] + event_struct [:entity_id, :notify_owner, :account, :token_id] @type t :: %__MODULE__{ entity_id: Entity.id, @@ -113,13 +115,33 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do token_id: BankToken.id | nil } - @spec new(BankAccount.t, Entity.id) :: + @spec new(BankAccount.t, Entity.idt) :: t - def new(account = %BankAccount{}, entity_id, token_id \\ nil) do + def new(account, entity_or_entity_id, token_id \\ nil) + def new(account = %BankAccount{}, entity = %Entity{}, token_id) do + is_account? = entity.entity_type == :account + + %__MODULE__{ + entity_id: entity.entity_id, + account: account, + token_id: token_id, + notify_owner: is_account? + } + end + def new(account = %BankAccount{}, entity_id = %Entity.ID{}, token_id) do + is_account? = EntityQuery.fetch(entity_id).entity_type == :account + entity_id = + if is_account? do + %Account.ID{id: entity_id.id, root: Account} + else + entity_id + end + %__MODULE__{ entity_id: entity_id, account: account, - token_id: token_id + token_id: token_id, + notify_owner: is_account? } end @@ -129,7 +151,7 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do the local data. """ - @event :bank_login_event + @event :bank_login @doc false def generate_payload(event, _socket) do @@ -145,9 +167,27 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do end def whom_to_notify(event) do - %{ - account: event.entity_id - } + atm_id = event.account.atm_id + bank_account = event.account.account_number + + entity_id = + case event.entity_id do + entity = %Entity{} -> + %Account.ID{id: entity.entity_id.id, root: Account} + entity_id = %Entity.ID{} -> + %Account.ID{id: entity_id.id, root: Account} + end + + base_map = + %{ + bank_acc: [{atm_id, bank_account}] + } + + if event.notify_owner do + Map.merge(%{account: entity_id}, base_map) + else + base_map + end end end end @@ -175,7 +215,7 @@ defmodule Helix.Universe.Bank.Event.Bank.Account do notify do - @event :bank_logout_event + @event :bank_logout def generate_payload(event, _socket) do data = diff --git a/lib/universe/bank/event/bank/account/password.ex b/lib/universe/bank/event/bank/account/password.ex index 0b22544b..60c22edf 100644 --- a/lib/universe/bank/event/bank/account/password.ex +++ b/lib/universe/bank/event/bank/account/password.ex @@ -22,27 +22,61 @@ defmodule Helix.Universe.Bank.Event.Bank.Account.Password do account: account } end + + notify do + + @event :bank_password_revealed + + @doc false + def generate_payload(event, _socket) do + data = + %{ + atm_id: event.account.atm_id, + account_number: event.account.account_number, + password: event.account.password + } + end + + def whom_to_notify(event), + do: %{account: event.entity_id} + end end event Changed do - alias Helix.Entity.Model.Entity - alias Helix.Universe.Bank.Model.BankAccount + alias Helix.Entity.Model.Entity + alias Helix.Universe.Bank.Model.BankAccount - @type t :: %__MODULE__{ - entity_id: Entity.id, - account: BankAccount.t - } + @type t :: %__MODULE__{ + account: BankAccount.t + } - event_struct [:entity_id, :account] + event_struct [:account] - @spec new(BankAccount.t, Entity.id) :: - t - def new(account = %BankAccount{}, entity_id) do - %__MODULE__{ - entity_id: entity_id, - account: account - } - end + @spec new(BankAccount.t) :: + t + def new(account = %BankAccount{}) do + %__MODULE__{ + account: account + } + end + + notify do + + @event :bank_password_changed + + @doc false + def generate_payload(event, _socket) do + data = + %{ + atm_id: event.account.atm_id, + account_number: event.account.account_number, + password: event.account.password + } + end + + def whom_to_notify(event), + do: %{account: event.account.owner_id} + end end end diff --git a/lib/universe/bank/event/bank/account/token.ex b/lib/universe/bank/event/bank/account/token.ex index 98580e2a..3a8ba57e 100644 --- a/lib/universe/bank/event/bank/account/token.ex +++ b/lib/universe/bank/event/bank/account/token.ex @@ -29,5 +29,24 @@ defmodule Helix.Universe.Bank.Event.Bank.Account.Token do account: account } end + + notify do + + @event :bank_token_acquired + + @doc false + def generate_payload(event, _socket) do + data = + %{ + atm_id: to_string(event.account.atm_id), + account_number: to_string(event.account.account_number), + token_id: event.token.token_id + } + end + + @doc false + def whom_to_notify(event), + do: %{account: event.entity_id} + end end end diff --git a/lib/universe/bank/event/handler/bank/account.ex b/lib/universe/bank/event/handler/bank/account.ex index fc1618c6..eda72847 100644 --- a/lib/universe/bank/event/handler/bank/account.ex +++ b/lib/universe/bank/event/handler/bank/account.ex @@ -5,6 +5,7 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do alias Helix.Event alias Helix.Entity.Query.Entity, as: EntityQuery alias Helix.Universe.Bank.Action.Bank, as: BankAction + alias Helix.Universe.Bank.Query.Bank, as: BankQuery alias Helix.Software.Event.Virus.Collected, as: VirusCollectedEvent alias Helix.Universe.Bank.Event.Bank.Account.Removed, @@ -13,6 +14,8 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do as: BankAccountUpdatedEvent alias Helix.Universe.Bank.Event.AccountCreate.Processed, as: AccountCreateProcessedEvent + alias Helix.Universe.Bank.Event.AccountClose.Processed, + as: AccountCloseProcessedEvent alias Helix.Universe.Bank.Event.RevealPassword.Processed, as: RevealPasswordProcessedEvent alias Helix.Universe.Bank.Event.ChangePassword.Processed, @@ -24,7 +27,7 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do flowing do with \ {:ok, _bank_account, events} <- - BankAction.open_account(event.requester, event.atm), + BankAction.open_account(event.requester, event.atm_id), on_success(fn -> Event.emit(events, from: event) end) do :ok @@ -33,12 +36,12 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do end def account_close_processed(event = %AccountCloseProcessedEvent{}) do - flowing + flowing do with \ - bank_account = BankQuery.fetch_account(event.atm_id, event.account_number) - true <- not is_nil(bank_account), - {:ok, events} <- BankAction.close_account(bank_account), - on_success(fn -> Event.emit(events) end) + bank_acc = BankQuery.fetch_account(event.atm_id, event.account_number), + true <- not is_nil(bank_acc), + {:ok, events} <- BankAction.close_account(bank_acc), + on_success(fn -> Event.emit(events, from: event) end) do :ok end @@ -80,9 +83,7 @@ defmodule Helix.Universe.Bank.Event.Handler.Bank.Account do with \ changed_by = %{} <- EntityQuery.fetch_by_server(event.gateway_id), {:ok, _bank_account, events} <- - BankAction.change_password( - event.account, changed_by.entity_id - ), + BankAction.change_password(event.account), on_success(fn -> Event.emit(events, from: event) end) do :ok diff --git a/lib/universe/bank/henforcer/bank.ex b/lib/universe/bank/henforcer/bank.ex index ca189fe8..e30758f9 100644 --- a/lib/universe/bank/henforcer/bank.ex +++ b/lib/universe/bank/henforcer/bank.ex @@ -180,13 +180,11 @@ defmodule Helix.Universe.Bank.Henforcer.Bank do @type transfer_error :: account_exists_error - | password_valid_error | transfer_no_funds_error @type can_transfer_relay :: %{ amount: BankAccount.amount, - password: BankAccount.password, to_account: BankAccount.t } @type transfer_no_funds_error :: @@ -195,20 +193,14 @@ defmodule Helix.Universe.Bank.Henforcer.Bank do {Network.ID, Network.ip}, BankAccount.account, {ATM.idt, BankAccount.account}, - non_neg_integer, - BankAccount.password + non_neg_integer ) :: {true, can_transfer_relay} | {false, transfer_error, %{}} @doc """ Henforces that given account can transfer to another given account """ - def can_transfer?( - {net_id, ip}, - to_acc_num, - {atm_id, acc_num}, - amount, - password) + def can_transfer?({net_id, ip}, to_acc_num, {atm_id, acc_num}, amount) do with \ {true, r1} <- NetworkHenforcer.nip_exists?(net_id, ip), @@ -217,14 +209,13 @@ defmodule Helix.Universe.Bank.Henforcer.Bank do {true, r3} <- account_exists?(to_atm_id, to_acc_num), {_r3, to_account} <- get_and_drop(r3, :bank_account), {true, r4} <- account_exists?(atm_id, acc_num), - {true, r5} <- has_enough_funds?(r4.bank_account, amount), - {true, r6} <- password_valid?(r4.bank_account, password) + {true, r5} <- has_enough_funds?(r4.bank_account, amount) do relay = %{ to_account: to_account } - reply_ok(relay([relay, r5, r6])) + reply_ok(relay([relay, r5])) end end diff --git a/lib/universe/bank/process/bank/account/close_account.ex b/lib/universe/bank/process/bank/account/close_account.ex index b7b17e59..a622a2bc 100644 --- a/lib/universe/bank/process/bank/account/close_account.ex +++ b/lib/universe/bank/process/bank/account/close_account.ex @@ -5,17 +5,23 @@ process Helix.Universe.Bank.Process.Bank.Account.AccountClose do alias Helix.Universe.Bank.Model.ATM alias Helix.Universe.Bank.Model.BankAccount - process_struct [:account] + process_struct [:atm_id, :account_number] @process_type :bank_close_account - @type t :: %__MODULE__{} + @type t :: %__MODULE__{ + atm_id: ATM.id, + account_number: BankAccount.account + } - @type creation_params :: %{} + @type creation_params :: %{ + atm_id: ATM.id, + account_number: BankAccount.account + } @type objective :: %{cpu: resource_usage} - @type resource :: %{ + @type resources :: %{ objective: objective, static: map, l_dynamic: [:cpu], @@ -25,12 +31,15 @@ process Helix.Universe.Bank.Process.Bank.Account.AccountClose do @type resources_params :: %{} @spec new(creation_params) :: t - def new(%{}) do - %__MODULE__{} + def new(params) do + %__MODULE__{ + atm_id: params.atm_id, + account_number: params.account_number + } end @spec resources(resources_params) :: resources - def resources(params = %{}) + def resources(params = %{}), do: get_resources params processable do @@ -43,40 +52,53 @@ process Helix.Universe.Bank.Process.Bank.Account.AccountClose do {:delete, [event]} end + end - resourceable do - alias Heli.Universe.Bank.Process.Bank.Account.AccountClose, - as: AccountCloseProcess + resourceable do - @type params :: AccountCloseProcess.resources_params - @type factors :: term + alias Helix.Universe.Bank.Process.Bank.Account.AccountClose, + as: AccountCloseProcess - # TODO proper balance - get_factors(%{}) do end + @type params :: AccountCloseProcess.resources_params + @type factors :: term - cpu(_) do + # TODO proper balance + get_factors(%{}) do end - end + # TODO: Use Time, not CPU #364 + cpu(_) do + 1 + end - dynamic do - [:cpu] - end + dynamic do + [:cpu] end + end - executable do + executable do - alias Helix.Universe.Bank.Process.Bank.Account.AccountClose, - as: AccountCloseProcess + alias Helix.Universe.Bank.Process.Bank.Account.AccountClose, + as: AccountCloseProcess - @type params :: AccountCloseProcess.creation_params + @type params :: AccountCloseProcess.creation_params - @type meta :: - %{ - optional(atom) => term - } + @type meta :: + %{ + optional(atom) => term + } - resources(_, _, %{}, _) do - %{} - end + resources(_, _, %{}, _) do + %{} end + + source_connection(_gateway, _target, _params, _meta) do + {:create, :bank_login} + end + end + process_viewable do + + @type data :: %{} + + render_empty_data() end +end diff --git a/lib/universe/bank/process/bank/account/create_account.ex b/lib/universe/bank/process/bank/account/create_account.ex index 20e273da..c2608d06 100644 --- a/lib/universe/bank/process/bank/account/create_account.ex +++ b/lib/universe/bank/process/bank/account/create_account.ex @@ -9,13 +9,13 @@ process Helix.Universe.Bank.Process.Bank.Account.AccountCreate do @process_type :bank_create_account - @type t :: %__MODULE__{} + @type t :: %__MODULE__{atm_id: ATM.id} - @type creation_params :: %{} + @type creation_params :: %{atm_id: ATM.id} @type objective :: %{cpu: resource_usage} - @type resource :: %{ + @type resources :: %{ objective: objective, static: map, l_dynamic: [:cpu], @@ -25,17 +25,17 @@ process Helix.Universe.Bank.Process.Bank.Account.AccountCreate do @type resources_params :: %{} @spec new(creation_params) :: t - def new do - %__MODULE__{} + def new(%{atm_id: atm_id}) do + %__MODULE__{atm_id: atm_id} end - @spec resources(resource_params) :: resources + @spec resources(resources_params) :: resources def resources(params = %{}), do: get_resources params processable do - alias Helix.Universe.Bank.Event.Bank.Account.AccountCreate.Processed, + alias Helix.Universe.Bank.Event.AccountCreate.Processed, as: AccountCreateProcess on_completion(process, data) do @@ -56,6 +56,7 @@ process Helix.Universe.Bank.Process.Bank.Account.AccountCreate do # TODO proper balance get_factors(%{}) do end + # TODO: Use Time, not CPU #364 cpu(_) do 1 end @@ -63,22 +64,32 @@ process Helix.Universe.Bank.Process.Bank.Account.AccountCreate do dynamic do [:cpu] end + end + + executable do - executable do + alias Helix.Universe.Bank.Process.Bank.Account.AccountCreate, + as: AccountCreateProcess - alias Helix.Universe.Bank.Process.Bank.Account.AccountCreate, - as: AccountCreateProcess + @type params :: AccountCreateProcess.creation_params - @type params :: AccountCreateProcess.creation_params + @type meta :: + %{ + optional(atom) => term + } - @type meta :: - ${ - optional(atom) => term - } + resources(_, _, %{}, _) do + %{} + end - resources(_, _, %{}) do - %{} - end + source_connection(_gateway, _target, _params, _meta) do + {:create, :bank_login} end end + process_viewable do + + @type data :: %{} + + render_empty_data() + end end diff --git a/lib/universe/bank/process/bank/account/password_change.ex b/lib/universe/bank/process/bank/account/password_change.ex index b6ed31e9..d5defa97 100644 --- a/lib/universe/bank/process/bank/account/password_change.ex +++ b/lib/universe/bank/process/bank/account/password_change.ex @@ -89,4 +89,10 @@ process Helix.Universe.Bank.Process.Bank.Account.ChangePassword do {:create, :bank_login} end end + process_viewable do + + @type data :: %{} + + render_empty_data() + end end diff --git a/lib/universe/bank/process/bank/account/password_reveal.ex b/lib/universe/bank/process/bank/account/password_reveal.ex index dbb9a2f7..1135b028 100644 --- a/lib/universe/bank/process/bank/account/password_reveal.ex +++ b/lib/universe/bank/process/bank/account/password_reveal.ex @@ -100,4 +100,10 @@ process Helix.Universe.Bank.Process.Bank.Account.RevealPassword do %{account: account} end end + process_viewable do + + @type data :: %{} + + render_empty_data() + end end diff --git a/lib/universe/bank/process/bank/transfer.ex b/lib/universe/bank/process/bank/transfer.ex index b87c8a46..e03b9cb1 100644 --- a/lib/universe/bank/process/bank/transfer.ex +++ b/lib/universe/bank/process/bank/transfer.ex @@ -115,4 +115,10 @@ process Helix.Universe.Bank.Process.Bank.Transfer do {:create, :wire_transfer} end end + process_viewable do + + @type data :: %{} + + render_empty_data() + end end diff --git a/lib/universe/bank/public/bank.ex b/lib/universe/bank/public/bank.ex index 2a3d8257..27d06850 100755 --- a/lib/universe/bank/public/bank.ex +++ b/lib/universe/bank/public/bank.ex @@ -81,27 +81,24 @@ defmodule Helix.Universe.Bank.Public.Bank do ) end - @spec open_account(Account.id, Server.id) :: + @spec open_account(Server.t, Account.id, Server.t, Event.relay) :: {:ok, BankAccount.t} | {:error, :internal} @doc """ Opens a BankAccount on given atm to given account_id. """ - def open_account(account_id, atm_id) do - # TODO: Make as a process - BankAccountFlow.open(account_id, atm_id) - end + def open_account(gateway, account_id, atm, relay), + do: BankAccountFlow.open(gateway, account_id, atm, relay) - @spec close_account(BankAccount.t) :: + @spec close_account(Server.t, BankAccount.t, Server.t, Event.relay) :: :ok | {:error, {:bank_account, :not_found}} | {:error, {:bank_account, :not_empty}} @doc """ Closes given account. """ - def close_account(account), - # TODO: Make as process - do: BankAccountFlow.close(account) + def close_account(gateway, account, atm, relay), + do: BankAccountFlow.close(gateway, account, atm, relay) @spec transfer( BankAccount.t, diff --git a/lib/universe/bank/websocket/channel/bank/join.ex b/lib/universe/bank/websocket/channel/bank/join.ex index 50b5040d..7780d1ee 100644 --- a/lib/universe/bank/websocket/channel/bank/join.ex +++ b/lib/universe/bank/websocket/channel/bank/join.ex @@ -229,7 +229,8 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do account_number, gateway.server_id, bounce_id, - password + password, + request.relay ) do gateway_data = @@ -290,7 +291,8 @@ join Helix.Universe.Bank.Websocket.Channel.Bank.Join do account_number, gateway.server_id, bounce_id, - token.token_id + token.token_id, + request.relay ) do gateway_data = diff --git a/lib/universe/bank/websocket/channel/requests/close_account.ex b/lib/universe/bank/websocket/channel/requests/close_account.ex index c3e69194..9af909b8 100644 --- a/lib/universe/bank/websocket/channel/requests/close_account.ex +++ b/lib/universe/bank/websocket/channel/requests/close_account.ex @@ -39,6 +39,7 @@ request Helix.Universe.Bank.Websocket.Requests.CloseAccount do bank_account = BankQuery.fetch_account(atm_id, account_number) atm = ServerQuery.fetch(atm_id) relay = request.relay + gateway = ServerQuery.fetch(socket.assigns.gateway.server_id) process = BankPublic.close_account(gateway, bank_account, atm, relay) case process do diff --git a/lib/universe/bank/websocket/channel/requests/create_account.ex b/lib/universe/bank/websocket/channel/requests/create_account.ex index c3103f0b..8b7e3702 100644 --- a/lib/universe/bank/websocket/channel/requests/create_account.ex +++ b/lib/universe/bank/websocket/channel/requests/create_account.ex @@ -8,17 +8,20 @@ request Helix.Universe.Bank.Websocket.Requests.CreateAccount do It Returns :ok or :error """ alias Helix.Server.Model.Server + alias Helix.Server.Henforcer.Server, as: ServerHenforcer alias Helix.Server.Query.Server, as: ServerQuery alias Helix.Universe.Bank.Public.Bank, as: BankPublic alias Helix.Universe.Bank.Henforcer.Bank, as: BankHenforcer def check_params(request, _socket) do with \ - {:ok, atm_id} <- Server.ID.cast(request.unsafe["atm_id"]) + {:ok, atm_id} <- Server.ID.cast(request.unsafe["atm_id"]), + {:ok, gateway_id} <- Server.ID.cast(request.unsafe["gateway"]) do params = %{ - atm_id: atm_id + atm_id: atm_id, + gateway_id: gateway_id } update_params(request, params, reply: true) @@ -35,12 +38,14 @@ request Helix.Universe.Bank.Websocket.Requests.CreateAccount do def check_permissions(request, _socket) do atm = ServerQuery.fetch(request.params.atm_id) with \ - {true, relay} <- BankHenforcer.can_create_account?(atm), - atm_id = relay.atm_id + {true, r1} <- BankHenforcer.can_create_account?(atm), + atm_id = r1.atm_id, + {true, r2} <- ServerHenforcer.server_exists?(request.params.gateway_id) do meta = %{ - atm_id: atm_id + atm_id: atm_id, + gateway_id: r2.server.server_id } update_meta(request, meta, reply: true) @@ -55,7 +60,8 @@ request Helix.Universe.Bank.Websocket.Requests.CreateAccount do def handle_request(request, socket) do atm_id = request.meta.atm_id account_id = socket.assigns.account_id - gateway = ServerQuery.fetch(socket.assigns.gateway.server_id) + gateway = ServerQuery.fetch(request.meta.gateway_id) + atm = ServerQuery.fetch(atm_id) relay = request.relay process = BankPublic.open_account(gateway, account_id, atm, relay) diff --git a/lib/universe/bank/websocket/channel/requests/transfer.ex b/lib/universe/bank/websocket/channel/requests/transfer.ex index 50b50296..228fa0d2 100644 --- a/lib/universe/bank/websocket/channel/requests/transfer.ex +++ b/lib/universe/bank/websocket/channel/requests/transfer.ex @@ -22,7 +22,6 @@ request Helix.Universe.Bank.Websocket.Requests.Transfer do {:ok, receiving_bank_acc} <- BankAccount.cast(receiving_bank_acc), {:ok, network_id} <- Network.ID.cast(request.unsafe["to_bank_net"]), {:ok, ip} <- IPv4.cast(request.unsafe["to_bank_ip"]), - {:ok, password} <- validate_input(request.unsafe["password"], :password), amount <- request.unsafe["amount"], {true, amount} <- (fn amount -> {amount > 0, amount} end).(amount) do @@ -31,7 +30,6 @@ request Helix.Universe.Bank.Websocket.Requests.Transfer do bank_account: receiving_bank_acc, bank_ip: ip, bank_net: network_id, - password: password, amount: amount } update_params(request, params, reply: true) @@ -48,7 +46,6 @@ request Helix.Universe.Bank.Websocket.Requests.Transfer do sending_acc = {socket.assigns.atm_id, socket.assigns.account_number} receiving_acc = request.params.bank_account amount = request.params.amount - password = request.params.password gateway_id = socket.assigns.gateway.server_id account_id = socket.assigns.account_id @@ -58,8 +55,7 @@ request Helix.Universe.Bank.Websocket.Requests.Transfer do nip, receiving_acc, sending_acc, - amount, - password + amount ), amount = relay.amount, to_account = relay.to_account diff --git a/lib/universe/npc/internal/web.ex b/lib/universe/npc/internal/web.ex index c63254fb..5b586893 100644 --- a/lib/universe/npc/internal/web.ex +++ b/lib/universe/npc/internal/web.ex @@ -1,6 +1,7 @@ defmodule Helix.Universe.NPC.Internal.Web do alias HELL.IPv4 + alias Helix.Cache.Query.Cache, as: CacheQuery alias Helix.Network.Model.Network alias Helix.Universe.NPC.Model.NPC @@ -18,9 +19,14 @@ defmodule Helix.Universe.NPC.Internal.Web do Map.merge(common, custom) end - def generate_content(%NPC{npc_type: :bank}, _net, _ip) do + def generate_content(%NPC{npc_type: :bank}, net, ip) do common = common("Nubank") - custom = %{} + {_, atm_id} = + CacheQuery.from_nip_get_server(net, ip) + + custom = %{ + atm_id: atm_id + } Map.merge(common, custom) end diff --git a/test/support/channel/setup.ex b/test/support/channel/setup.ex index 5a3408c1..bac0e82a 100644 --- a/test/support/channel/setup.ex +++ b/test/support/channel/setup.ex @@ -399,11 +399,7 @@ defmodule Helix.Test.Channel.Setup do account = AccountSetup.account!() account_id = account.account_id - tunnel = - NetworkSetup.tunnel!( - gateway_id: gateway_id, - target_id: atm_id - ) + tunnel = NetworkSetup.tunnel!(gateway_id: gateway_id, target_id: atm_id) connection = ConnectionSetup.connection( @@ -446,7 +442,9 @@ defmodule Helix.Test.Channel.Setup do """ def fake_connection_socket_assigns(opts \\ []) do gen_account_id = fn entity_id -> - entity_id |> to_string() |> Account.ID.cast!() + entity_id + |> to_string() + |> Account.ID.cast!() end entity_id = Keyword.get(opts, :entity_id, Entity.ID.generate()) diff --git a/test/support/event/setup/bank.ex b/test/support/event/setup/bank.ex index f0079fd9..46503eb8 100644 --- a/test/support/event/setup/bank.ex +++ b/test/support/event/setup/bank.ex @@ -71,10 +71,10 @@ defmodule Helix.Test.Event.Setup.Bank do do: RevealPasswordProcessedEvent.new(account, gateway_id, token_id) @doc """ - Accepts (BankAccount.t, Entity.id) + Accepts (BankAccount.t) """ - def password_changed(account, entity_id), - do: BankAccountPasswordChangedEvent.new(account, entity_id) + def password_changed(account), + do: BankAccountPasswordChangedEvent.new(account) @doc """ Accepts: (BankAccount.t, Entity.id) diff --git a/test/support/software/helper.ex b/test/support/software/helper.ex index b2eda377..9a71df60 100644 --- a/test/support/software/helper.ex +++ b/test/support/software/helper.ex @@ -137,8 +137,8 @@ defmodule Helix.Test.Software.Helper do path_size = (byte_size(path) - 1) * 8 case path do - <> <> "/" -> - <> + <> <> "/" -> + <> path -> path end diff --git a/test/support/universe/bank/setup.ex b/test/support/universe/bank/setup.ex index 6115bf2e..93ab994f 100755 --- a/test/support/universe/bank/setup.ex +++ b/test/support/universe/bank/setup.ex @@ -280,7 +280,7 @@ defmodule Helix.Test.Universe.Bank.Setup do # Login with the right password {:ok, _, connection} = BankAccountFlow.login_password( - acc.atm_id, acc.account_number, server.server_id, nil, acc.password + acc.atm_id, acc.account_number, server.server_id, nil, acc.password, nil ) {connection, %{acc: acc, server: server, entity: entity}} diff --git a/test/universe/bank/action/flow/bank_account_test.exs b/test/universe/bank/action/flow/bank_account_test.exs index fb09d1fb..7828d038 100644 --- a/test/universe/bank/action/flow/bank_account_test.exs +++ b/test/universe/bank/action/flow/bank_account_test.exs @@ -3,6 +3,7 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do use Helix.Test.Case.Integration alias HELL.Utils + alias Helix.Entity.Model.Entity alias Helix.Entity.Query.Database, as: DatabaseQuery alias Helix.Network.Query.Tunnel, as: TunnelQuery alias Helix.Process.Query.Process, as: ProcessQuery @@ -272,28 +273,51 @@ defmodule Helix.Universe.Bank.Action.Flow.BankAccountTest do end end - describe "open/2" do - test "opens account when everything is OK" do - account = AccountSetup.account!() + describe "open/4" do + test "creates a process to opens account when everything is OK" do + {account, %{server: gateway}} = AccountSetup.account(with_server: true) + account_id = account.account_id bank_acc = BankSetup.account!() atm_id = bank_acc.atm_id + atm = ServerQuery.fetch(atm_id) + + assert {:ok, process} = + BankAccountFlow.open(gateway, account_id, atm, @relay) + + assert process.data.atm_id == atm_id + assert process.source_entity_id == %Entity.ID{id: account_id.id} + + TOPHelper.force_completion(process) - assert {:ok, bank_acc} = BankAccountFlow.open(account_id, atm_id) - assert bank_acc.atm_id == atm_id - assert BankQuery.fetch_account(atm_id, bank_acc.account_number) + refute ProcessQuery.fetch(process.process_id) end end - describe "close/2" do - test "closes account" do - bank_acc = BankSetup.account!() + describe "close/4" do + test "creates a process to close the given bank account" do + {account, %{server: gateway}} = AccountSetup.account(with_server: true) + account_id = account.account_id + + entity_id = %Entity.ID{id: account_id.id} + + bank_acc = BankSetup.account!(owner_id: entity_id) atm_id = bank_acc.atm_id + atm = ServerQuery.fetch(atm_id) account_number = bank_acc.account_number - assert :ok = BankAccountFlow.close(bank_acc) + assert {:ok, process} = + BankAccountFlow.close(gateway, bank_acc, atm, @relay) + + assert process.data.atm_id == atm_id + assert process.data.account_number == account_number + + TOPHelper.force_completion(process) + + refute ProcessQuery.fetch(process.process_id) + refute BankQuery.fetch_account(atm_id, account_number) end end diff --git a/test/universe/bank/public/bank_test.exs b/test/universe/bank/public/bank_test.exs index 44469791..dfcdb7de 100644 --- a/test/universe/bank/public/bank_test.exs +++ b/test/universe/bank/public/bank_test.exs @@ -180,43 +180,11 @@ defmodule Helix.Test.Universe.Bank.Public.BankTest do end end - describe "open_account/2" do - test "creates the account" do - # Setups an Account. - account = AccountSetup.account!() - account_id = account.account_id - - # Gets the Bank Server. - {bank, _} = NPCHelper.bank - atm_id = List.first(bank.servers).id - - # Asserts that BankAccount is being created. - assert {:ok, bank_account} = - BankPublic.open_account(account_id, atm_id) - - # Asserts that BankAccount's Owner is the created account. - assert bank_account.owner_id == account_id - - # Assert that BankAccount is on database. - assert BankQuery.fetch_account(atm_id, bank_account.account_number) - end + describe "open_account/4" do + # Tested on BankAccountFlowTest end - describe "close_account/1" do - test "deletes the account" do - # Setups a BankAccount. - bank_account = BankSetup.account!() - - # Stores atm_id and account_number for trying fetch after deleting. - atm_id = bank_account.atm_id - account_number = bank_account.account_number - - # Asserts the BankAccount is being deleted. - assert :ok = - BankPublic.close_account(bank_account) - - # Refutes if BankAccount still exists on database. - refute BankQuery.fetch_account(atm_id, account_number) - end + describe "close_account/4" do + # Tested on BankAccountFlowTest end end diff --git a/test/universe/bank/websocket/channel/requests/close_account_test.exs b/test/universe/bank/websocket/channel/requests/close_account_test.exs index 6c1575df..7f993c9a 100644 --- a/test/universe/bank/websocket/channel/requests/close_account_test.exs +++ b/test/universe/bank/websocket/channel/requests/close_account_test.exs @@ -4,15 +4,6 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CloseAccount do import Phoenix.ChannelTest - alias Helix.Websocket.Requestable - alias Helix.Universe.Bank.Query.Bank, as: BankQuery - alias Helix.Universe.Bank.Requests.CloseAccount, as: BankCloseAccountRequest - - alias Helix.Test.Channel.Request.Helper, as: RequestHelper - alias Helix.Test.Channel.Helper, as: ChannelHelper - alias Helix.Test.Channel.Setup, as: ChannelSetup - alias Helix.Test.Universe.Bank.Setup, as: BankSetup - describe "BankCloseAccountRequest.handle_request/2" do # Tested on BankAccountInternal end diff --git a/test/universe/bank/websocket/channel/requests/create_account_test.exs b/test/universe/bank/websocket/channel/requests/create_account_test.exs index cb126af2..60675c09 100644 --- a/test/universe/bank/websocket/channel/requests/create_account_test.exs +++ b/test/universe/bank/websocket/channel/requests/create_account_test.exs @@ -3,8 +3,8 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do use Helix.Test.Case.Integration alias Helix.Websocket.Requestable + alias Helix.Entity.Model.Entity alias Helix.Network.Query.Network, as: NetworkQuery - alias Helix.Universe.Bank.Query.Bank, as: BankQuery alias Helix.Universe.Bank.Websocket.Requests.CreateAccount, as: BankCreateAccountRequest @@ -12,12 +12,15 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do alias Helix.Test.Server.Setup, as: ServerSetup alias Helix.Test.Universe.NPC.Helper, as: NPCHelper - @internet_id NetworkQuery.internet().network_id - describe "BankCreateAccountRequest.check_params/2" do test "accepts when receives valid information" do # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() + {socket, %{account: account}} = ChannelSetup.create_socket() + + entity_id = Entity.ID.cast!(to_string(account.account_id)) + + # Setups an Server new Server to created account + {gateway, _} = ServerSetup.server(entity_id: entity_id) # Gets a Bank from database. {bank, _} = NPCHelper.bank @@ -26,7 +29,8 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do # Creates the parameters for creating a account. payload = %{ - "atm_id" => to_string(atm_id) + "atm_id" => to_string(atm_id), + "gateway" => to_string(gateway.server_id) } # Creates a new BankCreateAccountRequest with the parameters. @@ -49,6 +53,7 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do payload = %{ "atm_id" => "DROP TABLE helix_dev_accounts;", + "gateway" => "FROM *" } # Create BankCreateAccountRequest with invalid parameters. @@ -67,7 +72,12 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do describe "BankCreateAccountRequest.check_permissions/2" do test "accepts when the ATM is a bank" do # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() + {socket, %{account: account}} = ChannelSetup.create_socket() + + entity_id = Entity.ID.cast!(to_string(account.account_id)) + + # Setups an Server new Server to created account + {gateway, _} = ServerSetup.server(entity_id: entity_id) # Gets a Bank and it's atm_id. {bank, _} = NPCHelper.bank @@ -77,6 +87,7 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do payload = %{ "atm_id" => to_string(atm_id), + "gateway" => to_string(gateway.server_id) } # Create BankCreateAccountRequest with valid parameters. @@ -97,7 +108,12 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do test "rejects when the ATM is not a server" do # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() + {socket, %{account: account}} = ChannelSetup.create_socket() + + entity_id = Entity.ID.cast!(to_string(account.account_id)) + + # Setups an Server new Server to created account + {gateway, _} = ServerSetup.server(entity_id: entity_id) # Setups a ordinary server. {not_bank, _} = ServerSetup.server() @@ -107,6 +123,7 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do payload = %{ "atm_id" => to_string(not_bank.server_id), + "gateway" => to_string(gateway.server_id) } # Create BankCreateAccountRequest with parameters. @@ -129,7 +146,12 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do describe "BankCreateAccountRequest.handle_request/2" do test "creates BankAccount on database" do # Setups an Account socket. - {socket, _} = ChannelSetup.create_socket() + {socket, %{account: account}} = ChannelSetup.create_socket() + + entity_id = Entity.ID.cast!(to_string(account.account_id)) + + # Setups an Server new Server to created account + {gateway, _} = ServerSetup.server(entity_id: entity_id) # Gets a Bank and it's atm_id. {bank, _} = NPCHelper.bank @@ -139,6 +161,7 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do payload = %{ "atm_id" => to_string(atm_id), + "gateway" => to_string(gateway.server_id) } # Creates a BankCreateAccountRequest with the parameters. @@ -156,11 +179,6 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.CreateAccountTest do # Asserts that the request is handled correctly. assert {:ok, request} = Requestable.handle_request(request, socket) - - acc = request.meta.bank_account - - # Asserts that the BankAccount is being created on the database. - assert BankQuery.fetch_account(acc.atm_id, acc.account_number) end end end diff --git a/test/universe/bank/websocket/channel/requests/logout_test.exs b/test/universe/bank/websocket/channel/requests/logout_test.exs index e8900752..97832b5f 100644 --- a/test/universe/bank/websocket/channel/requests/logout_test.exs +++ b/test/universe/bank/websocket/channel/requests/logout_test.exs @@ -8,8 +8,8 @@ defmodule Helix.Test.Universe.Bank.Websocket.Requests.LogoutTest do alias Helix.Test.Channel.Helper, as: ChannelHelper alias Helix.Test.Channel.Setup, as: ChannelSetup - describe "bootstrap" do - test "returns expected result" do + describe "logout" do + test "terminates connection with the channel" do # Setups an Account socket. {socket, %{entity: entity, server: gateway}} = ChannelSetup.create_socket()