Grant operator access safely under concurrent tests

2f51868a0a5f · Devin AI · · parent 4da7eff07b63

Grant operator access safely under concurrent tests

grant_operator/1 snapshotted the application-wide admin id list and its
cleanup restored that snapshot, so one async test's exit erased whatever
a concurrent test had granted in between and the operator's Admin row
vanished mid-test. Each grant now adds only its own id and its cleanup
removes only that id, with the read-modify-write serialized.

Co-Authored-By: Christopher David <chris@openagents.com>
Co-Authored-By
Christopher David <chris@openagents.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified test/support/conn_case.ex

Diff

1 file changed, +17 -4

test/support/conn_case.ex modified +17 -4

@@ -150,19 +150,32 @@ defmodule OpenAgentsWeb.ConnCase do

150 150
    Plug.Test.init_test_session(conn, %{"user_id" => user.id})
151 151
  end
152 152
153
  # The admin list is one application-wide value shared by every concurrently
154
  # running test, so each grant adds only its own id and its cleanup removes
155
  # only that id — restoring a snapshot would erase whatever another test
156
  # granted in between. The read-modify-write is serialized for the same
157
  # reason.
153 158
  def grant_operator(%OpenAgents.Accounts.User{github_id: github_id}) do
154
    original = Application.get_env(:openagents, :admin_github_ids, [])
155
    Application.put_env(:openagents, :admin_github_ids, [github_id | original])
159
    update_admin_github_ids(&[github_id | &1])
156 160
157 161
    ExUnit.Callbacks.on_exit(fn ->
158
      Application.put_env(:openagents, :admin_github_ids, original)
162
      update_admin_github_ids(&List.delete(&1, github_id))
159 163
    end)
160 164
161 165
    :ok
162 166
  end
163 167
164 168
  def revoke_operator do
165
    Application.put_env(:openagents, :admin_github_ids, [])
169
    update_admin_github_ids(fn _ids -> [] end)
170
    :ok
171
  end
172
173
  defp update_admin_github_ids(fun) do
174
    :global.trans({{:openagents, :admin_github_ids}, self()}, fn ->
175
      ids = Application.get_env(:openagents, :admin_github_ids, [])
176
      Application.put_env(:openagents, :admin_github_ids, fun.(ids))
177
    end)
178
166 179
    :ok
167 180
  end
168 181
end

This page updates live while a promote is in flight · changelog