172 lines
5 KiB
Elixir
172 lines
5 KiB
Elixir
defmodule Towerops.ResultTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Towerops.Result
|
|
|
|
doctest Result
|
|
|
|
describe "map/2" do
|
|
test "applies function to success value" do
|
|
assert Result.map({:ok, 5}, fn x -> x * 2 end) == {:ok, 10}
|
|
assert Result.map({:ok, "hello"}, &String.upcase/1) == {:ok, "HELLO"}
|
|
end
|
|
|
|
test "passes through error unchanged" do
|
|
assert Result.map({:error, :not_found}, fn x -> x * 2 end) == {:error, :not_found}
|
|
assert Result.map({:error, "failure"}, &String.upcase/1) == {:error, "failure"}
|
|
end
|
|
|
|
test "works with complex transformations" do
|
|
result = {:ok, %{name: "John", age: 30}}
|
|
assert Result.map(result, fn user -> user.name end) == {:ok, "John"}
|
|
end
|
|
end
|
|
|
|
describe "map_error/2" do
|
|
test "applies function to error value" do
|
|
assert Result.map_error({:error, :timeout}, fn _ -> :network_error end) ==
|
|
{:error, :network_error}
|
|
|
|
assert Result.map_error({:error, "db_error"}, &String.upcase/1) == {:error, "DB_ERROR"}
|
|
end
|
|
|
|
test "passes through success unchanged" do
|
|
assert Result.map_error({:ok, 42}, fn _ -> :network_error end) == {:ok, 42}
|
|
assert Result.map_error({:ok, "success"}, &String.upcase/1) == {:ok, "success"}
|
|
end
|
|
|
|
test "works with complex error transformations" do
|
|
result = {:error, %{code: 500, message: "Internal Error"}}
|
|
|
|
assert Result.map_error(result, fn err -> err.code end) == {:error, 500}
|
|
end
|
|
end
|
|
|
|
describe "and_then/2" do
|
|
test "chains successful operations" do
|
|
result = {:ok, 5}
|
|
|
|
assert Result.and_then(result, fn x -> {:ok, x * 2} end) == {:ok, 10}
|
|
end
|
|
|
|
test "stops on first error" do
|
|
result = {:error, :not_found}
|
|
|
|
assert Result.and_then(result, fn x -> {:ok, x * 2} end) == {:error, :not_found}
|
|
end
|
|
|
|
test "propagates error from chained function" do
|
|
result = {:ok, 5}
|
|
|
|
assert Result.and_then(result, fn _ -> {:error, :invalid} end) == {:error, :invalid}
|
|
end
|
|
|
|
test "works with nested operations" do
|
|
result = {:ok, 10}
|
|
|
|
final_result =
|
|
result
|
|
|> Result.and_then(fn x -> {:ok, x + 5} end)
|
|
|> Result.and_then(fn x -> {:ok, x * 2} end)
|
|
|> Result.and_then(fn x -> {:ok, "Result: #{x}"} end)
|
|
|
|
assert final_result == {:ok, "Result: 30"}
|
|
end
|
|
|
|
test "short-circuits on error in chain" do
|
|
result = {:ok, 10}
|
|
|
|
final_result =
|
|
result
|
|
|> Result.and_then(fn x -> {:ok, x + 5} end)
|
|
|> Result.and_then(fn _ -> {:error, :failed} end)
|
|
|> Result.and_then(fn x -> {:ok, x * 2} end)
|
|
|
|
assert final_result == {:error, :failed}
|
|
end
|
|
end
|
|
|
|
describe "unwrap_or/2" do
|
|
test "returns success value when ok" do
|
|
assert Result.unwrap_or({:ok, 42}, 0) == 42
|
|
assert Result.unwrap_or({:ok, "hello"}, "default") == "hello"
|
|
end
|
|
|
|
test "returns default value on error" do
|
|
assert Result.unwrap_or({:error, :not_found}, 0) == 0
|
|
assert Result.unwrap_or({:error, "failure"}, "default") == "default"
|
|
end
|
|
|
|
test "works with nil values" do
|
|
assert Result.unwrap_or({:ok, nil}, "default") == nil
|
|
assert Result.unwrap_or({:error, :not_found}, nil) == nil
|
|
end
|
|
|
|
test "works with complex default values" do
|
|
default = %{name: "Unknown", age: 0}
|
|
assert Result.unwrap_or({:error, :not_found}, default) == default
|
|
end
|
|
end
|
|
|
|
describe "ok?/1" do
|
|
test "returns true for success" do
|
|
assert Result.ok?({:ok, 42}) == true
|
|
assert Result.ok?({:ok, nil}) == true
|
|
assert Result.ok?({:ok, []}) == true
|
|
end
|
|
|
|
test "returns false for error" do
|
|
assert Result.ok?({:error, :not_found}) == false
|
|
assert Result.ok?({:error, "failure"}) == false
|
|
assert Result.ok?({:error, nil}) == false
|
|
end
|
|
end
|
|
|
|
describe "error?/1" do
|
|
test "returns true for error" do
|
|
assert Result.error?({:error, :not_found}) == true
|
|
assert Result.error?({:error, "failure"}) == true
|
|
assert Result.error?({:error, nil}) == true
|
|
end
|
|
|
|
test "returns false for success" do
|
|
assert Result.error?({:ok, 42}) == false
|
|
assert Result.error?({:ok, nil}) == false
|
|
assert Result.error?({:ok, []}) == false
|
|
end
|
|
end
|
|
|
|
describe "combined operations" do
|
|
test "map and and_then together" do
|
|
result = {:ok, "5"}
|
|
|
|
final_result =
|
|
result
|
|
|> Result.map(&String.to_integer/1)
|
|
|> Result.and_then(fn x -> {:ok, x * 2} end)
|
|
|> Result.map(fn x -> x + 3 end)
|
|
|
|
assert final_result == {:ok, 13}
|
|
end
|
|
|
|
test "map_error after failed operation" do
|
|
result = {:error, :db_timeout}
|
|
|
|
final_result =
|
|
result
|
|
|> Result.map(fn x -> x * 2 end)
|
|
|> Result.map_error(fn _ -> :service_unavailable end)
|
|
|
|
assert final_result == {:error, :service_unavailable}
|
|
end
|
|
|
|
test "conditional unwrap based on ok?" do
|
|
success = {:ok, 42}
|
|
error = {:error, :not_found}
|
|
|
|
assert Result.ok?(success) && Result.unwrap_or(success, 0) == 42
|
|
refute Result.ok?(error)
|
|
assert Result.unwrap_or(error, 0) == 0
|
|
end
|
|
end
|
|
end
|