4.4 KiB · text History b868907
defmodule GitGudWeb.AdminPackagesTest do
  @moduledoc """
  The admin view over registry disk, and the levers on it. Retention is
  on by default; with it off the page reports what it would free, and
  only the button deletes.
  """
  use GitGudWeb.ConnCase, async: false

  import Phoenix.LiveViewTest
  import GitGud.AccountsFixtures
  import GitGud.ForgeFixtures

  alias GitGud.Packages
  alias GitGud.Packages.PackageVersion
  alias GitGud.Repo
  alias GitGud.Settings

  setup do
    admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!()
    {_user, repo} = repository_fixture()

    publish(repo, "widget", 30)

    %{admin: admin, repo: repo}
  end

  test "shows what retention would free without deleting anything", %{conn: conn, admin: admin} do
    {:ok, _} = Settings.put("packages.retention_enforced", false)
    {:ok, lv, _html} = conn |> log_in_user(admin) |> live(~p"/admin/packages")

    html = render_async(lv)

    assert html =~ "Reporting only"
    assert html =~ "6 version(s)"
    # Nothing was deleted by looking at the page.
    assert Repo.aggregate(PackageVersion, :count) == 30
  end

  test "saving the levers stores them", %{conn: conn, admin: admin} do
    {:ok, lv, _html} = conn |> log_in_user(admin) |> live(~p"/admin/packages")
    render_async(lv)

    lv
    |> form("form[phx-submit=save]", %{
      "settings" => %{
        "packages.retention_enforced" => "false",
        "packages.retention_mode" => "keep_last",
        "packages.retention_keep" => "10",
        "packages.retention_bytes" => "",
        "packages.max_pins" => "5"
      }
    })
    |> render_submit()

    assert Settings.get("packages.retention_enforced") == false
    assert Settings.get("packages.retention_keep") == 10
    assert Settings.get("packages.max_pins") == 5
    # Blank means "no ceiling", not zero — zero would prune every
    # registry on the instance to one version each.
    assert Settings.get("packages.retention_bytes") == nil

    # An entry per setting that changed, old and new; none for the rest.
    changed =
      for e <- Repo.all(GitGud.Audit.Entry),
          e.action == "instance_setting.changed",
          into: %{},
          do: {e.target_label, e.metadata}

    assert changed["packages.retention_keep"] == %{"from" => 24, "to" => 10}
    assert changed["packages.max_pins"] == %{"from" => 12, "to" => 5}
    refute Map.has_key?(changed, "packages.retention_bytes")
  end

  test "an out-of-range lever is refused with the bound in the message", %{
    conn: conn,
    admin: admin
  } do
    {:ok, lv, _html} = conn |> log_in_user(admin) |> live(~p"/admin/packages")
    render_async(lv)

    html =
      lv
      |> form("form[phx-submit=save]", %{
        "settings" => %{
          "packages.retention_enforced" => "false",
          "packages.retention_mode" => "keep_last",
          "packages.retention_keep" => "0",
          "packages.retention_bytes" => "",
          "packages.max_pins" => "5"
        }
      })
      |> render_submit()

    assert html =~ "must be at least 1"
    assert Settings.get("packages.retention_keep") == 24
    # All or nothing: the good lever beside the bad one isn't kept either.
    assert Settings.get("packages.max_pins") == 12
    assert Repo.all(GitGud.Audit.Entry) == []
  end

  test "prune now deletes, and only on the button", %{conn: conn, admin: admin} do
    {:ok, _} = Settings.put("packages.retention_enforced", false)
    {:ok, lv, _html} = conn |> log_in_user(admin) |> live(~p"/admin/packages")
    render_async(lv)

    lv |> element("button[phx-click=prune_now]") |> render_click()
    html = render_async(lv)

    assert html =~ "Pruned 6 version(s)"
    assert Repo.aggregate(PackageVersion, :count) == 24

    assert [%{action: "packages.pruned", metadata: %{"versions" => 6}}] =
             Repo.all(GitGud.Audit.Entry)
  end

  test "a non-admin can't get in", %{conn: conn} do
    user = user_fixture()

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

    assert to == "/"
  end

  defp publish(repo, name, count) do
    for i <- 0..(count - 1) do
      {:ok, _, _, _} =
        Packages.publish_file(repo, "pypi", name, "1.0.#{i}", %{
          filename: "#{name}-1.0.#{i}.tar.gz",
          body: :crypto.strong_rand_bytes(64),
          content_type: "application/gzip",
          pushed_at: DateTime.add(~U[2026-01-01 00:00:00Z], i, :second)
        })
    end
  end
end