244 lines
8.2 KiB
Elixir
244 lines
8.2 KiB
Elixir
defmodule Mv.Membership.Import.HeaderMapperTest do
|
||
use ExUnit.Case, async: true
|
||
|
||
alias Mv.Membership.Import.HeaderMapper
|
||
|
||
describe "normalize_header/1" do
|
||
test "trims whitespace" do
|
||
assert HeaderMapper.normalize_header(" email ") == "email"
|
||
end
|
||
|
||
test "converts to lowercase" do
|
||
assert HeaderMapper.normalize_header("EMAIL") == "email"
|
||
assert HeaderMapper.normalize_header("E-Mail") == "e-mail"
|
||
end
|
||
|
||
test "normalizes Unicode characters" do
|
||
# ß -> ss
|
||
assert HeaderMapper.normalize_header("Straße") == "strasse"
|
||
# Umlaute transliteration (ä -> ae, ö -> oe, ü -> ue)
|
||
assert HeaderMapper.normalize_header("Müller") == "mueller"
|
||
assert HeaderMapper.normalize_header("Köln") == "koeln"
|
||
assert HeaderMapper.normalize_header("Grün") == "gruen"
|
||
end
|
||
|
||
test "compresses and removes whitespace" do
|
||
# Whitespace is removed entirely to ensure "first name" == "firstname"
|
||
assert HeaderMapper.normalize_header("first name") == "firstname"
|
||
assert HeaderMapper.normalize_header("email address") == "emailaddress"
|
||
end
|
||
|
||
test "unifies hyphen variants" do
|
||
# Different Unicode hyphen characters should become standard hyphen
|
||
# en dash
|
||
assert HeaderMapper.normalize_header("E–Mail") == "e-mail"
|
||
# minus sign
|
||
assert HeaderMapper.normalize_header("E−Mail") == "e-mail"
|
||
# standard hyphen
|
||
assert HeaderMapper.normalize_header("E-Mail") == "e-mail"
|
||
end
|
||
|
||
test "removes or unifies punctuation" do
|
||
# Parentheses, slashes, etc. are removed (whitespace is also removed)
|
||
assert HeaderMapper.normalize_header("E-Mail (privat)") == "e-mailprivat"
|
||
assert HeaderMapper.normalize_header("Telefon / Mobil") == "telefonmobil"
|
||
end
|
||
|
||
test "handles empty strings" do
|
||
assert HeaderMapper.normalize_header("") == ""
|
||
assert HeaderMapper.normalize_header(" ") == ""
|
||
end
|
||
end
|
||
|
||
describe "build_maps/2" do
|
||
test "maps English email variant correctly" do
|
||
headers = ["Email"]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
|
||
test "maps German email variant correctly" do
|
||
headers = ["E-Mail"]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
|
||
test "maps multiple member fields" do
|
||
headers = ["Email", "First Name", "Last Name"]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert member_map[:first_name] == 1
|
||
assert member_map[:last_name] == 2
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
|
||
test "handles Unicode and whitespace in headers" do
|
||
headers = [" E-Mail ", "Straße", " Telefon / Mobil "]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert member_map[:street] == 1
|
||
# "Telefon / Mobil" is not a known member field, so it should be unknown
|
||
assert length(unknown) == 1
|
||
assert custom_map == %{}
|
||
end
|
||
|
||
test "returns error when duplicate headers normalize to same field" do
|
||
headers = ["Email", "E-Mail"]
|
||
|
||
assert {:error, reason} = HeaderMapper.build_maps(headers, [])
|
||
assert reason =~ "duplicate"
|
||
assert reason =~ "email"
|
||
end
|
||
|
||
test "returns error when required field email is missing" do
|
||
headers = ["First Name", "Last Name"]
|
||
|
||
assert {:error, reason} = HeaderMapper.build_maps(headers, [])
|
||
assert reason =~ "Missing required header"
|
||
assert reason =~ "email"
|
||
assert reason =~ "accepted"
|
||
end
|
||
|
||
test "collects unknown columns" do
|
||
headers = ["Email", "FooBar", "UnknownColumn"]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert length(unknown) == 2
|
||
assert "FooBar" in unknown or "foobar" in unknown
|
||
assert "UnknownColumn" in unknown or "unknowncolumn" in unknown
|
||
assert custom_map == %{}
|
||
end
|
||
|
||
test "ignores empty headers after normalization" do
|
||
headers = ["Email", " ", ""]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
|
||
test "maps custom field columns correctly" do
|
||
headers = ["Email", "Lieblingsfarbe"]
|
||
custom_fields = [%{id: "cf1", name: "Lieblingsfarbe"}]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, custom_fields)
|
||
|
||
assert member_map[:email] == 0
|
||
assert custom_map["cf1"] == 1
|
||
assert unknown == []
|
||
end
|
||
|
||
test "custom field collision: member field wins" do
|
||
headers = ["Email"]
|
||
# Custom field with name "Email" should not override member field
|
||
custom_fields = [%{id: "cf1", name: "Email"}]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, custom_fields)
|
||
|
||
assert member_map[:email] == 0
|
||
# Custom field should not be in custom_map because member field has priority
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
|
||
test "handles custom field with Unicode normalization" do
|
||
headers = ["Email", "Straße"]
|
||
custom_fields = [%{id: "cf1", name: "Straße"}]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, custom_fields)
|
||
|
||
assert member_map[:email] == 0
|
||
# "Straße" is a member field (street), so it should be in member_map, not custom_map
|
||
assert member_map[:street] == 1
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
|
||
test "handles unknown custom field columns" do
|
||
headers = ["Email", "UnknownCustomField"]
|
||
custom_fields = [%{id: "cf1", name: "KnownField"}]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, custom_fields)
|
||
|
||
assert member_map[:email] == 0
|
||
assert custom_map == %{}
|
||
# UnknownCustomField should be in unknown list
|
||
assert length(unknown) == 1
|
||
end
|
||
|
||
test "handles duplicate custom field names after normalization" do
|
||
headers = ["Email", "CustomField", "Custom Field"]
|
||
custom_fields = [%{id: "cf1", name: "CustomField"}]
|
||
|
||
# Both "CustomField" and "Custom Field" normalize to the same, so this should error
|
||
assert {:error, reason} = HeaderMapper.build_maps(headers, custom_fields)
|
||
assert reason =~ "duplicate"
|
||
end
|
||
|
||
test "maps all supported member fields" do
|
||
headers = [
|
||
"Email",
|
||
"First Name",
|
||
"Last Name",
|
||
"Street",
|
||
"Postal Code",
|
||
"City"
|
||
]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert member_map[:first_name] == 1
|
||
assert member_map[:last_name] == 2
|
||
assert member_map[:street] == 3
|
||
assert member_map[:postal_code] == 4
|
||
assert member_map[:city] == 5
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
|
||
test "maps German member field variants" do
|
||
headers = ["E-Mail", "Vorname", "Nachname", "Straße", "PLZ", "Stadt"]
|
||
|
||
assert {:ok, %{member: member_map, custom: custom_map, unknown: unknown}} =
|
||
HeaderMapper.build_maps(headers, [])
|
||
|
||
assert member_map[:email] == 0
|
||
assert member_map[:first_name] == 1
|
||
assert member_map[:last_name] == 2
|
||
assert member_map[:street] == 3
|
||
assert member_map[:postal_code] == 4
|
||
assert member_map[:city] == 5
|
||
assert custom_map == %{}
|
||
assert unknown == []
|
||
end
|
||
end
|
||
end
|