A feature for shareable emoji packs, use it in download_from & tests
[akkoma] / test / web / emoji_api_controller_test.exs
index 13a34d38d201caa27b3381fef85f826d467345aa..1af4d3720e88f40565a3cd7fbfb99c5a8ce667b2 100644 (file)
@@ -1,10 +1,15 @@
-defmodule Pleroma.Web.EmojiAPI.EmojiAPIControllerTest do
+defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
   use Pleroma.Web.ConnCase
 
   import Tesla.Mock
 
   import Pleroma.Factory
 
+  @emoji_dir_path Path.join(
+                    Pleroma.Config.get!([:instance, :static_dir]),
+                    "emoji"
+                  )
+
   test "shared & non-shared pack information in list_packs is ok" do
     conn = build_conn()
     resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
@@ -38,16 +43,23 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIControllerTest do
 
     {:ok, arch} = :zip.unzip(resp, [:memory])
 
-    assert Enum.find(arch, fn {n, _} -> n == 'pack.yml' end)
+    assert Enum.find(arch, fn {n, _} -> n == 'pack.json' end)
     assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end)
   end
 
-  test "downloading a shared pack from another instance via download_from, deleting it" do
+  test "downloading shared & unshared packs from another instance via download_from, deleting them" do
     on_exit(fn ->
-      File.rm_rf!("test/instance_static/emoji/test_pack2")
+      File.rm_rf!("#{@emoji_dir_path}/test_pack2")
+      File.rm_rf!("#{@emoji_dir_path}/test_pack_nonshared2")
     end)
 
     mock(fn
+      %{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} ->
+        json(%{features: []})
+
+      %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
+        json(%{features: ["shareable_emoji_packs"]})
+
       %{
         method: :get,
         url: "https://example.com/api/pleroma/emoji/packs/list"
@@ -69,15 +81,36 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIControllerTest do
         |> get(emoji_api_path(conn, :download_shared, "test_pack"))
         |> response(200)
         |> text()
+
+      %{
+        method: :get,
+        url: "https://nonshared-pack"
+      } ->
+        text(File.read!("#{@emoji_dir_path}/test_pack_nonshared/nonshared.zip"))
     end)
 
     admin = insert(:user, info: %{is_admin: true})
 
-    conn = build_conn()
+    conn = build_conn() |> assign(:user, admin)
+
+    assert (conn
+            |> put_req_header("content-type", "application/json")
+            |> post(
+              emoji_api_path(
+                conn,
+                :download_from
+              ),
+              %{
+                instance_address: "https://old-instance",
+                pack_name: "test_pack",
+                as: "test_pack2"
+              }
+              |> Jason.encode!()
+            )
+            |> json_response(500))["error"] =~ "does not support"
 
     assert conn
            |> put_req_header("content-type", "application/json")
-           |> assign(:user, admin)
            |> post(
              emoji_api_path(
                conn,
@@ -90,16 +123,309 @@ defmodule Pleroma.Web.EmojiAPI.EmojiAPIControllerTest do
              }
              |> Jason.encode!()
            )
-           |> text_response(200) == "ok"
+           |> json_response(200) == "ok"
 
-    assert File.exists?("test/instance_static/emoji/test_pack2/pack.yml")
-    assert File.exists?("test/instance_static/emoji/test_pack2/blank.png")
+    assert File.exists?("#{@emoji_dir_path}/test_pack2/pack.json")
+    assert File.exists?("#{@emoji_dir_path}/test_pack2/blank.png")
 
     assert conn
-           |> assign(:user, admin)
            |> delete(emoji_api_path(conn, :delete, "test_pack2"))
-           |> response(200) == "ok"
+           |> json_response(200) == "ok"
+
+    refute File.exists?("#{@emoji_dir_path}/test_pack2")
+
+    # non-shared, downloaded from the fallback URL
+
+    conn = build_conn() |> assign(:user, admin)
+
+    assert conn
+           |> put_req_header("content-type", "application/json")
+           |> post(
+             emoji_api_path(
+               conn,
+               :download_from
+             ),
+             %{
+               instance_address: "https://example.com",
+               pack_name: "test_pack_nonshared",
+               as: "test_pack_nonshared2"
+             }
+             |> Jason.encode!()
+           )
+           |> json_response(200) == "ok"
+
+    assert File.exists?("#{@emoji_dir_path}/test_pack_nonshared2/pack.json")
+    assert File.exists?("#{@emoji_dir_path}/test_pack_nonshared2/blank.png")
+
+    assert conn
+           |> delete(emoji_api_path(conn, :delete, "test_pack_nonshared2"))
+           |> json_response(200) == "ok"
+
+    refute File.exists?("#{@emoji_dir_path}/test_pack_nonshared2")
+  end
+
+  describe "updating pack metadata" do
+    setup do
+      pack_file = "#{@emoji_dir_path}/test_pack/pack.json"
+      original_content = File.read!(pack_file)
+
+      on_exit(fn ->
+        File.write!(pack_file, original_content)
+      end)
+
+      {:ok,
+       admin: insert(:user, info: %{is_admin: true}),
+       pack_file: pack_file,
+       new_data: %{
+         "license" => "Test license changed",
+         "homepage" => "https://pleroma.social",
+         "description" => "Test description",
+         "share-files" => false
+       }}
+    end
+
+    test "for a pack without a fallback source", ctx do
+      conn = build_conn()
+
+      assert conn
+             |> assign(:user, ctx[:admin])
+             |> post(
+               emoji_api_path(conn, :update_metadata, "test_pack"),
+               %{
+                 "new_data" => ctx[:new_data]
+               }
+             )
+             |> json_response(200) == ctx[:new_data]
+
+      assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data]
+    end
+
+    test "for a pack with a fallback source", ctx do
+      mock(fn
+        %{
+          method: :get,
+          url: "https://nonshared-pack"
+        } ->
+          text(File.read!("#{@emoji_dir_path}/test_pack_nonshared/nonshared.zip"))
+      end)
+
+      new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
+
+      new_data_with_sha =
+        Map.put(
+          new_data,
+          "fallback-src-sha256",
+          "74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF"
+        )
+
+      conn = build_conn()
+
+      assert conn
+             |> assign(:user, ctx[:admin])
+             |> post(
+               emoji_api_path(conn, :update_metadata, "test_pack"),
+               %{
+                 "new_data" => new_data
+               }
+             )
+             |> json_response(200) == new_data_with_sha
+
+      assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha
+    end
+
+    test "when the fallback source doesn't have all the files", ctx do
+      mock(fn
+        %{
+          method: :get,
+          url: "https://nonshared-pack"
+        } ->
+          {:ok, {'empty.zip', empty_arch}} = :zip.zip('empty.zip', [], [:memory])
+          text(empty_arch)
+      end)
+
+      new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
+
+      conn = build_conn()
+
+      assert (conn
+              |> assign(:user, ctx[:admin])
+              |> post(
+                emoji_api_path(conn, :update_metadata, "test_pack"),
+                %{
+                  "new_data" => new_data
+                }
+              )
+              |> json_response(:bad_request))["error"] =~ "does not have all"
+    end
+  end
+
+  test "updating pack files" do
+    pack_file = "#{@emoji_dir_path}/test_pack/pack.json"
+    original_content = File.read!(pack_file)
+
+    on_exit(fn ->
+      File.write!(pack_file, original_content)
+
+      File.rm_rf!("#{@emoji_dir_path}/test_pack/blank_url.png")
+      File.rm_rf!("#{@emoji_dir_path}/test_pack/dir")
+      File.rm_rf!("#{@emoji_dir_path}/test_pack/dir_2")
+    end)
+
+    admin = insert(:user, info: %{is_admin: true})
+
+    conn = build_conn()
+
+    same_name = %{
+      "action" => "add",
+      "shortcode" => "blank",
+      "filename" => "dir/blank.png",
+      "file" => %Plug.Upload{
+        filename: "blank.png",
+        path: "#{@emoji_dir_path}/test_pack/blank.png"
+      }
+    }
+
+    different_name = %{same_name | "shortcode" => "blank_2"}
+
+    conn = conn |> assign(:user, admin)
+
+    assert (conn
+            |> post(emoji_api_path(conn, :update_file, "test_pack"), same_name)
+            |> json_response(:conflict))["error"] =~ "already exists"
+
+    assert conn
+           |> post(emoji_api_path(conn, :update_file, "test_pack"), different_name)
+           |> json_response(200) == %{"blank" => "blank.png", "blank_2" => "dir/blank.png"}
+
+    assert File.exists?("#{@emoji_dir_path}/test_pack/dir/blank.png")
+
+    assert conn
+           |> post(emoji_api_path(conn, :update_file, "test_pack"), %{
+             "action" => "update",
+             "shortcode" => "blank_2",
+             "new_shortcode" => "blank_3",
+             "new_filename" => "dir_2/blank_3.png"
+           })
+           |> json_response(200) == %{"blank" => "blank.png", "blank_3" => "dir_2/blank_3.png"}
+
+    refute File.exists?("#{@emoji_dir_path}/test_pack/dir/")
+    assert File.exists?("#{@emoji_dir_path}/test_pack/dir_2/blank_3.png")
+
+    assert conn
+           |> post(emoji_api_path(conn, :update_file, "test_pack"), %{
+             "action" => "remove",
+             "shortcode" => "blank_3"
+           })
+           |> json_response(200) == %{"blank" => "blank.png"}
+
+    refute File.exists?("#{@emoji_dir_path}/test_pack/dir_2/")
+
+    mock(fn
+      %{
+        method: :get,
+        url: "https://test-blank/blank_url.png"
+      } ->
+        text(File.read!("#{@emoji_dir_path}/test_pack/blank.png"))
+    end)
+
+    # The name should be inferred from the URL ending
+    from_url = %{
+      "action" => "add",
+      "shortcode" => "blank_url",
+      "file" => "https://test-blank/blank_url.png"
+    }
+
+    assert conn
+           |> post(emoji_api_path(conn, :update_file, "test_pack"), from_url)
+           |> json_response(200) == %{
+             "blank" => "blank.png",
+             "blank_url" => "blank_url.png"
+           }
+
+    assert File.exists?("#{@emoji_dir_path}/test_pack/blank_url.png")
+
+    assert conn
+           |> post(emoji_api_path(conn, :update_file, "test_pack"), %{
+             "action" => "remove",
+             "shortcode" => "blank_url"
+           })
+           |> json_response(200) == %{"blank" => "blank.png"}
+
+    refute File.exists?("#{@emoji_dir_path}/test_pack/blank_url.png")
+  end
+
+  test "creating and deleting a pack" do
+    on_exit(fn ->
+      File.rm_rf!("#{@emoji_dir_path}/test_created")
+    end)
+
+    admin = insert(:user, info: %{is_admin: true})
+
+    conn = build_conn() |> assign(:user, admin)
+
+    assert conn
+           |> put_req_header("content-type", "application/json")
+           |> put(
+             emoji_api_path(
+               conn,
+               :create,
+               "test_created"
+             )
+           )
+           |> json_response(200) == "ok"
+
+    assert File.exists?("#{@emoji_dir_path}/test_created/pack.json")
+
+    assert Jason.decode!(File.read!("#{@emoji_dir_path}/test_created/pack.json")) == %{
+             "pack" => %{},
+             "files" => %{}
+           }
+
+    assert conn
+           |> delete(emoji_api_path(conn, :delete, "test_created"))
+           |> json_response(200) == "ok"
+
+    refute File.exists?("#{@emoji_dir_path}/test_created/pack.json")
+  end
+
+  test "filesystem import" do
+    on_exit(fn ->
+      File.rm!("#{@emoji_dir_path}/test_pack_for_import/emoji.txt")
+      File.rm!("#{@emoji_dir_path}/test_pack_for_import/pack.json")
+    end)
+
+    conn = build_conn()
+    resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
+
+    refute Map.has_key?(resp, "test_pack_for_import")
+
+    admin = insert(:user, info: %{is_admin: true})
+
+    assert conn
+           |> assign(:user, admin)
+           |> post(emoji_api_path(conn, :import_from_fs))
+           |> json_response(200) == ["test_pack_for_import"]
+
+    resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
+    assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}
+
+    File.rm!("#{@emoji_dir_path}/test_pack_for_import/pack.json")
+    refute File.exists?("#{@emoji_dir_path}/test_pack_for_import/pack.json")
+
+    emoji_txt_content = "blank, blank.png, Fun\n\nblank2, blank.png"
+
+    File.write!("#{@emoji_dir_path}/test_pack_for_import/emoji.txt", emoji_txt_content)
+
+    assert conn
+           |> assign(:user, admin)
+           |> post(emoji_api_path(conn, :import_from_fs))
+           |> json_response(200) == ["test_pack_for_import"]
+
+    resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
 
-    refute File.exists?("test/instance_static/emoji/test_pack2")
+    assert resp["test_pack_for_import"]["files"] == %{
+             "blank" => "blank.png",
+             "blank2" => "blank.png"
+           }
   end
 end