exercism/elixir/take-a-number-deluxe/test/take_a_number_deluxe_test.exs

401 lines
13 KiB
Elixir

defmodule TakeANumberDeluxeTest do
use ExUnit.Case
alias TakeANumberDeluxe.Queue
describe "start_link/1" do
@tag task_id: 1
test "starts a new process" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert is_pid(pid)
assert pid != self()
assert Process.alive?(pid)
end
@tag task_id: 1
test "the process doesn't have a name" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert Process.info(pid, :registered_name) == {:registered_name, []}
end
@tag task_id: 1
test "min and max numbers get validated" do
Process.flag(:trap_exit, true)
assert TakeANumberDeluxe.start_link(min_number: 999, max_number: 99) ==
{:error, :invalid_configuration}
assert TakeANumberDeluxe.start_link(min_number: :not_a_number, max_number: 99) ==
{:error, :invalid_configuration}
assert TakeANumberDeluxe.start_link(min_number: 1, max_number: "not a number") ==
{:error, :invalid_configuration}
Process.flag(:trap_exit, false)
end
@tag task_id: 1
test "mix and max numbers can be passed in any order" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert is_pid(pid)
{:ok, pid} = TakeANumberDeluxe.start_link(max_number: 333, min_number: 100)
assert is_pid(pid)
end
@tag task_id: 1
test "the init/1 GenServer callback is defined" do
Code.ensure_loaded(TakeANumberDeluxe)
assert function_exported?(TakeANumberDeluxe, :init, 1)
end
end
describe "report_state/1" do
@tag task_id: 2
test "returns the state of the current process" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 4, max_number: 55)
{:ok, expected_state} = TakeANumberDeluxe.State.new(4, 55)
assert TakeANumberDeluxe.report_state(pid) == expected_state
end
@tag task_id: 2
test "different processes have different states" do
{:ok, pid1} = TakeANumberDeluxe.start_link(min_number: 4, max_number: 55)
{:ok, pid2} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 19)
assert TakeANumberDeluxe.report_state(pid1) != TakeANumberDeluxe.report_state(pid2)
end
@tag task_id: 2
test "the handle_call/3 GenServer callback is defined" do
Code.ensure_loaded(TakeANumberDeluxe)
assert function_exported?(TakeANumberDeluxe, :handle_call, 3)
end
end
describe "queue_new_number/1" do
@tag task_id: 3
test "returns the newly queued number" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 4, max_number: 55)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 4}
end
@tag task_id: 3
test "can queue multiple numbers" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 3}
end
@tag task_id: 3
test "returns an error when there are no more available numbers" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 3)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 3}
assert TakeANumberDeluxe.queue_new_number(pid) == {:error, :all_possible_numbers_are_in_use}
end
@tag task_id: 3
test "updates the state accordingly" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 3}
assert TakeANumberDeluxe.report_state(pid) ==
%TakeANumberDeluxe.State{
min_number: 1,
max_number: 99,
queue: Queue.new() |> Queue.push(1) |> Queue.push(2) |> Queue.push(3)
}
end
end
describe "serve_next_queued_number/1" do
@tag task_id: 4
test "returns the new number being served" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 100, max_number: 999)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 100}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 100}
end
@tag task_id: 4
test "can serve multiple numbers" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 3}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 3}
end
@tag task_id: 4
test "returns an error when there are no more queued" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 3)
assert TakeANumberDeluxe.serve_next_queued_number(pid) ==
{:error, :empty_queue}
end
@tag task_id: 4
test "accepts a priority number to skip the queue" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 3}
assert TakeANumberDeluxe.serve_next_queued_number(pid, 3) == {:ok, 3}
end
@tag task_id: 4
test "returns an error when the priority number is not in the queue" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 3)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.serve_next_queued_number(pid, 7) ==
{:error, :priority_number_not_found}
end
@tag task_id: 4
test "updates the state accordingly" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 3}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.report_state(pid) ==
%TakeANumberDeluxe.State{
min_number: 1,
max_number: 99,
queue: Queue.from_list([3])
}
end
end
describe "reset_state/1" do
@tag task_id: 5
test "returns :ok" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.reset_state(pid) == :ok
end
@tag task_id: 5
test "updates the state accordingly" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 7, max_number: 77)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 7}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 8}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 9}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 7}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 8}
assert TakeANumberDeluxe.reset_state(pid) == :ok
assert TakeANumberDeluxe.report_state(pid) ==
%TakeANumberDeluxe.State{
min_number: 7,
max_number: 77,
queue: Queue.new()
}
end
@tag task_id: 5
test "the handle_cast/2 GenServer callback is defined" do
Code.ensure_loaded(TakeANumberDeluxe)
assert function_exported?(TakeANumberDeluxe, :handle_cast, 2)
end
end
describe "auto shutdown handling unexpected messages" do
@tag task_id: 6
test "auto shutdown works after initializing" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "auto shutdown works after reporting state" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
assert TakeANumberDeluxe.report_state(pid)
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "auto shutdown works after taking a new number" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "auto shutdown works after an error when taking a new number" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 2,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:error, :all_possible_numbers_are_in_use}
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "auto shutdown works after serving the next queued number" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 1}
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "auto shutdown works after an error when serving the next queued number" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:error, :empty_queue}
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "resetting state preserves the auto shutdown timeout" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
{:ok, expected_state} = TakeANumberDeluxe.State.new(1, 99, timeout)
assert TakeANumberDeluxe.report_state(pid) == expected_state
assert TakeANumberDeluxe.reset_state(pid)
assert TakeANumberDeluxe.report_state(pid) == expected_state
end
@tag task_id: 6
test "auto shutdown works after resetting state" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
assert TakeANumberDeluxe.reset_state(pid) == :ok
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "unexpected messages do not affect the state" do
{:ok, pid} = TakeANumberDeluxe.start_link(min_number: 1, max_number: 99)
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 1}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 2}
assert TakeANumberDeluxe.queue_new_number(pid) == {:ok, 3}
assert TakeANumberDeluxe.serve_next_queued_number(pid) == {:ok, 1}
old_state = TakeANumberDeluxe.report_state(pid)
send(pid, {:hello, "there"})
assert TakeANumberDeluxe.report_state(pid) == old_state
end
@tag task_id: 6
test "auto shutdown works after handling unexpected messages" do
timeout = 50
{:ok, pid} =
TakeANumberDeluxe.start_link(
min_number: 1,
max_number: 99,
auto_shutdown_timeout: timeout
)
assert Process.alive?(pid)
send(pid, {:hello, "there"})
assert Process.alive?(pid)
:timer.sleep(timeout * 2)
refute Process.alive?(pid)
end
@tag task_id: 6
test "the handle_info/2 GenServer callback is defined" do
Code.ensure_loaded(TakeANumberDeluxe)
assert function_exported?(TakeANumberDeluxe, :handle_info, 2)
end
end
end