Files
wanderer/test/unit/api_utils_test.exs
2025-11-24 11:33:08 +01:00

411 lines
13 KiB
Elixir

defmodule WandererAppWeb.Helpers.APIUtilsTest do
use WandererApp.DataCase, async: false
import Mox
setup :verify_on_exit!
alias WandererAppWeb.Helpers.APIUtils
alias Phoenix.ConnTest
describe "fetch_map_id/1" do
test "returns {:ok, id} for valid UUID map_id" do
valid_uuid = "550e8400-e29b-41d4-a716-446655440000"
assert {:ok, ^valid_uuid} = APIUtils.fetch_map_id(%{"map_id" => valid_uuid})
end
test "returns error for invalid UUID format in map_id" do
assert {:error, "Invalid UUID format for map_id: \"invalid-uuid\""} =
APIUtils.fetch_map_id(%{"map_id" => "invalid-uuid"})
end
test "returns error for empty parameters" do
assert {:error, "Must provide either ?map_id=UUID or ?slug=SLUG"} =
APIUtils.fetch_map_id(%{})
end
test "returns error for unknown parameters" do
assert {:error, "Must provide either ?map_id=UUID or ?slug=SLUG"} =
APIUtils.fetch_map_id(%{"unknown" => "value"})
end
end
describe "require_param/2" do
test "returns {:ok, value} for present string parameter" do
params = %{"name" => "test_value"}
assert {:ok, "test_value"} = APIUtils.require_param(params, "name")
end
test "trims whitespace from string parameters" do
params = %{"name" => " test_value "}
assert {:ok, "test_value"} = APIUtils.require_param(params, "name")
end
test "returns error for empty string after trimming" do
params = %{"name" => " "}
assert {:error, "Param name cannot be empty"} = APIUtils.require_param(params, "name")
end
test "returns error for missing parameter" do
params = %{}
assert {:error, "Missing required param: name"} = APIUtils.require_param(params, "name")
end
test "returns {:ok, value} for non-string values" do
params = %{"count" => 42}
assert {:ok, 42} = APIUtils.require_param(params, "count")
end
end
describe "parse_int/1" do
test "parses valid integer strings" do
assert {:ok, 42} = APIUtils.parse_int("42")
assert {:ok, -10} = APIUtils.parse_int("-10")
assert {:ok, 0} = APIUtils.parse_int("0")
end
test "returns integer values unchanged" do
assert {:ok, 42} = APIUtils.parse_int(42)
assert {:ok, -10} = APIUtils.parse_int(-10)
end
test "returns error for invalid string formats" do
assert {:error, "Invalid integer format: abc"} = APIUtils.parse_int("abc")
assert {:error, "Invalid integer format: 42.5"} = APIUtils.parse_int("42.5")
assert {:error, "Invalid integer format: 42 "} = APIUtils.parse_int("42 ")
end
test "returns error for unsupported types" do
assert {:error, "Expected integer or string, got: 42.5"} = APIUtils.parse_int(42.5)
assert {:error, "Expected integer or string, got: nil"} = APIUtils.parse_int(nil)
end
end
describe "parse_int!/1" do
test "returns integer for valid input" do
assert 42 = APIUtils.parse_int!("42")
assert 42 = APIUtils.parse_int!(42)
end
test "raises ArgumentError for invalid input" do
assert_raise ArgumentError, "Invalid integer format: abc", fn ->
APIUtils.parse_int!("abc")
end
end
end
describe "validate_uuid/1" do
test "validates correct UUID format" do
valid_uuid = "550e8400-e29b-41d4-a716-446655440000"
assert {:ok, ^valid_uuid} = APIUtils.validate_uuid(valid_uuid)
end
test "returns error for invalid UUID format" do
assert {:error, "Invalid UUID format: invalid-uuid"} =
APIUtils.validate_uuid("invalid-uuid")
end
test "returns error for non-string input" do
assert {:error, "ID must be a UUID string"} = APIUtils.validate_uuid(123)
assert {:error, "ID must be a UUID string"} = APIUtils.validate_uuid(nil)
end
end
describe "extract_upsert_params/1" do
test "extracts valid parameters with solar_system_id" do
params = %{
"solar_system_id" => "30000142",
"position_x" => 100,
"position_y" => 200,
"status" => 1,
"visible" => true
}
assert {:ok, extracted} = APIUtils.extract_upsert_params(params)
assert extracted["solar_system_id"] == "30000142"
assert extracted["position_x"] == 100
assert extracted["position_y"] == 200
assert extracted["status"] == 1
assert extracted["visible"] == true
end
test "filters out nil values" do
params = %{
"solar_system_id" => "30000142",
"position_x" => 100,
"position_y" => nil,
"status" => nil
}
assert {:ok, extracted} = APIUtils.extract_upsert_params(params)
assert extracted["solar_system_id"] == "30000142"
assert extracted["position_x"] == 100
refute Map.has_key?(extracted, "position_y")
refute Map.has_key?(extracted, "status")
end
test "filters out unknown parameters" do
params = %{
"solar_system_id" => "30000142",
"unknown_param" => "should_be_filtered",
"position_x" => 100
}
assert {:ok, extracted} = APIUtils.extract_upsert_params(params)
assert extracted["solar_system_id"] == "30000142"
assert extracted["position_x"] == 100
refute Map.has_key?(extracted, "unknown_param")
end
test "returns error when solar_system_id is missing" do
params = %{"position_x" => 100}
assert {:error, "Missing solar_system_id in request body"} =
APIUtils.extract_upsert_params(params)
end
end
describe "extract_update_params/1" do
test "extracts allowed update parameters" do
params = %{
"position_x" => 100,
"position_y" => 200,
"status" => 1,
"visible" => true,
"description" => "Test system"
}
assert {:ok, extracted} = APIUtils.extract_update_params(params)
assert extracted["position_x"] == 100
assert extracted["position_y"] == 200
assert extracted["status"] == 1
assert extracted["visible"] == true
assert extracted["description"] == "Test system"
end
test "filters out disallowed parameters" do
params = %{
# Not allowed in updates
"solar_system_id" => "30000142",
"position_x" => 100,
"unknown_param" => "filtered"
}
assert {:ok, extracted} = APIUtils.extract_update_params(params)
assert extracted["position_x"] == 100
refute Map.has_key?(extracted, "solar_system_id")
refute Map.has_key?(extracted, "unknown_param")
end
test "filters out nil values" do
params = %{
"position_x" => 100,
"position_y" => nil,
"status" => nil
}
assert {:ok, extracted} = APIUtils.extract_update_params(params)
assert extracted["position_x"] == 100
refute Map.has_key?(extracted, "position_y")
refute Map.has_key?(extracted, "status")
end
end
describe "normalize_connection_params/1" do
test "normalizes connection parameters with required fields" do
params = %{
"solar_system_source" => "30000142",
"solar_system_target" => "30000144"
}
assert {:ok, normalized} = APIUtils.normalize_connection_params(params)
assert normalized["solar_system_source"] == 30_000_142
assert normalized["solar_system_target"] == 30_000_144
assert normalized["type"] == 0
assert normalized["mass_status"] == 0
assert normalized["time_status"] == 0
assert normalized["ship_size_type"] == 0
end
test "handles parameter aliases" do
params = %{
"source" => "30000142",
"target" => "30000144"
}
assert {:ok, normalized} = APIUtils.normalize_connection_params(params)
assert normalized["solar_system_source"] == 30_000_142
assert normalized["solar_system_target"] == 30_000_144
end
test "handles locked parameter normalization" do
# Test boolean true values
for locked_val <- [true, "true", 1, "1"] do
params = %{
"solar_system_source" => "30000142",
"solar_system_target" => "30000144",
"locked" => locked_val
}
assert {:ok, normalized} = APIUtils.normalize_connection_params(params)
assert normalized["locked"] == true
end
# Test boolean false values
for locked_val <- [false, "false", 0, "0"] do
params = %{
"solar_system_source" => "30000142",
"solar_system_target" => "30000144",
"locked" => locked_val
}
assert {:ok, normalized} = APIUtils.normalize_connection_params(params)
assert normalized["locked"] == false
end
end
test "handles optional parameters" do
params = %{
"solar_system_source" => "30000142",
"solar_system_target" => "30000144",
"custom_info" => "test info",
"wormhole_type" => "C1"
}
assert {:ok, normalized} = APIUtils.normalize_connection_params(params)
assert normalized["custom_info"] == "test info"
assert normalized["wormhole_type"] == "C1"
end
test "returns error for missing required fields" do
params = %{"solar_system_source" => "30000142"}
assert {:error, "Missing solar_system_target"} =
APIUtils.normalize_connection_params(params)
params = %{"solar_system_target" => "30000144"}
assert {:error, "Missing solar_system_source"} =
APIUtils.normalize_connection_params(params)
end
test "returns error for invalid integer formats" do
params = %{
"solar_system_source" => "invalid",
"solar_system_target" => "30000144"
}
assert {:error, "Invalid solar_system_source: invalid"} =
APIUtils.normalize_connection_params(params)
end
end
describe "respond_data/3" do
test "creates successful JSON response with data" do
conn = ConnTest.build_conn()
data = %{id: 1, name: "test"}
result = APIUtils.respond_data(conn, data, :ok)
assert result.status == 200
response = Phoenix.ConnTest.json_response(result, 200)
assert response == %{"data" => %{"id" => 1, "name" => "test"}}
end
test "creates JSON response with custom status" do
conn = ConnTest.build_conn()
data = %{id: 1}
result = APIUtils.respond_data(conn, data, :created)
assert result.status == 201
end
end
describe "error_response/4" do
test "creates error response with message only" do
conn = ConnTest.build_conn()
result = APIUtils.error_response(conn, :bad_request, "Invalid input")
assert result.status == 400
response = Phoenix.ConnTest.json_response(result, 400)
assert response == %{"error" => "Invalid input"}
end
test "creates error response with details" do
conn = ConnTest.build_conn()
details = %{field: "name", issue: "required"}
result = APIUtils.error_response(conn, :unprocessable_entity, "Validation failed", details)
assert result.status == 422
response = Phoenix.ConnTest.json_response(result, 422)
assert response == %{
"error" => "Validation failed",
"details" => %{"field" => "name", "issue" => "required"}
}
end
end
describe "error_not_found/2" do
test "creates 404 not found response" do
conn = ConnTest.build_conn()
result = APIUtils.error_not_found(conn, "Resource not found")
assert result.status == 404
response = Phoenix.ConnTest.json_response(result, 404)
assert response == %{"error" => "Resource not found"}
end
end
describe "format_error/1" do
test "formats string errors as-is" do
assert APIUtils.format_error("Error message") == "Error message"
end
test "formats atom errors as strings" do
assert APIUtils.format_error(:not_found) == "not_found"
end
test "formats other errors with inspect" do
assert APIUtils.format_error(%{error: "details"}) == "%{error: \"details\"}"
assert APIUtils.format_error(123) == "123"
end
end
describe "connection_to_json/1" do
test "extracts relevant connection fields" do
connection = %{
id: "uuid",
map_id: "map-uuid",
solar_system_source: 30_000_142,
solar_system_target: 30_000_144,
mass_status: 1,
time_status: 2,
ship_size_type: 3,
type: 0,
wormhole_type: "C1",
inserted_at: ~N[2024-01-01 12:00:00],
updated_at: ~N[2024-01-01 12:00:00],
# These should be filtered out
extra_field: "ignored"
}
result = APIUtils.connection_to_json(connection)
expected_fields = ~w(
id map_id solar_system_source solar_system_target mass_status
time_status ship_size_type type wormhole_type inserted_at updated_at
)a
assert Map.keys(result) |> Enum.sort() == expected_fields |> Enum.sort()
assert result.id == "uuid"
assert result.solar_system_source == 30_000_142
refute Map.has_key?(result, :extra_field)
end
end
end