4.7 KiB · text History b868907
defmodule GitGudWeb.AdminRunnersTest do
  @moduledoc """
  The instance admins' runners page: every runner whatever its scope,
  global runners minted here, any runner's architecture changed or the
  runner removed — each audited where the runner belongs, and each in
  sudo mode.
  """
  use GitGudWeb.ConnCase, async: false

  import Ecto.Query
  import Phoenix.LiveViewTest
  import GitGud.AccountsFixtures
  import GitGud.ForgeFixtures

  alias GitGud.Audit.Entry
  alias GitGud.Organizations
  alias GitGud.Repo
  alias GitGud.Workflows.Runner
  alias GitGud.Workflows.Runners

  setup do
    admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!()
    %{admin: admin}
  end

  defp entries, do: Repo.all(from e in Entry, order_by: e.id)

  test "lists every runner, with its scope", %{conn: conn, admin: admin} do
    {_owner, repo} = repository_fixture()
    handle = "ar#{System.unique_integer([:positive])}"
    {:ok, org} = Organizations.create_organization(admin, %{"handle" => handle, "name" => handle})

    {:ok, in_repo, _, _} = Runners.create_offline(%{name: "in-repo"}, repo)
    {:ok, in_org, _, _} = Runners.create_offline(%{name: "in-org"}, org)
    {:ok, global, _, _} = Runners.create_offline(%{name: "pool"})

    {:ok, lv, _} = conn |> log_in_user(admin) |> live(~p"/admin/runners")

    assert has_element?(lv, "#runner-row-#{in_repo.id}", repo.name)
    assert has_element?(lv, "#runner-row-#{in_org.id}", handle)
    assert has_element?(lv, "#runner-row-#{global.id}", "global")
  end

  test "mints a global runner, shown once and audited in the instance's log", %{
    conn: conn,
    admin: admin
  } do
    {:ok, lv, _} = conn |> log_in_user(admin) |> live(~p"/admin/runners")

    lv
    |> form("#new-runner-form", %{
      "new_runner" => %{"name" => "house", "labels" => "self-hosted, arc-runner-set"}
    })
    |> render_submit()

    runner = Repo.get_by!(Runner, name: "house")
    assert {runner.repository_id, runner.organization_id} == {nil, nil}
    assert runner.labels == %{"labels" => ["self-hosted", "arc-runner-set"]}
    assert has_element?(lv, "#new-runner-config", runner.uuid)
    assert has_element?(lv, "#runner-row-#{runner.id}", "never connected")

    assert [%Entry{action: "runner.created", repository_id: nil, organization_id: nil} = e] =
             entries()

    assert e.target_id == runner.id
    refute inspect(e.metadata) =~ "token"
  end

  test "changes an org runner's architecture, audited in the org's log", %{
    conn: conn,
    admin: admin
  } do
    owner = user_fixture()
    handle = "ar#{System.unique_integer([:positive])}"
    {:ok, org} = Organizations.create_organization(owner, %{"handle" => handle, "name" => handle})
    {:ok, runner, _, _} = Runners.create_offline(%{name: "e52c"}, org)

    {:ok, lv, _} = conn |> log_in_user(admin) |> live(~p"/admin/runners")

    lv
    |> form("#runner-architecture-#{runner.id}", %{"runner" => %{"architecture" => "aarch64"}})
    |> render_change()

    assert Repo.reload!(runner).architecture == "aarch64"
    assert [%Entry{action: "runner.architecture_changed", organization_id: oid}] = entries()
    assert oid == org.id
  end

  test "removes a runner", %{conn: conn, admin: admin} do
    {:ok, runner, _, _} = Runners.create_offline(%{name: "gone"})
    {:ok, lv, _} = conn |> log_in_user(admin) |> live(~p"/admin/runners")

    lv |> element("#runner-remove-#{runner.id}") |> render_click()

    refute Repo.get(Runner, runner.id)
    refute has_element?(lv, "#runner-row-#{runner.id}")
    assert [%Entry{action: "runner.deleted"}] = entries()
  end

  test "a runner connecting for the first time shows, with a toast", %{
    conn: conn,
    admin: admin
  } do
    {_owner, repo} = repository_fixture()
    {:ok, runner, _, _} = Runners.create_offline(%{name: "caprica"}, repo)
    {:ok, lv, _} = conn |> log_in_user(admin) |> live(~p"/admin/runners")

    {:ok, _} = Runners.touch_last_seen(runner)

    assert has_element?(lv, "#runner-row-#{runner.id}", "last seen")
    assert has_element?(lv, "#flash-info", "Runner 'caprica' connected.")
  end

  test "changes need sudo mode", %{conn: conn, admin: admin} do
    {:ok, runner, _, _} = Runners.create_offline(%{name: "kept"})
    stale = DateTime.add(DateTime.utc_now(:second), -30, :minute)

    {:ok, lv, _} =
      conn |> log_in_user(admin, token_authenticated_at: stale) |> live(~p"/admin/runners")

    assert {:error, {:redirect, %{to: to}}} =
             lv |> element("#runner-remove-#{runner.id}") |> render_click()

    assert to =~ "/users/log-in"
    assert Repo.get(Runner, runner.id)
    assert entries() == []
  end

  test "not for anyone else", %{conn: conn} do
    user = user_fixture()

    assert {:error, {:redirect, %{to: "/"}}} =
             conn |> log_in_user(user) |> live(~p"/admin/runners")
  end
end