4.9 KiB · text History b868907
defmodule GitGudWeb.PackageRetentionUiTest do
  @moduledoc """
  The two places a non-admin touches retention: pins and a package's
  own policy, on the repository's packages page — plus the org view
  that tells them why they should care.
  """
  use GitGudWeb.ConnCase, async: false

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

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

  setup :register_and_log_in_user

  setup %{user: user} do
    {:ok, repo} =
      GitGud.Repositories.create_repository(user, %{
        "name" => "pkgs#{System.unique_integer([:positive])}",
        "visibility" => "public"
      })

    repo = Repo.preload(repo, [:owner, :organization])
    publish(repo, "widget", 30)

    package = Packages.get_package(repo, "pypi", "widget")

    %{repo: repo, package: package, path: "/r/#{user.handle}/#{repo.name}/packages"}
  end

  test "shows what the registry is using, per format and in total", ctx do
    {:ok, _lv, html} = live(ctx.conn, ctx.path)

    assert html =~ "30 versions"
    # A size, rather than a raw byte count.
    assert html =~ ~r/\d+(\.\d+)? (B|KiB|MiB|GiB)/
  end

  test "an owner can pin a version out of retention's reach", ctx do
    {:ok, lv, _html} = live(ctx.conn, ctx.path)

    lv
    |> element(~s|button[phx-click="toggle"][phx-value-id="#{ctx.package.id}"]|)
    |> render_click()

    oldest = version(ctx.package, "1.0.0")

    html =
      lv |> element(~s|button[phx-click="pin"][phx-value-id="#{oldest.id}"]|) |> render_click()

    assert html =~ "pinned"
    assert Repo.reload(oldest).pinned_at

    # And retention now spares it.
    plan = Retention.plan(Repo.reload(ctx.package))
    refute Enum.any?(plan.prune, &(&1.version == "1.0.0"))
  end

  test "the pin limit is reported in the page, not swallowed", ctx do
    {:ok, _} = Settings.put("packages.max_pins", 1)
    {:ok, lv, _html} = live(ctx.conn, ctx.path)

    lv
    |> element(~s|button[phx-click="toggle"][phx-value-id="#{ctx.package.id}"]|)
    |> render_click()

    {:ok, _} = Retention.pin(version(ctx.package, "1.0.0"), ctx.user)

    html =
      lv
      |> element(~s|button[phx-click="pin"][phx-value-id="#{version(ctx.package, "1.0.1").id}"]|)
      |> render_click()

    assert html =~ "1 pins already"
  end

  test "a package can tighten its own policy but not loosen it", ctx do
    {:ok, lv, _html} = live(ctx.conn, ctx.path)

    lv
    |> element(~s|button[phx-click="toggle"][phx-value-id="#{ctx.package.id}"]|)
    |> render_click()

    lv
    |> form("form[phx-submit=set_retention]", %{
      "package_id" => ctx.package.id,
      "retention_mode" => "keep_last",
      "retention_keep" => "5",
      "retention_bytes" => ""
    })
    |> render_submit()

    assert Repo.reload(ctx.package).retention_keep == 5

    html =
      lv
      |> form("form[phx-submit=set_retention]", %{
        "package_id" => ctx.package.id,
        "retention_mode" => "keep_last",
        "retention_keep" => "500",
        "retention_bytes" => ""
      })
      |> render_submit()

    assert html =~ "keeps at most 24 versions"
    assert Repo.reload(ctx.package).retention_keep == 5
  end

  test "a visitor who can't administer the repo gets no controls", ctx do
    other = user_fixture()
    conn = log_in_user(build_conn(), other)

    {:ok, lv, _html} = live(conn, ctx.path)

    html =
      lv
      |> element(~s|button[phx-click="toggle"][phx-value-id="#{ctx.package.id}"]|)
      |> render_click()

    refute html =~ "phx-click=\"pin\""
    refute html =~ "set_retention"
  end

  describe "the org view" do
    setup %{user: user} do
      {:ok, org} =
        GitGud.Organizations.create_organization(user, %{
          "handle" => "acme#{System.unique_integer([:positive])}",
          "visibility" => "public"
        })

      {:ok, repo} =
        GitGud.Repositories.create_repository_for_org(org, user, %{
          "name" => "orgpkgs#{System.unique_integer([:positive])}",
          "visibility" => "public"
        })

      publish(Repo.preload(repo, [:owner, :organization]), "orgwidget", 30)

      %{org: org}
    end

    test "totals an organization's registries", %{conn: conn, org: org} do
      {:ok, lv, _html} = live(conn, ~p"/orgs/#{org.handle}/packages/storage")

      html = render_async(lv)

      assert html =~ "orgwidget"
      assert html =~ "would remove 6 version(s)"
    end
  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(128),
          content_type: "application/gzip",
          pushed_at: DateTime.add(~U[2026-01-01 00:00:00Z], i, :second)
        })
    end
  end

  defp version(package, version),
    do: Repo.get_by!(PackageVersion, package_id: package.id, version: version)
end