-
Notifications
You must be signed in to change notification settings - Fork 1
fix(tracking): record which code path unchecks a character's tracking box #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,14 +27,36 @@ defmodule WandererApp.MapCharacterSettingsRepo do | |
| def update(map_id, character_id, updated_settings) do | ||
| case get(map_id, character_id) do | ||
| {:ok, settings} when not is_nil(settings) -> | ||
| # This function takes arbitrary attributes, so a caller passing | ||
| # tracked: false would flip the flag without going through untrack/1 and | ||
| # the uncheck would never be recorded — silently breaking the guarantee | ||
| # that untrack/1 observes every one. Nothing does that today; this keeps | ||
| # the guarantee true if something ever starts. | ||
| untracking? = Map.get(settings, :tracked) == true and untracks?(updated_settings) | ||
|
|
||
| settings | ||
| |> WandererApp.Api.MapCharacterSettings.update(updated_settings) | ||
| |> case do | ||
| {:ok, _updated} = result -> | ||
| if untracking?, do: report_untrack(map_id, character_id) | ||
| result | ||
|
|
||
| error -> | ||
| error | ||
| end | ||
|
|
||
| _ -> | ||
| {:ok, nil} | ||
| end | ||
| end | ||
|
|
||
| # Ash accepts atom or string keys, so check both rather than trusting callers. | ||
| defp untracks?(attrs) when is_map(attrs) do | ||
| Map.get(attrs, :tracked, Map.get(attrs, "tracked")) == false | ||
| end | ||
|
|
||
| defp untracks?(_attrs), do: false | ||
|
|
||
| def get_tracked_by_map_filtered(map_id, character_ids), | ||
| do: | ||
| WandererApp.Api.MapCharacterSettings.tracked_by_map_filtered(%{ | ||
|
|
@@ -72,13 +94,37 @@ defmodule WandererApp.MapCharacterSettingsRepo do | |
| end | ||
| end | ||
|
|
||
| # Every path that unchecks a character's tracking box in the database funnels | ||
| # through here: the UI toggle (TrackingUtils), the ACL permission sweep | ||
| # (CharactersImpl) and character deletion (CharactersLive) all call this, and | ||
| # untrack!/1 delegates to it. It is therefore the one place that can observe an | ||
| # uncheck regardless of which path caused it. | ||
| # | ||
| # That matters because users report their tracking box unchecking itself while | ||
| # none of the known callers appears responsible. Reasoning backwards from the | ||
| # call sites could not settle it; recording the caller at the moment of the | ||
| # write does. The stacktrace is the payload — it names the path even if the | ||
| # path is one nobody has thought of yet. | ||
|
Comment on lines
+97
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline lib/wanderer_app/repositories/map_character_settings_repo.ex --items all --type function
rg -n -C 3 --type elixir \
'alias\s+WandererApp\.MapCharacterSettingsRepo|MapCharacterSettingsRepo\.(update|untrack)\s*\(' \
lib test
rg -n -C 3 --type elixir 'tracked:\s*false' lib testRepository: guarzo/wanderer Length of output: 15898 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- repository implementation ---'
cat -n lib/wanderer_app/repositories/map_character_settings_repo.ex
printf '%s\n' '--- all repository update calls ---'
rg -n -C 4 --type elixir \
'MapCharacterSettingsRepo\.update\s*\(' lib test
printf '%s\n' '--- direct API update calls ---'
rg -n -C 5 --type elixir \
'Api\.MapCharacterSettings\.update\s*\(|MapCharacterSettings\.update\s*\(' lib test
printf '%s\n' '--- tracked fields in update/create calls ---'
rg -n -C 8 --type elixir \
'(MapCharacterSettingsRepo|Api\.MapCharacterSettings|MapCharacterSettings)\.(update|create)\s*\(' lib testRepository: guarzo/wanderer Length of output: 32943 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- MapCharacterSettings resource and update action ---'
rg -n -C 6 --type elixir \
'defmodule .*MapCharacterSettings|update.*MapCharacterSettings|accept:|tracked' lib/wanderer_app lib/wanderer_app_web | \
head -n 400
printf '%s\n' '--- every MapCharacterSettings update reference ---'
rg -n -C 3 --type elixir \
'(MapCharacterSettings|MapCharacterSettingsRepo)\.(update|create|untrack|track)\s*\(' lib test
printf '%s\n' '--- every repository update definition and caller context ---'
rg -n -C 12 --type elixir \
'def\s+update\s*\(map_id,\s*character_id,\s*updated_settings\)|MapCharacterSettingsRepo\.update' lib testRepository: guarzo/wanderer Length of output: 50371 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate resource files ---'
git ls-files | rg 'map_character_settings|MapCharacterSettings'
printf '%s\n' '--- tracked-setting mutations outside the repository ---'
rg -n --type elixir \
'tracked\s*:\s*(false|true)|tracked\s*=>\s*(false|true)|\[:tracked\]' \
lib test \
| rg 'update|create|tracked|settings|MapCharacter' \
| head -n 250
printf '%s\n' '--- direct resource calls outside the repository ---'
rg -n --type elixir \
'(Api\.)?MapCharacterSettings\.(update|create)\s*\(' lib test \
| rg -v 'repositories/map_character_settings_repo\.ex'
printf '%s\n' '--- repository update argument shape ---'
sed -n '350,395p' lib/wanderer_app/character/tracker_manager_impl.ex
sed -n '1020,1050p' lib/wanderer_app/map/server/map_server_characters_impl.ex
sed -n '360,382p' lib/wanderer_app/map/operations/duplication.exRepository: guarzo/wanderer Length of output: 7824 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- MapCharacterSettings resource ---'
cat -n lib/wanderer_app/api/map_character_settings.ex
printf '%s\n' '--- literal tracked:false mutation verifier ---'
python3 - <<'PY'
from pathlib import Path
import re
root = Path(".")
for path in sorted(root.rglob("*.ex")) + sorted(root.rglob("*.exs")):
if any(part in {".git", "deps", "_build"} for part in path.parts):
continue
text = path.read_text(errors="replace")
for match in re.finditer(r'(?:MapCharacterSettingsRepo|MapCharacterSettings)\.(update|create)\s*\(', text):
start = match.start()
depth = 0
end = None
for i in range(text.find("(", start), len(text)):
if text[i] == "(":
depth += 1
elif text[i] == ")":
depth -= 1
if depth == 0:
end = i + 1
break
call = text[start:end] if end else text[start:start+500]
if re.search(r'\btracked\s*:\s*false\b', call):
line = text.count("\n", 0, start) + 1
print(f"{path}:{line}: {call.replace(chr(10), ' ')}")
PYRepository: guarzo/wanderer Length of output: 8396 Prevent 🤖 Prompt for AI Agents |
||
| def untrack(%{map_id: map_id, character_id: character_id}) do | ||
| # First ensure the record exists (get creates if not exists) | ||
| case get(map_id, character_id) do | ||
| {:ok, settings} when not is_nil(settings) -> | ||
| # Now update the tracked field | ||
| # Captured before the update, so this reports the true -> false | ||
| # transition rather than every call. A repeat untrack of an already | ||
| # untracked character changes nothing and must not be counted, or the | ||
| # metric stops meaning "a box was unchecked". | ||
| was_tracked = Map.get(settings, :tracked) == true | ||
|
|
||
| settings | ||
| |> WandererApp.Api.MapCharacterSettings.update(%{tracked: false}) | ||
| |> case do | ||
| {:ok, _updated} = result -> | ||
| if was_tracked, do: report_untrack(map_id, character_id) | ||
| result | ||
|
Comment on lines
+116
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Make transition reporting atomic. Two concurrent calls can both read Use a conditional update that changes only records still tracked. Emit only when that update reports that it changed a record. Add a concurrent regression test. 🤖 Prompt for AI Agents |
||
|
|
||
| error -> | ||
| error | ||
| end | ||
|
|
||
| error -> | ||
| Logger.error( | ||
|
|
@@ -89,6 +135,65 @@ defmodule WandererApp.MapCharacterSettingsRepo do | |
| end | ||
| end | ||
|
|
||
| # Known callers, mapped to a bounded set of sources so the metric can be | ||
| # grouped in Grafana. Anything else is :unknown — which is exactly the case | ||
| # worth looking at, and the log line carries the raw stack for it. | ||
| @untrack_sources %{ | ||
| WandererApp.Character.TrackingUtils => :ui_toggle, | ||
| WandererApp.Map.Server.CharactersImpl => :acl_sweep, | ||
| WandererAppWeb.CharactersLive => :character_deleted | ||
| } | ||
|
|
||
| defp report_untrack(map_id, character_id) do | ||
| {source, stack} = untrack_source() | ||
|
|
||
| Logger.warning( | ||
| "[MapCharacterSettings] Untracked character #{character_id} on map #{map_id} " <> | ||
| "(source: #{source}). Caller: #{stack}", | ||
| character_id: character_id, | ||
| map_id: map_id | ||
| ) | ||
|
|
||
| :telemetry.execute( | ||
| [:wanderer_app, :map, :character_settings, :untracked], | ||
| %{count: 1, system_time: System.system_time()}, | ||
| %{character_id: character_id, map_id: map_id, source: source} | ||
| ) | ||
| end | ||
|
|
||
| defp untrack_source do | ||
| case Process.info(self(), :current_stacktrace) do | ||
| {:current_stacktrace, stack} -> | ||
| {classify_stack(stack), format_stack(stack)} | ||
|
|
||
| _ -> | ||
| {:unknown, "unavailable"} | ||
| end | ||
| end | ||
|
|
||
| defp classify_stack(stack) do | ||
| Enum.find_value(stack, :unknown, fn {module, _fun, _arity, _loc} -> | ||
| Map.get(@untrack_sources, module) | ||
| end) | ||
| end | ||
|
|
||
| # Only frames outside this module, and only a handful: enough to name the | ||
| # caller without dumping an entire LiveView stack into the log. Process is | ||
| # dropped too — it is the Process.info/2 call that captured the stack, not a | ||
| # caller, and it would otherwise head every line. | ||
| defp format_stack(stack) do | ||
| stack | ||
| |> Enum.reject(fn {module, _fun, _arity, _loc} -> | ||
| module in [__MODULE__, Process] | ||
| end) | ||
| |> Enum.take(6) | ||
| |> Enum.map_join(" <- ", fn {module, fun, arity, loc} -> | ||
| file = loc |> Keyword.get(:file, ~c"?") |> to_string() | ||
| line = Keyword.get(loc, :line, 0) | ||
| "#{inspect(module)}.#{fun}/#{arity} (#{file}:#{line})" | ||
| end) | ||
| end | ||
|
|
||
| def track!(settings) do | ||
| case track(settings) do | ||
| {:ok, result} -> result | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,115 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defmodule WandererApp.Repositories.MapCharacterSettingsRepoUntrackTest do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @moduledoc """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Covers the provenance instrumentation on `MapCharacterSettingsRepo.untrack/1`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Users report their tracking box unchecking itself while none of the three | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| known callers — the UI toggle, the ACL permission sweep, and character | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deletion — appears responsible. Every one of them funnels through | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `untrack/1`, so it is the single point that can observe an uncheck regardless | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| of which path caused it, and the `:source` tag is what turns the count into a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| diagnosis. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The transition guard matters as much as the emit: a repeat untrack of an | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| already-untracked character changes nothing, and counting it would inflate the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metric against the reports it exists to explain. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use WandererApp.DataCase, async: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alias WandererApp.MapCharacterSettingsRepo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @event [:wanderer_app, :map, :character_settings, :untracked] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setup do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handler_id = "untrack-test-#{System.unique_integer([:positive])}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test_pid = self() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :telemetry.attach( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handler_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @event, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn _event, measurements, metadata, _ -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| send(test_pid, {:telemetry, measurements, metadata}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on_exit(fn -> :telemetry.detach(handler_id) end) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :ok | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe "untrack/1 instrumentation" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test "emits when a tracked character is unchecked" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{map_id: map_id, character_id: character_id} = tracked_settings() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, _} = MapCharacterSettingsRepo.untrack(%{map_id: map_id, character_id: character_id}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_receive {:telemetry, %{count: 1}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{character_id: ^character_id, map_id: ^map_id, source: source}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Called directly from a test process, so no known caller module appears in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # the stack. :unknown is the honest answer and is exactly the value that | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # should prompt a look at the accompanying log line. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert source == :unknown | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test "does not emit when the character was already untracked" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{map_id: map_id, character_id: character_id} = tracked_settings() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, _} = MapCharacterSettingsRepo.untrack(%{map_id: map_id, character_id: character_id}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_receive {:telemetry, _, _} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, _} = MapCharacterSettingsRepo.untrack(%{map_id: map_id, character_id: character_id}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refute_receive {:telemetry, _, _}, 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test "leaves the character untracked" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{map_id: map_id, character_id: character_id} = tracked_settings() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, updated} = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MapCharacterSettingsRepo.untrack(%{map_id: map_id, character_id: character_id}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert updated.tracked == false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe "update/3 cannot bypass the instrumentation" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test "reports an uncheck made through the generic update path" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{map_id: map_id, character_id: character_id} = tracked_settings() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, _} = MapCharacterSettingsRepo.update(map_id, character_id, %{tracked: false}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert_receive {:telemetry, %{count: 1}, %{character_id: ^character_id, map_id: ^map_id}} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test "does not report when the update leaves tracking alone" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{map_id: map_id, character_id: character_id} = tracked_settings() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, _} = MapCharacterSettingsRepo.update(map_id, character_id, %{ship_name: "Loki"}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refute_receive {:telemetry, _, _}, 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test "does not report when the update sets tracking on" do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{map_id: map_id, character_id: character_id} = tracked_settings() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, _} = MapCharacterSettingsRepo.update(map_id, character_id, %{tracked: true}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refute_receive {:telemetry, _, _}, 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+76
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add a regression test for string-key updates.
Suggested test+ test "reports an uncheck made through the generic update path with a string key" do
+ %{map_id: map_id, character_id: character_id} = tracked_settings()
+
+ {:ok, _} =
+ MapCharacterSettingsRepo.update(map_id, character_id, %{"tracked" => false})
+
+ assert_receive {:telemetry, %{count: 1},
+ %{character_id: ^character_id, map_id: ^map_id}}
+ end📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defp tracked_settings do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| map = WandererAppWeb.Factory.insert(:map, %{}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| character = WandererAppWeb.Factory.insert(:character, %{}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {:ok, _settings} = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WandererApp.Api.MapCharacterSettings.create(%{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| map_id: map.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| character_id: character.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tracked: true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| %{map_id: map.id, character_id: character.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the comment to match the supported behavior.
The implementation and tests now support direct
update/3calls withtracked: false. Therefore, “Nothing does that today” is no longer accurate. State that this path is supported and instrumented directly.🤖 Prompt for AI Agents