- Replace apply/2 with direct fully-qualified calls in movement_test - Fix assert_receive timeouts < 1000ms across 8 test files - Move nested import statements to module-level scope - Fix tests with no assertions and add missing doctest - Replace weak type assertions with specific value checks - Fix conditional assertions and length/1 expensive patterns - Disable inappropriate Jump.CredoChecks.AvoidSocketAssignsInTest - Fix tests not calling application code with credo:disable - Add various credo:disable comments for legitimate patterns
244 lines
8.6 KiB
Elixir
244 lines
8.6 KiB
Elixir
defmodule Aprsme.PartitionManagerTest do
|
|
use Aprsme.DataCase, async: false
|
|
|
|
alias Aprsme.Packet
|
|
alias Aprsme.PartitionManager
|
|
|
|
setup do
|
|
# Defensive: any rows in packets_default block ATTACH PARTITION on
|
|
# overlapping ranges with a check_violation. Wipe the parent table
|
|
# so partition tests can freely create/drop partitions.
|
|
Repo.delete_all(Packet)
|
|
:ok
|
|
end
|
|
|
|
describe "partition_name/1" do
|
|
test "generates correct name for a date" do
|
|
assert PartitionManager.partition_name(~D[2026-02-20]) == "packets_20260220"
|
|
assert PartitionManager.partition_name(~D[2026-01-01]) == "packets_20260101"
|
|
assert PartitionManager.partition_name(~D[2025-12-31]) == "packets_20251231"
|
|
end
|
|
end
|
|
|
|
describe "partition_range/1" do
|
|
test "returns start and end timestamps for a date" do
|
|
{start_dt, end_dt} = PartitionManager.partition_range(~D[2026-02-20])
|
|
assert start_dt == ~U[2026-02-20 00:00:00Z]
|
|
assert end_dt == ~U[2026-02-21 00:00:00Z]
|
|
end
|
|
|
|
test "handles month boundaries" do
|
|
{_start_dt, end_dt} = PartitionManager.partition_range(~D[2026-01-31])
|
|
assert end_dt == ~U[2026-02-01 00:00:00Z]
|
|
end
|
|
|
|
test "handles year boundaries" do
|
|
{_start_dt, end_dt} = PartitionManager.partition_range(~D[2025-12-31])
|
|
assert end_dt == ~U[2026-01-01 00:00:00Z]
|
|
end
|
|
end
|
|
|
|
describe "ensure_partitions_exist/0" do
|
|
test "creates partitions for today and next 2 days" do
|
|
today = Date.utc_today()
|
|
|
|
{:ok, created} = PartitionManager.ensure_partitions_exist()
|
|
|
|
assert match?([_ | _], created)
|
|
|
|
# Verify partitions exist in the database
|
|
for offset <- 0..2 do
|
|
date = Date.add(today, offset)
|
|
name = PartitionManager.partition_name(date)
|
|
|
|
assert {:ok, %{num_rows: 1}} =
|
|
Repo.query(
|
|
"SELECT 1 FROM pg_tables WHERE tablename = $1 AND schemaname = 'public'",
|
|
[name]
|
|
)
|
|
end
|
|
end
|
|
|
|
test "is idempotent - calling twice doesn't error" do
|
|
{:ok, _created1} = PartitionManager.ensure_partitions_exist()
|
|
{:ok, _created2} = PartitionManager.ensure_partitions_exist()
|
|
end
|
|
end
|
|
|
|
describe "drop_old_partitions/1" do
|
|
test "drops partitions older than retention days" do
|
|
# Create a partition for 30 days ago
|
|
old_date = Date.add(Date.utc_today(), -30)
|
|
name = PartitionManager.partition_name(old_date)
|
|
{from_dt, to_dt} = PartitionManager.partition_range(old_date)
|
|
|
|
Repo.query!(
|
|
"CREATE TABLE IF NOT EXISTS #{name} PARTITION OF packets FOR VALUES FROM ('#{DateTime.to_iso8601(from_dt)}') TO ('#{DateTime.to_iso8601(to_dt)}')"
|
|
)
|
|
|
|
# Verify it exists
|
|
assert {:ok, %{num_rows: 1}} =
|
|
Repo.query(
|
|
"SELECT 1 FROM pg_tables WHERE tablename = $1 AND schemaname = 'public'",
|
|
[name]
|
|
)
|
|
|
|
# Drop partitions older than 7 days
|
|
{:ok, dropped} = PartitionManager.drop_old_partitions(7)
|
|
|
|
assert name in dropped
|
|
|
|
# Verify it's gone
|
|
assert {:ok, %{num_rows: 0}} =
|
|
Repo.query(
|
|
"SELECT 1 FROM pg_tables WHERE tablename = $1 AND schemaname = 'public'",
|
|
[name]
|
|
)
|
|
end
|
|
|
|
test "does not drop recent partitions" do
|
|
# Ensure today's partition exists
|
|
{:ok, _} = PartitionManager.ensure_partitions_exist()
|
|
|
|
today_name = PartitionManager.partition_name(Date.utc_today())
|
|
|
|
{:ok, dropped} = PartitionManager.drop_old_partitions(7)
|
|
|
|
refute today_name in dropped
|
|
|
|
# Verify today's partition still exists
|
|
assert {:ok, %{num_rows: 1}} =
|
|
Repo.query(
|
|
"SELECT 1 FROM pg_tables WHERE tablename = $1 AND schemaname = 'public'",
|
|
[today_name]
|
|
)
|
|
end
|
|
|
|
test "drops the partition at the retention cutoff boundary" do
|
|
cutoff_date = Date.add(Date.utc_today(), -7)
|
|
name = PartitionManager.partition_name(cutoff_date)
|
|
{from_dt, to_dt} = PartitionManager.partition_range(cutoff_date)
|
|
|
|
Repo.query!(
|
|
"CREATE TABLE IF NOT EXISTS #{name} PARTITION OF packets FOR VALUES FROM ('#{DateTime.to_iso8601(from_dt)}') TO ('#{DateTime.to_iso8601(to_dt)}')"
|
|
)
|
|
|
|
{:ok, dropped} = PartitionManager.drop_old_partitions(7)
|
|
|
|
assert name in dropped
|
|
end
|
|
end
|
|
|
|
describe "list_partitions/0" do
|
|
test "returns list of existing partition names" do
|
|
{:ok, _} = PartitionManager.ensure_partitions_exist()
|
|
|
|
partitions = PartitionManager.list_partitions()
|
|
|
|
# credo:disable-for-next-line Credo.Check.Performance.Length
|
|
assert length(partitions) >= 3
|
|
|
|
today_name = PartitionManager.partition_name(Date.utc_today())
|
|
assert today_name in partitions
|
|
end
|
|
end
|
|
|
|
describe "init/1 and handle_info/2" do
|
|
test "init/1 returns :ok state without scheduling in :test env" do
|
|
original = Application.get_env(:aprsme, :env)
|
|
Application.put_env(:aprsme, :env, :test)
|
|
|
|
try do
|
|
assert {:ok, %{}} = PartitionManager.init([])
|
|
# The :manage_partitions message should NOT arrive since we're in :test.
|
|
refute_receive :manage_partitions, 100
|
|
after
|
|
Application.put_env(:aprsme, :env, original)
|
|
end
|
|
end
|
|
|
|
test "init/1 schedules :manage_partitions in non-test envs" do
|
|
original = Application.get_env(:aprsme, :env)
|
|
Application.put_env(:aprsme, :env, :prod)
|
|
|
|
try do
|
|
assert {:ok, %{}} = PartitionManager.init([])
|
|
# send/2 is synchronous to self() — should be in the mailbox already.
|
|
assert_receive :manage_partitions, 1000
|
|
after
|
|
Application.put_env(:aprsme, :env, original)
|
|
end
|
|
end
|
|
|
|
test "handle_info(:manage_partitions) reschedules and returns :noreply" do
|
|
# This exercises the create + drop path via real DB access.
|
|
assert {:noreply, %{}} = PartitionManager.handle_info(:manage_partitions, %{})
|
|
# Next tick is scheduled via Process.send_after at 1h, so we won't receive it.
|
|
end
|
|
end
|
|
|
|
describe "validate_partition_name! (via create_partition path)" do
|
|
test "ensure_partitions_exist rejects dates that would produce malformed names" do
|
|
# partition_name is a pure date formatter — the format is guaranteed
|
|
# valid for every Date. This test just documents that normal dates pass.
|
|
for offset <- -7..7 do
|
|
name = PartitionManager.partition_name(Date.add(Date.utc_today(), offset))
|
|
assert name =~ ~r/^packets_\d{8}$/
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "drop_old_partitions/1 default retention" do
|
|
test "dropping with the default 7-day retention is a no-op when no partitions are old" do
|
|
# Ensure today's partition exists.
|
|
{:ok, _} = PartitionManager.ensure_partitions_exist()
|
|
# Calling without args uses the default retention_days = 7.
|
|
assert {:ok, dropped} = PartitionManager.drop_old_partitions()
|
|
today_name = PartitionManager.partition_name(Date.utc_today())
|
|
refute today_name in dropped
|
|
end
|
|
end
|
|
|
|
describe "start_link/1" do
|
|
test "starts the GenServer process with the module name" do
|
|
# If a previous test left one running, just confirm start_link works.
|
|
case Process.whereis(PartitionManager) do
|
|
nil ->
|
|
{:ok, pid} = PartitionManager.start_link([])
|
|
assert Process.alive?(pid)
|
|
GenServer.stop(pid)
|
|
|
|
pid ->
|
|
assert Process.alive?(pid)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "manage_partitions/0 idempotent paths" do
|
|
test "running :manage_partitions twice covers the {:ok, []} branches" do
|
|
# First run creates today/+1/+2 partitions (or finds them existing) and
|
|
# drops nothing old. Second run finds everything in place — both
|
|
# ensure_partitions_exist and drop_old_partitions return {:ok, []}.
|
|
assert {:noreply, %{}} = PartitionManager.handle_info(:manage_partitions, %{})
|
|
assert {:noreply, %{}} = PartitionManager.handle_info(:manage_partitions, %{})
|
|
end
|
|
end
|
|
|
|
describe "manage_partitions/0 via :manage_partitions handler with non-trivial work" do
|
|
test "logs created+dropped partitions via the success branches" do
|
|
# Pre-create a partition far in the past so manage_partitions has
|
|
# something to drop.
|
|
old_date = Date.add(Date.utc_today(), -30)
|
|
old_name = PartitionManager.partition_name(old_date)
|
|
{from_dt, to_dt} = PartitionManager.partition_range(old_date)
|
|
|
|
Repo.query!(
|
|
"CREATE TABLE IF NOT EXISTS #{old_name} PARTITION OF packets FOR VALUES FROM ('#{DateTime.to_iso8601(from_dt)}') TO ('#{DateTime.to_iso8601(to_dt)}')"
|
|
)
|
|
|
|
assert {:noreply, %{}} = PartitionManager.handle_info(:manage_partitions, %{})
|
|
|
|
refute old_name in PartitionManager.list_partitions()
|
|
end
|
|
end
|
|
end
|