293 lines
9.4 KiB
Elixir
293 lines
9.4 KiB
Elixir
defmodule Mv.Membership.Import.MemberCSVTest do
|
|
use Mv.DataCase, async: false
|
|
|
|
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 "function exists and accepts file_content and opts" do
|
|
file_content = "email\njohn@example.com"
|
|
opts = []
|
|
|
|
# This will fail until the function is implemented
|
|
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
|
|
|
|
test "function has documentation" do
|
|
# Check that @doc exists by reading the module
|
|
assert function_exported?(MemberCSV, :prepare, 2)
|
|
end
|
|
end
|
|
|
|
describe "process_chunk/4" do
|
|
test "function exists and accepts chunk_rows_with_lines, column_map, custom_field_map, and opts" do
|
|
chunk_rows_with_lines = [{2, %{member: %{email: "john@example.com"}, custom: %{}}}]
|
|
column_map = %{email: 0}
|
|
custom_field_map = %{}
|
|
opts = []
|
|
|
|
# This will fail until the function is implemented
|
|
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" 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 = []
|
|
|
|
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
|
|
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}
|
|
|
|
custom_field_lookup = %{
|
|
to_string(custom_field.id) => %{id: custom_field.id, value_type: custom_field.value_type}
|
|
}
|
|
|
|
opts = [custom_field_lookup: custom_field_lookup]
|
|
|
|
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
|
|
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" do
|
|
chunk_rows_with_lines = []
|
|
column_map = %{}
|
|
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 == 0
|
|
assert chunk_result.errors == []
|
|
end
|
|
|
|
test "function has documentation" do
|
|
# Check that @doc exists by reading the module
|
|
assert function_exported?(MemberCSV, :process_chunk, 4)
|
|
end
|
|
end
|
|
|
|
describe "module documentation" do
|
|
test "module has @moduledoc" do
|
|
# Check that the module exists and has documentation
|
|
assert Code.ensure_loaded?(MemberCSV)
|
|
|
|
# Try to get the module documentation
|
|
{:docs_v1, _, _, _, %{"en" => moduledoc}, _, _} = Code.fetch_docs(MemberCSV)
|
|
assert is_binary(moduledoc)
|
|
assert String.length(moduledoc) > 0
|
|
end
|
|
end
|
|
end
|