From b424007eada8a5e6b23c36406c7b3f2175c703b0 Mon Sep 17 00:00:00 2001 From: Yashin Santos Date: Fri, 30 Oct 2020 18:50:30 -0300 Subject: [PATCH 1/5] feat: add show admin endpoint wip --- apps/rest_api/lib/controllers/admin/user.ex | 16 +++++ apps/rest_api/lib/ports/resource_manager.ex | 9 +++ apps/rest_api/lib/views/admin/user.ex | 11 ++++ .../test/controllers/admin/user_test.exs | 58 +++++++++++++++++++ apps/rest_api/test/support/conn_case.ex | 1 + 5 files changed, 95 insertions(+) diff --git a/apps/rest_api/lib/controllers/admin/user.ex b/apps/rest_api/lib/controllers/admin/user.ex index eb1a14b..e6d62a9 100644 --- a/apps/rest_api/lib/controllers/admin/user.ex +++ b/apps/rest_api/lib/controllers/admin/user.ex @@ -26,4 +26,20 @@ defmodule RestAPI.Controller.Admin.User do error end end + + def show(conn, %{"id" => username} = params) do + params + |> Map.put(:username, username) + |> ResourceManager.get_identity() + |> case do + {:ok, identity} when is_map(identity) -> + conn + |> put_status(:created) + |> put_view(User) + |> render("show.json", response: identity) + + {:error, error_reason} -> + error_reason + end + end end diff --git a/apps/rest_api/lib/ports/resource_manager.ex b/apps/rest_api/lib/ports/resource_manager.ex index 2ae1538..b168053 100644 --- a/apps/rest_api/lib/ports/resource_manager.ex +++ b/apps/rest_api/lib/ports/resource_manager.ex @@ -7,16 +7,25 @@ defmodule RestAPI.Ports.ResourceManager do @type possible_create_identity_response :: {:ok, struct()} | {:error, Ecto.Changeset.t() | :invalid_params} + @type possible_get_identity_responses :: + {:ok, struct()} | {:error, :not_found | :invalid_params} + @doc "Delegates to ResourceManager.create_identity/1" @callback create_identity(input :: map()) :: possible_create_identity_response() @doc "Delegates to ResourceManager.password_allowed?/1" @callback password_allowed?(password :: String.t()) :: boolean() + @callback get_identity(input :: String.t()) :: possible_get_identity_responses() + @doc "Create a new identity with it's credentials" @spec create_identity(input :: map()) :: possible_create_identity_response() def create_identity(input), do: implementation().create_identity(input) + @doc "Returns an user or application identity seaching by the given input" + @spec get_identity(input :: String.t()) :: possible_get_identity_responses() + def get_identity(input), do: implementation().get_identity(input) + @doc "Checks if the given password is strong enough to be used" @spec password_allowed?(password :: String.t()) :: boolean() def password_allowed?(password), do: implementation().password_allowed?(password) diff --git a/apps/rest_api/lib/views/admin/user.ex b/apps/rest_api/lib/views/admin/user.ex index d2696ba..2e16274 100644 --- a/apps/rest_api/lib/views/admin/user.ex +++ b/apps/rest_api/lib/views/admin/user.ex @@ -13,4 +13,15 @@ defmodule RestAPI.Views.Admin.User do update_at: response.updated_at } end + + def render("show.json", %{response: response}) do + %{ + id: response.id, + username: response.username, + status: response.status, + is_admin: response.is_admin, + inserted_at: response.inserted_at, + updated_at: response.updated_at + } + end end diff --git a/apps/rest_api/test/controllers/admin/user_test.exs b/apps/rest_api/test/controllers/admin/user_test.exs index 8d765b2..eba9d39 100644 --- a/apps/rest_api/test/controllers/admin/user_test.exs +++ b/apps/rest_api/test/controllers/admin/user_test.exs @@ -5,6 +5,7 @@ defmodule RestAPI.Controllers.Admin.User do alias RestAPI.Ports.{AuthenticatorMock, AuthorizerMock, ResourceManagerMock} @create_endpoint "/admin/v1/users" + @show_endpoint "/admin/v1/users/" describe "POST #{@create_endpoint}" do setup do @@ -163,6 +164,63 @@ defmodule RestAPI.Controllers.Admin.User do end end + describe "GET #{@show_endpoint}" do + setup do + access_token = "my-access-token" + claims = default_claims() + + {:ok, access_token: access_token, claims: claims, user: insert!(:user)} + end + + test "should render user identity", %{ + conn: conn, + access_token: access_token, + claims: claims, + user: user + } do + username = user.username + + expect(AuthenticatorMock, :validate_access_token, fn token -> + assert access_token == token + {:ok, claims} + end) + + expect(AuthenticatorMock, :get_session, fn %{"jti" => jti} -> + assert claims["jti"] == jti + {:ok, success_session(claims)} + end) + + expect(AuthorizerMock, :authorize_admin, fn %Plug.Conn{} -> :ok end) + + expect(ResourceManagerMock, :get_identity, fn input -> + assert is_map(input) + + {:ok, + %{ + id: user.id, + inserted_at: NaiveDateTime.utc_now(), + is_admin: user.is_admin, + status: user.status, + updated_at: NaiveDateTime.utc_now(), + username: username + }} + end) + + assert %{ + "id" => _id, + "inserted_at" => _inserted_at, + "updated_at" => _updated_at, + "is_admin" => false, + "status" => "active", + "username" => ^username + } = + conn + |> put_req_header("authorization", "Bearer #{access_token}") + |> get(@show_endpoint <> "username") + |> json_response(201) + end + end + defp default_claims do %{ "jti" => "03eds74a-c291-4b5f", diff --git a/apps/rest_api/test/support/conn_case.ex b/apps/rest_api/test/support/conn_case.ex index 5f30615..817bb80 100644 --- a/apps/rest_api/test/support/conn_case.ex +++ b/apps/rest_api/test/support/conn_case.ex @@ -24,6 +24,7 @@ defmodule RestAPI.ConnCase do import Phoenix.ConnTest import RestAPI.ConnCase import Mox + import ResourceManager.Factory alias RestAPI.Router.Helpers, as: Routes From a3ffcb20da13fd4bfbb9ddb2c8183ca428eedec6 Mon Sep 17 00:00:00 2001 From: Luiz Carlos Date: Sat, 7 Nov 2020 10:49:36 -0300 Subject: [PATCH 2/5] chore: refactor factories and dialyzer folder (#43) * chore: refactor factories * chore: change dialyzer folder * chore: update plt config --- .github/workflows/main.yml | 4 +- .gitignore | 3 + README.md | 14 +---- .../commands/client_credentials_test.exs | 8 +-- .../sign_in/commands/resource_owner_test.exs | 8 +-- apps/authenticator/test/support/factory.ex | 49 ++++++++++------ .../support/mocks/keys/private_key.pem} | 0 .../support/mocks/keys/public_key.pub} | 0 .../credentials/public_key_test.exs | 6 +- .../commands/create_identity_test.exs | 2 +- apps/resource_manager/test/support/factory.ex | 58 ++++++++++++------- .../support/mocks/keys/private_key.pem} | 0 .../support/mocks/keys/public_key.pub} | 0 apps/rest_api/test/support/conn_case.ex | 12 +++- apps/rest_api/test/support/factory.ex | 32 ++++++++++ mix.exs | 7 +-- 16 files changed, 131 insertions(+), 72 deletions(-) rename apps/authenticator/{priv/keys/authenticator_key.pem => test/support/mocks/keys/private_key.pem} (100%) rename apps/authenticator/{priv/keys/authenticator_key.pub => test/support/mocks/keys/public_key.pub} (100%) rename apps/resource_manager/{priv/keys/resource_manager_key.pem => test/support/mocks/keys/private_key.pem} (100%) rename apps/resource_manager/{priv/keys/resource_manager_key.pub => test/support/mocks/keys/public_key.pub} (100%) create mode 100644 apps/rest_api/test/support/factory.ex diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b3b6156..cfcb741 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -109,13 +109,13 @@ jobs: uses: actions/cache@v2 id: plt-cache with: - path: _build/plts + path: dializer/ key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plts-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} - name: Generate PLT if: steps.plt-cache.outputs.cache-hit != 'true' run: | - mkdir -p _build/plts + mkdir -p dialyzer/ mix dialyzer --plt - name: Run dialyzer diff --git a/.gitignore b/.gitignore index b84fcd9..9c5d871 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ # The directory Mix downloads your dependencies sources to. /deps/ +# The directory where the files will be saved on every build. +/dialyzer/ + # Where third-party dependencies like ExDoc output generated docs. /doc/ diff --git a/README.md b/README.md index 4c1ece3..2229ece 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,14 @@ **This is a work in progress and every contribution is welcome :)** -**TODO: Add description** - ## Requirements -- Elixir `1.11` using `OTP-23`; +- Elixir `1.11`; - Erlang `23.1`; - Docker-compose (Just when running in dev enviroment); ## Running it locally -First you should install all requirement, we recommend you to use [asdf](https://github.com/asdf-vm/asdf). - -If you are using `asdf` just run: - -- `asdf plugin-add elixir`; -- `asdf plugin-add erlang`; -- `asdf install`; - In order to prepare the application run: - `docker-compose up` to get your containers running; @@ -47,7 +37,7 @@ ResourceManager.Repo.all(ResourceManager.Identities.Schemas.ClientApplication) | Check out the [rest api guide](https://github.com/lcpojr/watcher_ex/blob/master/apps/rest_api/README.md) on the specific application `README.md`. -## Testing +### Testing Before you can run the tests you should setup the test dabatase by using `mix test_setup`. After that just call `mix test` or `mix coveralls` if you want to check code coverage. diff --git a/apps/authenticator/test/authenticator/sign_in/commands/client_credentials_test.exs b/apps/authenticator/test/authenticator/sign_in/commands/client_credentials_test.exs index 90b7654..2da748d 100644 --- a/apps/authenticator/test/authenticator/sign_in/commands/client_credentials_test.exs +++ b/apps/authenticator/test/authenticator/sign_in/commands/client_credentials_test.exs @@ -106,9 +106,9 @@ defmodule Authenticator.SignIn.Commands.ClientCredentialsTest do test "succeeds using client_assertions and generates an access_token" do scopes = RF.insert_list!(:scope, 3) app = RF.insert!(:client_application, grant_flows: ["client_credentials"]) - public_key = RF.insert!(:public_key, client_application: app, value: get_priv_public_key()) + public_key = RF.insert!(:public_key, client_application: app, value: get_public_key()) - signer = Joken.Signer.create("RS256", %{"pem" => get_priv_private_key()}) + signer = Joken.Signer.create("RS256", %{"pem" => get_private_key()}) client_assertion = ClientAssertion.generate_and_sign!( @@ -144,9 +144,9 @@ defmodule Authenticator.SignIn.Commands.ClientCredentialsTest do test "succeeds using client_assertions and generates a refresh_token" do scopes = RF.insert_list!(:scope, 3) app = RF.insert!(:client_application, grant_flows: ["client_credentials", "refresh_token"]) - public_key = RF.insert!(:public_key, client_application: app, value: get_priv_public_key()) + public_key = RF.insert!(:public_key, client_application: app, value: get_public_key()) - signer = Joken.Signer.create("RS256", %{"pem" => get_priv_private_key()}) + signer = Joken.Signer.create("RS256", %{"pem" => get_private_key()}) client_assertion = ClientAssertion.generate_and_sign!( diff --git a/apps/authenticator/test/authenticator/sign_in/commands/resource_owner_test.exs b/apps/authenticator/test/authenticator/sign_in/commands/resource_owner_test.exs index 2349eba..28ffef0 100644 --- a/apps/authenticator/test/authenticator/sign_in/commands/resource_owner_test.exs +++ b/apps/authenticator/test/authenticator/sign_in/commands/resource_owner_test.exs @@ -129,11 +129,11 @@ defmodule Authenticator.SignIn.Commands.ResourceOwnerTest do scopes = RF.insert_list!(:scope, 3) user = RF.insert!(:user) app = RF.insert!(:client_application) - public_key = RF.insert!(:public_key, client_application: app, value: get_priv_public_key()) + public_key = RF.insert!(:public_key, client_application: app, value: get_public_key()) hash = RF.gen_hashed_password("MyPassw@rd234") password = RF.insert!(:password, user: user, password_hash: hash) - signer = Joken.Signer.create("RS256", %{"pem" => get_priv_private_key()}) + signer = Joken.Signer.create("RS256", %{"pem" => get_private_key()}) client_assertion = ClientAssertion.generate_and_sign!( @@ -177,11 +177,11 @@ defmodule Authenticator.SignIn.Commands.ResourceOwnerTest do scopes = RF.insert_list!(:scope, 3) user = RF.insert!(:user) app = RF.insert!(:client_application, grant_flows: ["resource_owner", "refresh_token"]) - public_key = RF.insert!(:public_key, client_application: app, value: get_priv_public_key()) + public_key = RF.insert!(:public_key, client_application: app, value: get_public_key()) hash = RF.gen_hashed_password("MyPassw@rd234") password = RF.insert!(:password, user: user, password_hash: hash) - signer = Joken.Signer.create("RS256", %{"pem" => get_priv_private_key()}) + signer = Joken.Signer.create("RS256", %{"pem" => get_private_key()}) client_assertion = ClientAssertion.generate_and_sign!( diff --git a/apps/authenticator/test/support/factory.ex b/apps/authenticator/test/support/factory.ex index 079e88d..5801ced 100644 --- a/apps/authenticator/test/support/factory.ex +++ b/apps/authenticator/test/support/factory.ex @@ -6,7 +6,8 @@ defmodule Authenticator.Factory do alias Authenticator.Sessions.Tokens.{AccessToken, RefreshToken} alias Authenticator.SignIn.Schemas.{ApplicationAttempt, UserAttempt} - @doc false + @doc "Builds a default struct from the requested model" + @spec build(model :: atom()) :: struct() def build(:session) do jti = Ecto.UUID.generate() @@ -51,50 +52,60 @@ defmodule Authenticator.Factory do } end - @doc false + @doc "Returns the a model struct with the given attributes" + @spec build(factory_name :: atom(), attributes :: Keyword.t()) :: struct() def build(factory_name, attributes) when is_atom(factory_name) and is_list(attributes) do factory_name |> build() |> struct!(attributes) end - @doc false + @doc "Inserts a model with the given attributes on database" + @spec insert!(factory_name :: atom(), attributes :: Keyword.t()) :: struct() def insert!(factory_name, attributes \\ []) when is_atom(factory_name) do factory_name |> build(attributes) |> Repo.insert!() end - @doc false + @doc "Inserts a list of the given model on database" + @spec insert_list!( + factory_name :: atom(), + count :: integer(), + attributes :: Keyword.t() + ) :: list(struct()) def insert_list!(factory_name, count \\ 10, attributes \\ []) when is_atom(factory_name), do: Enum.map(0..count, fn _ -> insert!(factory_name, attributes) end) - @doc false - def build_access_token(claims), do: AccessToken.generate_and_sign(claims) + @doc "Returns an mocked access token using the given claims" + @spec build_access_token(claims :: map()) :: {:ok, token :: String.t(), claims :: map()} + def build_access_token(claims) when is_map(claims), do: AccessToken.generate_and_sign(claims) - @doc false - def build_refresh_token(claims), do: RefreshToken.generate_and_sign(claims) + @doc "Returns an mocked refresh token using the given claims" + @spec build_refresh_token(claims :: map()) :: {:ok, token :: String.t(), claims :: map()} + def build_refresh_token(claims) when is_map(claims), do: RefreshToken.generate_and_sign(claims) - @doc false + @doc "Returns an default token expiration" + @spec default_expiration() :: NaiveDateTime.t() def default_expiration do NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) |> NaiveDateTime.add(60 * 60 * 24, :second) end - @doc false - def get_priv_public_key do - :authenticator - |> :code.priv_dir() - |> Path.join("/keys/authenticator_key.pub") + @doc "Returns the mocked public key" + @spec get_public_key() :: String.t() + def get_public_key do + File.cwd!() + |> Path.join("/test/support/mocks/keys/public_key.pub") |> File.read!() end - @doc false - def get_priv_private_key do - :authenticator - |> :code.priv_dir() - |> Path.join("/keys/authenticator_key.pem") + @doc "Returns the mocked private key" + @spec get_private_key() :: String.t() + def get_private_key do + File.cwd!() + |> Path.join("/test/support/mocks/keys/private_key.pem") |> File.read!() end end diff --git a/apps/authenticator/priv/keys/authenticator_key.pem b/apps/authenticator/test/support/mocks/keys/private_key.pem similarity index 100% rename from apps/authenticator/priv/keys/authenticator_key.pem rename to apps/authenticator/test/support/mocks/keys/private_key.pem diff --git a/apps/authenticator/priv/keys/authenticator_key.pub b/apps/authenticator/test/support/mocks/keys/public_key.pub similarity index 100% rename from apps/authenticator/priv/keys/authenticator_key.pub rename to apps/authenticator/test/support/mocks/keys/public_key.pub diff --git a/apps/resource_manager/test/resource_manager/credentials/public_key_test.exs b/apps/resource_manager/test/resource_manager/credentials/public_key_test.exs index 48a3a3c..4515965 100644 --- a/apps/resource_manager/test/resource_manager/credentials/public_key_test.exs +++ b/apps/resource_manager/test/resource_manager/credentials/public_key_test.exs @@ -17,7 +17,7 @@ defmodule ResourceManager.Credentials.PublicKeysTest do params = %{ client_application_id: client_application.id, - value: get_priv_public_key() + value: get_public_key() } assert {:ok, %PublicKey{id: id} = public_key} = PublicKeys.create(params) @@ -32,7 +32,7 @@ defmodule ResourceManager.Credentials.PublicKeysTest do describe "#{PublicKeys}.update/2" do test "succeed if params are valid", ctx do - value = get_priv_public_key() + value = get_public_key() assert {:ok, %PublicKey{id: id, value: ^value} = public_key} = PublicKeys.update(ctx.public_key, %{value: value}) @@ -47,7 +47,7 @@ defmodule ResourceManager.Credentials.PublicKeysTest do test "raises if public_key does not exist" do assert_raise Ecto.NoPrimaryKeyValueError, fn -> - PublicKeys.update(%PublicKey{}, %{value: get_priv_public_key()}) + PublicKeys.update(%PublicKey{}, %{value: get_public_key()}) end end end diff --git a/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs b/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs index a4eec02..a2b9d14 100644 --- a/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs @@ -26,7 +26,7 @@ defmodule ResourceManager.Identities.Commands.CreateIdentityTest do input = %{ name: "my-client-application", description: "App for tests", - public_key: get_priv_public_key(), + public_key: get_public_key(), scopes: ctx.scopes } diff --git a/apps/resource_manager/test/support/factory.ex b/apps/resource_manager/test/support/factory.ex index 5e16ebf..d13ab9b 100644 --- a/apps/resource_manager/test/support/factory.ex +++ b/apps/resource_manager/test/support/factory.ex @@ -8,7 +8,8 @@ defmodule ResourceManager.Factory do @default_password "My-passw@rd123" - @doc false + @doc "Builds a default struct from the requested model" + @spec build(model :: atom()) :: struct() def build(:user) do %User{ username: "my-test-username#{System.unique_integer()}", @@ -37,7 +38,7 @@ defmodule ResourceManager.Factory do def build(:public_key) do %PublicKey{ - value: get_priv_public_key() + value: get_public_key() } end @@ -62,43 +63,60 @@ defmodule ResourceManager.Factory do } end - @doc false + @doc "Returns the a model struct with the given attributes" + @spec build(factory_name :: atom(), attributes :: Keyword.t()) :: struct() def build(factory_name, attributes) when is_atom(factory_name) and is_list(attributes) do factory_name |> build() |> struct!(attributes) end - @doc false + @doc "Inserts a model with the given attributes on database" + @spec insert!(factory_name :: atom(), attributes :: Keyword.t()) :: struct() def insert!(factory_name, attributes \\ []) when is_atom(factory_name) do factory_name |> build(attributes) |> Repo.insert!() end - @doc false + @doc "Inserts a list of the given model on database" + @spec insert_list!( + factory_name :: atom(), + count :: integer(), + attributes :: Keyword.t() + ) :: list(struct()) def insert_list!(factory_name, count \\ 10, attributes \\ []) when is_atom(factory_name), do: Enum.map(0..count, fn _ -> insert!(factory_name, attributes) end) - @doc false + @doc "Returns the given password hashed using the selected algorithm" + @spec gen_hashed_password( + password :: String.t(), + algorithm :: :argon2 | :bcrypt | :pbkdf2 + ) :: String.t() def gen_hashed_password(password \\ @default_password, alg \\ :argon2) - def gen_hashed_password(password, :argon2), do: Argon2.hash_pwd_salt(password) - def gen_hashed_password(password, :bcrypt), do: Bcrypt.hash_pwd_salt(password) - def gen_hashed_password(password, :pbkdf2), do: Pbkdf2.hash_pwd_salt(password) - - @doc false - def get_priv_public_key do - :resource_manager - |> :code.priv_dir() - |> Path.join("/keys/resource_manager_key.pub") + + def gen_hashed_password(password, :argon2) when is_binary(password), + do: Argon2.hash_pwd_salt(password) + + def gen_hashed_password(password, :bcrypt) when is_binary(password), + do: Bcrypt.hash_pwd_salt(password) + + def gen_hashed_password(password, :pbkdf2) when is_binary(password), + do: Pbkdf2.hash_pwd_salt(password) + + @doc "Returns the mocked public key" + @spec get_public_key() :: String.t() + def get_public_key do + File.cwd!() + |> Path.join("/test/support/mocks/keys/public_key.pub") |> File.read!() end - @doc false - def get_priv_private_key do - :resource_manager - |> :code.priv_dir() - |> Path.join("/keys/resource_manager_key.pem") + @doc "Returns the mocked private key" + @spec get_private_key() :: String.t() + def get_private_key do + File.cwd!() + |> Path.join("/test/support/mocks/keys/private_key.pem") |> File.read!() end end diff --git a/apps/resource_manager/priv/keys/resource_manager_key.pem b/apps/resource_manager/test/support/mocks/keys/private_key.pem similarity index 100% rename from apps/resource_manager/priv/keys/resource_manager_key.pem rename to apps/resource_manager/test/support/mocks/keys/private_key.pem diff --git a/apps/resource_manager/priv/keys/resource_manager_key.pub b/apps/resource_manager/test/support/mocks/keys/public_key.pub similarity index 100% rename from apps/resource_manager/priv/keys/resource_manager_key.pub rename to apps/resource_manager/test/support/mocks/keys/public_key.pub diff --git a/apps/rest_api/test/support/conn_case.ex b/apps/rest_api/test/support/conn_case.ex index 817bb80..cdfccba 100644 --- a/apps/rest_api/test/support/conn_case.ex +++ b/apps/rest_api/test/support/conn_case.ex @@ -16,15 +16,15 @@ defmodule RestAPI.ConnCase do """ use ExUnit.CaseTemplate + alias Ecto.Adapters.SQL.Sandbox using do quote do # Import conveniences for testing with connections import Plug.Conn import Phoenix.ConnTest - import RestAPI.ConnCase + import RestAPI.{ConnCase, Factory} import Mox - import ResourceManager.Factory alias RestAPI.Router.Helpers, as: Routes @@ -33,7 +33,13 @@ defmodule RestAPI.ConnCase do end end - setup _tags do + setup tags do + :ok = Sandbox.checkout(ResourceManager.Repo) + + unless tags[:async] do + Sandbox.mode(ResourceManager.Repo, {:shared, self()}) + end + {:ok, conn: Phoenix.ConnTest.build_conn()} end end diff --git a/apps/rest_api/test/support/factory.ex b/apps/rest_api/test/support/factory.ex new file mode 100644 index 0000000..8f787db --- /dev/null +++ b/apps/rest_api/test/support/factory.ex @@ -0,0 +1,32 @@ +defmodule RestAPI.Factory do + @moduledoc false + + alias ResourceManager.Identities.Schemas.User + alias ResourceManager.Repo + + @doc "Builds a default struct from the requested model" + @spec build(model :: atom()) :: struct() + def build(:user) do + %User{ + username: "my-test-username#{System.unique_integer()}", + status: "active", + is_admin: false + } + end + + @doc "Returns the a model struct with the given attributes" + @spec build(factory_name :: atom(), attributes :: Keyword.t()) :: struct() + def build(factory_name, attributes) when is_atom(factory_name) and is_list(attributes) do + factory_name + |> build() + |> struct!(attributes) + end + + @doc "Inserts a model with the given attributes on database" + @spec insert!(factory_name :: atom(), attributes :: Keyword.t()) :: struct() + def insert!(factory_name, attributes \\ []) when is_atom(factory_name) do + factory_name + |> build(attributes) + |> Repo.insert!() + end +end diff --git a/mix.exs b/mix.exs index 7349c03..c7918ea 100644 --- a/mix.exs +++ b/mix.exs @@ -52,8 +52,7 @@ defmodule WatcherEx.MixProject do main: "WatcherEx", extras: ["README.md"], deps: [ - ecto_sql: "https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.html", - argon2_elixir: "https://hexdocs.pm/argon2_elixir/api-reference.html" + ecto_sql: "https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.html" ] ] end @@ -61,8 +60,8 @@ defmodule WatcherEx.MixProject do defp dialyzer do [ plt_add_apps: [:ex_unit], - plt_core_path: "_build/plts", - plt_file: {:no_warn, "_build/plts/watcher_ex.plt"} + plt_core_path: "dialyzer/", + plt_file: {:no_warn, "_dialyzer/watcher_ex.plt"} ] end From db6f01304fc29c67bd98050567923fe4069a213d Mon Sep 17 00:00:00 2001 From: Yashin Santos Date: Tue, 8 Dec 2020 20:39:31 -0300 Subject: [PATCH 3/5] feat: improve tests --- .../test/controllers/admin/user_test.exs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/apps/rest_api/test/controllers/admin/user_test.exs b/apps/rest_api/test/controllers/admin/user_test.exs index eba9d39..3a4ff00 100644 --- a/apps/rest_api/test/controllers/admin/user_test.exs +++ b/apps/rest_api/test/controllers/admin/user_test.exs @@ -219,6 +219,40 @@ defmodule RestAPI.Controllers.Admin.User do |> get(@show_endpoint <> "username") |> json_response(201) end + + test "if id is not an id, should return error", %{ + conn: conn, + access_token: access_token, + claims: claims + } do + expect(AuthenticatorMock, :validate_access_token, fn token -> + assert access_token == token + {:ok, claims} + end) + + expect(AuthenticatorMock, :get_session, fn %{"jti" => jti} -> + assert claims["jti"] == jti + {:ok, success_session(claims)} + end) + + expect(AuthorizerMock, :authorize_admin, fn %Plug.Conn{} -> :ok end) + + expect(ResourceManagerMock, :get_identity, fn input -> + assert is_map(input) + + {:error, {:error, :invalid_params}} + end) + + assert %{ + "detail" => "The given parameters are invalid", + "error" => "bad_request", + "status" => 400 + } = + conn + |> put_req_header("authorization", "Bearer #{access_token}") + |> get(@show_endpoint <> "1") + |> json_response(400) + end end defp default_claims do From 0ca451ebce2be3799d3baf50b601ae27be20e071 Mon Sep 17 00:00:00 2001 From: Yashin Santos Date: Tue, 8 Dec 2020 21:14:17 -0300 Subject: [PATCH 4/5] feat: add ExUnit.CLIFormatter to show test output --- apps/authenticator/test/test_helper.exs | 2 +- apps/authorizer/test/test_helper.exs | 2 +- apps/resource_manager/test/test_helper.exs | 2 +- apps/rest_api/test/test_helper.exs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/authenticator/test/test_helper.exs b/apps/authenticator/test/test_helper.exs index a03bca7..559d899 100644 --- a/apps/authenticator/test/test_helper.exs +++ b/apps/authenticator/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.configure(formatters: [JUnitFormatter]) +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Authenticator.Repo, :manual) diff --git a/apps/authorizer/test/test_helper.exs b/apps/authorizer/test/test_helper.exs index 9f0e873..3140500 100644 --- a/apps/authorizer/test/test_helper.exs +++ b/apps/authorizer/test/test_helper.exs @@ -1,2 +1,2 @@ -ExUnit.configure(formatters: [JUnitFormatter]) +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) ExUnit.start() diff --git a/apps/resource_manager/test/test_helper.exs b/apps/resource_manager/test/test_helper.exs index 285fb60..c6fd043 100644 --- a/apps/resource_manager/test/test_helper.exs +++ b/apps/resource_manager/test/test_helper.exs @@ -1,4 +1,4 @@ -ExUnit.configure(formatters: [JUnitFormatter]) +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(ResourceManager.Repo, :manual) diff --git a/apps/rest_api/test/test_helper.exs b/apps/rest_api/test/test_helper.exs index 9f0e873..3140500 100644 --- a/apps/rest_api/test/test_helper.exs +++ b/apps/rest_api/test/test_helper.exs @@ -1,2 +1,2 @@ -ExUnit.configure(formatters: [JUnitFormatter]) +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) ExUnit.start() From 8cb0b42c0396bab4ed3d6b9f07e36aa3df6c334a Mon Sep 17 00:00:00 2001 From: Yashin Santos Date: Tue, 8 Dec 2020 21:17:17 -0300 Subject: [PATCH 5/5] fix: typespec --- apps/rest_api/lib/ports/resource_manager.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/rest_api/lib/ports/resource_manager.ex b/apps/rest_api/lib/ports/resource_manager.ex index b168053..89c1901 100644 --- a/apps/rest_api/lib/ports/resource_manager.ex +++ b/apps/rest_api/lib/ports/resource_manager.ex @@ -16,14 +16,14 @@ defmodule RestAPI.Ports.ResourceManager do @doc "Delegates to ResourceManager.password_allowed?/1" @callback password_allowed?(password :: String.t()) :: boolean() - @callback get_identity(input :: String.t()) :: possible_get_identity_responses() + @callback get_identity(input :: map()) :: possible_get_identity_responses() @doc "Create a new identity with it's credentials" @spec create_identity(input :: map()) :: possible_create_identity_response() def create_identity(input), do: implementation().create_identity(input) @doc "Returns an user or application identity seaching by the given input" - @spec get_identity(input :: String.t()) :: possible_get_identity_responses() + @spec get_identity(input :: map()) :: possible_get_identity_responses() def get_identity(input), do: implementation().get_identity(input) @doc "Checks if the given password is strong enough to be used"