Skip to content

Commit

Permalink
Implement unit tests for the public API (#8)
Browse files Browse the repository at this point in the history
* test: admin api tests

* add tests for gotrue

* gosh mox doesnt handle concurrency good
  • Loading branch information
zoedsoupe authored Feb 15, 2025
1 parent 097d055 commit c4ce6ba
Show file tree
Hide file tree
Showing 22 changed files with 1,467 additions and 75 deletions.
59 changes: 32 additions & 27 deletions lib/supabase/go_true.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule Supabase.GoTrue do
"""

alias Supabase.Client
alias Supabase.GoTrue.Schemas.ResendParams
alias Supabase.GoTrue.Schemas.SignInWithIdToken
alias Supabase.GoTrue.Schemas.SignInWithOauth
alias Supabase.GoTrue.Schemas.SignInWithOTP
Expand Down Expand Up @@ -53,12 +54,13 @@ defmodule Supabase.GoTrue do
## Examples
iex> credentials = %Supabase.GoTrue.SignInWithIdToken{}
iex> Supabase.GoTrue.sign_in_with_id_token(pid | client_name, credentials)
{:ok, %Supabase.GoTrue.User{}}
{:ok, %Supabase.GoTrue.Session{}}
"""
@impl true
def sign_in_with_id_token(%Client{} = client, credentials) do
with {:ok, credentials} <- SignInWithIdToken.parse(credentials) do
UserHandler.sign_in_with_id_token(client, credentials)
with {:ok, credentials} <- SignInWithIdToken.parse(credentials),
{:ok, resp} <- UserHandler.sign_in_with_id_token(client, credentials) do
Session.parse(resp.body)
end
end

Expand All @@ -76,7 +78,7 @@ defmodule Supabase.GoTrue do
"""
@impl true
def sign_in_with_oauth(%Client{} = client, credentials) do
with{:ok, credentials} <- SignInWithOauth.parse(credentials) do
with {:ok, credentials} <- SignInWithOauth.parse(credentials) do
url = UserHandler.get_url_for_provider(client, credentials)
{:ok, credentials.provider, url}
end
Expand All @@ -92,11 +94,11 @@ defmodule Supabase.GoTrue do
## Examples
iex> credentials = %Supabase.GoTrue.SignInWithOTP{}
iex> Supabase.GoTrue.sign_in_with_otp(pid | client_name, credentials)
{:ok, %Supabase.GoTrue.Session{}}
:ok
"""
@impl true
def sign_in_with_otp(%Client{} = client, credentials) do
with{:ok, credentials} <- SignInWithOTP.parse(credentials) do
with {:ok, credentials} <- SignInWithOTP.parse(credentials) do
UserHandler.sign_in_with_otp(client, credentials)
end
end
Expand All @@ -115,7 +117,7 @@ defmodule Supabase.GoTrue do
"""
@impl true
def verify_otp(%Client{} = client, params) do
with{:ok, response} <- UserHandler.verify_otp(client, params) do
with {:ok, response} <- UserHandler.verify_otp(client, params) do
Session.parse(response.body)
end
end
Expand All @@ -130,11 +132,11 @@ defmodule Supabase.GoTrue do
## Examples
iex> credentials = %Supabase.GoTrue.SignInWithSSO{}
iex> Supabase.GoTrue.sign_in_with_sso(pid | client_name, credentials)
{:ok, %Supabase.GoTrue.User{}}
{:ok, %Supabase.GoTrue.Session{}}
"""
@impl true
def sign_in_with_sso(%Client{} = client, credentials) do
with{:ok, credentials} <- SignInWithSSO.parse(credentials) do
with {:ok, credentials} <- SignInWithSSO.parse(credentials) do
UserHandler.sign_in_with_sso(client, credentials)
end
end
Expand All @@ -153,7 +155,7 @@ defmodule Supabase.GoTrue do
"""
@impl true
def sign_in_with_password(%Client{} = client, credentials) do
with{:ok, credentials} <- SignInWithPassword.parse(credentials),
with {:ok, credentials} <- SignInWithPassword.parse(credentials),
{:ok, response} <- UserHandler.sign_in_with_password(client, credentials) do
Session.parse(response.body)
end
Expand Down Expand Up @@ -192,14 +194,17 @@ defmodule Supabase.GoTrue do
iex> Supabase.GoTrue.reset_password_for_email(client, "[email protected]", redirect_to: "http://localohst:4000/reset-pass")
:ok
"""
@spec reset_password_for_email(Client.t(), String.t, opts) :: :ok | {:error, term}
when opts: [redirect_to: String.t] | [captcha_token: String.t] | [redirect_to: String.t, captcha_token: String.t]
@spec reset_password_for_email(Client.t(), String.t(), opts) :: :ok | {:error, term}
when opts:
[redirect_to: String.t()]
| [captcha_token: String.t()]
| [redirect_to: String.t(), captcha_token: String.t()]
def reset_password_for_email(%Client{} = client, email, opts) do
UserHandler.recover_password(client, email, Map.new(opts))
UserHandler.recover_password(client, email, Map.new(opts))
end

@doc """
Resends a signuo confirm email for the given email address.
Resends a signup confirm email for the given email address.
## Parameters
- `client` - The `Supabase` client to use for the request.
Expand All @@ -212,10 +217,11 @@ defmodule Supabase.GoTrue do
iex> Supabase.GoTrue.resend(client, "[email protected]", redirect_to: "http://localohst:4000/reset-pass")
:ok
"""
@spec resend(Client.t(), String.t, opts) :: :ok | {:error, term}
when opts: [redirect_to: String.t] | [captcha_token: String.t] | [redirect_to: String.t, captcha_token: String.t]
@spec resend(Client.t(), String.t(), ResendParams.t()) :: :ok | {:error, term}
def resend(%Client{} = client, email, opts) do
UserHandler.resend_signup(client, email, Map.new(opts))
with {:ok, params} <- ResendParams.parse(Map.new(opts)) do
UserHandler.resend(client, email, params)
end
end

@doc """
Expand All @@ -231,15 +237,15 @@ defmodule Supabase.GoTrue do
iex> Supabase.GoTrue.update_user(client, conn, params)
{:ok, conn}
"""
@spec update_user(Client.t, conn, UserParams.t) :: {:ok, conn} | {:error, term}
when conn: Plug.Conn.t | Phoenix.LiveView.Socket.t
@spec update_user(Client.t(), conn, UserParams.t()) :: {:ok, conn} | {:error, term}
when conn: Plug.Conn.t() | Phoenix.LiveView.Socket.t()
def update_user(%Client{} = client, conn, attrs) do
with{:ok, params} <- UserParams.parse(attrs) do
if conn.assigns.current_user do
UserHandler.update_user(client, conn, params)
else
{:error, :no_user_logged_in}
end
with {:ok, params} <- UserParams.parse(attrs) do
if conn.assigns.current_user do
UserHandler.update_user(client, conn, params)
else
{:error, :no_user_logged_in}
end
end
end

Expand All @@ -248,8 +254,7 @@ defmodule Supabase.GoTrue do
Check https://hexdocs.pm/supabase_gotrue/readme.html#usage
"""
def get_auth_module! do

Application.get_env(:supabase_gotrue, :auth_module) ||
raise(Supabase.GoTrue.MissingConfig, key: :auth_module)
raise(Supabase.GoTrue.MissingConfig, key: :auth_module)
end
end
14 changes: 7 additions & 7 deletions lib/supabase/go_true/admin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ defmodule Supabase.GoTrue.Admin do
"""
@impl true
def sign_out(%Client{} = client, %Session{} = session, scope) when scope in @scopes do
case AdminHandler.sign_out(client, session.access_token, scope) do
{:ok, _} -> :ok
{:error, %{code: :not_found}} -> :ok
{:error, %{code: :unauthorized}} -> :ok
err -> err
end
case AdminHandler.sign_out(client, session.access_token, scope) do
{:ok, _} -> :ok
{:error, %{code: :not_found}} -> :ok
{:error, %{code: :unauthorized}} -> :ok
err -> err
end
end

@doc """
Expand Down Expand Up @@ -144,7 +144,7 @@ defmodule Supabase.GoTrue.Admin do
"""
@impl true
def list_users(%Client{} = client, params \\ %{}) do
with {:ok, params} <- PaginationParams.page_params(params),
with {:ok, params} <- PaginationParams.page_params(Map.new(params)),
{:ok, response} <- AdminHandler.list_users(client, params),
{:ok, users} <- User.parse_list(response.body["users"]) do
total = Response.get_header(response, "x-total-count")
Expand Down
17 changes: 17 additions & 0 deletions lib/supabase/go_true/pkce.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,30 @@ defmodule Supabase.GoTrue.PKCE do

@verifier_length 56

@doc """
Generate a random string of a fized 56 length to be used as the
code verifier.
## Examples
iex> Supabase.GoTrue.PKCE.generate_verifier()
"""
def generate_verifier do
@verifier_length
|> :crypto.strong_rand_bytes()
|> Base.url_encode64(padding: false)
|> String.slice(0, @verifier_length)
end

@doc """
Generate a challenge from a verifier. The challenge is used to
verify the verifier when exchanging the code.
## Examples
iex> verifier = Supabase.GoTrue.PKCE.generate_verifier()
iex> Supabase.GoTrue.PKCE.generate_challenge(verifier)
"""
def generate_challenge(verifier) do
:sha256
|> :crypto.hash(verifier)
Expand Down
13 changes: 5 additions & 8 deletions lib/supabase/go_true/schemas/invite_user_params.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defmodule Supabase.GoTrue.Schemas.InviteUserParams do

embedded_schema do
field(:data, :map)
field(:redirect_to, :map)
field(:redirect_to, :string)
end

def parse(attrs) do
Expand All @@ -31,13 +31,10 @@ defmodule Supabase.GoTrue.Schemas.InviteUserParams do
defp parse_uri(changeset) do
redirect_to = get_change(changeset, :redirect_to)

if redirect_to do
case URI.new(redirect_to) do
{:ok, uri} -> put_change(changeset, :redirect_to, uri)
{:error, reason} -> add_error(changeset, :redirect_to, "Invalid URI: #{reason}")
end
else
changeset
cond do
is_nil(redirect_to) -> changeset
not is_binary(redirect_to) -> add_error(changeset, :redirect_to, "needs to be a binary")
true -> put_change(changeset, :redirect_to, URI.parse(redirect_to) |> URI.to_string())
end
end
end
2 changes: 1 addition & 1 deletion lib/supabase/go_true/schemas/pagination_params.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule Supabase.GoTrue.Schemas.PaginationParams do
def pagination(attrs) do
schema = %{next_page: :integer, last_page: :integer, total: :integer}

{%{}, schema}
{%{next_page: nil}, schema}
|> cast(attrs, Map.keys(schema))
|> validate_required([:total, :last_page])
|> apply_action(:parse)
Expand Down
47 changes: 47 additions & 0 deletions lib/supabase/go_true/schemas/resend_params.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule Supabase.GoTrue.Schemas.ResendParams do
@moduledoc """
This schema is used to validate and parse the parameters for resending a confirmation email.
"""

use Ecto.Schema

import Ecto.Changeset

import Supabase.GoTrue.Validations

@type otp_type :: :sms | :signup | :phone_change | :email_change

@type options :: %__MODULE__.Options{
email_redirect_to: URI.t() | nil,
captcha_token: String.t() | nil
}

@type t :: %__MODULE__{
type: otp_type,
options: options
}

@derive Jason.Encoder
@primary_key false
embedded_schema do
field(:type, Ecto.Enum, values: ~w[sms signup phone_change email_change]a)

embeds_one :options, Options, primary_key: false do
field(:email_redirect_to, :string)
field(:captcha_token, :string)
end
end

def parse(source \\ %__MODULE__{}, %{} = attrs) do
source
|> cast(attrs, [:type])
|> cast_embed(:options, with: &options_changeset/2, required: false)
|> validate_required_inclusion([:type])
|> apply_action(:insert)
end

defp options_changeset(changeset, :options) do
%__MODULE__.Options{}
|> cast(changeset.params["options"], [:email_redirect_to, :captcha_token])
end
end
16 changes: 8 additions & 8 deletions lib/supabase/go_true/schemas/sign_in_request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ defmodule Supabase.GoTrue.Schemas.SignInRequest do
field(:access_token, :string)
field(:nonce, :string)
field(:id_token, :string)
field :provider_id, :string
field :domain, :string
field :create_user, :boolean
field :redirect_to, :string
field :channel, :string
field :data, :map, default: %{}
field(:provider_id, :string)
field(:domain, :string)
field(:create_user, :boolean)
field(:redirect_to, :string)
field(:channel, :string)
field(:data, :map, default: %{})
field(:code_challenge, :string)
field(:code_challenge_method, :string)

Expand Down Expand Up @@ -64,7 +64,7 @@ defmodule Supabase.GoTrue.Schemas.SignInRequest do
%__MODULE__{}
|> cast(attrs, [:provider_id, :domain])
|> put_embed(:gotrue_meta_security, gotrue_meta, required: true)
|> validate_required_inclusion([:provider, :domain])
|> validate_required_inclusion([:provider_id, :domain])
|> apply_action(:insert)
end

Expand All @@ -86,7 +86,7 @@ defmodule Supabase.GoTrue.Schemas.SignInRequest do
%__MODULE__{}
|> cast(attrs, [:provider_id, :domain])
|> put_embed(:gotrue_meta_security, gotrue_meta, required: true)
|> validate_required_inclusion([:provider, :domain])
|> validate_required_inclusion([:provider_id, :domain])
|> apply_action(:insert)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/supabase/go_true/schemas/sign_in_with_id_token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule Supabase.GoTrue.Schemas.SignInWithIdToken do
import Ecto.Changeset

@type t :: %__MODULE__{
provider: :google | :apple | :azure | :facebook,
provider: :google | :apple | :azure | :facebook | :kakao,
token: String.t(),
access_token: String.t() | nil,
nonce: String.t() | nil,
Expand All @@ -27,7 +27,7 @@ defmodule Supabase.GoTrue.Schemas.SignInWithIdToken do
| nil
}

@providers ~w[google apple azure facebook]a
@providers ~w[google apple azure facebook kakao]a

embedded_schema do
field(:provider, Ecto.Enum, values: @providers)
Expand Down
4 changes: 2 additions & 2 deletions lib/supabase/go_true/schemas/sign_up_with_password.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule Supabase.GoTrue.Schemas.SignUpWithPassword do
@moduledoc """
@moduledoc """
This schema is used to validate and parse the parameters for signing up with a password.
"""

Expand Down Expand Up @@ -29,7 +29,7 @@ defmodule Supabase.GoTrue.Schemas.SignUpWithPassword do
field(:phone, :string)

embeds_one :options, Options, primary_key: false do
field(:email_redirect_to, :map)
field(:email_redirect_to, :string)
field(:data, :map)
field(:captcha_token, :string)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/supabase/go_true/session.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule Supabase.GoTrue.Session do
field(:access_token, :string)
field(:refresh_token, :string)
field(:expires_in, :integer)
field(:expires_at, :integer)
field(:expires_at, :utc_datetime)
field(:token_type, :string)

embeds_one(:user, User)
Expand Down
5 changes: 3 additions & 2 deletions lib/supabase/go_true/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ defmodule Supabase.GoTrue.User do
factors: list(Factor) | nil
}

@required_fields ~w[id app_metadata app_metadata aud created_at]a
@required_fields ~w[id app_metadata aud created_at]a
@optional_fields ~w[confirmation_sent_at recovery_sent_at email_change_sent_at new_email new_phone invited_at action_link email phone confirmed_at email_confirmed_at phone_confirmed_at last_sign_in_at role]a

@derive Jason.Encoder
@primary_key {:id, :binary_id, autogenerate: false}
embedded_schema do
field(:app_metadata, :map)
Expand All @@ -82,7 +83,7 @@ defmodule Supabase.GoTrue.User do
field(:email_confirmed_at, :naive_datetime)
field(:phone_confirmed_at, :naive_datetime)
field(:last_sign_in_at, :naive_datetime)
field :encrypted_password, :string
field(:encrypted_password, :string)
field(:role, :string)

embeds_many(:factors, Supabase.GoTrue.User.Factor)
Expand Down
Loading

0 comments on commit c4ce6ba

Please sign in to comment.