defmodule MvWeb.ImportTemplateControllerTest do use MvWeb.ConnCase, async: true setup %{conn: conn} do actor = Mv.Helpers.SystemActor.get_system_actor() {:ok, custom_field} = Mv.Membership.CustomField |> Ash.Changeset.for_create(:create, %{name: "Lieblingsfarbe", value_type: :string}) |> Ash.create(actor: actor) %{conn: conn, custom_field: custom_field} end describe "authenticated EN template" do setup %{conn: conn} do admin = Mv.Fixtures.user_with_role_fixture("admin") %{conn: MvWeb.ConnCase.conn_with_password_user(conn, admin)} end test "returns CSV with English headers and current custom fields", %{conn: conn} do conn = get(conn, ~p"/admin/import/template/en") assert response_content_type(conn, :csv) =~ "text/csv" body = response(conn, 200) header = body |> String.split("\n") |> List.first() assert header =~ "email" # EN headers use the canonical English variant from HeaderMapper, not the # underscore form, so the template stays faithful to the documented variant list. assert header =~ "first name" assert header =~ "last name" refute header =~ "first_name" assert header =~ "house number" refute header =~ "house_number" assert header =~ "Lieblingsfarbe" assert get_resp_header(conn, "content-disposition") |> Enum.any?(&(&1 =~ "member_import_en.csv")) end test "neutralizes formula-injection in a custom field header", %{conn: conn} do actor = Mv.Helpers.SystemActor.get_system_actor() {:ok, _} = Mv.Membership.CustomField |> Ash.Changeset.for_create(:create, %{ name: "=cmd|'/c calc'!A1", value_type: :string }) |> Ash.create(actor: actor) conn = get(conn, ~p"/admin/import/template/en") body = response(conn, 200) header = body |> String.split("\n") |> List.first() # The dangerous cell must be prefixed with a single quote so spreadsheet # software does not evaluate it as a formula, matching the export writer. refute header =~ ~r/(^|;)=cmd/ assert header =~ "'=cmd|'/c calc'!A1" end end describe "authenticated DE template" do setup %{conn: conn} do admin = Mv.Fixtures.user_with_role_fixture("admin") %{conn: MvWeb.ConnCase.conn_with_password_user(conn, admin)} end test "returns CSV with German headers and current custom fields", %{conn: conn} do conn = get(conn, ~p"/admin/import/template/de") body = response(conn, 200) header = body |> String.split("\n") |> List.first() assert header =~ "E-Mail" assert header =~ "Vorname" assert header =~ "Lieblingsfarbe" assert get_resp_header(conn, "content-disposition") |> Enum.any?(&(&1 =~ "member_import_de.csv")) end end describe "authorization" do @tag role: :unauthenticated test "unauthenticated request does not receive a CSV", %{conn: conn} do conn = get(conn, ~p"/admin/import/template/en") refute conn.status == 200 refute get_resp_header(conn, "content-type") |> Enum.any?(&(&1 =~ "text/csv")) refute to_string(conn.resp_body) =~ "email" end @tag role: :member test "user without import permission is forbidden", %{conn: conn} do conn = get(conn, ~p"/admin/import/template/en") refute conn.status == 200 refute get_resp_header(conn, "content-type") |> Enum.any?(&(&1 =~ "text/csv")) refute to_string(conn.resp_body) =~ "email" end end end