72 lines
2.4 KiB
Elixir
72 lines
2.4 KiB
Elixir
defmodule Mv.Membership.Import.ColumnResolverQueryTest do
|
|
# async: false — attaches a global telemetry handler to inspect emitted SQL.
|
|
use Mv.DataCase, async: false
|
|
|
|
alias Mv.Membership.Import.ColumnResolver
|
|
|
|
setup do
|
|
%{actor: Mv.Helpers.SystemActor.get_system_actor()}
|
|
end
|
|
|
|
describe "create_or_find_group/3 group lookup is name-filtered (no full-table scan)" do
|
|
test "resolving a new name absent from the snapshot queries by name, not the whole table",
|
|
%{actor: actor} do
|
|
# Populate the table so a full-table read would be costly and observable.
|
|
for n <- 1..20, do: Mv.Fixtures.group_fixture(%{name: "Existing #{n}"})
|
|
|
|
queries =
|
|
capture_group_select_queries(fn ->
|
|
# The name is absent from the (empty) snapshot, forcing a DB lookup
|
|
# before the create attempt. That lookup must filter by name.
|
|
assert {:ok, group} = ColumnResolver.create_or_find_group("New One", [], actor)
|
|
assert group.name == "New One"
|
|
end)
|
|
|
|
# No SELECT against the groups table issued during resolution may be an
|
|
# unfiltered full-table scan. The pre-create existence check must filter by
|
|
# name (carry a WHERE predicate).
|
|
refute Enum.any?(queries, &unfiltered_groups_select?/1),
|
|
"expected no unfiltered groups table scan, got:\n#{Enum.join(queries, "\n")}"
|
|
end
|
|
end
|
|
|
|
defp capture_group_select_queries(fun) do
|
|
test_pid = self()
|
|
handler_id = "test-group-query-#{System.unique_integer([:positive])}"
|
|
|
|
:telemetry.attach(
|
|
handler_id,
|
|
[:mv, :repo, :query],
|
|
fn _event, _measurements, metadata, _config ->
|
|
sql = metadata[:query] || ""
|
|
|
|
if String.contains?(sql, "SELECT") and String.contains?(sql, "\"groups\"") do
|
|
send(test_pid, {:group_query, sql})
|
|
end
|
|
end,
|
|
nil
|
|
)
|
|
|
|
try do
|
|
fun.()
|
|
after
|
|
:telemetry.detach(handler_id)
|
|
end
|
|
|
|
collect_group_queries([])
|
|
end
|
|
|
|
defp collect_group_queries(acc) do
|
|
receive do
|
|
{:group_query, sql} -> collect_group_queries([sql | acc])
|
|
after
|
|
0 -> Enum.reverse(acc)
|
|
end
|
|
end
|
|
|
|
# An unfiltered groups SELECT reads the whole table: it selects FROM "groups"
|
|
# with no WHERE clause at all. A name-filtered lookup carries a WHERE predicate.
|
|
defp unfiltered_groups_select?(sql) do
|
|
String.contains?(sql, "FROM \"groups\"") and not String.contains?(sql, "WHERE")
|
|
end
|
|
end
|