towerops/test/towerops/result_test.exs
2026-02-03 13:20:33 -06:00

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