test: adds tests for header normalization
This commit is contained in:
parent
b44d8a9d70
commit
0673684cc1
2 changed files with 416 additions and 13 deletions
244
test/mv/membership/import/header_mapper_test.exs
Normal file
244
test/mv/membership/import/header_mapper_test.exs
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
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
|
||||||
|
|
@ -44,7 +44,6 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
||||||
assert match?({:ok, _}, result) or match?({:error, _}, result)
|
assert match?({:ok, _}, result) or match?({:error, _}, result)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag :skip
|
|
||||||
test "returns {:ok, import_state} on success" do
|
test "returns {:ok, import_state} on success" do
|
||||||
file_content = "email\njohn@example.com"
|
file_content = "email\njohn@example.com"
|
||||||
opts = []
|
opts = []
|
||||||
|
|
@ -56,6 +55,8 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
||||||
assert Map.has_key?(import_state, :column_map)
|
assert Map.has_key?(import_state, :column_map)
|
||||||
assert Map.has_key?(import_state, :custom_field_map)
|
assert Map.has_key?(import_state, :custom_field_map)
|
||||||
assert Map.has_key?(import_state, :warnings)
|
assert Map.has_key?(import_state, :warnings)
|
||||||
|
assert import_state.column_map[:email] == 0
|
||||||
|
assert import_state.chunks != []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns {:error, reason} on failure" do
|
test "returns {:error, reason} on failure" do
|
||||||
|
|
@ -72,23 +73,177 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "process_chunk/3" do
|
describe "process_chunk/3" do
|
||||||
test "function exists and accepts chunk_rows_with_lines, column_map, and opts" do
|
test "function exists and accepts chunk_rows_with_lines, column_map, custom_field_map, and opts" do
|
||||||
chunk_rows_with_lines = [{2, %{"email" => "john@example.com"}}]
|
chunk_rows_with_lines = [{2, %{member: %{email: "john@example.com"}, custom: %{}}}]
|
||||||
column_map = %{email: 0}
|
column_map = %{email: 0}
|
||||||
|
custom_field_map = %{}
|
||||||
opts = []
|
opts = []
|
||||||
|
|
||||||
# This will fail until the function is implemented
|
# This will fail until the function is implemented
|
||||||
result = MemberCSV.process_chunk(chunk_rows_with_lines, column_map, opts)
|
result = MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||||
assert match?({:ok, _}, result) or match?({:error, _}, result)
|
assert match?({:ok, _}, result) or match?({:error, _}, result)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns {:ok, chunk_result} on success" do
|
test "creates member successfully with valid data" do
|
||||||
chunk_rows_with_lines = [{2, %{"email" => "john@example.com"}}]
|
chunk_rows_with_lines = [
|
||||||
column_map = %{email: 0}
|
{2, %{member: %{email: "john@example.com", first_name: "John"}, custom: %{}}}
|
||||||
|
]
|
||||||
|
|
||||||
|
column_map = %{email: 0, first_name: 1}
|
||||||
|
custom_field_map = %{}
|
||||||
opts = []
|
opts = []
|
||||||
|
|
||||||
assert {:ok, chunk_result} =
|
assert {:ok, chunk_result} =
|
||||||
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, opts)
|
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
|
||||||
|
members = Mv.Membership.list_members!()
|
||||||
|
assert Enum.any?(members, &(&1.email == "john@example.com"))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error for invalid email" do
|
||||||
|
chunk_rows_with_lines = [
|
||||||
|
{2, %{member: %{email: "invalid-email"}, custom: %{}}}
|
||||||
|
]
|
||||||
|
|
||||||
|
column_map = %{email: 0}
|
||||||
|
custom_field_map = %{}
|
||||||
|
opts = []
|
||||||
|
|
||||||
|
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"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error for duplicate email" do
|
||||||
|
# Create existing member first
|
||||||
|
{:ok, _existing} =
|
||||||
|
Mv.Membership.create_member(%{email: "duplicate@example.com", first_name: "Existing"})
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
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" do
|
||||||
|
# Create custom field first
|
||||||
|
{:ok, custom_field} =
|
||||||
|
Mv.Membership.CustomField
|
||||||
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
|
name: "Phone",
|
||||||
|
value_type: :string
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
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}
|
||||||
|
opts = []
|
||||||
|
|
||||||
|
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!()
|
||||||
|
member = Enum.find(members, &(&1.email == "withcustom@example.com"))
|
||||||
|
assert member != nil
|
||||||
|
|
||||||
|
{:ok, member_with_cf} = Ash.load(member, :custom_field_values)
|
||||||
|
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" 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 = []
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
test "preserves CSV line numbers in errors" do
|
||||||
|
chunk_rows_with_lines = [
|
||||||
|
{5, %{member: %{email: "invalid"}, custom: %{}}},
|
||||||
|
{10, %{member: %{email: "also-invalid"}, custom: %{}}}
|
||||||
|
]
|
||||||
|
|
||||||
|
column_map = %{email: 0}
|
||||||
|
custom_field_map = %{}
|
||||||
|
opts = []
|
||||||
|
|
||||||
|
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" do
|
||||||
|
chunk_rows_with_lines = [{2, %{member: %{email: "test@example.com"}, custom: %{}}}]
|
||||||
|
column_map = %{email: 0}
|
||||||
|
custom_field_map = %{}
|
||||||
|
opts = []
|
||||||
|
|
||||||
|
assert {:ok, chunk_result} =
|
||||||
|
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||||
|
|
||||||
# Check that chunk_result contains expected fields
|
# Check that chunk_result contains expected fields
|
||||||
assert Map.has_key?(chunk_result, :inserted)
|
assert Map.has_key?(chunk_result, :inserted)
|
||||||
|
|
@ -99,19 +254,23 @@ defmodule Mv.Membership.Import.MemberCSVTest do
|
||||||
assert is_list(chunk_result.errors)
|
assert is_list(chunk_result.errors)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns {:error, reason} on failure" do
|
test "returns {:ok, _} with zero counts for empty chunk" do
|
||||||
chunk_rows_with_lines = []
|
chunk_rows_with_lines = []
|
||||||
column_map = %{}
|
column_map = %{}
|
||||||
|
custom_field_map = %{}
|
||||||
opts = []
|
opts = []
|
||||||
|
|
||||||
# This might return {:ok, _} with zero counts or {:error, _}
|
assert {:ok, chunk_result} =
|
||||||
result = MemberCSV.process_chunk(chunk_rows_with_lines, column_map, opts)
|
MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
|
||||||
assert match?({:ok, _}, result) or match?({:error, _}, result)
|
|
||||||
|
assert chunk_result.inserted == 0
|
||||||
|
assert chunk_result.failed == 0
|
||||||
|
assert chunk_result.errors == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "function has documentation" do
|
test "function has documentation" do
|
||||||
# Check that @doc exists by reading the module
|
# Check that @doc exists by reading the module
|
||||||
assert function_exported?(MemberCSV, :process_chunk, 3)
|
assert function_exported?(MemberCSV, :process_chunk, 4)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue