5.1 KiB · text History b868907
defmodule GitGudWeb.PublicPackageFormatsTest do
  @moduledoc """
  Publishing a format from a repository that is not itself public.

  The case this exists for: an Android client whose source is private.
  The APK is meant to be on a phone, and an F-Droid client is anonymous,
  so without an opt-in the repository is absent from the `_all` index
  the phone subscribes to and 401s on its own URL.

  What is asserted here is mostly what the opt-in does *not* do —
  it widens anonymous reads of the named format and nothing else.
  """
  use GitGudWeb.ConnCase, async: false

  alias GitGud.Packages
  alias GitGud.Packages.Auth
  alias GitGud.Repositories
  alias GitGud.Repositories.Storage

  import GitGud.ForgeFixtures

  @firmware <<0xE9, 0x06, 0x02, 0x1F>> <> :crypto.strong_rand_bytes(256)

  setup do
    {user, repo} = repository_fixture(%{visibility: "private"})
    {:ok, _t, token} = Packages.create_registry_token(repo, "ci", ["pull", "push"])
    handle = Storage.owner_handle(user)

    # Publish with the token, which works regardless of visibility --
    # that is the CI path, and it is not what any of this changes.
    build_conn()
    |> put_req_header("authorization", token)
    |> put_req_header("content-type", "application/octet-stream")
    |> put("/pkg/raw/#{handle}/#{repo.name}/1.0.0/firmware.bin", @firmware)

    {:ok,
     user: user,
     repo: repo,
     token: token,
     handle: handle,
     root: "/pkg/raw/#{handle}/#{repo.name}"}
  end

  defp publish_formats(repo, formats) do
    {:ok, repo} = Repositories.update_repository(repo, %{"public_package_formats" => formats})
    repo
  end

  describe "without the opt-in" do
    test "a private repository's registry refuses an anonymous read", %{conn: conn, root: root} do
      assert conn |> get(root <> "/manifest.json") |> Map.fetch!(:status) == 401
    end

    test "and it is absent from what a namespace-wide index would serve", %{user: user} do
      assert Packages.namespace_catalog(user, :anonymous) == %{}
    end
  end

  describe "with the format opted in" do
    setup %{repo: repo}, do: {:ok, repo: publish_formats(repo, ["raw"])}

    test "an anonymous client reads it", %{conn: conn, root: root} do
      conn = get(conn, root <> "/manifest.json")
      assert conn.status == 200
      assert Jason.decode!(conn.resp_body)["latest"] == "1.0.0"
    end

    test "the bytes themselves come back", %{conn: conn, root: root} do
      conn = get(conn, root <> "/1.0.0/firmware.bin")
      assert conn.status == 200
      assert conn.resp_body == @firmware
    end

    test "and it appears in what a namespace-wide index would serve", %{user: user, repo: repo} do
      catalog = Packages.namespace_catalog(user, :anonymous)
      assert [%{repository: %{id: id}}] = catalog["raw"].packages
      assert id == repo.id
    end

    test "the repository itself is still private", %{repo: repo} do
      assert Repositories.get_repository!(repo.id).visibility == "private"
    end
  end

  describe "what the opt-in does not open" do
    setup %{repo: repo}, do: {:ok, repo: publish_formats(repo, ["fdroid"])}

    test "a format that was not ticked stays shut", %{conn: conn, root: root} do
      assert conn |> get(root <> "/manifest.json") |> Map.fetch!(:status) == 401
    end

    test "nor does it let an anonymous caller publish", %{conn: conn, handle: handle, repo: repo} do
      conn =
        conn
        |> put_req_header("content-type", "application/octet-stream")
        |> put("/pkg/fdroid/#{handle}/#{repo.name}/9.9.9/app.apk", @firmware)

      assert conn.status in [401, 403]
    end

    test "writes are refused for the opted-in format too", %{repo: repo} do
      assert Auth.authorize(:anonymous, repo, :write, "fdroid") == false
    end

    test "an unticked format is still refused at the authorize level", %{repo: repo} do
      assert Auth.authorize(:anonymous, repo, :read, "raw") == false
      assert Auth.authorize(:anonymous, repo, :read, "fdroid") == true
    end
  end

  describe "the catalog the dashboard renders" do
    test "lists the format only under the one opted into", %{user: user, repo: repo} do
      repo = publish_formats(repo, ["raw"])
      assert Auth.public_format?(repo, "raw")

      catalog = Packages.namespace_catalog(user, :anonymous)
      assert Map.has_key?(catalog, "raw")

      # And with nothing ticked, the anonymous catalog is empty again.
      publish_formats(repo, [])
      assert Packages.namespace_catalog(user, :anonymous) == %{}
    end
  end

  describe "the setting itself" do
    test "rejects a format the forge does not serve", %{repo: repo} do
      assert {:error, changeset} =
               Repositories.update_repository(repo, %{
                 "public_package_formats" => ["raw", "npm"]
               })

      errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
      assert "has an invalid entry" in errors.public_package_formats
    end

    test "clearing every box stores an empty list, not a blank entry", %{repo: repo} do
      repo = publish_formats(repo, ["raw", ""])
      assert repo.public_package_formats == ["raw"]

      repo = publish_formats(repo, [""])
      assert repo.public_package_formats == []
    end
  end
end