mitgliederverwaltung/test/mv/membership/import/member_csv_test.exs

902 lines
29 KiB
Elixir

defmodule Mv.Membership.Import.MemberCSVTest do
use Mv.DataCase, async: true
alias Mv.Membership.Import.MemberCSV
describe "Error struct" do
test "Error struct exists with required fields" do
# This will fail at runtime if the struct doesn't exist
# We use struct/2 to create the struct at runtime
error =
struct(MemberCSV.Error, %{
csv_line_number: 5,
field: :email,
message: "is not a valid email"
})
assert error.csv_line_number == 5
assert error.field == :email
assert error.message == "is not a valid email"
end
test "Error struct allows nil field" do
# This will fail at runtime if the struct doesn't exist
error =
struct(MemberCSV.Error, %{
csv_line_number: 10,
field: nil,
message: "Row is empty"
})
assert error.csv_line_number == 10
assert error.field == nil
assert error.message == "Row is empty"
end
end
describe "prepare/2" do
test "accepts file_content and opts and returns tagged tuple" do
file_content = "email\njohn@example.com"
opts = []
result = MemberCSV.prepare(file_content, opts)
assert match?({:ok, _}, result) or match?({:error, _}, result)
end
test "returns {:ok, import_state} on success" do
file_content = "email\njohn@example.com"
opts = []
assert {:ok, import_state} = MemberCSV.prepare(file_content, opts)
# Check that import_state contains expected fields
assert Map.has_key?(import_state, :chunks)
assert Map.has_key?(import_state, :column_map)
assert Map.has_key?(import_state, :custom_field_map)
assert Map.has_key?(import_state, :warnings)
assert import_state.column_map[:email] == 0
assert import_state.chunks != []
end
test "returns {:error, reason} on failure" do
file_content = ""
opts = []
assert {:error, _reason} = MemberCSV.prepare(file_content, opts)
end
end
describe "process_chunk/4" do
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
%{actor: system_actor}
end
test "accepts chunk_rows_with_lines, column_map, custom_field_map, and opts and returns tagged tuple",
%{
actor: actor
} do
chunk_rows_with_lines = [{2, %{member: %{email: "john@example.com"}, custom: %{}}}]
column_map = %{email: 0}
custom_field_map = %{}
opts = [actor: actor]
result = MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert match?({:ok, _}, result) or match?({:error, _}, result)
end
test "creates member successfully with valid data", %{actor: actor} do
chunk_rows_with_lines = [
{2, %{member: %{email: "john@example.com", first_name: "John"}, custom: %{}}}
]
column_map = %{email: 0, first_name: 1}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 1
assert chunk_result.failed == 0
assert chunk_result.errors == []
# Verify member was created
system_actor = Mv.Helpers.SystemActor.get_system_actor()
members = Mv.Membership.list_members!(actor: system_actor)
assert Enum.any?(members, &(&1.email == "john@example.com"))
end
test "returns error for invalid email", %{actor: actor} do
chunk_rows_with_lines = [
{2, %{member: %{email: "invalid-email"}, custom: %{}}}
]
column_map = %{email: 0}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 2
assert error.field == :email
# Error message should come from validate_row (Gettext-backed)
assert is_binary(error.message)
assert error.message != ""
end
test "returns error for missing email", %{actor: actor} do
chunk_rows_with_lines = [
{2, %{member: %{}, custom: %{}}}
]
column_map = %{}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 2
assert error.field == :email
assert is_binary(error.message)
end
test "returns error for whitespace-only email", %{actor: actor} do
chunk_rows_with_lines = [
{3, %{member: %{email: " "}, custom: %{}}}
]
column_map = %{email: 0}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 3
assert error.field == :email
end
test "returns error for duplicate email", %{actor: actor} do
# Create existing member first
{:ok, _existing} =
Mv.Membership.create_member(%{email: "duplicate@example.com", first_name: "Existing"},
actor: actor
)
chunk_rows_with_lines = [
{2, %{member: %{email: "duplicate@example.com", first_name: "New"}, custom: %{}}}
]
column_map = %{email: 0, first_name: 1}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 2
assert error.field == :email
assert error.message =~ "email" or error.message =~ "duplicate" or error.message =~ "unique"
end
test "creates member with custom field values", %{actor: actor} do
# Create custom field first
{:ok, custom_field} =
Mv.Membership.CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Phone",
value_type: :string
})
|> Ash.create(actor: actor)
chunk_rows_with_lines = [
{2,
%{
member: %{email: "withcustom@example.com"},
custom: %{to_string(custom_field.id) => "123-456-7890"}
}}
]
column_map = %{email: 0}
custom_field_map = %{to_string(custom_field.id) => 1}
custom_field_lookup = %{
to_string(custom_field.id) => %{
id: custom_field.id,
value_type: custom_field.value_type,
name: custom_field.name
}
}
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 1
assert chunk_result.failed == 0
# Verify member and custom field value were created
members = Mv.Membership.list_members!(actor: actor)
member = Enum.find(members, &(&1.email == "withcustom@example.com"))
assert member != nil
{:ok, member_with_cf} = Ash.load(member, :custom_field_values, actor: actor)
assert length(member_with_cf.custom_field_values) == 1
cfv = List.first(member_with_cf.custom_field_values)
assert cfv.custom_field_id == custom_field.id
assert cfv.value.value == "123-456-7890"
end
test "handles multiple rows with mixed success and failure", %{actor: actor} do
chunk_rows_with_lines = [
{2, %{member: %{email: "valid1@example.com"}, custom: %{}}},
{3, %{member: %{email: "invalid-email"}, custom: %{}}},
{4, %{member: %{email: "valid2@example.com"}, custom: %{}}}
]
column_map = %{email: 0}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 2
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 3
assert error.field == :email
# Error should come from validate_row, not from DB insert
assert is_binary(error.message)
end
test "preserves CSV line numbers in errors", %{actor: actor} do
chunk_rows_with_lines = [
{5, %{member: %{email: "invalid"}, custom: %{}}},
{10, %{member: %{email: "also-invalid"}, custom: %{}}}
]
column_map = %{email: 0}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.failed == 2
assert length(chunk_result.errors) == 2
line_numbers = Enum.map(chunk_result.errors, & &1.csv_line_number)
assert 5 in line_numbers
assert 10 in line_numbers
end
test "returns {:ok, chunk_result} on success", %{actor: actor} do
chunk_rows_with_lines = [{2, %{member: %{email: "test@example.com"}, custom: %{}}}]
column_map = %{email: 0}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
# Check that chunk_result contains expected fields
assert Map.has_key?(chunk_result, :inserted)
assert Map.has_key?(chunk_result, :failed)
assert Map.has_key?(chunk_result, :errors)
assert is_integer(chunk_result.inserted)
assert is_integer(chunk_result.failed)
assert is_list(chunk_result.errors)
end
test "returns {:ok, _} with zero counts for empty chunk", %{actor: actor} do
chunk_rows_with_lines = []
column_map = %{}
custom_field_map = %{}
opts = [actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 0
assert chunk_result.errors == []
end
test "error capping collects exactly 50 errors", %{actor: actor} do
# Create 50 rows with invalid emails
chunk_rows_with_lines =
1..50
|> Enum.map(fn i ->
{i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
end)
column_map = %{email: 0}
custom_field_map = %{}
opts = [existing_error_count: 0, max_errors: 50, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 50
assert length(chunk_result.errors) == 50
end
test "error capping collects only first 50 errors when more than 50 errors occur", %{
actor: actor
} do
# Create 60 rows with invalid emails
chunk_rows_with_lines =
1..60
|> Enum.map(fn i ->
{i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
end)
column_map = %{email: 0}
custom_field_map = %{}
opts = [existing_error_count: 0, max_errors: 50, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 60
assert length(chunk_result.errors) == 50
end
test "error capping respects existing_error_count", %{actor: actor} do
# Create 30 rows with invalid emails
chunk_rows_with_lines =
1..30
|> Enum.map(fn i ->
{i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
end)
column_map = %{email: 0}
custom_field_map = %{}
opts = [existing_error_count: 25, max_errors: 50, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 30
# Should only collect 25 errors (25 existing + 25 new = 50 limit)
assert length(chunk_result.errors) == 25
end
test "error capping collects no errors when limit already reached", %{actor: actor} do
# Create 10 rows with invalid emails
chunk_rows_with_lines =
1..10
|> Enum.map(fn i ->
{i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
end)
column_map = %{email: 0}
custom_field_map = %{}
opts = [existing_error_count: 50, max_errors: 50, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 10
assert chunk_result.errors == []
end
test "error capping with mixed success and failure", %{actor: actor} do
# Create 100 rows: 30 valid, 70 invalid
valid_rows =
1..30
|> Enum.map(fn i ->
{i + 1, %{member: %{email: "valid#{i}@example.com"}, custom: %{}}}
end)
invalid_rows =
31..100
|> Enum.map(fn i ->
{i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
end)
chunk_rows_with_lines = valid_rows ++ invalid_rows
column_map = %{email: 0}
custom_field_map = %{}
opts = [existing_error_count: 0, max_errors: 50, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 30
assert chunk_result.failed == 70
# Should only collect 50 errors (limit reached)
assert length(chunk_result.errors) == 50
end
test "error capping with custom max_errors", %{actor: actor} do
# Create 20 rows with invalid emails
chunk_rows_with_lines =
1..20
|> Enum.map(fn i ->
{i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
end)
column_map = %{email: 0}
custom_field_map = %{}
opts = [existing_error_count: 0, max_errors: 10, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 20
assert length(chunk_result.errors) == 10
end
end
describe "validate_row/3" do
test "returns error when email is missing" do
row_map = %{member: %{}, custom: %{}}
csv_line_number = 5
opts = []
assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert %MemberCSV.Error{} = error
assert error.csv_line_number == 5
assert error.field == :email
assert error.message != nil
assert error.message != ""
end
test "returns error when email is only whitespace" do
row_map = %{member: %{email: " "}, custom: %{}}
csv_line_number = 3
opts = []
assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert %MemberCSV.Error{} = error
assert error.csv_line_number == 3
assert error.field == :email
assert error.message != nil
end
test "returns error when email is nil" do
row_map = %{member: %{email: nil}, custom: %{}}
csv_line_number = 7
opts = []
assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert %MemberCSV.Error{} = error
assert error.csv_line_number == 7
assert error.field == :email
end
test "returns error when email format is invalid" do
row_map = %{member: %{email: "invalid-email"}, custom: %{}}
csv_line_number = 4
opts = []
assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert %MemberCSV.Error{} = error
assert error.csv_line_number == 4
assert error.field == :email
assert error.message != nil
end
test "returns {:ok, trimmed_row_map} when email is valid with whitespace" do
row_map = %{member: %{email: " john@example.com "}, custom: %{}}
csv_line_number = 2
opts = []
assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert trimmed_row_map.member.email == "john@example.com"
end
test "returns {:ok, trimmed_row_map} when email is valid without whitespace" do
row_map = %{member: %{email: "john@example.com"}, custom: %{}}
csv_line_number = 2
opts = []
assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert trimmed_row_map.member.email == "john@example.com"
end
test "trims all string values in member map" do
row_map = %{
member: %{
email: " john@example.com ",
first_name: " John ",
last_name: " Doe "
},
custom: %{}
}
csv_line_number = 2
opts = []
assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert trimmed_row_map.member.email == "john@example.com"
assert trimmed_row_map.member.first_name == "John"
assert trimmed_row_map.member.last_name == "Doe"
end
test "preserves custom map unchanged" do
row_map = %{
member: %{email: "john@example.com"},
custom: %{"field1" => "value1"}
}
csv_line_number = 2
opts = []
assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert trimmed_row_map.custom == %{"field1" => "value1"}
end
test "uses Gettext for error messages" do
row_map = %{member: %{}, custom: %{}}
csv_line_number = 5
opts = []
# Test with default locale (should work)
assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert is_binary(error.message)
# Test with German locale
Gettext.put_locale(MvWeb.Gettext, "de")
assert {:error, error_de} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert is_binary(error_de.message)
# Test with English locale
Gettext.put_locale(MvWeb.Gettext, "en")
assert {:error, error_en} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert is_binary(error_en.message)
# Reset to default
Gettext.put_locale(MvWeb.Gettext, "en")
end
test "handles empty opts gracefully" do
row_map = %{member: %{email: "john@example.com"}, custom: %{}}
csv_line_number = 2
opts = []
assert {:ok, _} = MemberCSV.validate_row(row_map, csv_line_number, opts)
end
test "handles missing member key gracefully" do
row_map = %{custom: %{}}
csv_line_number = 3
opts = []
assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
assert %MemberCSV.Error{} = error
assert error.csv_line_number == 3
end
end
describe "custom field import" do
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
%{actor: system_actor}
end
test "creates member with valid integer custom field value", %{actor: actor} do
# Create integer custom field
{:ok, custom_field} =
Mv.Membership.CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Alter",
value_type: :integer
})
|> Ash.create(actor: actor)
chunk_rows_with_lines = [
{2,
%{
member: %{email: "withage@example.com"},
custom: %{to_string(custom_field.id) => "25"}
}}
]
column_map = %{email: 0}
custom_field_map = %{to_string(custom_field.id) => 1}
custom_field_lookup = %{
to_string(custom_field.id) => %{
id: custom_field.id,
value_type: custom_field.value_type,
name: custom_field.name
}
}
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 1
assert chunk_result.failed == 0
# Verify member and custom field value were created
members = Mv.Membership.list_members!(actor: actor)
member = Enum.find(members, &(&1.email == "withage@example.com"))
assert member != nil
{:ok, member_with_cf} = Ash.load(member, :custom_field_values, actor: actor)
assert length(member_with_cf.custom_field_values) == 1
cfv = List.first(member_with_cf.custom_field_values)
assert cfv.custom_field_id == custom_field.id
assert cfv.value.value == 25
assert cfv.value.type == :integer
end
test "returns error for invalid integer custom field value", %{actor: actor} do
# Create integer custom field
{:ok, custom_field} =
Mv.Membership.CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Alter",
value_type: :integer
})
|> Ash.create(actor: actor)
chunk_rows_with_lines = [
{2,
%{
member: %{email: "invalidage@example.com"},
custom: %{to_string(custom_field.id) => "abc"}
}}
]
column_map = %{email: 0}
custom_field_map = %{to_string(custom_field.id) => 1}
custom_field_lookup = %{
to_string(custom_field.id) => %{
id: custom_field.id,
value_type: custom_field.value_type,
name: custom_field.name
}
}
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 2
assert error.message =~ "custom_field: Alter"
assert error.message =~ "Number"
assert error.message =~ "abc"
end
test "returns error for invalid date custom field value", %{actor: actor} do
# Create date custom field
{:ok, custom_field} =
Mv.Membership.CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Geburtstag",
value_type: :date
})
|> Ash.create(actor: actor)
chunk_rows_with_lines = [
{3,
%{
member: %{email: "invaliddate@example.com"},
custom: %{to_string(custom_field.id) => "not-a-date"}
}}
]
column_map = %{email: 0}
custom_field_map = %{to_string(custom_field.id) => 1}
custom_field_lookup = %{
to_string(custom_field.id) => %{
id: custom_field.id,
value_type: custom_field.value_type,
name: custom_field.name
}
}
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 3
assert error.message =~ "custom_field: Geburtstag"
assert error.message =~ "Date"
end
test "returns error for invalid email custom field value", %{actor: actor} do
# Create email custom field
{:ok, custom_field} =
Mv.Membership.CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Work Email",
value_type: :email
})
|> Ash.create(actor: actor)
chunk_rows_with_lines = [
{4,
%{
member: %{email: "invalidemailcf@example.com"},
custom: %{to_string(custom_field.id) => "not-an-email"}
}}
]
column_map = %{email: 0}
custom_field_map = %{to_string(custom_field.id) => 1}
custom_field_lookup = %{
to_string(custom_field.id) => %{
id: custom_field.id,
value_type: custom_field.value_type,
name: custom_field.name
}
}
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 4
assert error.message =~ "custom_field: Work Email"
assert error.message =~ "E-Mail"
end
test "returns error for invalid boolean custom field value", %{actor: actor} do
# Create boolean custom field
{:ok, custom_field} =
Mv.Membership.CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Is Active",
value_type: :boolean
})
|> Ash.create(actor: actor)
chunk_rows_with_lines = [
{5,
%{
member: %{email: "invalidbool@example.com"},
custom: %{to_string(custom_field.id) => "maybe"}
}}
]
column_map = %{email: 0}
custom_field_map = %{to_string(custom_field.id) => 1}
custom_field_lookup = %{
to_string(custom_field.id) => %{
id: custom_field.id,
value_type: custom_field.value_type,
name: custom_field.name
}
}
opts = [custom_field_lookup: custom_field_lookup, actor: actor]
assert {:ok, chunk_result} =
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
assert chunk_result.inserted == 0
assert chunk_result.failed == 1
assert length(chunk_result.errors) == 1
error = List.first(chunk_result.errors)
assert error.csv_line_number == 5
assert error.message =~ "custom_field: Is Active"
# Error message should indicate boolean/Yes-No validation failure
assert String.contains?(error.message, "Yes/No") ||
String.contains?(error.message, "true/false") ||
String.contains?(error.message, "boolean")
end
end
describe "prepare/2 with custom fields" do
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Create a custom field
{:ok, custom_field} =
Mv.Membership.CustomField
|> Ash.Changeset.for_create(:create, %{
name: "Membership Number",
value_type: :string
})
|> Ash.create(actor: system_actor)
%{actor: system_actor, custom_field: custom_field}
end
test "includes custom field in custom_field_map when header matches", %{
custom_field: custom_field
} do
# CSV with custom field column
csv_content = "email;Membership Number\njohn@example.com;12345"
assert {:ok, import_state} = MemberCSV.prepare(csv_content)
# Check that custom field is mapped
assert Map.has_key?(import_state.custom_field_map, to_string(custom_field.id))
assert import_state.column_map[:email] == 0
end
test "includes warning for unknown custom field column", %{custom_field: _custom_field} do
# CSV with unknown custom field column (not matching any existing custom field)
csv_content = "email;NichtExistierend\njohn@example.com;value"
assert {:ok, import_state} = MemberCSV.prepare(csv_content)
# Check that warning is present
assert import_state.warnings != []
warning = List.first(import_state.warnings)
assert warning =~ "NichtExistierend"
assert warning =~ "ignored"
assert warning =~ "custom field"
# Check that unknown column is not in custom_field_map
assert import_state.custom_field_map == %{}
# Member import should still succeed
assert import_state.column_map[:email] == 0
end
test "import succeeds even with unknown custom field columns", %{custom_field: _custom_field} do
# CSV with unknown custom field column
csv_content = "email;UnknownField\njohn@example.com;value"
assert {:ok, import_state} = MemberCSV.prepare(csv_content)
# Import state should be valid
assert import_state.column_map[:email] == 0
assert import_state.chunks != []
end
end
end