5.8 KiB · text History b868907
defmodule GitGudWeb.PackageAboutUiTest do
  @moduledoc """
  Describing a package, and where that shows up: the repository's
  packages page (where an admin writes it), the org catalog's cards,
  the icon those cards draw, and the mobile API.
  """
  use GitGudWeb.ConnCase, async: false

  import Phoenix.LiveViewTest
  import GitGud.AccountsFixtures

  alias GitGud.Organizations
  alias GitGud.Packages

  setup do
    owner = user_fixture()
    handle = "acme#{System.unique_integer([:positive])}"
    {:ok, org} = Organizations.create_organization(owner, %{"handle" => handle, "name" => "Acme"})

    {:ok, public} =
      GitGud.Repositories.create_repository_for_org(org, owner, %{
        "name" => "app",
        "visibility" => "public"
      })

    {:ok, private} =
      GitGud.Repositories.create_repository_for_org(org, owner, %{
        "name" => "hidden",
        "visibility" => "private"
      })

    icon = GitGud.ApkFixtures.icon_png()
    :ok = GitGud.Storage.put(Packages.artifact_bucket(), "fdroid-icons/test", icon)

    app =
      publish(public, "fdroid", "org.acme.app", %{
        "label" => "Acme",
        "icon_key" => "fdroid-icons/test",
        "icon_ext" => "png",
        "icon_sha256" => "abc"
      })

    server = publish(public, "deb", "acme-server", %{})
    secret = publish(private, "fdroid", "org.acme.secret", %{"icon_key" => "fdroid-icons/test"})

    %{
      owner: owner,
      org: org,
      public: public,
      app: app,
      server: server,
      secret: secret,
      icon: icon
    }
  end

  defp publish(repo, format, name, metadata) do
    {:ok, package, _v, _f} =
      Packages.publish_file(repo, format, name, "1", %{
        filename: "#{name}-1.bin",
        body: "#{format} #{name}",
        metadata: metadata
      })

    package
  end

  describe "the repository's packages page" do
    test "an admin describes a package, and it shows under the name", ctx do
      conn = log_in_user(ctx.conn, ctx.owner)
      {:ok, lv, _html} = live(conn, ~p"/r/#{ctx.org.handle}/app/packages")

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

      lv |> element("#edit-about-#{ctx.app.id}") |> render_click()

      lv
      |> form("#about-form-#{ctx.app.id}",
        about: %{summary: "Rockets, delivered", description: "Long form."}
      )
      |> render_submit(%{package_id: ctx.app.id})

      assert has_element?(lv, "#package-summary-#{ctx.app.id}", "Rockets, delivered")
      assert Packages.get_package!(ctx.app.id).description == "Long form."
    end

    test "an overlong summary is refused in place", ctx do
      conn = log_in_user(ctx.conn, ctx.owner)
      {:ok, lv, _html} = live(conn, ~p"/r/#{ctx.org.handle}/app/packages")

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

      lv |> element("#edit-about-#{ctx.app.id}") |> render_click()

      lv
      |> form("#about-form-#{ctx.app.id}", about: %{summary: String.duplicate("x", 81)})
      |> render_submit(%{package_id: ctx.app.id})

      assert has_element?(lv, "#about-form-#{ctx.app.id}")
      assert Packages.get_package!(ctx.app.id).summary == nil
    end

    test "a visitor gets no editor", ctx do
      {:ok, lv, _html} = live(ctx.conn, ~p"/r/#{ctx.org.handle}/app/packages")

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

      refute has_element?(lv, "#edit-about-#{ctx.app.id}")
    end
  end

  describe "the org catalog" do
    setup ctx do
      {:ok, _} = Packages.set_about(ctx.server, %{"summary" => "The backend"})
      :ok
    end

    test "is the default tab, with a card per package", ctx do
      conn = log_in_user(ctx.conn, ctx.owner)
      {:ok, lv, _html} = live(conn, ~p"/orgs/#{ctx.org.handle}/packages")

      assert has_element?(lv, "#org-packages-catalog")
      assert has_element?(lv, "#package-card-#{ctx.app.id}", "Acme")
      assert has_element?(lv, "#package-card-#{ctx.app.id} img[src$='/icon']")
      assert has_element?(lv, "#package-card-#{ctx.server.id}", "The backend")
    end

    test "filters by format through the URL", ctx do
      conn = log_in_user(ctx.conn, ctx.owner)
      {:ok, lv, _html} = live(conn, ~p"/orgs/#{ctx.org.handle}/packages/catalog/deb")

      assert has_element?(lv, "#package-card-#{ctx.server.id}")
      refute has_element?(lv, "#package-card-#{ctx.app.id}")

      lv |> element("#org-packages-tab-storage") |> render_click()
      assert has_element?(lv, "#org-packages-storage")
      refute has_element?(lv, "#org-packages-catalog")
    end

    test "the org page links to it", ctx do
      {:ok, lv, _html} = live(log_in_user(ctx.conn, ctx.owner), ~p"/orgs/#{ctx.org.handle}")
      assert has_element?(lv, "#org-packages-link")
    end
  end

  describe "the icon route" do
    test "serves a public package's icon to anyone", ctx do
      conn = get(ctx.conn, ~p"/r/#{ctx.org.handle}/app/packages/#{ctx.app.id}/icon")

      assert response(conn, 200) == ctx.icon
      assert response_content_type(conn, :png) =~ "image/png"
    end

    test "is a 404 for a private package to someone who can't read it", ctx do
      conn = get(ctx.conn, ~p"/r/#{ctx.org.handle}/hidden/packages/#{ctx.secret.id}/icon")
      assert response(conn, 404)

      conn =
        ctx.conn
        |> log_in_user(ctx.owner)
        |> get(~p"/r/#{ctx.org.handle}/hidden/packages/#{ctx.secret.id}/icon")

      assert response(conn, 200)
    end

    test "won't serve one repository's package under another's path", ctx do
      conn = get(ctx.conn, ~p"/r/#{ctx.org.handle}/app/packages/#{ctx.secret.id}/icon")
      assert response(conn, 404)
    end

    test "a package without an icon is a 404", ctx do
      conn = get(ctx.conn, ~p"/r/#{ctx.org.handle}/app/packages/#{ctx.server.id}/icon")
      assert response(conn, 404)
    end
  end
end