5.4 KiB · text History b868907
defmodule GitGudWeb.LabelLive.IndexTest do
  @moduledoc """
  The repository labels page changes labels for a repository admin only,
  and only this repository's labels (and its organization's, for
  opting out and back in) — whatever id the client sends.
  """

  use GitGudWeb.ConnCase, async: false

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

  alias GitGud.Labels
  alias GitGud.Labels.Label
  alias GitGud.Organizations
  alias GitGud.Repo
  alias GitGud.Repositories

  defp labels_path(repo) do
    handle = Repositories.Storage.repo_handle(Repo.preload(repo, [:owner, :organization]))
    ~p"/r/#{handle}/#{repo.name}/labels"
  end

  defp org_repo do
    admin = user_fixture()
    handle = "acme-#{System.unique_integer([:positive])}"
    {:ok, org} = Organizations.create_organization(admin, %{"handle" => handle})

    {:ok, repo} =
      Repositories.create_repository_for_org(org, admin, %{
        "name" => unique_repo_name(),
        "visibility" => "public"
      })

    {admin, org, repo}
  end

  describe "a repository admin" do
    test "adds and deletes the repository's own labels", %{conn: conn} do
      {owner, repo} = repository_fixture()
      {:ok, lv, _} = live(log_in_user(conn, owner), labels_path(repo))

      lv
      |> form("#label-form", %{"label" => %{"name" => "docs", "color" => "#3b82f6"}})
      |> render_submit()

      assert [label] = Labels.list_labels(repo)
      assert has_element?(lv, "#label-#{label.id}")

      lv |> element("#label-delete-#{label.id}") |> render_click()
      assert Labels.list_labels(repo) == []
      refute has_element?(lv, "#label-#{label.id}")
    end

    test "can't delete another repository's label by forging its id", %{conn: conn} do
      {owner, repo} = repository_fixture()
      {_, other} = repository_fixture()
      foreign = label_fixture(other)

      {:ok, lv, _} = live(log_in_user(conn, owner), labels_path(repo))
      _ = render_hook(lv, "delete", %{"id" => to_string(foreign.id)})

      assert Repo.get(Label, foreign.id)
    end

    test "a junk id is ignored rather than crashing the page", %{conn: conn} do
      {owner, repo} = repository_fixture()
      {:ok, lv, _} = live(log_in_user(conn, owner), labels_path(repo))

      for event <- ~w(delete opt_out opt_in) do
        _ = render_hook(lv, event, %{"id" => "nope"})
        _ = render_hook(lv, event, %{"id" => "999999999"})
      end

      assert has_element?(lv, "#label-form")
    end

    test "can't delete an inherited org label from the repository page", %{conn: conn} do
      {admin, org, repo} = org_repo()

      {:ok, org_label} =
        Labels.create_organization_label(org, %{"name" => "sec", "color" => "#ef4444"})

      {:ok, lv, _} = live(log_in_user(conn, admin), labels_path(repo))
      _ = render_hook(lv, "delete", %{"id" => to_string(org_label.id)})

      assert Repo.get(Label, org_label.id)
    end

    test "opts out of and back into its organization's label", %{conn: conn} do
      {admin, org, repo} = org_repo()

      {:ok, org_label} =
        Labels.create_organization_label(org, %{"name" => "sec", "color" => "#ef4444"})

      {:ok, lv, _} = live(log_in_user(conn, admin), labels_path(repo))

      lv |> element("#label-hide-#{org_label.id}") |> render_click()
      assert Labels.opted_out?(repo, org_label)

      lv |> element("#label-unhide-#{org_label.id}") |> render_click()
      refute Labels.opted_out?(repo, org_label)
    end

    test "can't opt out of another organization's label", %{conn: conn} do
      {admin, _org, repo} = org_repo()
      {_, other_org, _} = org_repo()

      {:ok, foreign} =
        Labels.create_organization_label(other_org, %{"name" => "theirs", "color" => "#ef4444"})

      {:ok, lv, _} = live(log_in_user(conn, admin), labels_path(repo))
      _ = render_hook(lv, "opt_out", %{"id" => to_string(foreign.id)})

      refute Labels.opted_out?(repo, foreign)
    end
  end

  describe "someone who can read but not administer the repository" do
    setup do
      {owner, repo} = repository_fixture(%{visibility: "public"})
      label = label_fixture(repo, %{"name" => "keep"})
      %{owner: owner, repo: repo, label: label, reader: user_fixture()}
    end

    test "sees the labels without the controls", %{conn: conn} = ctx do
      {:ok, lv, _} = live(log_in_user(conn, ctx.reader), labels_path(ctx.repo))

      assert has_element?(lv, "#label-#{ctx.label.id}")
      refute has_element?(lv, "#label-form")
      refute has_element?(lv, "#label-delete-#{ctx.label.id}")
    end

    test "can't delete or add one by pushing the events", %{conn: conn} = ctx do
      {:ok, lv, _} = live(log_in_user(conn, ctx.reader), labels_path(ctx.repo))

      _ = render_hook(lv, "delete", %{"id" => to_string(ctx.label.id)})

      _ =
        render_hook(lv, "save", %{"label" => %{"name" => "sneaky", "color" => "#3b82f6"}})

      assert [%{id: id}] = Labels.list_labels(ctx.repo)
      assert id == ctx.label.id
    end

    test "can't opt an org repository out of its labels", %{conn: conn} do
      {_admin, org, repo} = org_repo()

      {:ok, org_label} =
        Labels.create_organization_label(org, %{"name" => "sec", "color" => "#ef4444"})

      {:ok, lv, _} = live(log_in_user(conn, user_fixture()), labels_path(repo))
      refute has_element?(lv, "#label-hide-#{org_label.id}")

      _ = render_hook(lv, "opt_out", %{"id" => to_string(org_label.id)})
      refute Labels.opted_out?(repo, org_label)
    end
  end
end