prop/test/microwaveprop/radio/import_run_test.exs
Graham McIntire 48833f15be
feat(import): async CSV import with progress-tracking schema
For large CSVs (e.g. the 34k-row ARRL dump), the synchronous commit
path blocked for minutes and a crash mid-commit left no audit trail.
Refactor to run inserts + enrichment enqueues via Oban.

- New import_runs table: stores preview rows + running counters (total,
  processed, imported, refined, error) + status + errors map + timing.
- New :contact_import Oban queue (limit 4) + ContactImportWorker.
  Args: {import_run_id, offset, limit}. Each chunk (100 rows) inserts
  its slice via CsvImport.commit_rows/1, atomically increments counters
  in one update_all, flips status, and broadcasts progress on
  'csv_import:<id>' PubSub topic.
- CsvImport.commit_rows/1 extracted from commit/1 (back-compat preserved).
  New CsvImport.enqueue/2 persists the run + dispatches N chunk jobs.

Also: submit page copy updated from '902 MHz and up' to '50 MHz and up'
matching the band allowlist expansion done earlier.

LiveView UI for /imports/:id is a separate commit.
2026-04-17 09:31:22 -05:00

75 lines
2.4 KiB
Elixir

defmodule Microwaveprop.Radio.ImportRunTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.Radio.ImportRun
@valid_attrs %{
submitter_email: "test@example.com",
rows: %{"rows" => [], "refinement_ids" => []},
total_rows: 0
}
describe "changeset/2" do
test "accepts a minimal valid attrs map" do
changeset = ImportRun.changeset(%ImportRun{}, @valid_attrs)
assert changeset.valid?
end
test "defaults status to pending via schema" do
run = %ImportRun{}
assert run.status == "pending"
assert run.processed_rows == 0
assert run.imported_count == 0
assert run.refined_count == 0
assert run.error_count == 0
assert run.errors == %{}
end
test "requires submitter_email" do
changeset = ImportRun.changeset(%ImportRun{}, Map.delete(@valid_attrs, :submitter_email))
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).submitter_email
end
test "requires rows" do
changeset = ImportRun.changeset(%ImportRun{}, Map.delete(@valid_attrs, :rows))
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).rows
end
test "requires total_rows" do
changeset = ImportRun.changeset(%ImportRun{}, Map.delete(@valid_attrs, :total_rows))
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).total_rows
end
test "rejects a bad status" do
changeset = ImportRun.changeset(%ImportRun{}, Map.put(@valid_attrs, :status, "weird"))
refute changeset.valid?
assert errors_on(changeset).status != []
end
test "accepts every declared status" do
for status <- ImportRun.statuses() do
changeset = ImportRun.changeset(%ImportRun{}, Map.put(@valid_attrs, :status, status))
assert changeset.valid?, "expected #{inspect(status)} to be valid"
end
end
test "round-trips through the database" do
assert {:ok, run} =
%ImportRun{}
|> ImportRun.changeset(%{
submitter_email: "a@example.com",
rows: %{"rows" => [%{"row_num" => 2}], "refinement_ids" => []},
total_rows: 1
})
|> Repo.insert()
reloaded = Repo.get!(ImportRun, run.id)
assert reloaded.status == "pending"
assert reloaded.total_rows == 1
assert reloaded.rows == %{"rows" => [%{"row_num" => 2}], "refinement_ids" => []}
end
end
end