prop/test/microwaveprop_web/live/import_live_test.exs
Graham McIntire 247d066ec5
feat(import): live /imports/:id progress page + gate upload bars on submit
SubmitLive now hands off to the async pipeline:
- confirm_csv/confirm_adif → CsvImport.enqueue/2 → push_navigate to
  /imports/:id. ADIF preview shape matches CSV, so one enqueue call
  serves both.
- allow_upload(auto_upload: false) so bytes only stream when the user
  clicks submit. Progress bar + percentage now gated on
  entry.progress > 0 and < 100 — invisible during file selection,
  only rendered while the actual upload is in-flight.
- Old in-page csv_results / @csv_result / reset_csv removed; the
  /imports/:id page replaces them.

New ImportLive at /imports/🆔 subscribes to 'csv_import:<id>' PubSub,
shows total/processed/imported/refined/error counters in a daisyUI
stats grid, progress bar, status badge, and a collapsed errors table
when error_count > 0. Missing or malformed run_id redirects to /submit
with a flash.

7 new frontend tests (5 ImportLive, 2 updated SubmitLive), 1902 total,
credo clean.
2026-04-17 09:31:22 -05:00

137 lines
3.8 KiB
Elixir

defmodule MicrowavepropWeb.ImportLiveTest do
use MicrowavepropWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias Microwaveprop.Radio.ImportRun
alias Microwaveprop.Repo
defp insert_run(attrs) do
defaults = %{
submitter_email: "importer@example.com",
rows: %{"rows" => [], "refinement_ids" => []},
total_rows: 10,
processed_rows: 0,
imported_count: 0,
refined_count: 0,
error_count: 0,
errors: %{},
status: "pending"
}
{:ok, run} =
%ImportRun{}
|> ImportRun.changeset(Map.merge(defaults, attrs))
|> Repo.insert()
run
end
describe "mount" do
test "renders the initial state of a pending import", %{conn: conn} do
run = insert_run(%{total_rows: 25, submitter_email: "alice@example.com"})
{:ok, _lv, html} = live(conn, ~p"/imports/#{run.id}")
assert html =~ "Contact Import"
assert html =~ "alice@example.com"
assert html =~ "25"
assert html =~ ~r/id="import-status-badge"[^>]*>\s*Pending/
assert html =~ ~s(id="import-progress-bar")
assert html =~ ~s(id="import-imported-count")
assert html =~ ~s(id="import-refined-count")
assert html =~ ~s(id="import-error-count")
end
test "redirects to /submit with a flash when the import is not found", %{conn: conn} do
missing_id = Ecto.UUID.generate()
assert {:error, {:live_redirect, %{to: "/submit", flash: flash}}} =
live(conn, ~p"/imports/#{missing_id}")
assert flash["error"] =~ "Import not found"
end
test "redirects to /submit for an obviously bad id", %{conn: conn} do
assert {:error, {:live_redirect, %{to: "/submit"}}} =
live(conn, ~p"/imports/not-a-uuid")
end
end
describe "live updates" do
test "updates counters when an import_progress message is broadcast", %{conn: conn} do
run = insert_run(%{total_rows: 100})
{:ok, lv, _html} = live(conn, ~p"/imports/#{run.id}")
updated = %{
run
| processed_rows: 50,
imported_count: 48,
refined_count: 1,
error_count: 1,
status: "running",
errors: %{"7" => ["band is invalid"]}
}
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"csv_import:#{run.id}",
{:import_progress, run.id, updated}
)
html = render(lv)
assert html =~ ~r/id="import-imported-count"[^>]*>\s*48/
assert html =~ ~r/id="import-refined-count"[^>]*>\s*1/
assert html =~ ~r/id="import-error-count"[^>]*>\s*1/
assert html =~ ~r/id="import-status-badge"[^>]*>\s*Running/
assert html =~ "band is invalid"
end
test "shows the complete UI once status flips to complete", %{conn: conn} do
run = insert_run(%{total_rows: 3})
{:ok, lv, _html} = live(conn, ~p"/imports/#{run.id}")
done = %{
run
| processed_rows: 3,
imported_count: 3,
status: "complete",
completed_at: DateTime.utc_now()
}
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"csv_import:#{run.id}",
{:import_progress, run.id, done}
)
html = render(lv)
assert html =~ "Import complete"
assert html =~ ~s(id="import-complete-panel")
assert html =~ "View contacts"
assert html =~ ~r/id="import-status-badge"[^>]*>\s*Complete/
end
test "shows the errors section when error_count > 0", %{conn: conn} do
run =
insert_run(%{
total_rows: 2,
processed_rows: 2,
imported_count: 1,
error_count: 1,
status: "complete",
errors: %{"2" => ["band is invalid"]}
})
{:ok, _lv, html} = live(conn, ~p"/imports/#{run.id}")
assert html =~ ~s(id="import-errors-section")
assert html =~ "Row 2"
assert html =~ "band is invalid"
end
end
end