Skip to content

Commit

Permalink
feat: add slots related unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joaodiaslobo committed Feb 3, 2024
1 parent ae9203d commit a669e9f
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 17 deletions.
11 changes: 6 additions & 5 deletions data/slots.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
probability,multiplier
0.00005,10.0
0.0001,7.0
0.003,3.0
0.009,2.0
0.05,1.0
0.3,1.0
0.08,2.0
0.04,3.0
0.025,5.0
0.00499,10.0
0.00001,100.0
2 changes: 2 additions & 0 deletions lib/safira/slots/payout.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ defmodule Safira.Slots.Payout do
payout
|> cast(attrs, [:probability, :multiplier])
|> validate_required([:probability, :multiplier])
|> validate_number(:multiplier, greater_than: 0)
|> validate_number(:probability, greater_than: 0, less_than_or_equal_to: 1)
end
end
10 changes: 7 additions & 3 deletions lib/safira/slots/slots.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
defmodule Safira.Slots do
@moduledoc """
The Slots context.
"""

import Ecto.Query, warn: false

alias Ecto.Multi

alias Safira.Repo

alias Safira.Accounts.Attendee
alias Safira.Contest
alias Safira.Contest.DailyToken
alias Safira.Slots.Payout
alias Safira.Accounts.Attendee
alias Safira.Slots.AttendeePayout
alias Safira.Slots.Payout

@doc """
Creates a payout.
Expand All @@ -32,7 +36,7 @@ defmodule Safira.Slots do
def spin(attendee, bet) do
spin_transaction(attendee, bet)
|> case do
{:error, :attendee, changeset, data} ->
{:error, :attendee_state, changeset, data} ->
if Map.get(get_errors(changeset), :token_balance) != nil do
{:error, :not_enough_tokens}
else
Expand Down
24 changes: 15 additions & 9 deletions lib/safira_web/controllers/slots/slots_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ defmodule SafiraWeb.SlotsController do
if is_nil(attendee) do
conn
|> put_status(:unauthorized)
|> json(%{error: "Only attendees can spin the wheel"})
|> json(%{error: "Only attendees can play the slots"})
else
case Integer.parse(bet) do
{bet, _} ->
case Slots.spin(attendee, bet) do
{:ok, outcome} ->
render(conn, :spin_result, outcome)
{bet, ""} ->
if bet > 0 do
case Slots.spin(attendee, bet) do
{:ok, outcome} ->
render(conn, :spin_result, outcome)

{:error, :not_enough_tokens} ->
conn
|> put_status(:unauthorized)
|> json(%{error: "Insufficient token balance"})
{:error, :not_enough_tokens} ->
conn
|> put_status(:unauthorized)
|> json(%{error: "Insufficient token balance"})
end
else
conn
|> put_status(:bad_request)
|> json(%{error: "Bet should be a positive integer"})
end

_ ->
Expand Down
2 changes: 2 additions & 0 deletions lib/safira_web/controllers/slots/slots_json.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule SafiraWeb.SlotsJSON do
@moduledoc false

def spin_result(data) do
payout = Map.get(data, :payout)
tokens = Map.get(data, :tokens)
Expand Down
28 changes: 28 additions & 0 deletions test/factories/slots_factory.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule Safira.SlotsFactory do
@moduledoc """
A factory to build all slots related structs
"""
defmacro __using__(_opts) do
quote do
def attendee_payout_factory do
payout = build(:payout)
bet = Enum.random(1..100)
tokens = (payout.multiplier * bet) |> round()

%Safira.Slots.AttendeePayout{
attendee: build(:attendee),
payout: payout,
bet: bet,
tokens: tokens
}
end

def payout_factory do
%Safira.Slots.Payout{
probability: 0.5,
multiplier: Enum.random(1..10) / 1
}
end
end
end
end
40 changes: 40 additions & 0 deletions test/safira/slots_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule Safira.SlotsTest do
use Safira.DataCase
alias Safira.Slots

describe "payouts" do
alias Safira.Slots.Payout

@valid_attrs %{
multiplier: 10.0,
probability: 0.42
}

@invalid_attrs1 %{
multiplier: -1.0,
probability: 0.42
}

@invalid_attrs2 %{
multiplier: 10.0,
probability: 2.0
}

@invalid_attrs3 %{
multiplier: 10.0,
probability: -2.0
}

test "create_payout/1 with valid data creates a payout" do
assert {:ok, %Payout{} = payout} = Slots.create_payout(@valid_attrs)
assert payout.multiplier == 10.0
assert payout.probability == 0.42
end

test "create_payout/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Slots.create_payout(@invalid_attrs1)
assert {:error, %Ecto.Changeset{}} = Slots.create_payout(@invalid_attrs2)
assert {:error, %Ecto.Changeset{}} = Slots.create_payout(@invalid_attrs3)
end
end
end
81 changes: 81 additions & 0 deletions test/safira_web/controllers/slots_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
defmodule SafiraWeb.SlotsControllerTest do
use SafiraWeb.ConnCase

describe "spin" do
test "with valid bet (attendee with enough tokens)" do
user = create_user_strategy(:user)
insert(:attendee, user: user, token_balance: 1000)
payout = create_payout_strategy(:payout, probability: 1.0)
bet = Enum.random(1..100)
%{conn: conn, user: _user} = api_authenticate(user)

conn =
conn
|> post(Routes.slots_path(conn, :spin, bet: bet))
|> doc()

assert json_response(conn, 200) == %{
"multiplier" => payout.multiplier,
"tokens" => (bet * payout.multiplier) |> round()
}
end

test "with valid bet (attendee without enough tokens)" do
user = create_user_strategy(:user)
insert(:attendee, user: user, token_balance: 0)
create_payout_strategy(:payout, probability: 1.0)
%{conn: conn, user: _user} = api_authenticate(user)

conn =
conn
|> post(Routes.slots_path(conn, :spin, bet: 100))

assert json_response(conn, 401) == %{
"error" => "Insufficient token balance"
}
end

test "with invalid bet (float value)" do
user = create_user_strategy(:user)
insert(:attendee, user: user)
create_payout_strategy(:payout, probability: 1.0)
%{conn: conn, user: _user} = api_authenticate(user)

conn =
conn
|> post(Routes.slots_path(conn, :spin, bet: 1.2))

assert json_response(conn, 400) == %{
"error" => "Bet should be an integer"
}
end

test "with invalid bet (negative value)" do
user = create_user_strategy(:user)
insert(:attendee, user: user)
create_payout_strategy(:payout, probability: 1.0)
%{conn: conn, user: _user} = api_authenticate(user)

conn =
conn
|> post(Routes.slots_path(conn, :spin, bet: -1))

assert json_response(conn, 400) == %{
"error" => "Bet should be a positive integer"
}
end

test "when user is not an attendee" do
user = create_user_strategy(:user)
insert(:staff, user: user)
create_payout_strategy(:payout, probability: 1.0)
%{conn: conn, user: _user} = api_authenticate(user)

conn =
conn
|> post(Routes.slots_path(conn, :spin, bet: 100))

assert json_response(conn, 401)["error"] == "Only attendees can play the slots"
end
end
end
16 changes: 16 additions & 0 deletions test/strategies/payout_strategy.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Safira.PayoutStrategy do
@moduledoc """
ExMachina strategy for creating payouts
"""
use ExMachina.Strategy, function_name: :create_payout_strategy

def handle_create_payout_strategy(record, _opts) do
{:ok, payout} =
Safira.Slots.create_payout(%{
probability: record.probability,
multiplier: record.multiplier
})

payout
end
end
2 changes: 2 additions & 0 deletions test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ defmodule Safira.Factory do

use Safira.UserStrategy
use Safira.PrizeStrategy
use Safira.PayoutStrategy

use Safira.AccountsFactory
use Safira.ContestFactory
use Safira.StoreFactory
use Safira.SlotsFactory
use Safira.RouletteFactory
use Safira.InteractionFactory
end

0 comments on commit a669e9f

Please sign in to comment.